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>,
|
||||
mut commands: Commands,
|
||||
asset_server: Res<AssetServer>,
|
||||
textures: Res<super::LayoutTextures>,
|
||||
level_assets: Res<Assets<structs::LevelAsset>>,
|
||||
) {
|
||||
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() {
|
||||
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 {
|
||||
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() {
|
||||
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 {
|
||||
// TODO: replace with proper item-by-id system
|
||||
|
|
|
|||
|
|
@ -12,8 +12,6 @@ use crate::{
|
|||
|
||||
use super::*;
|
||||
|
||||
const CRATE_CLOSED_ASSET: &'static str = "sprites/interactive/crate_closed.png";
|
||||
|
||||
#[derive(Component, Clone, Copy, Default, Reflect, Debug, PartialEq, Eq)]
|
||||
#[reflect(Component, Clone, Default, Debug, PartialEq)]
|
||||
#[require(Sprite, InteractiveObject, Inventory)]
|
||||
|
|
@ -38,15 +36,26 @@ pub fn on_container_interact(
|
|||
}
|
||||
|
||||
pub fn container_bundle(
|
||||
asset_server: &Res<AssetServer>,
|
||||
textures: &Res<LayoutTextures>,
|
||||
position: Vec2,
|
||||
inventory_size: UVec2,
|
||||
) -> 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,
|
||||
Transform::from_xyz(position.x, position.y - meters(0.5), 0.),
|
||||
Sprite::from_image(image),
|
||||
sprite,
|
||||
Inventory::new(inventory_size),
|
||||
Children::spawn((
|
||||
Spawn((
|
||||
|
|
@ -54,6 +63,11 @@ pub fn container_bundle(
|
|||
Sensor,
|
||||
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)),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -6,12 +6,9 @@ use crate::meters;
|
|||
|
||||
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)]
|
||||
#[reflect(Component, Clone, Default, PartialEq, Debug)]
|
||||
#[require(Sprite, InteractiveObject)]
|
||||
#[require(InteractiveObject)]
|
||||
pub struct Door(pub i8);
|
||||
|
||||
#[derive(Component, Clone, Copy, Reflect, Default, PartialEq, Eq, Debug)]
|
||||
|
|
@ -19,20 +16,33 @@ pub struct Door(pub i8);
|
|||
#[require(Collider, LightOccluder2d)]
|
||||
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 {
|
||||
fn default() -> Self {
|
||||
Self(1)
|
||||
}
|
||||
}
|
||||
|
||||
impl Door {
|
||||
#[inline(always)]
|
||||
pub fn is_facing_left(&self) -> bool {
|
||||
self.0 < 0
|
||||
}
|
||||
}
|
||||
|
||||
pub fn on_door_interact(
|
||||
event: On<InteractionEvent>,
|
||||
mut commands: Commands,
|
||||
locked_query: Query<(), With<Locked>>,
|
||||
door_query: Query<(&Door, &Children)>,
|
||||
door_collider_query: Query<Entity, With<DoorCollider>>,
|
||||
mut sprite_query: Query<(&mut Sprite, &mut Transform)>,
|
||||
asset_server: Res<AssetServer>,
|
||||
mut sprite_query: Query<&mut Transform, With<Sprite>>,
|
||||
highlight_query: Query<(), With<DoorHighlight>>,
|
||||
textures: Res<LayoutTextures>,
|
||||
) {
|
||||
if locked_query.get(event.entity).is_ok() {
|
||||
return;
|
||||
|
|
@ -43,13 +53,18 @@ pub fn on_door_interact(
|
|||
let maybe_door_collider = children.iter()
|
||||
.find_map(|id| if let Ok(id) = door_collider_query.get(id) { Some(id) } else { None } );
|
||||
for child in children {
|
||||
if let Ok((mut sprite, mut transform)) = sprite_query.get_mut(*child) {
|
||||
let (image, translation) = if maybe_door_collider.is_none() { (DOOR_CLOSED_ASSET, 0.) }
|
||||
else { (DOOR_OPENED_ASSET, door.0 as f32 * meters(0.5)) };
|
||||
sprite.image = asset_server.load(image);
|
||||
match sprite_query.get_mut(*child) {
|
||||
Ok(mut transform) => {
|
||||
let translation = if maybe_door_collider.is_none() { 0. }
|
||||
else { door.0 as f32 * meters(0.5) };
|
||||
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 {
|
||||
|
|
@ -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 image = asset_server.load(DOOR_CLOSED_ASSET);
|
||||
let (sprite, highlight_sprite) = door_sprites(&textures.door_closed, facing_left);
|
||||
(
|
||||
Door(direction),
|
||||
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(),
|
||||
(
|
||||
Collider::cuboid(meters(0.5), meters(1.)),
|
||||
Transform::default(),
|
||||
Sensor,
|
||||
Transform::from_xyz(0., 0., 0.),
|
||||
),
|
||||
(
|
||||
Sprite {
|
||||
image,
|
||||
flip_x: facing_left,
|
||||
..default()
|
||||
},
|
||||
Transform::from_xyz(0., 0., 0.),
|
||||
sprite,
|
||||
),
|
||||
(
|
||||
highlight_sprite,
|
||||
Transform::from_xyz(0., 0., 1.),
|
||||
DoorHighlight,
|
||||
Visibility::Hidden,
|
||||
)
|
||||
],
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ use bevy_light_2d::prelude::*;
|
|||
|
||||
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)]
|
||||
#[reflect(Component, Clone, Default, Debug, PartialEq)]
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
use std::collections::HashMap;
|
||||
|
||||
use bevy::prelude::*;
|
||||
|
||||
pub mod asset;
|
||||
|
|
@ -35,3 +37,38 @@ pub struct LevelAssetHandle(Handle<asset::structs::LevelAsset>);
|
|||
pub struct InteractionEvent {
|
||||
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());
|
||||
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::<GameState>()
|
||||
.insert_resource(ui::WindowSize::default())
|
||||
.insert_resource(layout::LayoutTextures::default())
|
||||
.add_systems(Startup, (
|
||||
setup_global,
|
||||
layout::systems::setup_world,
|
||||
layout::systems::load_layout_textures,
|
||||
))
|
||||
.add_systems(Update, (
|
||||
insert_entity_name,
|
||||
|
|
|
|||
|
|
@ -7,8 +7,8 @@ use crate::layout::asset::structs::LevelAsset;
|
|||
fn deserialize_from_str() {
|
||||
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::<LevelAsset>(level_str).unwrap();
|
||||
let level_alt = toml::de::from_str::<LevelAsset>(level_alt_str).unwrap();
|
||||
let _ = toml::de::from_str::<LevelAsset>(level_str).unwrap();
|
||||
let _ = toml::de::from_str::<LevelAsset>(level_alt_str).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
|||