Pathfinding With Built-In Systems

Pathfind and GridPos Components

To use the NorthstarPlugin pathfinding systems, insert a Pathfind and GridPos component.

The GridPos you will need to maintain as the current GridPos of the entity.


#![allow(unused)]
fn main() {
commands
    .spawn((
        Name::new("Player"),
        Pathfind {
            goal: UVec3::new(12, 12, 0),
            // you can set use_astar to true to bypass HPA* and use the traditional A* pathfinding algorithm if desired.
            use_astar: false,
        },
        GridPos(UVec3::new(1, 1, 0)),
        Blocking, // Insert the Blocking component if using collision and this entity should block.
    ));
}

There are also shortened functions for creating a new component using HPA* or A*


#![allow(unused)]
fn main() {
commands
    .spawn((
        Name::new("Player"),
        // new will default to HPA*.
        Pathfind::new(UVec3::new(12, 12, 0)),
        GridPos(UVec3::new(1, 1, 0)),
        Blocking, // Insert the Blocking component if using collision and this entity should block.
    ))
    .spawn((
        Name::new("Npc"),
        // new_astar will default to using A*.
        Pathfind::new_astar(UVec3::new(14, 14, 0)),
        GridPos(UVec3::new(2, 2, 0)),
        Blocking,
    ));
}

NextPos

The pathfind system looks for any entity with a Changed Pathfind component. It then runs the pathfinding algorithm and when a valid path is found it will insert the next position in the path as a NextPos component.

Consume the NextPos component by handling the movement and then remove the NextPos component. The next_position system will insert a new NextPos component in a later frame.

If collision is enabled the next_position system will also handle local collision avoidance and adjust the entities path if there is a blocking entity in the path in the avoidance_distance look ahead set in GridSettings.

Example movement system:


#![allow(unused)]
fn main() {
fn movement(
    mut commands: Commands,
    mut query: Query<(Entity, &mut GridPos, &NextPos)>,
) {
    for (entity, mut grid_pos, next_pos) in query.iter_mut() {
        // Set the entities GridPos to the NextPos UVec3.
        grid_pos.0 = next_pos.0;

        // Update the entities translation
        let translation = Vec3::new(
            next.0.x as f32 * 32.0, // Assuming tiles are 32x32
            next.0.y as f32 * 32.0,
            0.0
        );

        commands.entity(entity)
            .insert(Transform::from_translation(translation))
            .remove::<NextPos>();
    }
}
}

Pathfinding/Collision Marker Components

PathfindingFailed will inserted into the entity if a path is not found for the desired goal. You can create your own system to handle these failures, or let the built-in system attempt another pathfind for the goal on the next frame.

AvoidanceFailed will be inserted when collision is enabled and the entity is not able to path around a local blocking entity. The reroute_path system will attempt to find a new full HPA* path in an attempt to resolve the issue. You can handle these in your own custom system if desired.

RerouteFailed is added to the component when all attempts to resolve collision pathing issues have failed and means that there's no viable path at all to the entities desired goal. At this point you will need to handle the issue in your own custom system. Whether this is looking for a new goal or waiting a set amount of time before attempting pathing is up to you. Remove the RerouteFailed component from the entity when the entity is ready to attempt pathfinding again.


#![allow(unused)]
fn main() {
fn handle_reroute_failed(
    mut commands: Commands,
    mut query: Query<(Entity, &Pathfind), With<&RerouteFailed>>,
) {
    for (entity, pathfind) {
        let some_new_goal = UVec3::new(3, 30, 0); // Just some random new goal
        commands
            .entity(entity)
            .insert(Pathfind::new(some_new_goal)
            .remove::<RerouteFailed>();
    }
}
}

Manual Pathfinding

You don't need to use the pathfinding systems in the NorthstarPlugin in order to take advantage of this crate.

You can use both, or choose to not add the NorthstarPlugin and call the pathfinding functions completely manually.

If you don't use NorthstarPlugin you'll need to maintain your own BlockingMap or HashMap<UVec3, Entity> to pass to the pathfind function to provide it a list of blocked positions.

All of the pathfinding calls can be done on the Grid component.


#![allow(unused)]
fn main() {
fn manual_pathfind(
    mut commands: Commands,
    player: Single<(Entity, &GridPos, &MoveAction), With<Player>>,
    grid: Single<&CardinalGrid>,
    // If using collision you can use the BlockingMap resource to track blockers.
    blocking: Res<BlockingMap>,
) {
    let grid = grid.into_inner();
    let (player, grid_pos, move_action) = player.into_inner();

    let path = grid.pathfind(grid_pos.0, move_action.0, blocking, true);

    // Setting use_partial to true will allow the pathfinding to return a partial path if a complete path isn't found.

    // If you're not using collision you can pass an empty hashmap for the blocking map.
    let path = grid.pathfind(grid_pos.0, move_action.0, HashMap::new(), true);
}
}