feat!: Half-implemented sprite container
- Updated all assets - Refactored door again - Half of the sprites are visually broken for now
BIN
assets/sprites/interactive/container.png
Normal file
|
After Width: | Height: | Size: 269 B |
|
Before Width: | Height: | Size: 205 B |
|
Before Width: | Height: | Size: 263 B |
|
Before Width: | Height: | Size: 129 B After Width: | Height: | Size: 139 B |
|
Before Width: | Height: | Size: 130 B After Width: | Height: | Size: 177 B |
|
Before Width: | Height: | Size: 110 B After Width: | Height: | Size: 126 B |
|
Before Width: | Height: | Size: 744 B After Width: | Height: | Size: 896 B |
|
Before Width: | Height: | Size: 156 B |
|
Before Width: | Height: | Size: 113 B |
|
Before Width: | Height: | Size: 126 B |
|
Before Width: | Height: | Size: 157 B After Width: | Height: | Size: 157 B |
|
Before Width: | Height: | Size: 138 B |
|
Before Width: | Height: | Size: 171 B |
|
Before Width: | Height: | Size: 111 B |
|
|
@ -24,6 +24,7 @@ pub fn load_level (
|
||||||
level_handle: Res<LevelAssetHandle>,
|
level_handle: Res<LevelAssetHandle>,
|
||||||
mut commands: Commands,
|
mut commands: Commands,
|
||||||
asset_server: Res<AssetServer>,
|
asset_server: Res<AssetServer>,
|
||||||
|
textures: Res<super::LayoutTextures>,
|
||||||
level_assets: Res<Assets<structs::LevelAsset>>,
|
level_assets: Res<Assets<structs::LevelAsset>>,
|
||||||
) {
|
) {
|
||||||
let Some(level) = level_assets.get(&level_handle.0) else {
|
let Some(level) = level_assets.get(&level_handle.0) else {
|
||||||
|
|
@ -61,7 +62,7 @@ pub fn load_level (
|
||||||
|
|
||||||
for DoorData { pos, facing_left, lock } in level.interactive.doors.iter() {
|
for DoorData { pos, facing_left, lock } in level.interactive.doors.iter() {
|
||||||
let door_pos = vec2(meters(pos.x + 0.5), meters(pos.y));
|
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));
|
let mut door = parent.spawn(door_bundle(&textures, door_pos, *facing_left));
|
||||||
if let Some(lock_facing_left) = lock {
|
if let Some(lock_facing_left) = lock {
|
||||||
door.with_child(padlock_bundle(&asset_server, *lock_facing_left));
|
door.with_child(padlock_bundle(&asset_server, *lock_facing_left));
|
||||||
}
|
}
|
||||||
|
|
@ -87,7 +88,7 @@ pub fn load_level (
|
||||||
for ContainerData { pos, size, items } in level.interactive.containers.iter() {
|
for ContainerData { pos, size, items } in level.interactive.containers.iter() {
|
||||||
let pos = vec2(meters(pos.x), meters(pos.y));
|
let pos = vec2(meters(pos.x), meters(pos.y));
|
||||||
|
|
||||||
let mut container = parent.spawn(container_bundle(&asset_server, pos, *size));
|
let mut container = parent.spawn(container_bundle(&textures, pos, *size));
|
||||||
|
|
||||||
for item in items {
|
for item in items {
|
||||||
// TODO: replace with proper item-by-id system
|
// TODO: replace with proper item-by-id system
|
||||||
|
|
|
||||||
|
|
@ -12,8 +12,6 @@ use crate::{
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
const CRATE_CLOSED_ASSET: &'static str = "sprites/interactive/crate_closed.png";
|
|
||||||
|
|
||||||
#[derive(Component, Clone, Copy, Default, Reflect, Debug, PartialEq, Eq)]
|
#[derive(Component, Clone, Copy, Default, Reflect, Debug, PartialEq, Eq)]
|
||||||
#[reflect(Component, Clone, Default, Debug, PartialEq)]
|
#[reflect(Component, Clone, Default, Debug, PartialEq)]
|
||||||
#[require(Sprite, InteractiveObject, Inventory)]
|
#[require(Sprite, InteractiveObject, Inventory)]
|
||||||
|
|
@ -38,15 +36,26 @@ pub fn on_container_interact(
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn container_bundle(
|
pub fn container_bundle(
|
||||||
asset_server: &Res<AssetServer>,
|
textures: &Res<LayoutTextures>,
|
||||||
position: Vec2,
|
position: Vec2,
|
||||||
inventory_size: UVec2,
|
inventory_size: UVec2,
|
||||||
) -> impl Bundle {
|
) -> impl Bundle {
|
||||||
let image = asset_server.load(CRATE_CLOSED_ASSET);
|
let texture_atlas = TextureAtlas {
|
||||||
|
layout: textures.container.atlas.clone(),
|
||||||
|
index: textures.container.indices["main"],
|
||||||
|
};
|
||||||
|
let hl_texture_atlas = texture_atlas.clone().with_index(textures.container.indices["highlighted"]);
|
||||||
|
let sprite = Sprite {
|
||||||
|
image: textures.container.image.clone(),
|
||||||
|
texture_atlas: Some(texture_atlas),
|
||||||
|
..default()
|
||||||
|
};
|
||||||
|
let mut highlight_sprite = sprite.clone();
|
||||||
|
highlight_sprite.texture_atlas = Some(hl_texture_atlas);
|
||||||
(
|
(
|
||||||
Container,
|
Container,
|
||||||
Transform::from_xyz(position.x, position.y - meters(0.5), 0.),
|
Transform::from_xyz(position.x, position.y - meters(0.5), 0.),
|
||||||
Sprite::from_image(image),
|
sprite,
|
||||||
Inventory::new(inventory_size),
|
Inventory::new(inventory_size),
|
||||||
Children::spawn((
|
Children::spawn((
|
||||||
Spawn((
|
Spawn((
|
||||||
|
|
@ -54,6 +63,11 @@ pub fn container_bundle(
|
||||||
Sensor,
|
Sensor,
|
||||||
Transform::from_xyz(0., meters(0.5), 0.),
|
Transform::from_xyz(0., meters(0.5), 0.),
|
||||||
)),
|
)),
|
||||||
|
Spawn((
|
||||||
|
highlight_sprite,
|
||||||
|
Transform::from_xyz(0., 0., 1.),
|
||||||
|
Visibility::Hidden,
|
||||||
|
)),
|
||||||
)),
|
)),
|
||||||
Name::new(format!("Container {}x{}", inventory_size.x, inventory_size.y)),
|
Name::new(format!("Container {}x{}", inventory_size.x, inventory_size.y)),
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -6,12 +6,9 @@ use crate::meters;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
const DOOR_OPENED_ASSET: &'static str = "sprites/interactive/door_opened.png";
|
|
||||||
const DOOR_CLOSED_ASSET: &'static str = "sprites/interactive/door_closed.png";
|
|
||||||
|
|
||||||
#[derive(Component, Clone, Copy, Reflect, PartialEq, Eq, Debug)]
|
#[derive(Component, Clone, Copy, Reflect, PartialEq, Eq, Debug)]
|
||||||
#[reflect(Component, Clone, Default, PartialEq, Debug)]
|
#[reflect(Component, Clone, Default, PartialEq, Debug)]
|
||||||
#[require(Sprite, InteractiveObject)]
|
#[require(InteractiveObject)]
|
||||||
pub struct Door(pub i8);
|
pub struct Door(pub i8);
|
||||||
|
|
||||||
#[derive(Component, Clone, Copy, Reflect, Default, PartialEq, Eq, Debug)]
|
#[derive(Component, Clone, Copy, Reflect, Default, PartialEq, Eq, Debug)]
|
||||||
|
|
@ -19,20 +16,33 @@ pub struct Door(pub i8);
|
||||||
#[require(Collider, LightOccluder2d)]
|
#[require(Collider, LightOccluder2d)]
|
||||||
pub struct DoorCollider;
|
pub struct DoorCollider;
|
||||||
|
|
||||||
|
#[derive(Component, Clone, Copy, Reflect, Default, PartialEq, Eq, Debug)]
|
||||||
|
#[reflect(Component, Clone, Default, PartialEq, Debug)]
|
||||||
|
#[require(Sprite)]
|
||||||
|
pub struct DoorHighlight;
|
||||||
|
|
||||||
impl Default for Door {
|
impl Default for Door {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self(1)
|
Self(1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Door {
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn is_facing_left(&self) -> bool {
|
||||||
|
self.0 < 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn on_door_interact(
|
pub fn on_door_interact(
|
||||||
event: On<InteractionEvent>,
|
event: On<InteractionEvent>,
|
||||||
mut commands: Commands,
|
mut commands: Commands,
|
||||||
locked_query: Query<(), With<Locked>>,
|
locked_query: Query<(), With<Locked>>,
|
||||||
door_query: Query<(&Door, &Children)>,
|
door_query: Query<(&Door, &Children)>,
|
||||||
door_collider_query: Query<Entity, With<DoorCollider>>,
|
door_collider_query: Query<Entity, With<DoorCollider>>,
|
||||||
mut sprite_query: Query<(&mut Sprite, &mut Transform)>,
|
mut sprite_query: Query<&mut Transform, With<Sprite>>,
|
||||||
asset_server: Res<AssetServer>,
|
highlight_query: Query<(), With<DoorHighlight>>,
|
||||||
|
textures: Res<LayoutTextures>,
|
||||||
) {
|
) {
|
||||||
if locked_query.get(event.entity).is_ok() {
|
if locked_query.get(event.entity).is_ok() {
|
||||||
return;
|
return;
|
||||||
|
|
@ -43,13 +53,18 @@ pub fn on_door_interact(
|
||||||
let maybe_door_collider = children.iter()
|
let maybe_door_collider = children.iter()
|
||||||
.find_map(|id| if let Ok(id) = door_collider_query.get(id) { Some(id) } else { None } );
|
.find_map(|id| if let Ok(id) = door_collider_query.get(id) { Some(id) } else { None } );
|
||||||
for child in children {
|
for child in children {
|
||||||
if let Ok((mut sprite, mut transform)) = sprite_query.get_mut(*child) {
|
match sprite_query.get_mut(*child) {
|
||||||
let (image, translation) = if maybe_door_collider.is_none() { (DOOR_CLOSED_ASSET, 0.) }
|
Ok(mut transform) => {
|
||||||
else { (DOOR_OPENED_ASSET, door.0 as f32 * meters(0.5)) };
|
let translation = if maybe_door_collider.is_none() { 0. }
|
||||||
sprite.image = asset_server.load(image);
|
else { door.0 as f32 * meters(0.5) };
|
||||||
transform.translation.x = translation;
|
transform.translation.x = translation;
|
||||||
break;
|
},
|
||||||
|
Err(_) => continue,
|
||||||
}
|
}
|
||||||
|
let texture = if maybe_door_collider.is_none() { &textures.door_closed } else { &textures.door_opened };
|
||||||
|
let (sprite, highlight_sprite) = door_sprites(texture, door.is_facing_left());
|
||||||
|
let needed_sprite = if highlight_query.get(*child).is_err() { sprite } else { highlight_sprite };
|
||||||
|
commands.entity(*child).insert(needed_sprite);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(id) = maybe_door_collider {
|
if let Some(id) = maybe_door_collider {
|
||||||
|
|
@ -68,9 +83,26 @@ pub fn door_collider_bundle() -> impl Bundle {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn door_bundle(asset_server: &Res<AssetServer>, position: Vec2, facing_left: bool) -> impl Bundle {
|
fn door_sprites(texture: &AtlasLayoutTexture, facing_left: bool) -> (Sprite, Sprite) {
|
||||||
|
let texture_atlas = TextureAtlas {
|
||||||
|
layout: texture.atlas.clone(),
|
||||||
|
index: texture.indices["main"],
|
||||||
|
};
|
||||||
|
let hl_texture_atlas = texture_atlas.clone().with_index(texture.indices["highlighted"]);
|
||||||
|
let sprite = Sprite {
|
||||||
|
image: texture.image.clone(),
|
||||||
|
flip_x: facing_left,
|
||||||
|
texture_atlas: Some(texture_atlas),
|
||||||
|
..default()
|
||||||
|
};
|
||||||
|
let mut highlight_sprite = sprite.clone();
|
||||||
|
highlight_sprite.texture_atlas = Some(hl_texture_atlas);
|
||||||
|
(sprite, highlight_sprite)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn door_bundle(textures: &Res<LayoutTextures>, position: Vec2, facing_left: bool) -> impl Bundle {
|
||||||
let direction = if facing_left { -1 } else { 1 };
|
let direction = if facing_left { -1 } else { 1 };
|
||||||
let image = asset_server.load(DOOR_CLOSED_ASSET);
|
let (sprite, highlight_sprite) = door_sprites(&textures.door_closed, facing_left);
|
||||||
(
|
(
|
||||||
Door(direction),
|
Door(direction),
|
||||||
Transform::from_xyz(position.x, position.y, 0.),
|
Transform::from_xyz(position.x, position.y, 0.),
|
||||||
|
|
@ -79,17 +111,18 @@ pub fn door_bundle(asset_server: &Res<AssetServer>, position: Vec2, facing_left:
|
||||||
door_collider_bundle(),
|
door_collider_bundle(),
|
||||||
(
|
(
|
||||||
Collider::cuboid(meters(0.5), meters(1.)),
|
Collider::cuboid(meters(0.5), meters(1.)),
|
||||||
|
Transform::default(),
|
||||||
Sensor,
|
Sensor,
|
||||||
Transform::from_xyz(0., 0., 0.),
|
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
Sprite {
|
sprite,
|
||||||
image,
|
|
||||||
flip_x: facing_left,
|
|
||||||
..default()
|
|
||||||
},
|
|
||||||
Transform::from_xyz(0., 0., 0.),
|
|
||||||
),
|
),
|
||||||
|
(
|
||||||
|
highlight_sprite,
|
||||||
|
Transform::from_xyz(0., 0., 1.),
|
||||||
|
DoorHighlight,
|
||||||
|
Visibility::Hidden,
|
||||||
|
)
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ use bevy_light_2d::prelude::*;
|
||||||
|
|
||||||
use crate::meters;
|
use crate::meters;
|
||||||
|
|
||||||
const LAMP_IMAGE_PATH: &'static str = "sprites/interactive/lamp.png";
|
const LAMP_IMAGE_PATH: &'static str = "sprites/level/lamp.png";
|
||||||
|
|
||||||
#[derive(Component, Clone, Copy, Default, Reflect, Debug, PartialEq, Eq)]
|
#[derive(Component, Clone, Copy, Default, Reflect, Debug, PartialEq, Eq)]
|
||||||
#[reflect(Component, Clone, Default, Debug, PartialEq)]
|
#[reflect(Component, Clone, Default, Debug, PartialEq)]
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
|
|
||||||
pub mod asset;
|
pub mod asset;
|
||||||
|
|
@ -35,3 +37,38 @@ pub struct LevelAssetHandle(Handle<asset::structs::LevelAsset>);
|
||||||
pub struct InteractionEvent {
|
pub struct InteractionEvent {
|
||||||
pub entity: Entity,
|
pub entity: Entity,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Reflect, Clone, PartialEq, Eq, Debug, Deref, DerefMut, Default)]
|
||||||
|
#[reflect(Clone, PartialEq, Debug, Default)]
|
||||||
|
pub struct SingleLayoutTexture(Handle<Image>);
|
||||||
|
|
||||||
|
#[derive(Reflect, Clone, PartialEq, Eq, Debug, Default)]
|
||||||
|
#[reflect(Clone, PartialEq, Debug, Default)]
|
||||||
|
pub struct AtlasLayoutTexture {
|
||||||
|
image: Handle<Image>,
|
||||||
|
atlas: Handle<TextureAtlasLayout>,
|
||||||
|
indices: HashMap<String, usize>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AtlasLayoutTexture {
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn new(
|
||||||
|
image: Handle<Image>,
|
||||||
|
atlas: Handle<TextureAtlasLayout>,
|
||||||
|
indices: HashMap<String, usize>,
|
||||||
|
) -> Self {
|
||||||
|
Self { image, atlas, indices }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Resource, Reflect, Clone, PartialEq, Eq, Debug, Default)]
|
||||||
|
#[reflect(Resource, Clone, PartialEq, Debug, Default)]
|
||||||
|
pub struct LayoutTextures {
|
||||||
|
pub container: AtlasLayoutTexture,
|
||||||
|
pub door_closed: AtlasLayoutTexture,
|
||||||
|
pub door_opened: AtlasLayoutTexture,
|
||||||
|
pub light: SingleLayoutTexture,
|
||||||
|
pub lock: AtlasLayoutTexture,
|
||||||
|
pub stairs: AtlasLayoutTexture,
|
||||||
|
pub tilemap: SingleLayoutTexture,
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -91,3 +91,50 @@ pub fn setup_world(
|
||||||
let level_handle = asset_server.load(startup_args.level.clone());
|
let level_handle = asset_server.load(startup_args.level.clone());
|
||||||
commands.insert_resource(LevelAssetHandle(level_handle));
|
commands.insert_resource(LevelAssetHandle(level_handle));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn double_atlas(
|
||||||
|
atlases: &mut ResMut<Assets<TextureAtlasLayout>>,
|
||||||
|
indices: &mut HashMap<String, usize>,
|
||||||
|
single_size: UVec2
|
||||||
|
) -> Handle<TextureAtlasLayout> {
|
||||||
|
let mut atlas = TextureAtlasLayout::new_empty(uvec2(single_size.x * 2, single_size.y));
|
||||||
|
indices.insert(
|
||||||
|
"main".to_owned(),
|
||||||
|
atlas.add_texture(URect::from_corners(
|
||||||
|
uvec2(0, 0),
|
||||||
|
uvec2(single_size.x, single_size.y)
|
||||||
|
)),
|
||||||
|
);
|
||||||
|
indices.insert(
|
||||||
|
"highlighted".to_owned(),
|
||||||
|
atlas.add_texture(URect::from_corners(
|
||||||
|
uvec2(single_size.x, 0),
|
||||||
|
uvec2(single_size.x * 2, single_size.y)
|
||||||
|
)),
|
||||||
|
);
|
||||||
|
atlases.add(atlas)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn load_layout_textures(
|
||||||
|
asset_server: Res<AssetServer>,
|
||||||
|
mut atlases: ResMut<Assets<TextureAtlasLayout>>,
|
||||||
|
mut textures: ResMut<LayoutTextures>,
|
||||||
|
) {
|
||||||
|
// Container
|
||||||
|
let image = asset_server.load("sprites/interactive/container.png");
|
||||||
|
let mut indices = HashMap::new();
|
||||||
|
let atlas = double_atlas(&mut atlases, &mut indices, uvec2(32, 16));
|
||||||
|
textures.container = AtlasLayoutTexture::new(image, atlas, indices);
|
||||||
|
|
||||||
|
// Closed door
|
||||||
|
let image = asset_server.load("sprites/interactive/door_closed.png");
|
||||||
|
let mut indices = HashMap::new();
|
||||||
|
let atlas = double_atlas(&mut atlases, &mut indices, uvec2(4, 32));
|
||||||
|
textures.door_closed = AtlasLayoutTexture::new(image, atlas, indices);
|
||||||
|
|
||||||
|
// Opened door
|
||||||
|
let image = asset_server.load("sprites/interactive/door_opened.png");
|
||||||
|
let mut indices = HashMap::new();
|
||||||
|
let atlas = double_atlas(&mut atlases, &mut indices, uvec2(16, 32));
|
||||||
|
textures.door_opened = AtlasLayoutTexture::new(image, atlas, indices);
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -110,9 +110,11 @@ impl Plugin for ExpeditionPlugin {
|
||||||
.init_state::<LoadingState>()
|
.init_state::<LoadingState>()
|
||||||
.init_state::<GameState>()
|
.init_state::<GameState>()
|
||||||
.insert_resource(ui::WindowSize::default())
|
.insert_resource(ui::WindowSize::default())
|
||||||
|
.insert_resource(layout::LayoutTextures::default())
|
||||||
.add_systems(Startup, (
|
.add_systems(Startup, (
|
||||||
setup_global,
|
setup_global,
|
||||||
layout::systems::setup_world,
|
layout::systems::setup_world,
|
||||||
|
layout::systems::load_layout_textures,
|
||||||
))
|
))
|
||||||
.add_systems(Update, (
|
.add_systems(Update, (
|
||||||
insert_entity_name,
|
insert_entity_name,
|
||||||
|
|
|
||||||
|
|
@ -7,8 +7,8 @@ use crate::layout::asset::structs::LevelAsset;
|
||||||
fn deserialize_from_str() {
|
fn deserialize_from_str() {
|
||||||
let level_str = include_str!("../../assets/levels/level.toml");
|
let level_str = include_str!("../../assets/levels/level.toml");
|
||||||
let level_alt_str = include_str!("../../assets/levels/level_alt.toml");
|
let level_alt_str = include_str!("../../assets/levels/level_alt.toml");
|
||||||
let level = toml::de::from_str::<LevelAsset>(level_str).unwrap();
|
let _ = toml::de::from_str::<LevelAsset>(level_str).unwrap();
|
||||||
let level_alt = toml::de::from_str::<LevelAsset>(level_alt_str).unwrap();
|
let _ = toml::de::from_str::<LevelAsset>(level_alt_str).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
||||||