From fd04e1ef242a840d0cd0db00e39dffd8730d476f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Mj=C3=A5land?= Date: Sat, 4 Apr 2026 23:33:19 +0200 Subject: [PATCH 1/3] generate layered tilemaps * Generate tilemaps at different z heights to allow entities to be obscured by terrain --- crates/common/src/systems.rs | 2 +- .../src/chunk_visualisation/plugin.rs | 16 +++++++--- .../src/chunk_visualisation/types.rs | 31 ++++++++++--------- 3 files changed, 30 insertions(+), 19 deletions(-) diff --git a/crates/common/src/systems.rs b/crates/common/src/systems.rs index 74ff678..71224e0 100644 --- a/crates/common/src/systems.rs +++ b/crates/common/src/systems.rs @@ -7,7 +7,7 @@ pub fn apply_world_coordinates( for (mut transform, coordinates) in query.iter_mut() { transform.translation.x = coordinates.0.x * TILE_SIZE.x; transform.translation.y = coordinates.0.y * TILE_SIZE.y; - transform.translation.z = 0.1; + transform.translation.z = coordinates.0.z + 0.1; transform.set_changed(); } } diff --git a/crates/map_generation/src/chunk_visualisation/plugin.rs b/crates/map_generation/src/chunk_visualisation/plugin.rs index 1f75ceb..21bb4c9 100644 --- a/crates/map_generation/src/chunk_visualisation/plugin.rs +++ b/crates/map_generation/src/chunk_visualisation/plugin.rs @@ -54,7 +54,7 @@ pub(crate) fn on_insert( .insert(ChildOf(world_map.entity)); // Setup hashmaps for tilemaps - let mut tilemaps = Tilemaps::default(); + let mut tilemaps = Tilemaps::new(chunk_visualisation_settings.visible_layers); // we iterate over every x and y coordinate of the current chunk and go from the current camera layer downwards visible_layers + 1 layers for x in 0..CHUNK_SIZE.x { @@ -80,7 +80,7 @@ pub(crate) fn on_insert( } } - for (tile_type, map) in &tilemaps.0 { + for ((z, tile_type), map) in &tilemaps.0 { if !map.is_empty() { let tileset_image = match *tile_type { TileType::Full => &tileset.floor_tileset, @@ -88,13 +88,21 @@ pub(crate) fn on_insert( TileType::Animated => &tileset.water_tileset, TileType::Fog => &tileset.fog_tileset, }; - spawn_tile_map(&mut commands, tile_type, map, target, tileset_image); + spawn_tile_map( + &mut commands, + camera_layer.0 - (*z), + tile_type, + map, + target, + tileset_image, + ); } } } fn spawn_tile_map( commands: &mut Commands, + z: i32, tile_type: &TileType, tile_map: &Tilemap, parent_chunk: Entity, @@ -127,7 +135,7 @@ fn spawn_tile_map( texture: TilemapTexture::Single(tileset.clone()), tile_size, anchor: TilemapAnchor::BottomLeft, - transform: Transform::from_xyz(0.0, 0.0, tile_type.z_offset()), + transform: Transform::from_xyz(0.0, 0.0, z as f32 + tile_type.z_offset()), ..default() }) .insert(ChildOf(parent_chunk)); diff --git a/crates/map_generation/src/chunk_visualisation/types.rs b/crates/map_generation/src/chunk_visualisation/types.rs index afcdf05..b71be00 100644 --- a/crates/map_generation/src/chunk_visualisation/types.rs +++ b/crates/map_generation/src/chunk_visualisation/types.rs @@ -44,10 +44,10 @@ impl TileType { /// Returns the tilemap offset based on its type pub(crate) const fn z_offset(&self) -> f32 { match *self { - TileType::Full => -0.5, + TileType::Full => -0.1, TileType::Half => 0.0, - TileType::Animated => -0.5, - TileType::Fog => 1.0, + TileType::Animated => -0.1, + TileType::Fog => 0.2, } } } @@ -133,15 +133,18 @@ pub(crate) enum TilePosType { pub(crate) type Tilemap = HashMap; /// Contains all relevant tilemaps -pub(crate) struct Tilemaps(pub(crate) HashMap); +pub(crate) struct Tilemaps(pub(crate) HashMap<(i32, TileType), Tilemap>); -impl Default for Tilemaps { - fn default() -> Self { +impl Tilemaps { + pub(crate) fn new(layers: i32) -> Self { let mut map = HashMap::new(); - map.insert(TileType::Full, HashMap::new()); - map.insert(TileType::Half, HashMap::new()); - map.insert(TileType::Animated, HashMap::new()); - map.insert(TileType::Fog, HashMap::new()); + for i in 0..=layers { + map.insert((i, TileType::Full), HashMap::new()); + map.insert((i, TileType::Half), HashMap::new()); + map.insert((i, TileType::Animated), HashMap::new()); + map.insert((i, TileType::Fog), HashMap::new()); + } + map.insert((0, TileType::Blocked), HashMap::new()); Tilemaps(map) } } @@ -185,7 +188,7 @@ impl ToTiles for BlockType { // add its state to the flag flags |= solid << index; } - tilemaps.0.entry(TileType::Half).and_modify(|m| { + tilemaps.0.entry((z, TileType::Half)).and_modify(|m| { m.insert( TilePosType::Half((pos.x, pos.y)), TileWrapper::Half((*self, flags)), @@ -194,7 +197,7 @@ impl ToTiles for BlockType { true } BlockType::Solid(_) => { - tilemaps.0.entry(TileType::Full).and_modify(|m| { + tilemaps.0.entry((z, TileType::Full)).and_modify(|m| { m.insert( TilePosType::Full(TilePos::new(pos.x, pos.y)), TileWrapper::Full(*self), @@ -203,7 +206,7 @@ impl ToTiles for BlockType { true } BlockType::Liquid => { - tilemaps.0.entry(TileType::Animated).and_modify(|m| { + tilemaps.0.entry((z, TileType::Animated)).and_modify(|m| { m.insert( TilePosType::Full(TilePos::new(pos.x, pos.y)), TileWrapper::Animated, @@ -212,7 +215,7 @@ impl ToTiles for BlockType { true } BlockType::None if z != 0 => { - tilemaps.0.entry(TileType::Fog).and_modify(|m| { + tilemaps.0.entry((z, TileType::Fog)).and_modify(|m| { m.insert( TilePosType::Full(TilePos::new(pos.x, pos.y)), TileWrapper::Fog(z as f32 / visible_layers), From 18131c11ba0145d0925da471c531b0bfb21c4fd1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Mj=C3=A5land?= Date: Sat, 4 Apr 2026 23:53:05 +0200 Subject: [PATCH 2/3] Draw solid color where view layer has solid blocks * Obscures entities drawn on the layers further down --- assets/tilesets/tileset_blocked.png | Bin 0 -> 211 bytes crates/assets/src/tileset_asset.rs | 8 +++++ .../src/chunk_visualisation/plugin.rs | 1 + .../src/chunk_visualisation/types.rs | 29 +++++++++++++++++- 4 files changed, 37 insertions(+), 1 deletion(-) create mode 100644 assets/tilesets/tileset_blocked.png diff --git a/assets/tilesets/tileset_blocked.png b/assets/tilesets/tileset_blocked.png new file mode 100644 index 0000000000000000000000000000000000000000..0855ae39833a7fe8eb01b0427e219c10f68ed6d4 GIT binary patch literal 211 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1SJ1Ryj={W7>k44ofvPP)Tsw@SkfJR9T^xl z_H+M9WCf{A_6YK2V5m}KU}$JzVE6?TYIwoGP-?)y@G60U!DZ_he10xe`QNZIi( va_WQaq7mvV{e#c5Ff}?T2yn149h}OVp2ASmpmA_IP!ofvtDnm{r-UW|e{D3U literal 0 HcmV?d00001 diff --git a/crates/assets/src/tileset_asset.rs b/crates/assets/src/tileset_asset.rs index 099899a..c455d47 100644 --- a/crates/assets/src/tileset_asset.rs +++ b/crates/assets/src/tileset_asset.rs @@ -7,6 +7,7 @@ use bevy::{ pub struct TilesetAsset { pub soil_tileset: Handle, pub fog_tileset: Handle, + pub blocked_tileset: Handle, pub water_tileset: Handle, pub floor_tileset: Handle, } @@ -14,6 +15,7 @@ pub struct TilesetAsset { impl TilesetAsset { const SOIL_PATH: &'static str = "tilesets/tileset_soil.png"; const FOG_PATH: &'static str = "tilesets/tileset_fog.png"; + const BLOCKED_PATH: &'static str = "tilesets/tileset_blocked.png"; const WATER_PATH: &'static str = "tilesets/tileset_water.png"; const FLOOR_PATH: &'static str = "tilesets/tileset_floors.png"; } @@ -34,6 +36,12 @@ impl FromWorld for TilesetAsset { settings.sampler = ImageSampler::nearest(); }, ), + blocked_tileset: assets.load_with_settings( + TilesetAsset::BLOCKED_PATH, + |settings: &mut ImageLoaderSettings| { + settings.sampler = ImageSampler::nearest(); + }, + ), water_tileset: assets.load_with_settings( TilesetAsset::WATER_PATH, |settings: &mut ImageLoaderSettings| { diff --git a/crates/map_generation/src/chunk_visualisation/plugin.rs b/crates/map_generation/src/chunk_visualisation/plugin.rs index 21bb4c9..5b18d6b 100644 --- a/crates/map_generation/src/chunk_visualisation/plugin.rs +++ b/crates/map_generation/src/chunk_visualisation/plugin.rs @@ -87,6 +87,7 @@ pub(crate) fn on_insert( TileType::Half => &tileset.soil_tileset, TileType::Animated => &tileset.water_tileset, TileType::Fog => &tileset.fog_tileset, + TileType::Blocked => &tileset.blocked_tileset, }; spawn_tile_map( &mut commands, diff --git a/crates/map_generation/src/chunk_visualisation/types.rs b/crates/map_generation/src/chunk_visualisation/types.rs index b71be00..234ee87 100644 --- a/crates/map_generation/src/chunk_visualisation/types.rs +++ b/crates/map_generation/src/chunk_visualisation/types.rs @@ -1,6 +1,10 @@ use std::{collections::HashMap, hash::Hash}; -use bevy::{color::palettes::css::WHITE, ecs::relationship::RelatedSpawnerCommands, prelude::*}; +use bevy::{ + color::palettes::css::{BLACK, WHITE}, + ecs::relationship::RelatedSpawnerCommands, + prelude::*, +}; use bevy_ecs_tilemap::prelude::*; use common::{constants::TILE_SIZE, traits::Neighbors, types::IWorldCoordinates}; @@ -12,6 +16,7 @@ pub(crate) enum TileType { Half, Animated, Fog, + Blocked, } impl TileType { @@ -38,6 +43,7 @@ impl TileType { TileType::Half => "Full-Tile Tilemap", TileType::Animated => "Animated Tilemap", TileType::Fog => "Fog Tilemap", + TileType::Blocked => "Blocked Tilemap", } } @@ -48,6 +54,7 @@ impl TileType { TileType::Half => 0.0, TileType::Animated => -0.1, TileType::Fog => 0.2, + TileType::Blocked => -0.1, } } } @@ -62,6 +69,8 @@ pub(crate) enum TileWrapper { Animated, /// Wrapper for a fog type, carries the opacity in percent (ranging 0.0 to 1.0) Fog(f32), + // Wrapper for a tile that is not visible + Blocked, } impl TileWrapper { @@ -118,6 +127,17 @@ impl TileWrapper { .id(); tile_storage.set(&position, tile_entity); } + (TileWrapper::Blocked, TilePosType::Full(position)) => { + let tile_entity = parent + .spawn(TileBundle { + position, + tilemap_id, + color: TileColor(BLACK.into()), + ..default() + }) + .id(); + tile_storage.set(&position, tile_entity); + } _ => (), } } @@ -194,6 +214,13 @@ impl ToTiles for BlockType { TileWrapper::Half((*self, flags)), ); }); + // Blocks the view of the tile + tilemaps.0.entry((z, TileType::Blocked)).and_modify(|m| { + m.insert( + TilePosType::Full(TilePos::new(pos.x, pos.y)), + TileWrapper::Blocked, + ); + }); true } BlockType::Solid(_) => { From fcb11b8ea0e67a1baa609e974227b54528059497 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Mj=C3=A5land?= Date: Sun, 5 Apr 2026 00:14:06 +0200 Subject: [PATCH 3/3] Clip entities above camera view layer * Move the camera in z to fit with the current view layer/height * Reduce near clipping plane to exclude entities drawn at higher z levels than the camera --- crates/camera/src/camera.rs | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/crates/camera/src/camera.rs b/crates/camera/src/camera.rs index 83cb741..e8d56ae 100644 --- a/crates/camera/src/camera.rs +++ b/crates/camera/src/camera.rs @@ -66,6 +66,10 @@ fn setup(mut commands: Commands) { commands.spawn(( input_map, Camera2d, + Projection::Orthographic(OrthographicProjection { + near: -0.5, + ..OrthographicProjection::default_2d() + }), CameraLayer(0), DespawnOnExit(AppState::MainGame), )); @@ -85,8 +89,17 @@ fn zoom( } } -fn scroll(query: Single<(&mut CameraLayer, &ActionState), With>) { - let (mut layer, action_state) = query.into_inner(); +fn scroll( + query: Single< + ( + &mut CameraLayer, + &ActionState, + &mut Transform, + ), + With, + >, +) { + let (mut layer, action_state, mut transform) = query.into_inner(); let mut delta = 0; if action_state.just_pressed(&CameraControls::ScrollUp) { delta += 1; @@ -95,6 +108,7 @@ fn scroll(query: Single<(&mut CameraLayer, &ActionState), With