feat: Updated tilemap bundle

- Layout is now defined from bottom left corner
- Added rotation to lockpick bundle
- Added level TOML structures that match current level
This commit is contained in:
Alexey 2026-03-27 13:04:47 +03:00
commit 24153e476f
7 changed files with 225 additions and 72 deletions

110
assets/levels/level.toml Normal file
View file

@ -0,0 +1,110 @@
[[tiles.floors]]
x = 0
y = 0
w = 16
[[tiles.floors]]
x = 0
y = 4
w = 16
[[tiles.floors]]
x = 0
y = 8
w = 16
[[tiles.walls]]
x = 0
y = 1
h = 3
[[tiles.walls]]
x = 0
y = 5
h = 3
[[tiles.walls]]
x = 15
y = 1
h = 3
[[tiles.walls]]
x = 15
y = 5
h = 3
[[tiles.wall_connectors]]
x = 4
y = 3
[[tiles.wall_connectors]]
x = 4
y = 7
[[tiles.wall_connectors]]
x = 11
y = 3
[[tiles.wall_connectors]]
x = 11
y = 7
[interactive.player]
x = 1
y = 1
[[interactive.doors]]
x = 4
y = 1
facing = "right"
[[interactive.doors]]
x = 4
y = 5
facing = "right"
lock = "right"
[[interactive.doors]]
x = 11
y = 1
facing = "left"
lock = "left"
[[interactive.doors]]
x = 11
y = 1
facing = "left"
[[interactive.stairs]]
x = 8
y = 1
floors = 2
[[interactive.containers]]
x = 2
y = 1
[[interactive.containers]]
x = 14
y = 1
[[interactive.containers]]
x = 2
y = 5
w = 4
h = 4
[interactive.containers.items]
id = "lockpick"
x = 2
y = 2
[[interactive.containers]]
x = 14
y = 5
w = 2
h = 2
[interactive.containers.items]
id = "lockpick"
rotated = true

View file

@ -0,0 +1,48 @@
[tiles]
floors = [
{ x = 0, y = 0, w = 16 },
{ x = 0, y = 4, w = 16 },
{ x = 0, y = 8, w = 16 },
]
walls = [
{ x = 0, y = 1, h = 3 },
{ x = 0, y = 5, h = 3 },
{ x = 15, y = 1, h = 3 },
{ x = 15, y = 5, h = 3 },
]
wall_connectors = [
{ x = 4, y = 3 },
{ x = 4, y = 7 },
{ x = 11, y = 3 },
{ x = 11, y = 7 },
]
[interactive]
player = { x = 1, y = 1 }
doors = [
{ x = 4, y = 1, facing = "right" },
{ x = 4, y = 5, facing = "right", lock = "right" },
{ x = 11, y = 1, facing = "left", lock = "left" },
{ x = 11, y = 5, facing = "left" },
]
stairs = [
{ x = 8, y = 1, floors = 2 },
]
containers = [
{ x = 2, y = 1 },
{ x = 14, y = 1 },
{
x = 2, y = 5,
w = 4, h = 4,
items = [
{ id = "lockpick", x = 2, y = 2 },
],
},
{
x = 14, y = 5,
w = 2, h = 2,
items = [
{ id = "lockpick", rotated = true },
],
},
]

View file

@ -9,10 +9,14 @@ const LOCKPICK_SPRITE: &'static str = "sprites/items/lockpick.png";
#[require(Item)] #[require(Item)]
pub struct Lockpick; pub struct Lockpick;
pub fn lockpick_bundle(asset_server: &Res<AssetServer>, position: UVec2) -> impl Bundle { pub fn lockpick_bundle(asset_server: &Res<AssetServer>, position: UVec2, rotated: bool) -> impl Bundle {
let image = asset_server.load(LOCKPICK_SPRITE); let image = asset_server.load(LOCKPICK_SPRITE);
let mut item = Item::new(uvec2(1, 1), position);
if rotated {
item.rotate();
}
( (
Item::new(uvec2(1, 1), position), item,
ItemData { ItemData {
image, image,
name: "Lockpick".into(), name: "Lockpick".into(),

1
src/layout/asset.rs Normal file
View file

@ -0,0 +1 @@

View file

@ -1,5 +1,6 @@
use bevy::prelude::*; use bevy::prelude::*;
pub mod asset;
pub mod container; pub mod container;
pub mod door; pub mod door;
pub mod lock; pub mod lock;

View file

@ -78,80 +78,66 @@ pub fn setup_world(
mut commands: Commands, mut commands: Commands,
asset_server: Res<AssetServer>, asset_server: Res<AssetServer>,
) { ) {
// floor 1F let tiles = [
let mut tiles = (0..16).map(|x| { ( 0, ( 0, 0, 16, 1 )), // 1F floor
(0, uvec2(x, 1)) ( 0, ( 0, 4, 16, 1 )), // 1F ceiling / 2F floor
}).collect::<Vec<(u16, UVec2)>>(); ( 0, ( 0, 8, 16, 1 )), // 2F ceiling
// floor/ceil 1-2F ( 1, ( 0, 1, 1, 3 )), // 1F left wall
tiles.extend((0..16).map(|x| { ( 1, ( 0, 5, 1, 3 )), // 2F left wall
(0, uvec2(x, 5)) ( 1, ( 15, 1, 1, 3 )), // 1F right wall
})); ( 1, ( 15, 5, 1, 3 )), // 2F right wall
// ceil 2F ( 2, ( 4, 3, 1, 1 )), // 1F left door connector
tiles.extend((0..16).map(|x| { ( 2, ( 4, 7, 1, 1 )), // 2F left door connector
(0, uvec2(x, 9)) ( 2, ( 11, 3, 1, 1 )), // 1F right door connector
})); ( 2, ( 11, 7, 1, 1 )), // 2F right door connector
].into_iter().map(|(id, (x, y, w, h))| {
(id, URect::from_corners(uvec2(x, y), uvec2(x + w - 1, y + h - 1)))
}).collect::<Vec<(u16, URect)>>();
// walls commands.spawn(tilemap::tilemap_bundle(&asset_server, tiles));
tiles.extend([
(0, 4), (0, 3), (0, 2),
(0, 8), (0, 7), (0, 6),
(15, 4), (15, 3), (15, 2),
(15, 8), (15, 7), (15, 6),
].iter().map(|(x, y)| {
(1, uvec2(*x, *y))
}));
// wall connectors
tiles.extend([
(4, 8), (4, 4), (11, 8), (11, 4),
].iter().map(|(x, y)| {
(2, uvec2(*x, *y))
}));
commands.spawn(tilemap::tilemap_bundle(&asset_server, uvec2(16, 10), tiles));
commands.spawn(door::door_bundle(&asset_server, vec2(meters(-3.5), 0.), false)); commands.spawn(player_bundle(&asset_server, vec2(meters(1.5), meters(1.))));
commands.spawn(door::door_bundle(&asset_server, vec2(meters(-3.5), meters(4.)), false))
commands.spawn(door::door_bundle(&asset_server, vec2(meters(4.5), meters(1.)), false));
commands.spawn(door::door_bundle(&asset_server, vec2(meters(4.5), meters(5.)), false))
.with_child(lock::padlock_bundle(&asset_server, false)); .with_child(lock::padlock_bundle(&asset_server, false));
commands.spawn(door::door_bundle(&asset_server, vec2(meters(3.5), 0.), true)) commands.spawn(door::door_bundle(&asset_server, vec2(meters(11.5), meters(1.)), true))
.with_child(lock::padlock_bundle(&asset_server, true)); .with_child(lock::padlock_bundle(&asset_server, true));
commands.spawn(door::door_bundle(&asset_server, vec2(meters(3.5), meters(4.)), true)); commands.spawn(door::door_bundle(&asset_server, vec2(meters(11.5), meters(5.)), true));
commands.spawn(stairs::stairs_bundle( commands.spawn(stairs::stairs_bundle(
&asset_server, &asset_server,
vec2(meters(0.), 0.), vec2(meters(8.), meters(1.)),
Some(vec2(meters(2.), meters(4.))), Some(vec2(meters(2.), meters(4.))),
None, None,
)); ));
commands.spawn(stairs::stairs_bundle( commands.spawn(stairs::stairs_bundle(
&asset_server, &asset_server,
vec2(meters(0.), meters(4.)), vec2(meters(8.), meters(5.)),
None, None,
Some(vec2(meters(-2.), meters(-4.))), Some(vec2(meters(-2.), meters(-4.))),
)); ));
commands.spawn(container::container_bundle( commands.spawn(container::container_bundle(
&asset_server, &asset_server,
vec2(meters(-6.), 0.), vec2(meters(2.), meters(1.)),
uvec2(1, 1), uvec2(1, 1),
)); ));
commands.spawn(container::container_bundle( commands.spawn(container::container_bundle(
&asset_server, &asset_server,
vec2(meters(-6.), meters(4.)), vec2(meters(14.), meters(1.)),
uvec2(1, 1),
)).with_child(lockpick_bundle(&asset_server, UVec2::ZERO));
commands.spawn(container::container_bundle(
&asset_server,
vec2(meters(6.), 0.),
uvec2(1, 1), uvec2(1, 1),
)); ));
commands.spawn(container::container_bundle( commands.spawn(container::container_bundle(
&asset_server, &asset_server,
vec2(meters(6.), meters(4.)), vec2(meters(2.), meters(5.)),
uvec2(1, 1), uvec2(4, 4),
)).with_child(lockpick_bundle(&asset_server, UVec2::ZERO)); )).with_child(lockpick_bundle(&asset_server, uvec2(2, 2), false));
commands.spawn(container::container_bundle(
commands.spawn(player_bundle(&asset_server, vec2(meters(-6.), meters(4.)))); &asset_server,
vec2(meters(14.), meters(5.)),
uvec2(2, 2),
)).with_child(lockpick_bundle(&asset_server, UVec2::ZERO, true));
} }

View file

@ -23,11 +23,15 @@ pub struct Tilemap;
pub fn tilemap_bundle( pub fn tilemap_bundle(
asset_server: &Res<AssetServer>, asset_server: &Res<AssetServer>,
size: UVec2, tiles: Vec<(u16, URect)>,
tiles: Vec<(u16, UVec2)>,
) -> impl Bundle { ) -> impl Bundle {
let mut size = uvec2(0, 0);
for tile in tiles.iter() {
size.x = size.x.max(tile.1.max.x + 1);
size.y = size.y.max(tile.1.max.y + 1);
}
let tile_data: Vec<Option<TileData>> = (0..size.element_product()).map(|xy| { let tile_data: Vec<Option<TileData>> = (0..size.element_product()).map(|xy| {
if let Some((id, _)) = tiles.iter().find(|(_, pos)| pos.x == xy % size.x && pos.y == xy / size.x) { if let Some((id, _)) = tiles.iter().find(|(_, rect)| rect.contains(uvec2(xy % size.x, xy / size.x))) {
Some(TileData::from_tileset_index(*id)) Some(TileData::from_tileset_index(*id))
} else { None } } else { None }
}).collect(); }).collect();
@ -44,27 +48,26 @@ pub fn tilemap_bundle(
), ),
..default() ..default()
}, },
Transform::from_xyz(0., meters(0.5) * size.y as f32 - meters(3.), 0.), Transform::from_xyz(
meters(0.5) * size.x as f32,
meters(0.5) * size.y as f32 - meters(1.),
0.,
),
TilemapChunkTileData(tile_data), TilemapChunkTileData(tile_data),
Children::spawn(SpawnIter(tiles.into_iter().map(move |(_, pos)| {( Children::spawn(SpawnIter(tiles.into_iter().map(move |(_, rect)| {
// TODO: optimize colliders let mut rect = rect.as_rect();
Collider::cuboid(meters(0.5), meters(0.5)), rect.max = vec2(rect.max.x + 1., rect.max.y + 1.);
let (width, height) = (rect.width(), rect.height());
let offset = rect.center();
(
Collider::cuboid(meters(width * 0.5), meters(height * 0.5)),
Transform::from_xyz( Transform::from_xyz(
meters(0.5 + pos.x as f32 - (size.x as f32 * 0.5)), meters(offset.x - size.x as f32 * 0.5),
meters(0.5 + pos.y as f32 - (size.y as f32 * 0.5)), meters(offset.y - size.y as f32 * 0.5),
0., 0.,
), ),
)}) )}
)), ))),
//Children::spawn(SpawnIter(colliders.into_iter()
// .map(move |(collider, pos)| {(
// collider.clone(),
// Transform::from_xyz(
// pos.x - meters(0.5) * size.x as f32,
// pos.y - meters(0.5) * size.y as f32,
// 0.
// ),
// )})
//)),
) )
} }