generated from 2ndbeam/bevy-template
Compare commits
2 commits
0e18bb6df5
...
0ab2620724
| Author | SHA1 | Date | |
|---|---|---|---|
| 0ab2620724 | |||
| 24153e476f |
11 changed files with 634 additions and 72 deletions
113
assets/levels/level.toml
Normal file
113
assets/levels/level.toml
Normal file
|
|
@ -0,0 +1,113 @@
|
||||||
|
[meta]
|
||||||
|
tiles = [ "floors", "walls", "wall_connectors" ]
|
||||||
|
|
||||||
|
[[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 = 5
|
||||||
|
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
|
||||||
51
assets/levels/level_alt.toml
Normal file
51
assets/levels/level_alt.toml
Normal file
|
|
@ -0,0 +1,51 @@
|
||||||
|
[meta]
|
||||||
|
tiles = [ "floors", "walls", "wall_connectors" ]
|
||||||
|
|
||||||
|
[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 },
|
||||||
|
]
|
||||||
|
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 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
@ -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(),
|
||||||
|
|
|
||||||
73
src/layout/asset/mod.rs
Normal file
73
src/layout/asset/mod.rs
Normal file
|
|
@ -0,0 +1,73 @@
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use bevy::prelude::*;
|
||||||
|
|
||||||
|
use crate::{layout::{Level, asset::structs::{DoorData, StairsData}, door::door_bundle, lock::padlock_bundle, stairs::stairs_bundle, tilemap::tilemap_bundle}, meters, player::player_bundle};
|
||||||
|
|
||||||
|
pub mod structs;
|
||||||
|
|
||||||
|
pub fn load_level (
|
||||||
|
InRef(level_handle): InRef<Handle<structs::LevelAsset>>,
|
||||||
|
mut commands: Commands,
|
||||||
|
asset_server: Res<AssetServer>,
|
||||||
|
level_assets: Res<Assets<structs::LevelAsset>>,
|
||||||
|
) {
|
||||||
|
let Some(level) = level_assets.get(level_handle) else {
|
||||||
|
error!("Could not load level asset from {level_handle:?}");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let Some(default_tile) = level.meta.tiles.first() else {
|
||||||
|
error!("Level meta does not contain tile ids");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let tile_ids: HashMap<String, u16> = level.meta.tiles.iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(v, k)| (k.to_owned(), v as u16))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let tiles: Vec<(u16, URect)> = level.tiles.iter()
|
||||||
|
.flat_map(|(id, tiles)| {
|
||||||
|
let id = match tile_ids.get(id.as_str()) {
|
||||||
|
Some(id) => *id,
|
||||||
|
None => {
|
||||||
|
warn!("Tile ID {id} not found in level meta, using {default_tile}...");
|
||||||
|
0
|
||||||
|
}
|
||||||
|
};
|
||||||
|
tiles.clone().into_iter().map(move |t| (id, t))
|
||||||
|
}).collect();
|
||||||
|
|
||||||
|
let player_pos = vec2(meters(level.interactive.player.x + 0.5), meters(level.interactive.player.y));
|
||||||
|
|
||||||
|
commands.spawn(Level).with_children(|parent| {
|
||||||
|
parent.spawn(tilemap_bundle(&asset_server, tiles));
|
||||||
|
parent.spawn(player_bundle(&asset_server, player_pos));
|
||||||
|
|
||||||
|
for DoorData {pos, facing_left, lock} in level.interactive.doors.iter() {
|
||||||
|
let door_pos = vec2(meters(pos.x + 0.5), meters(pos.y));
|
||||||
|
let mut door = parent.spawn(door_bundle(&asset_server, door_pos, *facing_left));
|
||||||
|
if let Some(lock_facing_left) = lock {
|
||||||
|
door.with_child(padlock_bundle(&asset_server, *lock_facing_left));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for StairsData {pos, floors} in level.interactive.stairs.iter() { match floors {
|
||||||
|
&0 | &1 => continue,
|
||||||
|
_ => {
|
||||||
|
let mut pos = vec2(meters(pos.x), meters(pos.y));
|
||||||
|
let mut down = None;
|
||||||
|
let mut up = Some(vec2(meters(2.), meters(4.)));
|
||||||
|
for i in 0..*floors {
|
||||||
|
parent.spawn(stairs_bundle(&asset_server, pos, up, down));
|
||||||
|
pos.y += meters(4.);
|
||||||
|
if i == floors - 1 {
|
||||||
|
up = None;
|
||||||
|
}
|
||||||
|
down = Some(vec2(meters(-2.), meters(-4.)));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
});
|
||||||
|
}
|
||||||
212
src/layout/asset/structs/inner.rs
Normal file
212
src/layout/asset/structs/inner.rs
Normal file
|
|
@ -0,0 +1,212 @@
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
pub(super) fn default_floors() -> u8 { 2 }
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize, Default, PartialEq, Clone, Copy, Reflect)]
|
||||||
|
#[reflect(Debug, Default, Deserialize, Serialize, PartialEq, Clone)]
|
||||||
|
#[serde(default)]
|
||||||
|
pub(super) struct Pos {
|
||||||
|
pub x: f32,
|
||||||
|
pub y: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Pos> for Vec2 {
|
||||||
|
fn from(Pos { x, y }: Pos) -> Self {
|
||||||
|
Self { x, y }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize, Default, PartialEq, Eq, Clone, Copy, Reflect)]
|
||||||
|
#[reflect(Debug, Default, Deserialize, Serialize, PartialEq, Clone)]
|
||||||
|
#[serde(default)]
|
||||||
|
pub(super) struct UPos {
|
||||||
|
pub x: u32,
|
||||||
|
pub y: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<UPos> for UVec2 {
|
||||||
|
fn from(UPos { x, y }: UPos) -> Self {
|
||||||
|
Self { x, y }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Serialize, PartialEq, Eq, Clone, Copy, Reflect)]
|
||||||
|
#[reflect(Debug, Default, Deserialize, Serialize, PartialEq, Clone)]
|
||||||
|
#[serde(default)]
|
||||||
|
pub(super) struct USize {
|
||||||
|
pub w: u32,
|
||||||
|
pub h: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for USize {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self { w: 1, h: 1 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<USize> for UVec2 {
|
||||||
|
fn from(USize { w, h }: USize) -> Self {
|
||||||
|
Self { x: w, y: h }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Serialize, Default, PartialEq, Eq, Clone, Copy, Reflect)]
|
||||||
|
#[reflect(Debug, Default, Deserialize, Serialize, PartialEq, Clone)]
|
||||||
|
#[serde(default)]
|
||||||
|
pub(super) struct USizeRect {
|
||||||
|
#[serde(flatten)]
|
||||||
|
pub pos: UPos,
|
||||||
|
#[serde(flatten)]
|
||||||
|
pub size: USize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<USizeRect> for URect {
|
||||||
|
fn from(USizeRect { pos, size: USize { w, h } }: USizeRect) -> Self {
|
||||||
|
URect::from_corners(pos.into(), uvec2(pos.x + w - 1, pos.y + h - 1))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize, Default, PartialEq, Eq, Clone, Copy, Reflect)]
|
||||||
|
#[reflect(Debug, Default, Deserialize, Serialize, PartialEq, Clone)]
|
||||||
|
#[serde(rename_all = "lowercase")]
|
||||||
|
pub(super) enum Facing {
|
||||||
|
#[default]
|
||||||
|
Left,
|
||||||
|
Right,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Facing> for bool {
|
||||||
|
fn from(value: Facing) -> Self {
|
||||||
|
match value {
|
||||||
|
Facing::Left => true,
|
||||||
|
Facing::Right => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize, Default, PartialEq, Clone, Copy, Reflect)]
|
||||||
|
#[reflect(Debug, Default, Deserialize, Serialize, PartialEq, Clone)]
|
||||||
|
pub(super) struct DoorDataInner {
|
||||||
|
#[serde(flatten)]
|
||||||
|
pub pos: Pos,
|
||||||
|
#[serde(default)]
|
||||||
|
pub facing: Option<Facing>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub lock: Option<Facing>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<DoorDataInner> for DoorData {
|
||||||
|
fn from(DoorDataInner { pos, facing, lock }: DoorDataInner) -> Self {
|
||||||
|
let lock =
|
||||||
|
if let Some(lock) = lock { Some(lock.into()) }
|
||||||
|
else { None };
|
||||||
|
Self {
|
||||||
|
pos: pos.into(),
|
||||||
|
facing_left: facing.unwrap_or_default().into(),
|
||||||
|
lock,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone, Copy, Reflect)]
|
||||||
|
#[reflect(Debug, Default, Deserialize, Serialize, PartialEq, Clone)]
|
||||||
|
pub(super) struct StairsInnerData {
|
||||||
|
#[serde(flatten)]
|
||||||
|
pub pos: Pos,
|
||||||
|
#[serde(default = "default_floors")]
|
||||||
|
pub floors: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<StairsInnerData> for StairsData {
|
||||||
|
fn from(StairsInnerData { pos, floors }: StairsInnerData) -> Self {
|
||||||
|
Self {
|
||||||
|
pos: pos.into(),
|
||||||
|
floors,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for StairsInnerData {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
pos: Pos::default(),
|
||||||
|
floors: default_floors(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize, Default, PartialEq, Eq, Clone, Reflect)]
|
||||||
|
#[reflect(Debug, Default, Deserialize, Serialize, PartialEq, Clone)]
|
||||||
|
pub(super) struct ItemDataInner {
|
||||||
|
pub id: String,
|
||||||
|
#[serde(default, flatten)]
|
||||||
|
pub pos: UPos,
|
||||||
|
#[serde(default)]
|
||||||
|
pub rotated: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<ItemDataInner> for ItemData {
|
||||||
|
fn from(ItemDataInner { id, pos, rotated }: ItemDataInner) -> Self {
|
||||||
|
Self {
|
||||||
|
id,
|
||||||
|
pos: pos.into(),
|
||||||
|
rotated,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize, Default, PartialEq, Clone, Reflect)]
|
||||||
|
#[reflect(Debug, Default, Deserialize, Serialize, PartialEq, Clone)]
|
||||||
|
pub(super) struct ContainerDataInner {
|
||||||
|
#[serde(flatten)]
|
||||||
|
pub pos: Pos,
|
||||||
|
#[serde(flatten, default)]
|
||||||
|
pub size: Option<USize>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub items: Option<Vec<ItemData>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<ContainerDataInner> for ContainerData {
|
||||||
|
fn from(ContainerDataInner { pos, size, items }: ContainerDataInner) -> Self {
|
||||||
|
Self {
|
||||||
|
pos: pos.into(),
|
||||||
|
size: size.unwrap_or_default().into(),
|
||||||
|
items,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize, Default, PartialEq, Clone, Reflect)]
|
||||||
|
#[reflect(Debug, Default, Deserialize, Serialize, PartialEq, Clone)]
|
||||||
|
pub(super) struct InteractiveInner {
|
||||||
|
pub player: Pos,
|
||||||
|
#[serde(default)]
|
||||||
|
pub doors: Option<Vec<DoorData>>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub stairs: Option<Vec<StairsData>>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub containers: Option<Vec<ContainerData>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<InteractiveInner> for Interactive {
|
||||||
|
fn from(InteractiveInner { player, doors, stairs, containers }: InteractiveInner) -> Self {
|
||||||
|
Self {
|
||||||
|
player: player.into(),
|
||||||
|
doors: doors.unwrap_or_default(),
|
||||||
|
stairs: stairs.unwrap_or_default(),
|
||||||
|
containers: containers.unwrap_or_default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize, Default, PartialEq, Eq, Clone, Reflect)]
|
||||||
|
#[reflect(Debug, Default, Deserialize, Serialize, PartialEq, Clone)]
|
||||||
|
#[serde(transparent)]
|
||||||
|
pub(super) struct TilesInner(HashMap<String, Vec<USizeRect>>);
|
||||||
|
|
||||||
|
impl From<TilesInner> for Tiles {
|
||||||
|
fn from(TilesInner(tiles): TilesInner) -> Self {
|
||||||
|
// This is probably the funniest one-liner I've ever written
|
||||||
|
Tiles { tiles: tiles.into_iter().map(|(k, v)| (k, v.into_iter().map(|v| v.into()).collect())).collect() }
|
||||||
|
}
|
||||||
|
}
|
||||||
98
src/layout/asset/structs/mod.rs
Normal file
98
src/layout/asset/structs/mod.rs
Normal file
|
|
@ -0,0 +1,98 @@
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use bevy::prelude::*;
|
||||||
|
|
||||||
|
use serde::{
|
||||||
|
Deserialize,
|
||||||
|
Serialize,
|
||||||
|
};
|
||||||
|
|
||||||
|
mod inner;
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Serialize, PartialEq, Eq, Clone, Reflect)]
|
||||||
|
#[reflect(Debug, Deserialize, Serialize, PartialEq, Clone)]
|
||||||
|
pub struct Meta {
|
||||||
|
pub tiles: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize, Default, PartialEq, Clone, Copy, Reflect)]
|
||||||
|
#[reflect(Debug, Default, Deserialize, Serialize, PartialEq, Clone)]
|
||||||
|
#[serde(from = "inner::DoorDataInner")]
|
||||||
|
pub struct DoorData {
|
||||||
|
pub pos: Vec2,
|
||||||
|
#[serde(default)]
|
||||||
|
pub facing_left: bool,
|
||||||
|
#[serde(default)]
|
||||||
|
pub lock: Option<bool>,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone, Copy, Reflect)]
|
||||||
|
#[reflect(Debug, Default, Deserialize, Serialize, PartialEq, Clone)]
|
||||||
|
#[serde(from = "inner::StairsInnerData")]
|
||||||
|
pub struct StairsData {
|
||||||
|
pub pos: Vec2,
|
||||||
|
#[serde(default = "default_floors")]
|
||||||
|
pub floors: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for StairsData {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
pos: Vec2::default(),
|
||||||
|
floors: inner::default_floors(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize, Default, PartialEq, Eq, Clone, Reflect)]
|
||||||
|
#[reflect(Debug, Default, Deserialize, Serialize, PartialEq, Clone)]
|
||||||
|
#[serde(from = "inner::ItemDataInner")]
|
||||||
|
pub struct ItemData {
|
||||||
|
pub id: String,
|
||||||
|
#[serde(default)]
|
||||||
|
pub pos: UVec2,
|
||||||
|
#[serde(default)]
|
||||||
|
pub rotated: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize, Default, PartialEq, Clone, Reflect)]
|
||||||
|
#[reflect(Debug, Default, Deserialize, Serialize, PartialEq, Clone)]
|
||||||
|
#[serde(from = "inner::ContainerDataInner")]
|
||||||
|
pub struct ContainerData {
|
||||||
|
pub pos: Vec2,
|
||||||
|
#[serde(default)]
|
||||||
|
pub size: UVec2,
|
||||||
|
#[serde(default)]
|
||||||
|
pub items: Option<Vec<ItemData>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize, Default, PartialEq, Clone, Reflect)]
|
||||||
|
#[reflect(Debug, Default, Deserialize, Serialize, PartialEq, Clone)]
|
||||||
|
#[serde(from = "inner::InteractiveInner")]
|
||||||
|
pub struct Interactive {
|
||||||
|
pub player: Vec2,
|
||||||
|
#[serde(default)]
|
||||||
|
pub doors: Vec<DoorData>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub stairs: Vec<StairsData>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub containers: Vec<ContainerData>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize, Default, PartialEq, Eq, Clone, Deref, DerefMut, Reflect)]
|
||||||
|
#[reflect(Debug, Default, Deserialize, Serialize, PartialEq, Clone)]
|
||||||
|
#[serde(from = "inner::TilesInner")]
|
||||||
|
pub struct Tiles {
|
||||||
|
#[serde(flatten)]
|
||||||
|
pub tiles: HashMap<String, Vec<URect>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone, Reflect, Asset)]
|
||||||
|
#[reflect(Debug, Deserialize, Serialize, PartialEq, Clone)]
|
||||||
|
pub struct LevelAsset {
|
||||||
|
pub meta: Meta,
|
||||||
|
pub tiles: Tiles,
|
||||||
|
#[serde(default)]
|
||||||
|
pub interactive: Interactive,
|
||||||
|
}
|
||||||
|
|
@ -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;
|
||||||
|
|
@ -19,6 +20,10 @@ pub struct InteractiveObject;
|
||||||
#[reflect(Component, Debug, PartialEq, Default, Clone)]
|
#[reflect(Component, Debug, PartialEq, Default, Clone)]
|
||||||
pub struct Locked;
|
pub struct Locked;
|
||||||
|
|
||||||
|
#[derive(Component, Debug, PartialEq, Eq, Default, Clone, Copy, Reflect)]
|
||||||
|
#[reflect(Component, Debug, PartialEq, Default, Clone)]
|
||||||
|
pub struct Level;
|
||||||
|
|
||||||
#[derive(EntityEvent, Reflect, Clone, Copy, PartialEq, Eq, Debug)]
|
#[derive(EntityEvent, Reflect, Clone, Copy, PartialEq, Eq, Debug)]
|
||||||
#[reflect(Event, Debug, PartialEq, Clone)]
|
#[reflect(Event, Debug, PartialEq, Clone)]
|
||||||
pub struct InteractionEvent {
|
pub struct InteractionEvent {
|
||||||
|
|
|
||||||
|
|
@ -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));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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.
|
|
||||||
// ),
|
|
||||||
// )})
|
|
||||||
//)),
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
16
src/tests/level.rs
Normal file
16
src/tests/level.rs
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
use super::super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn deserialize_levels() {
|
||||||
|
let level_str = include_str!("../../assets/levels/level.toml");
|
||||||
|
let level_alt_str = include_str!("../../assets/levels/level_alt.toml");
|
||||||
|
let level = toml::de::from_str::<layout::asset::structs::LevelAsset>(level_str).unwrap();
|
||||||
|
let level_alt = toml::de::from_str::<layout::asset::structs::LevelAsset>(level_alt_str).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(level.meta, level_alt.meta);
|
||||||
|
assert_eq!(level.interactive, level_alt.interactive);
|
||||||
|
for (tiles_id, tiles) in level.tiles.tiles {
|
||||||
|
let (_, other_tiles) = level_alt.tiles.iter().find(|(k, _)| k == &&tiles_id).unwrap();
|
||||||
|
assert_eq!(&tiles, other_tiles);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,2 +1,3 @@
|
||||||
mod input;
|
mod input;
|
||||||
mod inventory;
|
mod inventory;
|
||||||
|
mod level;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue