feat: data-oriented chunks optimization with tile registry
- Phase 1: Bit-packed standability (ChunkData with bitsets) - Phase 2: Reactive connectivity (dirty chunks) - Phase 3: Async terrain baking (AsyncComputeTaskPool) - Pathfinding weight system (rock=50 preferred, bedrock=150 avoided) - Movement speed affected by tile weight - External tiles.toml for hot-reloadable tile definitions - TileRegistry singleton for async-safe tile lookups - Fixed world_to_chunk to use CHUNK_SIZE_TILE (128) not CHUNK_SIZE (8) - Fixed infinite spawner with Local<bool> state guards - Fixed spawn coordinate grid alignment Note: Zigzag pathfinding bug introduced - needs investigation
This commit is contained in:
@@ -3,6 +3,7 @@ use crate::constants::TILE_SIZE;
|
||||
use crate::constants::*;
|
||||
use crate::entities::item::{spawn_prefab, MiscPrefab};
|
||||
use crate::entities::shared_components::Ambulatory;
|
||||
use crate::game::SpawnDelay;
|
||||
use crate::world::tiles::tilemap;
|
||||
use crate::world::VisibleGameEntity;
|
||||
use bevy::prelude::*;
|
||||
@@ -36,7 +37,7 @@ impl Pig {
|
||||
},
|
||||
transform: Transform::from_translation(position).with_scale(Vec3::splat(PIXEL_RATIO)),
|
||||
visibility: Visibility::Hidden,
|
||||
drop_timer: PigDropTimer(Timer::from_seconds(5.0, TimerMode::Repeating)), // NEW
|
||||
drop_timer: PigDropTimer(Timer::from_seconds(5.0, TimerMode::Repeating)),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -49,18 +50,30 @@ pub fn spawn_pigs(
|
||||
asset_server: Res<AssetServer>,
|
||||
mut rng_q: Query<&mut WyRand, With<GlobalRng>>,
|
||||
config: Res<GameConfig>,
|
||||
mut delay: ResMut<SpawnDelay>,
|
||||
mut has_spawned: Local<bool>,
|
||||
) {
|
||||
if *has_spawned {
|
||||
return;
|
||||
}
|
||||
|
||||
if delay.0 < 60 {
|
||||
delay.0 += 1;
|
||||
return;
|
||||
}
|
||||
|
||||
*has_spawned = true;
|
||||
|
||||
if let Ok(mut rng) = rng_q.single_mut() {
|
||||
for _ in 0..config.spawn_counts.pigs {
|
||||
let raw_x = rng.random_range(-32.0f32..32.0f32);
|
||||
let raw_y = rng.random_range(-32.0f32..32.0f32);
|
||||
let grid_x = (raw_x / TILE_SIZE).round() * TILE_SIZE;
|
||||
let grid_y = (raw_y / TILE_SIZE).round() * TILE_SIZE;
|
||||
let grid_z = 35.0 * TILE_SIZE;
|
||||
|
||||
let pig = commands
|
||||
.spawn(Pig::new(
|
||||
&asset_server,
|
||||
Vec3::new(
|
||||
rng.random_range(-32.0f32..32.0f32).round(),
|
||||
rng.random_range(-32.0f32..32.0f32).round(),
|
||||
35.0,
|
||||
) * TILE_SIZE,
|
||||
))
|
||||
.spawn(Pig::new(&asset_server, Vec3::new(grid_x, grid_y, grid_z)))
|
||||
.id();
|
||||
commands.entity(pig).insert(VisibleGameEntity);
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ use crate::config::GameConfig;
|
||||
use crate::constants::TILE_SIZE;
|
||||
use crate::constants::*;
|
||||
use crate::entities::shared_components::Ambulatory;
|
||||
use crate::game::SpawnDelay;
|
||||
use crate::world::VisibleGameEntity;
|
||||
use bevy::prelude::*;
|
||||
use bevy_rand::prelude::*;
|
||||
@@ -42,17 +43,32 @@ pub fn spawn_rabbits(
|
||||
asset_server: Res<AssetServer>,
|
||||
mut rng_q: Query<&mut WyRand, With<GlobalRng>>,
|
||||
config: Res<GameConfig>,
|
||||
mut delay: ResMut<SpawnDelay>,
|
||||
mut has_spawned: Local<bool>,
|
||||
) {
|
||||
if *has_spawned {
|
||||
return;
|
||||
}
|
||||
|
||||
if delay.0 < 60 {
|
||||
delay.0 += 1;
|
||||
return;
|
||||
}
|
||||
|
||||
*has_spawned = true;
|
||||
|
||||
if let Ok(mut rng) = rng_q.single_mut() {
|
||||
for _ in 0..config.spawn_counts.rabbits {
|
||||
let raw_x = rng.random_range(-32.0f32..32.0f32);
|
||||
let raw_y = rng.random_range(-32.0f32..32.0f32);
|
||||
let grid_x = (raw_x / TILE_SIZE).round() * TILE_SIZE;
|
||||
let grid_y = (raw_y / TILE_SIZE).round() * TILE_SIZE;
|
||||
let grid_z = 35.0 * TILE_SIZE;
|
||||
|
||||
let rab = commands
|
||||
.spawn(Rabbit::new(
|
||||
&asset_server,
|
||||
Vec3::new(
|
||||
rng.random_range(-32.0f32..32.0f32).round(),
|
||||
rng.random_range(-32.0f32..32.0f32).round(),
|
||||
35.0,
|
||||
) * TILE_SIZE,
|
||||
Vec3::new(grid_x, grid_y, grid_z),
|
||||
))
|
||||
.id();
|
||||
commands.entity(rab).insert(VisibleGameEntity);
|
||||
|
||||
@@ -2,6 +2,7 @@ use crate::config::GameConfig;
|
||||
use crate::constants::TILE_SIZE;
|
||||
use crate::constants::*;
|
||||
use crate::entities::shared_components::Ambulatory;
|
||||
use crate::game::SpawnDelay;
|
||||
use crate::world::VisibleGameEntity;
|
||||
use bevy::prelude::*;
|
||||
use bevy_rand::prelude::*;
|
||||
@@ -42,18 +43,30 @@ pub fn spawn_dorfs(
|
||||
asset_server: Res<AssetServer>,
|
||||
mut rng_q: Query<&mut WyRand, With<GlobalRng>>,
|
||||
config: Res<GameConfig>,
|
||||
mut delay: ResMut<SpawnDelay>,
|
||||
mut has_spawned: Local<bool>,
|
||||
) {
|
||||
if *has_spawned {
|
||||
return;
|
||||
}
|
||||
|
||||
if delay.0 < 60 {
|
||||
delay.0 += 1;
|
||||
return;
|
||||
}
|
||||
|
||||
*has_spawned = true;
|
||||
|
||||
if let Ok(mut rng) = rng_q.single_mut() {
|
||||
for _ in 0..config.spawn_counts.dorfs {
|
||||
let raw_x = rng.random_range(-8.0f32..8.0f32);
|
||||
let raw_y = rng.random_range(-8.0f32..8.0f32);
|
||||
let grid_x = (raw_x / TILE_SIZE).round() * TILE_SIZE;
|
||||
let grid_y = (raw_y / TILE_SIZE).round() * TILE_SIZE;
|
||||
let grid_z = 35.0 * TILE_SIZE;
|
||||
|
||||
let cit = commands
|
||||
.spawn(Dorf::new(
|
||||
&asset_server,
|
||||
Vec3::new(
|
||||
rng.random_range(-8.0f32..8.0f32).round(),
|
||||
rng.random_range(-8.0f32..8.0f32).round(),
|
||||
35.0,
|
||||
) * TILE_SIZE,
|
||||
))
|
||||
.spawn(Dorf::new(&asset_server, Vec3::new(grid_x, grid_y, grid_z)))
|
||||
.id();
|
||||
commands.entity(cit).insert(VisibleGameEntity);
|
||||
}
|
||||
|
||||
@@ -507,7 +507,11 @@ pub fn movement(mut query: Query<(&mut Ambulatory, &mut Transform)>, tilemap: Re
|
||||
}
|
||||
|
||||
if ambulatory.walk_speed > 0. {
|
||||
if ambulatory.step_recovery <= ambulatory.walk_speed as u32 {
|
||||
let tile_weight = get_tile_weight(&tilemap, current_pos.as_ivec3());
|
||||
let speed_multiplier = (tile_weight as f32) / 50.0;
|
||||
let threshold = (ambulatory.walk_speed * speed_multiplier) as u32;
|
||||
|
||||
if ambulatory.step_recovery <= threshold {
|
||||
ambulatory.step_recovery += 1;
|
||||
return;
|
||||
} else {
|
||||
@@ -553,18 +557,37 @@ fn is_standable_tile(tilemap: &TileMap, pos: IVec3) -> bool {
|
||||
tilemap.is_standable(pos)
|
||||
}
|
||||
|
||||
fn calculate_movement_cost(move_dir: IVec3) -> i32 {
|
||||
match (
|
||||
/// Get the A* weight for a tile position. Lower is better (faster to traverse).
|
||||
/// Returns 100 (default) if tile not found.
|
||||
#[inline]
|
||||
fn get_tile_weight(tilemap: &TileMap, pos: IVec3) -> u8 {
|
||||
tilemap.get_astar_weight(pos)
|
||||
}
|
||||
|
||||
/// Calculate movement cost including tile weight.
|
||||
/// Base costs: cardinal=10, diagonal=14, vertical~50.
|
||||
/// Tile weight adds: (weight - 50) / 5 to make heavier tiles more costly.
|
||||
fn calculate_movement_cost(move_dir: IVec3, tile_weight: u8) -> i32 {
|
||||
let base_cost = match (
|
||||
move_dir.x.abs() / ITILE_SIZE,
|
||||
move_dir.y.abs() / ITILE_SIZE,
|
||||
move_dir.z.abs() / ITILE_SIZE,
|
||||
) {
|
||||
(1, 0, 0) | (0, 1, 0) => 10,
|
||||
(1, 1, 0) => 14,
|
||||
(1, 0, 1) | (0, 1, 1) => 42,
|
||||
(1, 1, 1) => 56,
|
||||
(1, 0, 0) | (0, 1, 0) => 10, // Cardinal
|
||||
(1, 1, 0) => 14, // Diagonal
|
||||
(1, 0, 1) | (0, 1, 1) => 42, // Vertical + cardinal
|
||||
(1, 1, 1) => 56, // Vertical + diagonal
|
||||
_ => 0,
|
||||
};
|
||||
|
||||
if base_cost == 0 {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Weight cost: normalize so rock(50) adds 0, grass(100) adds 10, bedrock(150) adds 20
|
||||
let weight_cost = (tile_weight as i32).saturating_sub(50) / 5;
|
||||
|
||||
base_cost + weight_cost
|
||||
}
|
||||
|
||||
fn octile_distance_3d(a: IVec3, b: IVec3) -> i32 {
|
||||
@@ -677,7 +700,8 @@ fn calculate_path_with_scratchpad(
|
||||
continue;
|
||||
}
|
||||
|
||||
let movement_cost = calculate_movement_cost(move_dir);
|
||||
let tile_weight = get_tile_weight(tilemap, neighbor_pos);
|
||||
let movement_cost = calculate_movement_cost(move_dir, tile_weight);
|
||||
if movement_cost == 0 {
|
||||
continue;
|
||||
}
|
||||
@@ -770,7 +794,8 @@ pub fn calculate_provisional_path(
|
||||
continue;
|
||||
}
|
||||
|
||||
let movement_cost = calculate_movement_cost(move_dir);
|
||||
let tile_weight = get_tile_weight(tilemap, neighbor_pos);
|
||||
let movement_cost = calculate_movement_cost(move_dir, tile_weight);
|
||||
if movement_cost == 0 {
|
||||
continue;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user