first committ, still fighting with... a lot! 😅
This commit is contained in:
commit
495556500f
74 changed files with 18135 additions and 0 deletions
379
src/demo/level.rs
Normal file
379
src/demo/level.rs
Normal file
|
@ -0,0 +1,379 @@
|
|||
//! Spawn the main level.
|
||||
|
||||
use bevy::{
|
||||
input::mouse::AccumulatedMouseMotion,
|
||||
platform::collections::{HashMap, HashSet},
|
||||
prelude::*,
|
||||
window::PrimaryWindow,
|
||||
};
|
||||
use bevy_ecs_ldtk::{ldtk::NeighbourLevel, prelude::*};
|
||||
|
||||
use crate::{
|
||||
asset_tracking::LoadResource, audio::music, demo::player::PlayerAssets, screens::Screen,
|
||||
};
|
||||
|
||||
use super::player::Player;
|
||||
|
||||
pub(super) fn plugin(app: &mut App) {
|
||||
app.add_plugins(LdtkPlugin)
|
||||
.insert_resource(LevelSelection::iid("d53f9950-c640-11ed-8430-4942c04951ff"))
|
||||
.insert_resource(LdtkSettings {
|
||||
level_spawn_behavior: LevelSpawnBehavior::UseWorldTranslation {
|
||||
load_level_neighbors: true,
|
||||
},
|
||||
..default()
|
||||
})
|
||||
.init_resource::<LevelWalls>()
|
||||
.init_resource::<MultiLevelWalls>();
|
||||
|
||||
app.register_type::<PanLevel>();
|
||||
app.insert_resource(PanLevel::default());
|
||||
app.register_type::<LevelAssets>();
|
||||
app.load_resource::<LevelAssets>();
|
||||
app.register_type::<LevelWalls>();
|
||||
app.register_type::<MultiLevelWalls>();
|
||||
app.register_ldtk_int_cell::<WallBundle>(1);
|
||||
app.add_systems(
|
||||
Update,
|
||||
(
|
||||
translate_grid_coords_entities,
|
||||
level_selection_follow_player,
|
||||
cache_wall_locations,
|
||||
pan_camera,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
#[derive(Resource, Default, Asset, Clone, Reflect)]
|
||||
#[reflect(Resource)]
|
||||
struct PanLevel {
|
||||
offset: Vec2,
|
||||
}
|
||||
|
||||
#[derive(Resource, Asset, Clone, Reflect)]
|
||||
#[reflect(Resource)]
|
||||
pub struct LevelAssets {
|
||||
#[dependency]
|
||||
music: Handle<AudioSource>,
|
||||
world: LdtkProjectHandle,
|
||||
}
|
||||
|
||||
impl FromWorld for LevelAssets {
|
||||
fn from_world(world: &mut World) -> Self {
|
||||
let assets = world.resource::<AssetServer>();
|
||||
Self {
|
||||
music: assets.load("audio/music/Fluffing A Duck.ogg"),
|
||||
world: assets.load("levels/world.ldtk").into(),
|
||||
// world: assets.load("levels/collectathon.ldtk").into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const GRID_SIZE: i32 = 16;
|
||||
|
||||
fn pan_camera(
|
||||
mut pan: ResMut<PanLevel>,
|
||||
mouse_buttons: Res<ButtonInput<MouseButton>>,
|
||||
mouse_motion: Res<AccumulatedMouseMotion>,
|
||||
) {
|
||||
let delta = mouse_motion.delta;
|
||||
|
||||
if mouse_buttons.pressed(MouseButton::Middle) {
|
||||
pan.offset += delta;
|
||||
info!("pan offset: {}", pan.offset);
|
||||
}
|
||||
}
|
||||
|
||||
fn translate_grid_coords_entities(
|
||||
mut grid_coords_entities: Query<(&mut Transform, &GridCoords), Changed<GridCoords>>,
|
||||
pan: Res<PanLevel>,
|
||||
) {
|
||||
for (mut transform, grid_coords) in grid_coords_entities.iter_mut() {
|
||||
transform.translation = (
|
||||
//pan.offset +
|
||||
bevy_ecs_ldtk::utils::grid_coords_to_translation(*grid_coords, IVec2::splat(GRID_SIZE))
|
||||
)
|
||||
.extend(transform.translation.z);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Component)]
|
||||
struct Wall;
|
||||
|
||||
#[derive(Default, Bundle, LdtkIntCell)]
|
||||
struct WallBundle {
|
||||
wall: Wall,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Eq, Hash, PartialEq, Reflect)]
|
||||
pub enum Direction {
|
||||
#[default]
|
||||
N,
|
||||
E,
|
||||
S,
|
||||
W,
|
||||
}
|
||||
|
||||
impl TryFrom<&str> for Direction {
|
||||
type Error = &'static str;
|
||||
|
||||
fn try_from(value: &str) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
"n" => Ok(Direction::N),
|
||||
"e" => Ok(Direction::E),
|
||||
"s" => Ok(Direction::S),
|
||||
"w" => Ok(Direction::W),
|
||||
_ => Err("Invalid direction string"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn convert_neighbors(
|
||||
neighbors: &[NeighbourLevel],
|
||||
) -> Result<HashMap<Direction, LevelIid>, &'static str> {
|
||||
info!("got neighbors: {:?}", neighbors);
|
||||
let gino = neighbors
|
||||
.iter()
|
||||
// .map(|neighbor| {
|
||||
// let direction = Direction::try_from(neighbor.dir.as_str())?;
|
||||
// Ok((direction, LevelIid::from(neighbor.level_iid.clone())))
|
||||
// })
|
||||
.filter_map(|neighbor| {
|
||||
Direction::try_from(neighbor.dir.as_str())
|
||||
.ok()
|
||||
.map(|dir| (dir, LevelIid::from(neighbor.level_iid.clone())))
|
||||
})
|
||||
.collect();
|
||||
info!("converted to: {:?}", gino);
|
||||
Ok(gino)
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Resource, Reflect)]
|
||||
#[reflect(Resource)]
|
||||
pub struct LevelWalls {
|
||||
wall_locations: HashSet<GridCoords>,
|
||||
level_width: i32,
|
||||
level_height: i32,
|
||||
level_neighbours: HashMap<Direction, LevelIid>,
|
||||
}
|
||||
|
||||
impl LevelWalls {
|
||||
pub fn in_wall(&self, grid_coords: &GridCoords) -> bool {
|
||||
grid_coords.x < 0
|
||||
|| grid_coords.y < 0
|
||||
|| grid_coords.x >= self.level_width
|
||||
|| grid_coords.y >= self.level_height
|
||||
|| self.wall_locations.contains(grid_coords)
|
||||
}
|
||||
|
||||
pub fn debug_collisions(&self, player_pos: &GridCoords) {
|
||||
info!(
|
||||
"map for a level that is x: {} by y: {}",
|
||||
self.level_width, self.level_height
|
||||
);
|
||||
for y in (0..self.level_height) {
|
||||
for x in 0..self.level_width {
|
||||
let coords = GridCoords::new(x, y);
|
||||
if coords == *player_pos {
|
||||
print!("@");
|
||||
} else if self.in_wall(&coords) {
|
||||
print!("X");
|
||||
} else {
|
||||
print!("_");
|
||||
}
|
||||
}
|
||||
println!();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Resource, Reflect)]
|
||||
#[reflect(Resource)]
|
||||
pub struct MultiLevelWalls {
|
||||
cache: HashMap<LevelIid, LevelWalls>,
|
||||
}
|
||||
|
||||
impl MultiLevelWalls {
|
||||
pub fn in_wall(&self, level: &LevelIid, grid_coords: &GridCoords) -> bool {
|
||||
self.cache[level].in_wall(grid_coords)
|
||||
}
|
||||
pub fn debug_collisions(&self, level: &LevelIid, player_pos: &GridCoords) {
|
||||
if let Some(level) = self.cache.get(level) {
|
||||
level.debug_collisions(player_pos);
|
||||
} else {
|
||||
warn!("No walls cached for level: {:?}", level);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A system that spawns the main level.
|
||||
pub fn spawn_level(
|
||||
mut commands: Commands,
|
||||
window: Single<&Window, With<PrimaryWindow>>,
|
||||
level_assets: Res<LevelAssets>,
|
||||
) {
|
||||
let half_size = window.size() / 2.0;
|
||||
|
||||
commands.spawn((
|
||||
Name::new("Ldtk level"),
|
||||
LdtkWorldBundle {
|
||||
ldtk_handle: level_assets.world.clone(),
|
||||
transform: Transform::from_xyz(-half_size.x, half_size.y, 0.0),
|
||||
..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(
|
||||
mut levels_wall_cache: ResMut<MultiLevelWalls>,
|
||||
mut level_events: EventReader<LevelEvent>,
|
||||
walls: Query<(&ChildOf, &GridCoords), With<Wall>>,
|
||||
ldtk_project_entities: Query<&LdtkProjectHandle>,
|
||||
ldtk_project_assets: Res<Assets<LdtkProject>>,
|
||||
) -> Result {
|
||||
let multi_level_walls = levels_wall_cache.into_inner();
|
||||
|
||||
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 mut wall_locations = HashSet::<GridCoords>::default();
|
||||
info!("current level neighbours: {:?}", level.neighbours);
|
||||
if let Some(layers) = level.layer_instances.clone() {
|
||||
// info!("layers: {:?}", layers);
|
||||
layers.iter().for_each(|field| {
|
||||
info!("Layer field: {:?}", field.identifier);
|
||||
if field.identifier == "Walls" {
|
||||
info!("Found walls layer: {:?}", field.int_grid_csv);
|
||||
info!("Trying to format it");
|
||||
// FIXME: a .rev() here? It doesn't look necessary from what gets printed
|
||||
// remember to fix the supposed "map dragging" too
|
||||
for y in (0..(level.px_hei / GRID_SIZE)).rev() {
|
||||
for x in (0..(level.px_wid / GRID_SIZE)) {
|
||||
let index = (y * level.px_wid / GRID_SIZE + x) as usize;
|
||||
if let Some(value) = field.int_grid_csv.get(index) {
|
||||
if *value == 1 {
|
||||
print!("X");
|
||||
wall_locations.insert(GridCoords::new(x, y));
|
||||
} else {
|
||||
print!("_");
|
||||
}
|
||||
}
|
||||
}
|
||||
println!();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
// 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(
|
||||
level_iid.clone(), // You'll need to clone the key since HashMap takes ownership
|
||||
LevelWalls {
|
||||
wall_locations,
|
||||
level_width: level.px_wid / GRID_SIZE,
|
||||
level_height: level.px_hei / GRID_SIZE,
|
||||
level_neighbours: convert_neighbors(&level.neighbours).unwrap_or_default(), // Convert neighbours to a HashMap
|
||||
},
|
||||
);
|
||||
|
||||
info!(
|
||||
"new level tile dimensions are x: {} y {}",
|
||||
multi_level_walls.cache[level_iid].level_width,
|
||||
multi_level_walls.cache[level_iid].level_height
|
||||
);
|
||||
multi_level_walls.cache[level_iid].debug_collisions(&GridCoords::default());
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn level_selection_follow_player(
|
||||
players: Query<&GlobalTransform, With<Player>>,
|
||||
levels: Query<(&LevelIid, &GlobalTransform)>,
|
||||
ldtk_projects: Query<&LdtkProjectHandle>,
|
||||
ldtk_project_assets: Res<Assets<LdtkProject>>,
|
||||
mut level_selection: ResMut<LevelSelection>,
|
||||
) -> Result {
|
||||
if let Ok(player_transform) = players.single() {
|
||||
let ldtk_project = ldtk_project_assets
|
||||
.get(ldtk_projects.single()?)
|
||||
.expect("ldtk project should be loaded before player is spawned");
|
||||
|
||||
for (level_iid, level_transform) in levels.iter() {
|
||||
let level = ldtk_project
|
||||
.get_raw_level_by_iid(level_iid.get())
|
||||
.expect("level should exist in only project");
|
||||
|
||||
let level_bounds = Rect {
|
||||
min: Vec2::new(
|
||||
level_transform.translation().x,
|
||||
level_transform.translation().y,
|
||||
),
|
||||
max: Vec2::new(
|
||||
level_transform.translation().x + level.px_wid as f32,
|
||||
level_transform.translation().y + level.px_hei as f32,
|
||||
),
|
||||
};
|
||||
|
||||
if level_bounds.contains(player_transform.translation().truncate()) {
|
||||
*level_selection = LevelSelection::Iid(level_iid.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue