finally understand how to remap coordinates between LDtk and Bevy 😵‍💫

This commit is contained in:
Roberto Maurizzi 2025-07-17 17:00:51 +08:00
parent ff9007c0f2
commit 2f727e403d
Signed by: robm
GPG key ID: F26E59AFAAADEA55

View file

@ -6,7 +6,11 @@ use bevy::{
prelude::*, prelude::*,
window::PrimaryWindow, window::PrimaryWindow,
}; };
use bevy_ecs_ldtk::{ldtk::NeighbourLevel, prelude::*}; use bevy_ecs_ldtk::{
ldtk::{NeighbourLevel, TileInstance},
prelude::*,
utils::int_grid_index_to_grid_coords,
};
use crate::{ use crate::{
asset_tracking::LoadResource, audio::music, demo::player::PlayerAssets, screens::Screen, asset_tracking::LoadResource, audio::music, demo::player::PlayerAssets, screens::Screen,
@ -17,6 +21,7 @@ use super::player::Player;
pub(super) fn plugin(app: &mut App) { pub(super) fn plugin(app: &mut App) {
app.add_plugins(LdtkPlugin) app.add_plugins(LdtkPlugin)
.insert_resource(LevelSelection::iid("d53f9950-c640-11ed-8430-4942c04951ff")) .insert_resource(LevelSelection::iid("d53f9950-c640-11ed-8430-4942c04951ff"))
// .insert_resource(LevelSelection::iid("34f51d20-8990-11ee-b0d1-cfeb0e9e30f6"))
.insert_resource(LdtkSettings { .insert_resource(LdtkSettings {
level_spawn_behavior: LevelSpawnBehavior::UseWorldTranslation { level_spawn_behavior: LevelSpawnBehavior::UseWorldTranslation {
load_level_neighbors: true, load_level_neighbors: true,
@ -40,7 +45,8 @@ pub(super) fn plugin(app: &mut App) {
level_selection_follow_player, level_selection_follow_player,
cache_wall_locations, cache_wall_locations,
pan_camera, pan_camera,
), )
.chain(),
); );
} }
@ -64,6 +70,7 @@ impl FromWorld for LevelAssets {
Self { Self {
music: assets.load("audio/music/Fluffing A Duck.ogg"), music: assets.load("audio/music/Fluffing A Duck.ogg"),
world: assets.load("levels/world.ldtk").into(), world: assets.load("levels/world.ldtk").into(),
// world: assets.load("levels/tile-based-game.ldtk").into(),
// world: assets.load("levels/collectathon.ldtk").into(), // world: assets.load("levels/collectathon.ldtk").into(),
} }
} }
@ -97,11 +104,13 @@ fn translate_grid_coords_entities(
// TODO: what is this used for? Why it doesn't work for a moving Player? // TODO: what is this used for? Why it doesn't work for a moving Player?
for (mut transform, grid_coords) in grid_coords_entities.iter_mut() { for (mut transform, grid_coords) in grid_coords_entities.iter_mut() {
info!("Changed GridCoords: {grid_coords:?}"); info!("Changed GridCoords: {grid_coords:?}");
info!("Previous traslation: {:?}", transform.translation);
transform.translation = (bevy_ecs_ldtk::utils::grid_coords_to_translation( transform.translation = (bevy_ecs_ldtk::utils::grid_coords_to_translation(
*grid_coords, *grid_coords,
IVec2::splat(GRID_SIZE), IVec2::splat(GRID_SIZE),
)) ))
.extend(transform.translation.z); .extend(transform.translation.z);
info!("Updated traslation: {:?}", transform.translation);
} }
} }
@ -155,6 +164,8 @@ pub fn convert_neighbors(
#[reflect(Resource)] #[reflect(Resource)]
pub struct LevelWalls { pub struct LevelWalls {
wall_locations: HashSet<GridCoords>, wall_locations: HashSet<GridCoords>,
level_tile_pos_x: i32,
level_tile_pos_y: i32,
level_width: i32, level_width: i32,
level_height: i32, level_height: i32,
level_neighbours: HashMap<Direction, LevelIid>, level_neighbours: HashMap<Direction, LevelIid>,
@ -177,18 +188,36 @@ impl LevelWalls {
"map for a level that is x: {} by y: {}", "map for a level that is x: {} by y: {}",
self.level_width, self.level_height self.level_width, self.level_height
); );
for y in (0..self.level_height) { info!("player pos: {:?}", player_pos);
println!(
"level world coordinates: x: {} y: {}",
self.level_tile_pos_x, self.level_tile_pos_y
);
// FIXME: kwwp aligned with MultiLevelWalls.in_wall.translated_coords
let player_translated_coords = GridCoords::new(
player_pos.x - self.level_tile_pos_x,
player_pos.y - (-self.level_tile_pos_y - self.level_height),
);
println!("Player absolute coords: {:?}", *player_pos);
println!("Player relative coords: {player_translated_coords:?}");
for y in (0..self.level_height).rev() {
for x in 0..self.level_width { for x in 0..self.level_width {
// let coords = GridCoords::new(x + self.level_tile_pos_x, y + self.level_tile_pos_y);
let coords = GridCoords::new(x, y); let coords = GridCoords::new(x, y);
if coords == *player_pos { if coords.x == 0 {
print!("[X :{:03} Y: {:03}] ", coords.x, coords.y);
}
if coords == player_translated_coords {
print!("@"); print!("@");
} else if self.in_wall(&coords) { } else if self.in_wall(&coords) {
print!("X"); print!("X");
} else { } else {
print!("_"); print!("_");
} }
if coords.x == (self.level_width - 1) {
println!("[X :{:03} Y: {:03}] ", coords.x, coords.y);
}
} }
println!(" [y: {y:02}]");
} }
} }
} }
@ -201,7 +230,23 @@ pub struct MultiLevelWalls {
impl MultiLevelWalls { impl MultiLevelWalls {
pub fn in_wall(&self, level: &LevelIid, grid_coords: &GridCoords) -> bool { pub fn in_wall(&self, level: &LevelIid, grid_coords: &GridCoords) -> bool {
self.cache[level].in_wall(grid_coords) let translated_coords = GridCoords::new(
// FIXME: x seems to work, y... not so much.
// Level UNDER the "zero level strip" by a level_height has:
// level world coordinates: x: -16 y: 0
// Player absolute coords: GridCoords { x: -8, y: -1 } y should be 15
// Player relative coords: GridCoords { x: 8, y: -1 }
// Level ABOVE the "zero level strip" by a level_height has:
// level world coordinates: x: 32 y: -32
// Player absolute coords: GridCoords { x: 42, y: 17 }
// Player relative coords: GridCoords { x: 10, y: 17 } y should be 1
// level world coordinates: x: 32 y: -16
// Player absolute coords: GridCoords { x: 41, y: 16 }
// Player relative coords: GridCoords { x: 9, y: 16 } y should be 0
grid_coords.x - self.cache[level].level_tile_pos_x,
grid_coords.y - (-self.cache[level].level_tile_pos_y - self.cache[level].level_height),
);
self.cache[level].in_wall(&translated_coords)
} }
pub fn debug_collisions(&self, level: &LevelIid, player_pos: &GridCoords) { pub fn debug_collisions(&self, level: &LevelIid, player_pos: &GridCoords) {
if let Some(level) = self.cache.get(level) { if let Some(level) = self.cache.get(level) {
@ -218,68 +263,27 @@ pub fn spawn_level(
window: Single<&Window, With<PrimaryWindow>>, window: Single<&Window, With<PrimaryWindow>>,
level_assets: Res<LevelAssets>, level_assets: Res<LevelAssets>,
) { ) {
let half_size = window.size() / 2.0; // let half_size = window.size() / 2.0;
commands.spawn(( commands.spawn((
Name::new("Ldtk level"), Name::new("Ldtk level"),
StateScoped(Screen::Gameplay), StateScoped(Screen::Gameplay),
LdtkWorldBundle { LdtkWorldBundle {
ldtk_handle: level_assets.world.clone(), ldtk_handle: level_assets.world.clone(),
transform: Transform::from_xyz(-half_size.x, half_size.y, 0.0), // transform: Transform::from_xyz(-half_size.x, half_size.y, 0.0),
..Default::default() ..Default::default()
}, },
)); ));
} }
fn _old_cache_wall_locations(
level_selection: Res<LevelSelection>,
mut level_walls: ResMut<LevelWalls>,
mut level_events: EventReader<LevelEvent>,
walls: Query<&GridCoords, With<Wall>>,
ldtk_project_entities: Query<&LdtkProjectHandle>,
ldtk_project_assets: Res<Assets<LdtkProject>>,
) -> Result {
for level_event in level_events.read() {
if let LevelEvent::Spawned(level_iid) = level_event {
let ldtk_project = ldtk_project_assets
.get(ldtk_project_entities.single()?)
.expect("LdtkProject should be loaded when level is spawned");
let level = ldtk_project
.get_raw_level_by_iid(level_iid.get())
.expect("spawned level should exist in project");
let wall_locations = walls.iter().copied().collect();
info!(
"loading level of dimension x: {} by y: {}",
level.px_wid, level.px_hei
);
let new_level_walls = LevelWalls {
wall_locations,
level_width: level.px_wid / GRID_SIZE,
level_height: level.px_hei / GRID_SIZE,
..Default::default()
};
*level_walls = new_level_walls;
info!(
"new level tile dimensions are x: {} y {}",
level_walls.level_width, level_walls.level_height
);
level_walls.debug_collisions(&GridCoords::default());
}
}
Ok(())
}
fn cache_wall_locations( fn cache_wall_locations(
mut levels_wall_cache: ResMut<MultiLevelWalls>, mut levels_wall_cache: ResMut<MultiLevelWalls>,
mut level_events: EventReader<LevelEvent>, mut level_events: EventReader<LevelEvent>,
walls: Query<(&ChildOf, &GridCoords), With<Wall>>,
ldtk_project_entities: Query<&LdtkProjectHandle>, ldtk_project_entities: Query<&LdtkProjectHandle>,
ldtk_project_assets: Res<Assets<LdtkProject>>, ldtk_project_assets: Res<Assets<LdtkProject>>,
) -> Result { ) -> Result {
let multi_level_walls = levels_wall_cache.into_inner(); let multi_level_walls = levels_wall_cache.into_inner();
let pippo: TileInstance = TileInstance::default();
for level_event in level_events.read() { for level_event in level_events.read() {
if let LevelEvent::Spawned(level_iid) = level_event { if let LevelEvent::Spawned(level_iid) = level_event {
@ -291,47 +295,60 @@ fn cache_wall_locations(
.expect("spawned level should exist in project"); .expect("spawned level should exist in project");
let mut wall_locations = HashSet::<GridCoords>::default(); let mut wall_locations = HashSet::<GridCoords>::default();
info!("current level neighbours: {:?}", level.neighbours); let mut last_coord: GridCoords = GridCoords::default();
trace!("current level neighbours: {:?}", level.neighbours);
trace!(
"Level world coordinates: x [{}], y[{}]",
level.world_x, level.world_y
);
let level_tile_pos_x = level.world_x / GRID_SIZE;
let level_tile_pos_y = level.world_y / GRID_SIZE;
let level_tile_width = level.px_wid / GRID_SIZE;
trace!(
"Level tile coordinates: x [{}], y[{}]",
level_tile_pos_x, level_tile_pos_y
);
if let Some(layers) = level.layer_instances.clone() { if let Some(layers) = level.layer_instances.clone() {
// info!("layers: {:?}", layers); // info!("layers: {:?}", layers);
layers.iter().for_each(|field| { layers.iter().for_each(|field| {
info!("Layer field: {:?}", field.identifier); info!("Layer field: {:?}", field.identifier);
if field.identifier == "Walls" { if field.identifier == "Walls" {
info!("Found walls layer: {:?}", field.int_grid_csv); info!("Found walls layer: {:?}", field.int_grid_csv);
info!("Trying to format it"); for (i, value) in field.int_grid_csv.iter().enumerate() {
// FIXME: a .rev() here? It doesn't look necessary from what gets printed let gc = int_grid_index_to_grid_coords(
// remember to fix the supposed "map dragging" too i,
for y in (0..(level.px_hei / GRID_SIZE)) { (level.px_wid / GRID_SIZE) as u32,
for x in (0..(level.px_wid / GRID_SIZE)) { (level.px_hei / GRID_SIZE) as u32,
let index = (y * level.px_wid / GRID_SIZE + x) as usize; );
if let Some(value) = field.int_grid_csv.get(index) { let Some(gc) = gc else {
warn!("Invalid GridCoords for index {i}");
continue;
};
last_coord = GridCoords::new(gc.x, gc.y);
if last_coord.x == 0 {
print!("[X :{:03} Y: {:03}] ", last_coord.x, last_coord.y);
}
if *value == 1 { if *value == 1 {
print!("X"); print!("X");
wall_locations.insert(GridCoords::new(x, y)); wall_locations.insert(last_coord);
} else { } else {
print!("_"); print!("_");
} }
if last_coord.x == (level_tile_width - 1) {
println!(" [X :{:03} Y: {:03}]", last_coord.x, last_coord.y);
} }
} }
println!(" [y: {y:02}]");
}
} }
}); });
} }
// level.iter_fields().for_each(|field| {
// info!("Field: {:?}", field);
// });
// let wall_locations = walls.iter().map(|e| e.1).copied().collect();
info!(
"loading level of dimension x: {} by y: {}",
level.px_wid, level.px_hei
);
multi_level_walls.cache.insert( multi_level_walls.cache.insert(
level_iid.clone(), // You'll need to clone the key since HashMap takes ownership level_iid.clone(), // You'll need to clone the key since HashMap takes ownership
LevelWalls { LevelWalls {
wall_locations, wall_locations,
level_tile_pos_x,
level_tile_pos_y,
level_width: level.px_wid / GRID_SIZE, level_width: level.px_wid / GRID_SIZE,
level_height: level.px_hei / GRID_SIZE, level_height: level.px_hei / GRID_SIZE,
level_neighbours: convert_neighbors(&level.neighbours).unwrap_or_default(), // Convert neighbours to a HashMap level_neighbours: convert_neighbors(&level.neighbours).unwrap_or_default(), // Convert neighbours to a HashMap
@ -344,7 +361,6 @@ fn cache_wall_locations(
multi_level_walls.cache[level_iid].level_height, multi_level_walls.cache[level_iid].level_height,
multi_level_walls.cache[level_iid].level_neighbours, multi_level_walls.cache[level_iid].level_neighbours,
); );
multi_level_walls.cache[level_iid].debug_collisions(&GridCoords::default());
} }
} }
Ok(()) Ok(())