1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
// This is a basic example for setting up the plugin.
use bevy::prelude::*;
use bevy_northstar::prelude::*;
fn main() {
App::new()
.add_plugins(DefaultPlugins)
// Add the Northstar Plugin with a selected neighborhood to use the built in pathfinding systems
.add_plugins(NorthstarPlugin::<CardinalNeighborhood>::default())
// Add the Debug Plugin to visualize the grid and pathfinding
// This is optional, but it helps to visualize the grid and pathfinding.
// We use it here because we don't have a tilemap or any other visual representation.
.add_plugins(NorthstarDebugPlugin::<CardinalNeighborhood>::default())
.add_systems(Startup, (startup, build_grid.after(startup)))
.add_systems(Update, (draw_player, move_player, input))
.run();
}
fn startup(mut commands: Commands) {
commands.spawn(Camera2d);
// Build the grid settings.
let grid_settings = GridSettingsBuilder::new_2d(64, 48).chunk_size(8).build();
// Spawn the grid used for pathfinding.
commands
.spawn(CardinalGrid::new(&grid_settings))
// Spawning the debug grid as a child of the grid entity.
.with_child((
DebugGridBuilder::new(12, 12) // 12x12 tile pixel size.
.enable_cells() // Draws every cell in the grid. We're using it to draw "tiles".
.build(),
// Offset the debug grid to the center of the world.
DebugOffset(Vec3::new(-384.0, -288.0, 0.0)),
));
// Let's position the player on the map first.
let player_transform = Transform::from_translation(Vec3::new(
4.0 * 12.0 - 384.0, // Align with the grid cell size and offset.
4.0 * 12.0 - 288.0,
0.0,
));
// Let's spawn a player entity that will be used to demonstrate pathfinding.
commands.spawn((
Name::new("Player"),
AgentPos(UVec3::new(4, 4, 0)), // Starting position in the grid.
player_transform,
));
}
fn build_grid(grid: Single<&mut CardinalGrid>) {
let mut grid = grid.into_inner();
// Let's set every 3rd cell in the grid to be impassable.
// In a real game you would set the grid to match your tilemap or level design.
// Then iterate over your tiles and set the navigation data accordingly.
for x in 0..grid.width() {
for y in 0..grid.height() {
// Create some staggered impassable cells.
if x % 2 == 0 && y % 3 == 0 {
grid.set_nav(UVec3::new(x, y, 0), Nav::Impassable);
}
}
}
info!("Building the grid...");
// The grid needs to be built after setting the grid cell nav data.
// Building the grid will calculate the chunk entrances and cache internal paths.
grid.build();
info!("Grid built successfully!");
}
fn draw_player(query: Query<&Transform, With<AgentPos>>, mut gizmos: Gizmos) {
for transform in &query {
// Draw a simple circle at the agent's position.
gizmos.circle_2d(
Vec2::new(transform.translation.x, transform.translation.y),
4.0, // Radius of the circle.
Color::srgba_u8(0, 255, 0, 255), // Color of the circle.
);
}
}
fn move_player(
mut query: Query<(Entity, &mut AgentPos, &NextPos, &mut Transform)>,
mut commands: Commands,
) {
let offset = Vec3::new(-384.0, -288.0, 0.0); // Offset to center on the world.
for (entity, mut agent_pos, next_pos, mut transform) in &mut query {
// Set the transform position to the agent's position in the grid.
transform.translation = Vec3::new(
next_pos.0.x as f32 * 12.0 + offset.x, // Align with the grid cell size.
next_pos.0.y as f32 * 12.0 + offset.y,
0.0,
);
// Update the agent's position to the next position.
agent_pos.0 = next_pos.0;
// Now we remove the NextPos component from the player to consume it.
// This is important to get the next updated position in the path.
commands.entity(entity).remove::<NextPos>();
}
}
fn input(
input: Res<ButtonInput<MouseButton>>,
window: Single<&Window>,
camera: Single<(&Camera, &GlobalTransform, &Transform), With<Camera>>,
player: Single<Entity, With<AgentPos>>,
mut commands: Commands,
) {
// Most of this isn't important for using the crate and is standard Bevy usage.
// We just want to demonstrate how to use the pathfinding system with a mouse click.
if input.just_pressed(MouseButton::Left) {
let window = window.into_inner();
let (camera, camera_transform, _) = camera.into_inner();
let player = player.into_inner();
if let Some(cursor_position) = window
.cursor_position()
.and_then(|cursor| camera.viewport_to_world_2d(camera_transform, cursor).ok())
{
let offset = Vec2::new(-384.0, -288.0); // Offset to center on the world.
let cursor_position = cursor_position - offset;
let goal = UVec3::new(
(cursor_position.x / 12.0).round() as u32,
(cursor_position.y / 12.0).round() as u32,
0, // Assuming a 2D grid, z-coordinate is 0.
);
// This is the important bit here.
// We insert a Pathfind component with the goal position.
// The pathfinding system will insert a NextPos component
// on the next frame.
commands.entity(player).insert(Pathfind::new(goal));
}
}
}