generated from 2ndbeam/bevy-template
feat: lighting system
- Added lamp bundle - Moved door collider to its children - Updated level structure
This commit is contained in:
parent
3cddecf592
commit
08751ff12f
12 changed files with 134 additions and 31 deletions
11
Cargo.lock
generated
11
Cargo.lock
generated
|
|
@ -1035,6 +1035,16 @@ dependencies = [
|
||||||
"tracing",
|
"tracing",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bevy_light_2d"
|
||||||
|
version = "0.9.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7d40446e54d26895cdf32a5c6c0f5dd3104bbdeff53fc392ec85fb2d164fa51a"
|
||||||
|
dependencies = [
|
||||||
|
"bevy",
|
||||||
|
"smallvec",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bevy_log"
|
name = "bevy_log"
|
||||||
version = "0.18.0"
|
version = "0.18.0"
|
||||||
|
|
@ -2523,6 +2533,7 @@ dependencies = [
|
||||||
"bevy",
|
"bevy",
|
||||||
"bevy_common_assets",
|
"bevy_common_assets",
|
||||||
"bevy_input",
|
"bevy_input",
|
||||||
|
"bevy_light_2d",
|
||||||
"bevy_rapier2d",
|
"bevy_rapier2d",
|
||||||
"clap",
|
"clap",
|
||||||
"leafwing-input-manager",
|
"leafwing-input-manager",
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ edition = "2024"
|
||||||
bevy = { version = "0.18.0", features = ["bevy_remote", "debug", "experimental_bevy_ui_widgets"] }
|
bevy = { version = "0.18.0", features = ["bevy_remote", "debug", "experimental_bevy_ui_widgets"] }
|
||||||
bevy_common_assets = { version = "0.15.0", features = ["toml"] }
|
bevy_common_assets = { version = "0.15.0", features = ["toml"] }
|
||||||
bevy_input = { version = "0.18.0", features = ["serde", "serialize"] }
|
bevy_input = { version = "0.18.0", features = ["serde", "serialize"] }
|
||||||
|
bevy_light_2d = "0.9.0"
|
||||||
bevy_rapier2d = { version = "0.33.0", features = ["rapier-debug-render"] }
|
bevy_rapier2d = { version = "0.33.0", features = ["rapier-debug-render"] }
|
||||||
clap = { version = "4.6.0", features = ["derive"] }
|
clap = { version = "4.6.0", features = ["derive"] }
|
||||||
leafwing-input-manager = "0.20.0"
|
leafwing-input-manager = "0.20.0"
|
||||||
|
|
|
||||||
|
|
@ -111,3 +111,13 @@ h = 2
|
||||||
[[interactive.containers.items]]
|
[[interactive.containers.items]]
|
||||||
id = "lockpick"
|
id = "lockpick"
|
||||||
rotated = true
|
rotated = true
|
||||||
|
|
||||||
|
[[interactive.lamps]]
|
||||||
|
x = 8
|
||||||
|
y = 3
|
||||||
|
|
||||||
|
[[interactive.lamps]]
|
||||||
|
x = 8
|
||||||
|
y = 7
|
||||||
|
intensity = 3
|
||||||
|
radius = 6
|
||||||
|
|
|
||||||
|
|
@ -52,3 +52,8 @@ containers = [
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
lamps = [
|
||||||
|
{ x = 8, y = 3 },
|
||||||
|
{ x = 8, y = 7, intensity = 3, radius = 16 },
|
||||||
|
{ x = 8, y = 11, intensity = 2, radius = 16 },
|
||||||
|
]
|
||||||
|
|
|
||||||
|
|
@ -3,20 +3,9 @@ use std::collections::HashMap;
|
||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
LoadingState,
|
LoadingState, item::lockpick::lockpick_bundle, layout::{
|
||||||
meters,
|
Level, LevelAssetHandle, asset::structs::*, container::container_bundle, door::door_bundle, light::lamp_bundle, lock::padlock_bundle, stairs::stairs_bundle, tilemap::tilemap_bundle
|
||||||
item::lockpick::lockpick_bundle,
|
}, meters, player::player_bundle
|
||||||
layout::{
|
|
||||||
Level,
|
|
||||||
LevelAssetHandle,
|
|
||||||
asset::structs::*,
|
|
||||||
container::container_bundle,
|
|
||||||
door::door_bundle,
|
|
||||||
lock::padlock_bundle,
|
|
||||||
stairs::stairs_bundle,
|
|
||||||
tilemap::tilemap_bundle
|
|
||||||
},
|
|
||||||
player::player_bundle,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub mod structs;
|
pub mod structs;
|
||||||
|
|
@ -107,5 +96,10 @@ pub fn load_level (
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for LampData { pos, intensity, radius } in level.interactive.lamps.iter() {
|
||||||
|
let pos = vec2(meters(pos.x), meters(pos.y - 0.5));
|
||||||
|
parent.spawn(lamp_bundle(&asset_server, pos, *intensity, meters(*radius)));
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -176,6 +176,31 @@ impl From<ContainerDataInner> for ContainerData {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(super) fn default_intensity() -> f32 { 2. }
|
||||||
|
|
||||||
|
pub(super) fn default_radius() -> f32 { 4. }
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize, Default, PartialEq, Clone, Reflect)]
|
||||||
|
#[reflect(Debug, Default, Deserialize, Serialize, PartialEq, Clone)]
|
||||||
|
pub(super) struct LampDataInner {
|
||||||
|
#[serde(flatten)]
|
||||||
|
pub pos: Pos,
|
||||||
|
#[serde(default = "default_intensity")]
|
||||||
|
pub intensity: f32,
|
||||||
|
#[serde(default = "default_radius")]
|
||||||
|
pub radius: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<LampDataInner> for LampData {
|
||||||
|
fn from(LampDataInner { pos, intensity, radius }: LampDataInner) -> Self {
|
||||||
|
Self {
|
||||||
|
pos: pos.into(),
|
||||||
|
intensity,
|
||||||
|
radius,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Default, PartialEq, Clone, Reflect)]
|
#[derive(Debug, Serialize, Deserialize, Default, PartialEq, Clone, Reflect)]
|
||||||
#[reflect(Debug, Default, Deserialize, Serialize, PartialEq, Clone)]
|
#[reflect(Debug, Default, Deserialize, Serialize, PartialEq, Clone)]
|
||||||
pub(super) struct InteractiveInner {
|
pub(super) struct InteractiveInner {
|
||||||
|
|
@ -186,15 +211,18 @@ pub(super) struct InteractiveInner {
|
||||||
pub stairs: Option<Vec<StairsData>>,
|
pub stairs: Option<Vec<StairsData>>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub containers: Option<Vec<ContainerData>>,
|
pub containers: Option<Vec<ContainerData>>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub lamps: Option<Vec<LampData>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<InteractiveInner> for Interactive {
|
impl From<InteractiveInner> for Interactive {
|
||||||
fn from(InteractiveInner { player, doors, stairs, containers }: InteractiveInner) -> Self {
|
fn from(InteractiveInner { player, doors, stairs, containers, lamps }: InteractiveInner) -> Self {
|
||||||
Self {
|
Self {
|
||||||
player: player.into(),
|
player: player.into(),
|
||||||
doors: doors.unwrap_or_default(),
|
doors: doors.unwrap_or_default(),
|
||||||
stairs: stairs.unwrap_or_default(),
|
stairs: stairs.unwrap_or_default(),
|
||||||
containers: containers.unwrap_or_default(),
|
containers: containers.unwrap_or_default(),
|
||||||
|
lamps: lamps.unwrap_or_default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -67,6 +67,15 @@ pub struct ContainerData {
|
||||||
pub items: Vec<ItemData>,
|
pub items: Vec<ItemData>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize, Default, PartialEq, Clone, Reflect)]
|
||||||
|
#[reflect(Debug, Default, Deserialize, Serialize, PartialEq, Clone)]
|
||||||
|
#[serde(from = "inner::LampDataInner")]
|
||||||
|
pub struct LampData {
|
||||||
|
pub pos: Vec2,
|
||||||
|
pub intensity: f32,
|
||||||
|
pub radius: f32,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Default, PartialEq, Clone, Reflect)]
|
#[derive(Debug, Serialize, Deserialize, Default, PartialEq, Clone, Reflect)]
|
||||||
#[reflect(Debug, Default, Deserialize, Serialize, PartialEq, Clone)]
|
#[reflect(Debug, Default, Deserialize, Serialize, PartialEq, Clone)]
|
||||||
#[serde(from = "inner::InteractiveInner")]
|
#[serde(from = "inner::InteractiveInner")]
|
||||||
|
|
@ -78,6 +87,8 @@ pub struct Interactive {
|
||||||
pub stairs: Vec<StairsData>,
|
pub stairs: Vec<StairsData>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub containers: Vec<ContainerData>,
|
pub containers: Vec<ContainerData>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub lamps: Vec<LampData>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Default, PartialEq, Eq, Clone, Deref, DerefMut, Reflect)]
|
#[derive(Debug, Serialize, Deserialize, Default, PartialEq, Eq, Clone, Deref, DerefMut, Reflect)]
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
|
use bevy_light_2d::prelude::*;
|
||||||
use bevy_rapier2d::prelude::*;
|
use bevy_rapier2d::prelude::*;
|
||||||
|
|
||||||
use crate::meters;
|
use crate::meters;
|
||||||
|
|
@ -13,6 +14,11 @@ const DOOR_CLOSED_ASSET: &'static str = "sprites/interactive/door_closed.png";
|
||||||
#[require(Sprite, InteractiveObject)]
|
#[require(Sprite, InteractiveObject)]
|
||||||
pub struct Door(pub i8);
|
pub struct Door(pub i8);
|
||||||
|
|
||||||
|
#[derive(Component, Clone, Copy, Reflect, Default, PartialEq, Eq, Debug)]
|
||||||
|
#[reflect(Component, Clone, Default, PartialEq, Debug)]
|
||||||
|
#[require(Collider, LightOccluder2d)]
|
||||||
|
pub struct DoorCollider;
|
||||||
|
|
||||||
impl Default for Door {
|
impl Default for Door {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self(1)
|
Self(1)
|
||||||
|
|
@ -23,26 +29,22 @@ 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>>,
|
||||||
collider_query: Query<(), (With<Door>, With<Collider>)>,
|
|
||||||
no_collider_query: Query<(), (With<Door>, Without<Collider>)>,
|
|
||||||
door_query: Query<(&Door, &Children)>,
|
door_query: Query<(&Door, &Children)>,
|
||||||
|
door_collider_query: Query<Entity, With<DoorCollider>>,
|
||||||
mut sprite_query: Query<(&mut Sprite, &mut Transform)>,
|
mut sprite_query: Query<(&mut Sprite, &mut Transform)>,
|
||||||
asset_server: Res<AssetServer>,
|
asset_server: Res<AssetServer>,
|
||||||
) {
|
) {
|
||||||
if locked_query.get(event.entity).is_ok() {
|
if locked_query.get(event.entity).is_ok() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let was_opened = if collider_query.get(event.entity).is_ok() { false }
|
|
||||||
else if no_collider_query.get(event.entity).is_ok() { true }
|
|
||||||
else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
let Ok((door, children)) = door_query.get(event.entity) else {
|
let Ok((door, children)) = door_query.get(event.entity) else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
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 {
|
for child in children {
|
||||||
if let Ok((mut sprite, mut transform)) = sprite_query.get_mut(*child) {
|
if let Ok((mut sprite, mut transform)) = sprite_query.get_mut(*child) {
|
||||||
let (image, translation) = if was_opened { (DOOR_CLOSED_ASSET, 0.) }
|
let (image, translation) = if maybe_door_collider.is_none() { (DOOR_CLOSED_ASSET, 0.) }
|
||||||
else { (DOOR_OPENED_ASSET, door.0 as f32 * meters(0.5)) };
|
else { (DOOR_OPENED_ASSET, door.0 as f32 * meters(0.5)) };
|
||||||
sprite.image = asset_server.load(image);
|
sprite.image = asset_server.load(image);
|
||||||
transform.translation.x = translation;
|
transform.translation.x = translation;
|
||||||
|
|
@ -50,15 +52,20 @@ pub fn on_door_interact(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if was_opened {
|
if let Some(id) = maybe_door_collider {
|
||||||
commands.entity(event.entity).insert(door_collider());
|
commands.entity(id).despawn();
|
||||||
} else {
|
} else {
|
||||||
commands.entity(event.entity).remove::<Collider>();
|
commands.entity(event.entity).with_child(door_collider_bundle());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn door_collider() -> Collider {
|
pub fn door_collider_bundle() -> impl Bundle {
|
||||||
Collider::cuboid(meters(0.06125), meters(1.))
|
let size = vec2(meters(0.06125), meters(1.));
|
||||||
|
(
|
||||||
|
DoorCollider,
|
||||||
|
Collider::cuboid(size.x, size.y),
|
||||||
|
LightOccluder2d { shape: LightOccluder2dShape::Rectangle { half_size: size } },
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn door_bundle(asset_server: &Res<AssetServer>, position: Vec2, facing_left: bool) -> impl Bundle {
|
pub fn door_bundle(asset_server: &Res<AssetServer>, position: Vec2, facing_left: bool) -> impl Bundle {
|
||||||
|
|
@ -68,8 +75,8 @@ pub fn door_bundle(asset_server: &Res<AssetServer>, position: Vec2, facing_left:
|
||||||
Door(direction),
|
Door(direction),
|
||||||
Transform::from_xyz(position.x, position.y, 0.),
|
Transform::from_xyz(position.x, position.y, 0.),
|
||||||
Name::new(format!("Door ({}, {})", position.x, position.y)),
|
Name::new(format!("Door ({}, {})", position.x, position.y)),
|
||||||
door_collider(),
|
|
||||||
children![
|
children![
|
||||||
|
door_collider_bundle(),
|
||||||
(
|
(
|
||||||
Collider::cuboid(meters(0.5), meters(1.)),
|
Collider::cuboid(meters(0.5), meters(1.)),
|
||||||
Sensor,
|
Sensor,
|
||||||
|
|
|
||||||
26
src/layout/light.rs
Normal file
26
src/layout/light.rs
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
use bevy::prelude::*;
|
||||||
|
use bevy_light_2d::prelude::*;
|
||||||
|
|
||||||
|
use crate::meters;
|
||||||
|
|
||||||
|
const LAMP_IMAGE_PATH: &'static str = "sprites/interactive/lamp.png";
|
||||||
|
|
||||||
|
#[derive(Component, Clone, Copy, Default, Reflect, Debug, PartialEq, Eq)]
|
||||||
|
#[reflect(Component, Clone, Default, Debug, PartialEq)]
|
||||||
|
#[require(Transform)]
|
||||||
|
pub struct Lamp;
|
||||||
|
|
||||||
|
pub fn lamp_bundle(asset_server: &Res<AssetServer>, pos: Vec2, intensity: f32, radius: f32) -> impl Bundle {
|
||||||
|
let image = asset_server.load(LAMP_IMAGE_PATH);
|
||||||
|
(
|
||||||
|
SpotLight2d {
|
||||||
|
intensity,
|
||||||
|
radius,
|
||||||
|
source_width: meters(0.5),
|
||||||
|
cast_shadows: true,
|
||||||
|
..default()
|
||||||
|
},
|
||||||
|
Transform::from_xyz(pos.x, pos.y, 0.),
|
||||||
|
Sprite::from_image(image),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -3,6 +3,7 @@ use bevy::prelude::*;
|
||||||
pub mod asset;
|
pub mod asset;
|
||||||
pub mod container;
|
pub mod container;
|
||||||
pub mod door;
|
pub mod door;
|
||||||
|
pub mod light;
|
||||||
pub mod lock;
|
pub mod lock;
|
||||||
pub mod stairs;
|
pub mod stairs;
|
||||||
pub mod systems;
|
pub mod systems;
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ use bevy::{
|
||||||
TilemapChunkTileData,
|
TilemapChunkTileData,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
use bevy_light_2d::prelude::*;
|
||||||
use bevy_rapier2d::prelude::*;
|
use bevy_rapier2d::prelude::*;
|
||||||
|
|
||||||
use crate::meters;
|
use crate::meters;
|
||||||
|
|
@ -58,10 +59,13 @@ pub fn tilemap_bundle(
|
||||||
let mut rect = rect.as_rect();
|
let mut rect = rect.as_rect();
|
||||||
rect.max = vec2(rect.max.x + 1., rect.max.y + 1.);
|
rect.max = vec2(rect.max.x + 1., rect.max.y + 1.);
|
||||||
|
|
||||||
let (width, height) = (rect.width(), rect.height());
|
let (width, height) = (meters(rect.width() * 0.5), meters(rect.height() * 0.5));
|
||||||
let offset = rect.center();
|
let offset = rect.center();
|
||||||
(
|
(
|
||||||
Collider::cuboid(meters(width * 0.5), meters(height * 0.5)),
|
Collider::cuboid(width, height),
|
||||||
|
LightOccluder2d {
|
||||||
|
shape: LightOccluder2dShape::Rectangle { half_size: vec2(width, height) },
|
||||||
|
},
|
||||||
Transform::from_xyz(
|
Transform::from_xyz(
|
||||||
meters(offset.x - size.x as f32 * 0.5),
|
meters(offset.x - size.x as f32 * 0.5),
|
||||||
meters(offset.y - size.y as f32 * 0.5),
|
meters(offset.y - size.y as f32 * 0.5),
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ use bevy_rapier2d::{
|
||||||
prelude::*,
|
prelude::*,
|
||||||
rapier::prelude::IntegrationParameters,
|
rapier::prelude::IntegrationParameters,
|
||||||
};
|
};
|
||||||
|
use bevy_light_2d::prelude::*;
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
|
|
||||||
pub mod input;
|
pub mod input;
|
||||||
|
|
@ -60,6 +61,9 @@ pub fn camera_bundle() -> impl Bundle {
|
||||||
scale: 1.,
|
scale: 1.,
|
||||||
..OrthographicProjection::default_2d()
|
..OrthographicProjection::default_2d()
|
||||||
}),
|
}),
|
||||||
|
Light2d {
|
||||||
|
ambient_light: AmbientLight2d { brightness: 0.25, ..default() }
|
||||||
|
},
|
||||||
Name::new("Camera2d"),
|
Name::new("Camera2d"),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -101,6 +105,7 @@ impl Plugin for ExpeditionPlugin {
|
||||||
TomlAssetPlugin::<layout::asset::structs::LevelAsset>::new(&["toml"]),
|
TomlAssetPlugin::<layout::asset::structs::LevelAsset>::new(&["toml"]),
|
||||||
input::plugin::InputAssetPlugin::<input::InputAction>::default(),
|
input::plugin::InputAssetPlugin::<input::InputAction>::default(),
|
||||||
input::plugin::InputAssetPlugin::<input::UiAction>::default(),
|
input::plugin::InputAssetPlugin::<input::UiAction>::default(),
|
||||||
|
Light2dPlugin,
|
||||||
))
|
))
|
||||||
.init_state::<LoadingState>()
|
.init_state::<LoadingState>()
|
||||||
.init_state::<GameState>()
|
.init_state::<GameState>()
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue