working path finding, non working targeting
This commit is contained in:
+91
-94
@@ -1,14 +1,13 @@
|
||||
use crate::{
|
||||
constants::*,
|
||||
tile,
|
||||
tilemap::{generate_surface_noise, ChunkMap, CHUNK_SIZE},
|
||||
tilemap::{ChunkMap, CHUNK_SIZE},
|
||||
};
|
||||
use bevy::{math::ivec3, prelude::*};
|
||||
use noise::Negate;
|
||||
use noise::Vector3;
|
||||
|
||||
#[derive(Bundle)]
|
||||
pub struct Citizen {
|
||||
walkness: Ambulatory,
|
||||
ambulatory: Ambulatory,
|
||||
sprite: Sprite,
|
||||
transform: Transform,
|
||||
}
|
||||
@@ -16,7 +15,7 @@ pub struct Citizen {
|
||||
impl Citizen {
|
||||
pub fn new(asset_server: &Res<AssetServer>, position: Vec3) -> Self {
|
||||
Citizen {
|
||||
walkness: Ambulatory {
|
||||
ambulatory: Ambulatory {
|
||||
speed: TILE_SIZE,
|
||||
target: None,
|
||||
current_path: None,
|
||||
@@ -72,7 +71,7 @@ impl Plugin for PathfindingPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.add_systems(
|
||||
FixedUpdate,
|
||||
(update_citizen_wandering_targets, citizen_movement),
|
||||
(update_citizen_wandering_targets, citizen_movement).chain(),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -84,7 +83,7 @@ pub fn update_citizen_wandering_targets(
|
||||
) {
|
||||
let mut rng = rand::thread_rng();
|
||||
|
||||
for (mut ambulatory, transform) in query.iter_mut() {
|
||||
for (mut ambulatory, _) in query.iter_mut() {
|
||||
if ambulatory.target.is_none()
|
||||
|| (ambulatory.current_path.is_some()
|
||||
&& ambulatory.path_index >= ambulatory.current_path.as_ref().unwrap().len())
|
||||
@@ -114,7 +113,7 @@ pub fn update_citizen_wandering_targets(
|
||||
ambulatory.target = Some(Vec3::new(
|
||||
target_x as f32 * TILE_SIZE,
|
||||
target_y as f32 * TILE_SIZE,
|
||||
z as f32 * TILE_SIZE + 1.0,
|
||||
z as f32 * TILE_SIZE + TILE_SIZE + 1.0,
|
||||
));
|
||||
ambulatory.current_path = None;
|
||||
ambulatory.path_index = 0;
|
||||
@@ -142,22 +141,25 @@ pub fn citizen_movement(
|
||||
current_pos.z - TILE_SIZE - 0.1,
|
||||
);
|
||||
|
||||
// Must have solid ground below
|
||||
if let Some(floor_tile) = tilemap.floor_tiles.get(&below_pos.as_ivec3()) {
|
||||
if !floor_tile.1 {
|
||||
transform.translation.z -= TILE_SIZE;
|
||||
continue;
|
||||
}
|
||||
if !is_standable_tile(&tilemap, current_pos.as_ivec3()) {
|
||||
// println!("drop");
|
||||
transform.translation = below_pos + Vec3::new(0.0, 0.0, 0.1);
|
||||
// println!("transform.translation: {:?}", transform.translation);
|
||||
continue;
|
||||
}
|
||||
let mut rng = rand::thread_rng();
|
||||
if rng.gen_range(0..8) > 0 {
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Some(target) = ambulatory.target {
|
||||
// Calculate path if needed
|
||||
if ambulatory.current_path.is_none() {
|
||||
println!(
|
||||
"from {:?} to {:?}",
|
||||
transform.translation.as_ivec3(),
|
||||
target.as_ivec3() - ivec3(0, 0, 1)
|
||||
);
|
||||
// println!(
|
||||
// "from {:?} to {:?}",
|
||||
// transform.translation.as_ivec3(),
|
||||
// target.as_ivec3() - ivec3(0, 0, 1)
|
||||
// );
|
||||
ambulatory.current_path = Some(calculate_path(
|
||||
&tilemap,
|
||||
transform.translation.as_ivec3(),
|
||||
@@ -170,22 +172,19 @@ pub fn citizen_movement(
|
||||
if let Some(path) = &ambulatory.current_path {
|
||||
if ambulatory.path_index < path.len() {
|
||||
let next_point = path[ambulatory.path_index];
|
||||
let next_world_pos = Vec3::new(
|
||||
next_point.x + TILE_SIZE / 2.0,
|
||||
next_point.y + TILE_SIZE / 2.0,
|
||||
next_point.z + TILE_SIZE / 2.0,
|
||||
);
|
||||
|
||||
let direction = (next_world_pos - transform.translation).normalize();
|
||||
transform.translation += direction * ambulatory.speed;
|
||||
let direction = (next_point - transform.translation).normalize();
|
||||
transform.translation = next_point;
|
||||
|
||||
// Update sprite direction (only for x movement)
|
||||
if direction.x != 0.0 {
|
||||
transform.scale.x = transform.scale.x.abs() * direction.x.signum();
|
||||
if direction.x > 0.0 {
|
||||
transform.scale.x = PIXEL_RATIO;
|
||||
} else if direction.x < 0.0 {
|
||||
transform.scale.x = -PIXEL_RATIO;
|
||||
}
|
||||
|
||||
// Check if we've reached the next point
|
||||
if transform.translation.distance(next_world_pos) < ambulatory.speed {
|
||||
if transform.translation.distance(next_point) < ambulatory.speed {
|
||||
ambulatory.path_index += 1;
|
||||
}
|
||||
}
|
||||
@@ -194,7 +193,7 @@ pub fn citizen_movement(
|
||||
}
|
||||
}
|
||||
|
||||
fn is_valid_move(tilemap: &TileMap, pos: IVec3) -> bool {
|
||||
fn is_standable_tile(tilemap: &TileMap, pos: IVec3) -> bool {
|
||||
// Check if the tile at current position is not blocking
|
||||
if let Some(current_tile) = tilemap.floor_tiles.get(&pos) {
|
||||
if current_tile.0 != 0 {
|
||||
@@ -211,6 +210,17 @@ fn is_valid_move(tilemap: &TileMap, pos: IVec3) -> bool {
|
||||
}
|
||||
|
||||
fn calculate_path(tilemap: &TileMap, start: IVec3, goal: IVec3) -> Vec<Vec3> {
|
||||
if !is_standable_tile(tilemap, start) {
|
||||
println!("Start pos invalid: {}", start);
|
||||
println!("Bugger (1)");
|
||||
exit(0);
|
||||
}
|
||||
if !is_standable_tile(tilemap, goal) {
|
||||
println!("Goal pos invalid: {}", goal);
|
||||
println!("Bugger (2)");
|
||||
exit(0);
|
||||
}
|
||||
|
||||
let mut open_set = BinaryHeap::new();
|
||||
let mut came_from = HashMap::new();
|
||||
let mut g_scores = HashMap::new();
|
||||
@@ -218,103 +228,90 @@ fn calculate_path(tilemap: &TileMap, start: IVec3, goal: IVec3) -> Vec<Vec3> {
|
||||
|
||||
let start_node = PathNode {
|
||||
position: start,
|
||||
f_score: 0,
|
||||
f_score: manhattan_distance_3d(start, goal),
|
||||
g_score: 0,
|
||||
};
|
||||
|
||||
open_set.push(start_node.clone());
|
||||
open_set.push(start_node);
|
||||
g_scores.insert(start, 0);
|
||||
|
||||
// Define allowed movements: orthogonal + diagonal on same level, and up/down
|
||||
let allowed_moves = vec![
|
||||
// Cardinal directions (same level)
|
||||
IVec3::new(ITILE_SIZE, 0, 0),
|
||||
// Orthogonal moves
|
||||
IVec3::new(-ITILE_SIZE, 0, 0),
|
||||
IVec3::new(0, ITILE_SIZE, 0),
|
||||
IVec3::new(ITILE_SIZE, 0, 0),
|
||||
IVec3::new(0, -ITILE_SIZE, 0),
|
||||
// Diagonal directions (same level)
|
||||
IVec3::new(ITILE_SIZE, ITILE_SIZE, 0),
|
||||
IVec3::new(ITILE_SIZE, -ITILE_SIZE, 0),
|
||||
IVec3::new(-ITILE_SIZE, ITILE_SIZE, 0),
|
||||
IVec3::new(0, ITILE_SIZE, 0),
|
||||
// Diagonal moves
|
||||
IVec3::new(-ITILE_SIZE, -ITILE_SIZE, 0),
|
||||
IVec3::new(-ITILE_SIZE, ITILE_SIZE, 0),
|
||||
IVec3::new(ITILE_SIZE, -ITILE_SIZE, 0),
|
||||
IVec3::new(ITILE_SIZE, ITILE_SIZE, 0),
|
||||
// Diagonal with vertical moves (left/negative preference)
|
||||
IVec3::new(-ITILE_SIZE, 0, ITILE_SIZE),
|
||||
IVec3::new(-ITILE_SIZE, 0, -ITILE_SIZE),
|
||||
IVec3::new(ITILE_SIZE, 0, ITILE_SIZE),
|
||||
IVec3::new(ITILE_SIZE, 0, -ITILE_SIZE),
|
||||
IVec3::new(0, -ITILE_SIZE, ITILE_SIZE),
|
||||
IVec3::new(0, -ITILE_SIZE, -ITILE_SIZE),
|
||||
IVec3::new(0, ITILE_SIZE, ITILE_SIZE),
|
||||
IVec3::new(0, ITILE_SIZE, -ITILE_SIZE),
|
||||
// Full 3D diagonal moves
|
||||
IVec3::new(-ITILE_SIZE, -ITILE_SIZE, ITILE_SIZE),
|
||||
IVec3::new(-ITILE_SIZE, -ITILE_SIZE, -ITILE_SIZE),
|
||||
IVec3::new(-ITILE_SIZE, ITILE_SIZE, ITILE_SIZE),
|
||||
IVec3::new(-ITILE_SIZE, ITILE_SIZE, -ITILE_SIZE),
|
||||
IVec3::new(ITILE_SIZE, -ITILE_SIZE, ITILE_SIZE),
|
||||
IVec3::new(ITILE_SIZE, -ITILE_SIZE, -ITILE_SIZE),
|
||||
IVec3::new(ITILE_SIZE, ITILE_SIZE, ITILE_SIZE),
|
||||
IVec3::new(ITILE_SIZE, ITILE_SIZE, -ITILE_SIZE),
|
||||
];
|
||||
|
||||
while let Some(current) = open_set.pop() {
|
||||
if current.position == goal {
|
||||
while let Some(current_node) = open_set.pop() {
|
||||
let current = current_node.position;
|
||||
|
||||
if current == goal {
|
||||
println!("path found");
|
||||
return reconstruct_path(came_from, current.position);
|
||||
return reconstruct_path(came_from, current);
|
||||
}
|
||||
|
||||
closed_set.insert(current.position);
|
||||
closed_set.insert(current);
|
||||
|
||||
for move_dir in &allowed_moves {
|
||||
// Try same level
|
||||
let mut neighbor_pos = current.position + *move_dir;
|
||||
for &move_dir in &allowed_moves {
|
||||
let neighbor_pos = current + move_dir;
|
||||
|
||||
// Try one up if same level is invalid
|
||||
let up_pos = neighbor_pos + IVec3::new(0, 0, ITILE_SIZE);
|
||||
if !is_valid_move(tilemap, neighbor_pos) && is_valid_move(tilemap, up_pos) {
|
||||
neighbor_pos = up_pos;
|
||||
}
|
||||
|
||||
// Try one down if same level is invalid
|
||||
let down_pos = neighbor_pos - IVec3::new(0, 0, ITILE_SIZE);
|
||||
if !is_valid_move(tilemap, neighbor_pos) && is_valid_move(tilemap, down_pos) {
|
||||
neighbor_pos = down_pos;
|
||||
}
|
||||
|
||||
if !is_valid_move(tilemap, neighbor_pos) || closed_set.contains(&neighbor_pos) {
|
||||
if !is_standable_tile(tilemap, neighbor_pos) || closed_set.contains(&neighbor_pos) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
let current_level = ITILE_SIZE;
|
||||
let upper = 2*ITILE_SIZE;
|
||||
let lower = -ITILE_SIZE;
|
||||
|
||||
// Calculate movement cost
|
||||
let movement_cost = match (
|
||||
move_dir.x.abs() + move_dir.y.abs(),
|
||||
neighbor_pos.z - current.position.z,
|
||||
move_dir.x.abs() / ITILE_SIZE + move_dir.y.abs() / ITILE_SIZE,
|
||||
move_dir.z.abs() / ITILE_SIZE,
|
||||
) {
|
||||
(current_level, 0) => 10, // Orthogonal movement
|
||||
(upper, 0) => 14, // Diagonal movement
|
||||
(_, current_level) => 20, // Moving up one level
|
||||
(_, lower) => 15, // Moving down one level
|
||||
(1, 0) => 10, // Orthogonal movement
|
||||
(2, 0) => 10, // Diagonal movement
|
||||
(_, 1) => 14, // Moving up or down
|
||||
_ => continue, // Invalid movement
|
||||
};
|
||||
|
||||
let new_g = g_scores[¤t.position] + movement_cost;
|
||||
let new_g = g_scores.get(¤t).unwrap_or(&i32::MAX) + movement_cost;
|
||||
|
||||
if !g_scores.contains_key(&neighbor_pos) || new_g < g_scores[&neighbor_pos] {
|
||||
if new_g < *g_scores.get(&neighbor_pos).unwrap_or(&i32::MAX) {
|
||||
came_from.insert(neighbor_pos, current);
|
||||
g_scores.insert(neighbor_pos, new_g);
|
||||
let h = manhattan_distance_3d(neighbor_pos, goal);
|
||||
let f = new_g + h;
|
||||
|
||||
open_set.push(PathNode {
|
||||
let neighbor_node = PathNode {
|
||||
position: neighbor_pos,
|
||||
f_score: f,
|
||||
g_score: new_g,
|
||||
});
|
||||
came_from.insert(neighbor_pos, current.position);
|
||||
};
|
||||
open_set.push(neighbor_node);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
println!("No path found");
|
||||
// If no path found, return direct line
|
||||
vec![
|
||||
Vec3::new(start.x as f32, start.y as f32, start.z as f32),
|
||||
Vec3::new(goal.x as f32, goal.y as f32, goal.z as f32),
|
||||
]
|
||||
}
|
||||
|
||||
fn is_walkable(tilemap: &TileMap, position: IVec3) -> bool {
|
||||
if let Some(floor_tile) = tilemap.floor_tiles.get(&position) {
|
||||
floor_tile.2 // walkable
|
||||
} else {
|
||||
false
|
||||
}
|
||||
Vec::new()
|
||||
}
|
||||
|
||||
fn manhattan_distance_3d(a: IVec3, b: IVec3) -> i32 {
|
||||
@@ -346,9 +343,9 @@ pub fn spawn_citizens(mut commands: Commands, asset_server: Res<AssetServer>) {
|
||||
|
||||
// Spawn a handful of citizens
|
||||
for _ in 0..1 {
|
||||
let x: f32 = rng.gen_range(-15.0..10.0);
|
||||
let y: f32 = rng.gen_range(-10.0..10.0);
|
||||
let mut position = Vec3::new(x.round(), y.round(), 5.0) * TILE_SIZE;
|
||||
let x: f32 = rng.gen_range(-8.0..8.0);
|
||||
let y: f32 = rng.gen_range(-8.0..8.0);
|
||||
let mut position = Vec3::new(x.round(), y.round(), 30.0) * TILE_SIZE;
|
||||
position.z += 0.1;
|
||||
|
||||
commands.spawn(Citizen::new(&asset_server, position));
|
||||
|
||||
+1
-1
@@ -53,7 +53,7 @@ fn main() {
|
||||
tile::camera_z_movement,
|
||||
tile::update_tile_visibility
|
||||
.run_if(|camera_moved: Res<tile::CameraMoved>| camera_moved.0),
|
||||
// log_fps_system,
|
||||
log_fps_system,
|
||||
),
|
||||
)
|
||||
.run();
|
||||
|
||||
+8
-8
@@ -84,7 +84,7 @@ pub fn calculate_visibility(pos: IVec3, tilemap: &TileMap) -> [u32; 8] {
|
||||
|
||||
let mut is_occluded = false;
|
||||
|
||||
'vertical_check: for z_offset in 1..5 {
|
||||
'vertical_check: for z_offset in 1..10 {
|
||||
let above_pos = IVec3::new(pos.x, pos.y, pos.z + (z_offset * ITILE_SIZE));
|
||||
if above_pos.z <= camera_z {
|
||||
if let Some(&(_, opaque, _, _, _)) = tilemap.floor_tiles.get(&above_pos) {
|
||||
@@ -93,10 +93,10 @@ pub fn calculate_visibility(pos: IVec3, tilemap: &TileMap) -> [u32; 8] {
|
||||
break 'vertical_check;
|
||||
}
|
||||
}
|
||||
if z_offset >= 4 {
|
||||
is_occluded = true;
|
||||
break 'vertical_check;
|
||||
}
|
||||
// if z_offset >= 4 {
|
||||
// is_occluded = true;
|
||||
// break 'vertical_check;
|
||||
// }
|
||||
// if let Some(&(_, solid, _)) = tilemap.fixtures.get(&above_pos) {
|
||||
// if solid {
|
||||
// is_occluded = true;
|
||||
@@ -198,7 +198,7 @@ fn handle_chunk_loading(
|
||||
);
|
||||
|
||||
// Spawn tiles and add them to tilemap
|
||||
for z in -15..=5 {
|
||||
for z in -10..=5 {
|
||||
let position = Vec3::new(
|
||||
(world_x as f32 * TILE_SIZE).round(),
|
||||
(world_y as f32 * TILE_SIZE).round(),
|
||||
@@ -287,8 +287,8 @@ fn handle_chunk_loading(
|
||||
|
||||
fn setup_initial_chunks(mut event_writer: EventWriter<LoadChunkEvent>) {
|
||||
println!("setup_initial_chunks");
|
||||
for x in -8..=8 {
|
||||
for y in -8..=8 {
|
||||
for x in -3..=3 {
|
||||
for y in -2..=2 {
|
||||
event_writer.send(LoadChunkEvent {
|
||||
chunk_position: IVec2::new(x, y),
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user