feat: soft entity collision, dead code removal, leaf standability fix
- Add TileOccupancy resource and rebuild_tile_occupancy system to track per-tile entity counts for soft collision avoidance - Add collision_delay to Ambulatory; entities try relative-left step on occupied tiles, wait 1 tick, then push through - Fix leaf canopy standability: leaves are now can_stand_in=true, can_stand_on=false (walkable, not standable-on) - Delete FloorTilePrefab / FixtureTilePrefab / FloorTile / FixtureTile / TileState (all fully dead — TileMap + ChunkData are sole truth) - Delete tile_spawns from TerrainBlob (populated but never consumed) - Delete leaf ghost entity spawn in forestry (orphaned invisible ECS entity) - Replace log prefab spawn with inline commands.spawn(Transform, Visibility) - Add TileMap::remove_fixture for future digging/explosion use - Skip collision avoidance when current tile has >2 entities (handles spawn cluster deadlock)
This commit is contained in:
@@ -151,14 +151,10 @@ impl TilemapBenchmark {
|
||||
}
|
||||
|
||||
/// Runs every frame — samples entity counts and frame timing.
|
||||
pub fn track_benchmark(
|
||||
mut bench: ResMut<TilemapBenchmark>,
|
||||
time: Res<Time>,
|
||||
floor_tiles: Query<(), With<super::FloorTile>>,
|
||||
) {
|
||||
pub fn track_benchmark(mut bench: ResMut<TilemapBenchmark>, time: Res<Time>) {
|
||||
bench.frame_count += 1;
|
||||
bench.frame_times.push(time.delta());
|
||||
bench.floor_tile_count = floor_tiles.iter().count() as u32;
|
||||
bench.floor_tile_count = 0;
|
||||
|
||||
// Trim rolling window to last 300 frames for p99 calculation
|
||||
if bench.frame_times.len() > 300 {
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
pub mod benchmark;
|
||||
pub mod chunk_data;
|
||||
pub mod components;
|
||||
pub mod prefabs;
|
||||
pub mod rendering;
|
||||
pub mod tilemap;
|
||||
pub mod tilemap_chunk;
|
||||
@@ -9,8 +7,6 @@ pub mod visibility;
|
||||
|
||||
pub use benchmark::*;
|
||||
pub use chunk_data::*;
|
||||
pub use components::*;
|
||||
pub use prefabs::*;
|
||||
pub use tilemap::*;
|
||||
pub use tilemap_chunk::*;
|
||||
pub use visibility::*;
|
||||
|
||||
+4
-195
@@ -1,195 +1,4 @@
|
||||
use bevy::prelude::*;
|
||||
|
||||
use crate::{
|
||||
config::TileRegistry,
|
||||
world::{
|
||||
tiles::{FixtureTile, FloorTile, TileState},
|
||||
FIXTURE_ID_OFFSET,
|
||||
},
|
||||
};
|
||||
|
||||
#[derive(Bundle)]
|
||||
pub struct FloorTilePrefab {
|
||||
transform: Transform,
|
||||
tile: FloorTile,
|
||||
tile_state: TileState,
|
||||
visibility: Visibility,
|
||||
}
|
||||
|
||||
impl FloorTilePrefab {
|
||||
pub fn grass(position: Vec3) -> Self {
|
||||
let def = TileRegistry::global().floor("grass");
|
||||
FloorTilePrefab {
|
||||
transform: Transform::from_translation(position),
|
||||
tile: FloorTile {
|
||||
id: def.id as u32,
|
||||
opaque: !def.transparent,
|
||||
walkable: def.can_stand_on,
|
||||
astar_weight: def.astar_weight,
|
||||
visible_range: [0; 8],
|
||||
},
|
||||
tile_state: TileState {
|
||||
timer: Timer::from_seconds(1.0, TimerMode::Repeating),
|
||||
},
|
||||
visibility: Visibility::Hidden,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn dirt(position: Vec3) -> Self {
|
||||
let def = TileRegistry::global().floor("dirt");
|
||||
FloorTilePrefab {
|
||||
transform: Transform::from_translation(position),
|
||||
tile: FloorTile {
|
||||
id: def.id as u32,
|
||||
opaque: !def.transparent,
|
||||
walkable: def.can_stand_on,
|
||||
astar_weight: def.astar_weight,
|
||||
visible_range: [0; 8],
|
||||
},
|
||||
tile_state: TileState {
|
||||
timer: Timer::from_seconds(1.0, TimerMode::Repeating),
|
||||
},
|
||||
visibility: Visibility::Hidden,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn rock(position: Vec3) -> Self {
|
||||
let def = TileRegistry::global().floor("rock");
|
||||
FloorTilePrefab {
|
||||
transform: Transform::from_translation(position),
|
||||
tile: FloorTile {
|
||||
id: def.id as u32,
|
||||
opaque: !def.transparent,
|
||||
walkable: def.can_stand_on,
|
||||
astar_weight: def.astar_weight,
|
||||
visible_range: [0; 8],
|
||||
},
|
||||
tile_state: TileState {
|
||||
timer: Timer::from_seconds(1.0, TimerMode::Repeating),
|
||||
},
|
||||
visibility: Visibility::Hidden,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn air(position: Vec3) -> Self {
|
||||
let def = TileRegistry::global().floor("air");
|
||||
FloorTilePrefab {
|
||||
transform: Transform::from_translation(position),
|
||||
tile: FloorTile {
|
||||
id: def.id as u32,
|
||||
opaque: !def.transparent,
|
||||
walkable: def.can_stand_on,
|
||||
astar_weight: def.astar_weight,
|
||||
visible_range: [0; 8],
|
||||
},
|
||||
tile_state: TileState {
|
||||
timer: Timer::from_seconds(1.0, TimerMode::Repeating),
|
||||
},
|
||||
visibility: Visibility::Hidden,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn bedrock(position: Vec3) -> Self {
|
||||
let def = TileRegistry::global().floor("bedrock");
|
||||
FloorTilePrefab {
|
||||
transform: Transform::from_translation(position),
|
||||
tile: FloorTile {
|
||||
id: def.id as u32,
|
||||
opaque: !def.transparent,
|
||||
walkable: def.can_stand_on,
|
||||
astar_weight: def.astar_weight,
|
||||
visible_range: [0; 8],
|
||||
},
|
||||
tile_state: TileState {
|
||||
timer: Timer::from_seconds(1.0, TimerMode::Repeating),
|
||||
},
|
||||
visibility: Visibility::Hidden,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn spawn(self, commands: &mut Commands) -> Entity {
|
||||
commands
|
||||
.spawn((self.tile, self.transform, self.tile_state, self.visibility))
|
||||
.id()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Bundle)]
|
||||
pub struct FixtureTilePrefab {
|
||||
transform: Transform,
|
||||
tile: FixtureTile,
|
||||
visibility: Visibility,
|
||||
}
|
||||
|
||||
impl FixtureTilePrefab {
|
||||
pub fn dirt_wall(position: Vec3) -> Self {
|
||||
let def = TileRegistry::global().fixture("dirt_wall");
|
||||
FixtureTilePrefab {
|
||||
transform: Transform::from_translation(position),
|
||||
tile: FixtureTile {
|
||||
id: FIXTURE_ID_OFFSET + def.id,
|
||||
solid: def.solid,
|
||||
visible_range: [0; 8],
|
||||
},
|
||||
visibility: Visibility::Hidden,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn rock_wall(position: Vec3) -> Self {
|
||||
let def = TileRegistry::global().fixture("rock_wall");
|
||||
FixtureTilePrefab {
|
||||
transform: Transform::from_translation(position),
|
||||
tile: FixtureTile {
|
||||
id: FIXTURE_ID_OFFSET + def.id,
|
||||
solid: def.solid,
|
||||
visible_range: [0; 8],
|
||||
},
|
||||
visibility: Visibility::Hidden,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn log(position: Vec3) -> Self {
|
||||
let def = TileRegistry::global().fixture("log");
|
||||
FixtureTilePrefab {
|
||||
transform: Transform::from_translation(position),
|
||||
tile: FixtureTile {
|
||||
id: FIXTURE_ID_OFFSET + def.id,
|
||||
solid: def.solid,
|
||||
visible_range: [0; 8],
|
||||
},
|
||||
visibility: Visibility::Hidden,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn leaves(position: Vec3) -> Self {
|
||||
let def = TileRegistry::global().fixture("leaves");
|
||||
FixtureTilePrefab {
|
||||
transform: Transform::from_translation(position),
|
||||
tile: FixtureTile {
|
||||
id: FIXTURE_ID_OFFSET + def.id,
|
||||
solid: def.solid,
|
||||
visible_range: [0; 8],
|
||||
},
|
||||
visibility: Visibility::Hidden,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn bedrock_wall(position: Vec3) -> Self {
|
||||
let def = TileRegistry::global().fixture("bedrock_wall");
|
||||
FixtureTilePrefab {
|
||||
transform: Transform::from_translation(position),
|
||||
tile: FixtureTile {
|
||||
id: FIXTURE_ID_OFFSET + def.id,
|
||||
solid: def.solid,
|
||||
visible_range: [0; 8],
|
||||
},
|
||||
visibility: Visibility::Hidden,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn spawn(self, commands: &mut Commands) -> Entity {
|
||||
commands
|
||||
.spawn((self.tile, self.transform, self.visibility))
|
||||
.id()
|
||||
}
|
||||
}
|
||||
// Prefab types (FloorTilePrefab, FixtureTilePrefab) were deleted — they were
|
||||
// entirely dead code. FloorTile and FixtureTile ECS components (in the former
|
||||
// components.rs) were also never read. The TileMap + ChunkData bitsets are the
|
||||
// sole source of truth for standability.
|
||||
|
||||
@@ -237,6 +237,24 @@ impl TileMap {
|
||||
.unwrap_or(100)
|
||||
}
|
||||
|
||||
/// Remove a fixture tile, clearing both the HashMap entry and ChunkData bitsets.
|
||||
/// BOTH must be cleared — leaving ChunkData stale causes is_standable bugs.
|
||||
pub fn remove_fixture(&mut self, pos: &IVec3) -> Option<FixtureTileData> {
|
||||
use super::chunk_data::ChunkData;
|
||||
|
||||
let removed = self.fixture_tiles.remove(pos);
|
||||
let chunk_pos = world_to_chunk(*pos);
|
||||
if let Some(chunk) = self.chunks.get_mut(&chunk_pos) {
|
||||
let (lx, ly, z) = ChunkData::world_to_local(*pos);
|
||||
let idx = ChunkData::pos_to_index(lx, ly, z);
|
||||
let word = idx / 32;
|
||||
let clear_mask = !(1u32 << (idx % 32));
|
||||
chunk.stand_in_fixture[word] &= clear_mask;
|
||||
chunk.stand_on_fixture[word] &= clear_mask;
|
||||
}
|
||||
removed
|
||||
}
|
||||
|
||||
/// Remove all tile data for a specific chunk from the TileMap.
|
||||
/// Iterates all positions in the chunk volume and removes from HashMaps.
|
||||
/// Used during chunk unloading to clean up tile data.
|
||||
|
||||
@@ -280,7 +280,7 @@ pub fn spawn_tilemap_chunks(
|
||||
let half_chunk = (CHUNK_SIZE_TILE as f32) / 2.0 - TILE_SIZE / 2.0;
|
||||
let world_x = (key.chunk_pos.x as f32) * (CHUNK_SIZE_TILE as f32) + half_chunk;
|
||||
let world_y = (key.chunk_pos.y as f32) * (CHUNK_SIZE_TILE as f32) + half_chunk;
|
||||
let z_depth = -(Z_BELOW - key.z_index as f32) * TILE_SIZE;
|
||||
let z_depth = (key.z_index as f32 - Z_BELOW) * TILE_SIZE;
|
||||
|
||||
let visible = z_diff >= 0 && z_diff <= 8;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user