Merge pull request #5 from Back777space/open-doors

Open doors
This commit is contained in:
lorrens
2025-04-06 22:56:11 +02:00
committed by GitHub
6 changed files with 240 additions and 104 deletions

View File

@@ -1,6 +1,6 @@
use bevy::prelude::*; use bevy::prelude::*;
mod objects; pub mod objects;
mod ui; mod ui;
#[derive(Component)] #[derive(Component)]

View File

@@ -2,52 +2,76 @@ use bevy::prelude::*;
use bevy_rapier3d::prelude::ColliderDisabled; use bevy_rapier3d::prelude::ColliderDisabled;
use crate::{ use crate::{
level_instantiation::Door, player::{toolbar::Item, Player, PlayerAction}, util::single_mut player::{Player, PlayerAction, toolbar::Item},
util::single_mut,
}; };
use super::{Interact, ui::InteractionOpportunity}; use super::{Interact, ui::InteractionOpportunity};
#[derive(Component, Clone, Copy)]
pub struct PickUpAble;
pub fn plugin(_app: &mut App) {} #[derive(Component, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
pub struct KeyCardId(pub usize);
#[derive(Component, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
pub struct OpenedByKey(pub KeyCardId);
#[derive(Resource, Default, Clone, Copy)]
pub struct WrongKey(pub bool);
impl OpenedByKey {
pub fn can_be_opened_by(&self, card: KeyCardId) -> bool {
self.0 == card
}
}
pub fn plugin(app: &mut App) {
app.init_resource::<WrongKey>();
}
pub fn handle_pick_up( pub fn handle_pick_up(
mut commands: Commands, mut commands: Commands,
// current action // current action
mut player_query: Query<(&PlayerAction, &Transform, &mut Item), With<Player>>, mut player_query: Query<(&PlayerAction, &Transform, &mut Item), With<Player>>,
mut vis_query: Query<&mut Visibility>, mut vis_query: Query<&mut Visibility, With<PickUpAble>>,
children: Query<&mut Children>, children: Query<&mut Children>,
mut item_transform: Query<&mut Transform, (With<Interact>, Without<Player>)>, mut item_transform: Query<&mut Transform, (With<Interact>, Without<Player>, With<PickUpAble>)>,
// current interactable // current interactable
mut interaction_opportunity: ResMut<InteractionOpportunity>, mut interaction_opportunity: ResMut<InteractionOpportunity>,
) { ) {
let (action, transform, mut item) = single_mut!(player_query); let (PlayerAction::PickUp, transform, mut item) = single_mut!(player_query) else {
if *action == PlayerAction::PickUp { return;
// take out the interaction, because we are picking it up };
let Some(target) = interaction_opportunity.0.take() else { // take out the interaction, because we are picking it up
return; let Some(target) = interaction_opportunity.0.take() else {
}; return;
let replaced = item.set_item(target); };
if let Ok(mut vis) = vis_query.get_mut(target) { // check if it is pickable and set visibility
*vis = Visibility::Hidden; let Ok(mut vis) = vis_query.get_mut(target) else {
return;
};
*vis = Visibility::Hidden;
// change colliders
if let Ok(colliders) = children.get(target) {
for &collider in colliders {
commands.entity(collider).insert(ColliderDisabled);
} }
if let Ok(colliders) = children.get(target) { }
let replaced = item.set_item(target);
if let Some(dropped) = replaced {
if let Ok(mut vis) = vis_query.get_mut(dropped) {
*vis = Visibility::Visible;
}
if let Ok(colliders) = children.get(dropped) {
for &collider in colliders { for &collider in colliders {
commands.entity(collider).insert(ColliderDisabled); commands.entity(collider).remove::<ColliderDisabled>();
} }
} }
if let Some(dropped) = replaced { // for simplicities sake, set the transform of the item that is dropped to that of the player
if let Ok(mut vis) = vis_query.get_mut(dropped) { let mut item_transform = item_transform.get_mut(dropped).unwrap();
*vis = Visibility::Visible; *item_transform = *transform;
}
if let Ok(colliders) = children.get(dropped) {
for &collider in colliders {
commands.entity(collider).remove::<ColliderDisabled>();
}
}
// for simplicities sake, set the transform of the item that is dropped to that of the player
let mut item_transform = item_transform.get_mut(dropped).unwrap();
*item_transform = *transform;
}
} }
} }
@@ -59,37 +83,58 @@ pub fn handle_drop(
mut item_transform: Query<&mut Transform, (With<Interact>, Without<Player>)>, mut item_transform: Query<&mut Transform, (With<Interact>, Without<Player>)>,
children: Query<&mut Children>, children: Query<&mut Children>,
) { ) {
let (action, transform, mut item) = single_mut!(player_query); let (PlayerAction::Drop, transform, mut item) = single_mut!(player_query) else {
if *action == PlayerAction::Drop { return;
let Some(item) = item.take() else { };
return; let Some(item) = item.take() else {
}; return;
if let Ok(mut vis) = vis_query.get_mut(item) { };
*vis = Visibility::Visible; if let Ok(mut vis) = vis_query.get_mut(item) {
} *vis = Visibility::Visible;
if let Ok(colliders) = children.get(item) {
for &collider in colliders {
commands.entity(collider).remove::<ColliderDisabled>();
}
}
// for simplicities sake, set the transform of the item that is dropped to that of the player
let mut item_transform = item_transform.get_mut(item).unwrap();
*item_transform = *transform;
} }
if let Ok(colliders) = children.get(item) {
for &collider in colliders {
commands.entity(collider).remove::<ColliderDisabled>();
}
}
// for simplicities sake, set the transform of the item that is dropped to that of the player
let mut item_transform = item_transform.get_mut(item).unwrap();
*item_transform = *transform;
} }
pub fn handle_door_interaction( pub fn handle_door_interaction(
player_query: Query<&PlayerAction, With<Player>>, mut doors: Query<&mut Door> mut commands: Commands,
mut player_query: Query<(&PlayerAction, &mut Item), With<Player>>,
card_query: Query<&KeyCardId>,
mut wrong_key: ResMut<WrongKey>,
// current interactable
interaction_opportunity: ResMut<InteractionOpportunity>,
door_query: Query<(Entity, &OpenedByKey), With<Interact>>,
) { ) {
let Ok(action) = player_query.get_single() else { // the card
let Ok((PlayerAction::OpenDoor, mut item)) = player_query.get_single_mut() else {
return; return;
}; };
if *action != PlayerAction::OpenDoor { let Some(current_card) = item.inner().and_then(|e| card_query.get(e).ok()) else {
return; return;
} };
for mut door in doors.iter_mut() { // the door
door.is_open = !door.is_open; let Some((door, door_keyhole)) = interaction_opportunity
.0
.and_then(|e| door_query.get(e).ok())
else {
return;
};
if door_keyhole.can_be_opened_by(*current_card) {
commands.entity(door).despawn_recursive();
// unwrap is safe because of the item.inner().and_then
commands.entity(item.inner().unwrap()).despawn_recursive();
item.take();
} else {
// wrong key :)
wrong_key.0 = true;
} }
} }

View File

@@ -1,12 +1,13 @@
use crate::GameState;
use crate::player::Player; use crate::player::Player;
use crate::util::single; use crate::util::single;
use crate::{GameState, level_instantiation::Door};
use bevy::{prelude::*, window::PrimaryWindow}; use bevy::{prelude::*, window::PrimaryWindow};
use bevy_egui::{EguiContexts, EguiPlugin, egui}; use bevy_egui::{EguiContexts, EguiPlugin, egui};
use bevy_rapier3d::prelude::*; use bevy_rapier3d::prelude::*;
use bevy_rapier3d::rapier::prelude::CollisionEventFlags; use bevy_rapier3d::rapier::prelude::CollisionEventFlags;
use std::iter; use std::iter;
use super::objects::WrongKey;
use super::{Interact, objects}; use super::{Interact, objects};
pub(super) fn plugin(app: &mut App) { pub(super) fn plugin(app: &mut App) {
@@ -17,7 +18,11 @@ pub(super) fn plugin(app: &mut App) {
( (
update_interaction_opportunities, update_interaction_opportunities,
display_interaction_prompt, display_interaction_prompt,
(objects::handle_pick_up, objects::handle_drop, objects::handle_door_interaction), (
objects::handle_pick_up,
objects::handle_drop,
objects::handle_door_interaction,
),
) )
.chain() .chain()
.run_if(in_state(GameState::Playing)), .run_if(in_state(GameState::Playing)),
@@ -31,6 +36,7 @@ fn update_interaction_opportunities(
mut collision_events: EventReader<CollisionEvent>, mut collision_events: EventReader<CollisionEvent>,
player_query: Query<Entity, With<Player>>, player_query: Query<Entity, With<Player>>,
parents: Query<&Parent>, parents: Query<&Parent>,
mut wrong_key: ResMut<WrongKey>,
target_query: Query<Entity, (Without<Player>, With<Interact>)>, target_query: Query<Entity, (Without<Player>, With<Interact>)>,
mut interaction_opportunity: ResMut<InteractionOpportunity>, mut interaction_opportunity: ResMut<InteractionOpportunity>,
) { ) {
@@ -59,6 +65,8 @@ fn update_interaction_opportunities(
if started { if started {
interaction_opportunity.0.replace(interactable); interaction_opportunity.0.replace(interactable);
} else { } else {
// reset when gone
wrong_key.0 = false;
interaction_opportunity.0.take_if(|t| *t == interactable); interaction_opportunity.0.take_if(|t| *t == interactable);
} }
} }
@@ -68,17 +76,20 @@ fn display_interaction_prompt(
interaction_opportunity: Res<InteractionOpportunity>, interaction_opportunity: Res<InteractionOpportunity>,
mut egui_contexts: EguiContexts, mut egui_contexts: EguiContexts,
primary_windows: Query<&Window, With<PrimaryWindow>>, primary_windows: Query<&Window, With<PrimaryWindow>>,
names: Query<&Name, With<Interact>>, // only the interactables ofcourse wrong_key: Res<WrongKey>,
names: Query<(&Name, Option<&Door>), With<Interact>>, // only the interactables ofcourse
) { ) {
let Some(opportunity) = interaction_opportunity.0 else { let Some(opportunity) = interaction_opportunity.0 else {
return; return;
}; };
let window = single!(primary_windows); let window = single!(primary_windows);
let entity_name = names let Ok(interaction_str) = names.get(opportunity).map(|(name, door)| match door {
.get(opportunity) Some(_) => "T: Open Door".into(),
.map(|name| name.as_str()) None => format!("E: Pick Up {name}"),
.expect("A named Interactable object"); }) else {
return;
};
// objective or item // objective or item
egui::Window::new("Interaction") egui::Window::new("Interaction")
@@ -87,6 +98,9 @@ fn display_interaction_prompt(
.auto_sized() .auto_sized()
.fixed_pos(egui::Pos2::new(window.width() / 2., window.height() / 2.)) .fixed_pos(egui::Pos2::new(window.width() / 2., window.height() / 2.))
.show(egui_contexts.ctx_mut(), |ui| { .show(egui_contexts.ctx_mut(), |ui| {
ui.label(format!("E: Pick Up {entity_name}")); ui.label(interaction_str);
if wrong_key.0 {
ui.label("Wrong Key :)");
}
}); });
} }

View File

@@ -5,7 +5,13 @@ use bevy_rapier3d::prelude::*;
use rand::{Rng, seq::IteratorRandom}; use rand::{Rng, seq::IteratorRandom};
use crate::{ use crate::{
asset_loading::{GltfAssets, ImageAssets}, interaction::Interact, player::{toolbar::ItemIcon, Player, PlayerAction}, GameState GameState,
asset_loading::{GltfAssets, ImageAssets},
interaction::{
Interact,
objects::{KeyCardId, OpenedByKey, PickUpAble},
},
player::{Player, PlayerAction, toolbar::ItemIcon},
}; };
pub fn map_plugin(app: &mut App) { pub fn map_plugin(app: &mut App) {
@@ -589,11 +595,59 @@ fn spawn_objects(
} else { } else {
Transform::from_xyz(2.0 * x as f32, 0.5, -2.0 * z as f32) Transform::from_xyz(2.0 * x as f32, 0.5, -2.0 * z as f32)
}; };
// Correct Key Card
commands commands
.spawn(( .spawn((
transform, transform,
Interact, Interact,
PickUpAble,
RigidBody::Dynamic, RigidBody::Dynamic,
KeyCardId(i),
Name::new(format!("Id Card {i}")),
Visibility::Visible,
ItemIcon::new(image_assets.id_card.clone()),
SceneRoot(asset.clone()),
))
.with_children(|parent| {
parent.spawn((
ColliderMassProperties::Mass(10.0),
Collider::cuboid(0.05, 0.05, 0.01),
Transform::from_rotation(Quat::from_euler(
EulerRot::XYZ,
-10.0f32.to_radians(), // X-axis rotation (tilt)
-5.0f32.to_radians(), // Y-axis rotation
10.0f32.to_radians(), // Z-axis rotation
)),
));
parent.spawn((
ActiveEvents::COLLISION_EVENTS,
Transform::default(),
Collider::ball(0.5), // Interaction radius
Sensor,
));
});
// wrong key card
let (x, z) = loop {
let positions = level.nodes.keys();
let random_pos = positions.choose(&mut rand::rng()).unwrap().clone();
if random_pos != begin_node && random_pos != end_node {
break random_pos;
}
};
let transform = if x <= z {
Transform::from_xyz(2.0 * x as f32, 0.5, -2.0 * z as f32)
.with_rotation(Quat::from_rotation_y(90.0_f32.to_radians()))
} else {
Transform::from_xyz(2.0 * x as f32, 0.5, -2.0 * z as f32)
};
commands
.spawn((
transform,
Interact,
PickUpAble,
RigidBody::Dynamic,
KeyCardId(usize::MAX),
Name::new(format!("Id Card {i}")), Name::new(format!("Id Card {i}")),
Visibility::Visible, Visibility::Visible,
ItemIcon::new(image_assets.id_card.clone()), ItemIcon::new(image_assets.id_card.clone()),
@@ -624,7 +678,7 @@ fn spawn_objects(
pub struct Door { pub struct Door {
pub is_open: bool, pub is_open: bool,
pub open_direction: (i32, i32), pub open_direction: (i32, i32),
pub position: (i32, i32) pub position: (i32, i32),
} }
fn spawn_doors( fn spawn_doors(
@@ -644,43 +698,55 @@ fn spawn_doors(
let asset = gltf.default_scene.as_ref().unwrap(); let asset = gltf.default_scene.as_ref().unwrap();
let scene_root = SceneRoot(asset.clone()); let scene_root = SceneRoot(asset.clone());
for level in levels.levels.iter() { for (i, level) in levels.levels.iter().enumerate() {
let (x, z) = level.end_node; let (x, z) = level.end_node;
let (direction, transform) = if x >= z { let (direction, transform) = if x >= z {
( (
(0, 1), (0, 1),
Transform::from_xyz(2.0 * x as f32, 0.0, -2.0 * z as f32) Transform::from_xyz(2.0 * x as f32, 0.0, -2.0 * z as f32)
.with_rotation(Quat::from_rotation_y(90.0_f32.to_radians())) .with_rotation(Quat::from_rotation_y(-90.0_f32.to_radians())),
) )
} else { } else {
( (
(1, 0), (1, 0),
Transform::from_xyz(2.0 * x as f32, 0.0, -2.0 * z as f32) Transform::from_xyz(2.0 * x as f32, 0.0, -2.0 * z as f32),
) )
}; };
commands.spawn(( commands
RigidBody::Fixed, .spawn((
Door { // very scuff but need this
is_open: false, OpenedByKey(KeyCardId(i)),
open_direction: direction, Interact,
position: (x, z) RigidBody::Fixed,
}, Name::new("Door"),
transform, Door {
scene_root.clone(), is_open: false,
)).with_children(|parent| { open_direction: direction,
for (collider, transform) in collider.clone() { position: (x, z),
parent.spawn((collider, transform)); },
} transform,
}); scene_root.clone(),
))
.with_children(|parent| {
for (collider, transform) in collider.clone() {
parent.spawn((collider, transform));
}
// make it "interactable" in the world
parent.spawn((
ActiveEvents::COLLISION_EVENTS,
Transform::default(),
Collider::ball(0.5), // Interaction radius
Sensor,
));
});
} }
} }
fn handle_door_pos(mut query: Query<(&Door, &mut Transform)>) { fn handle_door_pos(mut query: Query<(&Door, &mut Transform)>) {
for (door, mut transform) in query.iter_mut() { for (door, mut transform) in query.iter_mut() {
if door.is_open { if door.is_open {
transform.translation.y = 2.0 transform.translation.y = 2.0
} } else {
else {
transform.translation.y = 0.0; transform.translation.y = 0.0;
} }
} }

View File

@@ -1,5 +1,9 @@
use bevy::{ use bevy::{
input::mouse::AccumulatedMouseMotion, pbr::VolumetricFog, prelude::*, render::view::RenderLayers, window::{PrimaryWindow, WindowResized} input::mouse::AccumulatedMouseMotion,
pbr::VolumetricFog,
prelude::*,
render::view::RenderLayers,
window::{PrimaryWindow, WindowResized},
}; };
use bevy_kira_audio::{Audio, AudioControl}; use bevy_kira_audio::{Audio, AudioControl};
use bevy_rapier3d::prelude::*; use bevy_rapier3d::prelude::*;
@@ -62,8 +66,7 @@ pub struct Flashlight {
} }
#[derive(Debug, Component)] #[derive(Debug, Component)]
pub struct SpotlightFlashlight { pub struct SpotlightFlashlight {}
}
#[derive(Component)] #[derive(Component)]
pub struct FlashlightButtonAnimation { pub struct FlashlightButtonAnimation {
@@ -91,7 +94,12 @@ pub fn plugin(app: &mut App) {
apply_head_bob, apply_head_bob,
on_resize_system, on_resize_system,
(handle_flashlight, handle_spotlight).chain(), (handle_flashlight, handle_spotlight).chain(),
(update_flashlight_button_animation, update_flashlight_charge, update_flashlight_sprite).chain(), (
update_flashlight_button_animation,
update_flashlight_charge,
update_flashlight_sprite,
)
.chain(),
) )
.run_if(in_state(GameState::Playing)), .run_if(in_state(GameState::Playing)),
) )
@@ -159,7 +167,10 @@ pub fn init_player(
let window = window.single(); let window = window.single();
let transform = flashlight_base_transform(window.width(), window.height()); let transform = flashlight_base_transform(window.width(), window.height());
parent.spawn(( parent.spawn((
Flashlight { charge: 4.0, is_on: false }, Flashlight {
charge: 4.0,
is_on: false,
},
Sprite::from_image(flashlights.flash_hold_4.clone()), Sprite::from_image(flashlights.flash_hold_4.clone()),
transform.0.clone(), transform.0.clone(),
transform, transform,
@@ -169,15 +180,15 @@ pub fn init_player(
// feitelijke pitslamp // feitelijke pitslamp
parent.spawn(( parent.spawn((
SpotlightFlashlight{}, SpotlightFlashlight {},
SpotLight { SpotLight {
intensity: 0.0, intensity: 0.0,
color: Color::srgba(0.9, 0.628, 0.392, 1.0), color: Color::srgba(0.9, 0.628, 0.392, 1.0),
range: 5.0, range: 5.0,
outer_angle: 28.0_f32.to_radians(), // wide cone for flashlight outer_angle: 28.0_f32.to_radians(), // wide cone for flashlight
inner_angle: 12.0_f32.to_radians(), // narrower inner cone inner_angle: 12.0_f32.to_radians(), // narrower inner cone
shadows_enabled: false, // (set to true for realism) shadows_enabled: false, // (set to true for realism)
radius: 0.35, // low value for hard light radius: 0.35, // low value for hard light
..default() ..default()
}, },
Transform::from_xyz(0.0, -0.15, 0.5), Transform::from_xyz(0.0, -0.15, 0.5),
@@ -300,7 +311,7 @@ pub fn handle_input(
if keyboard_input.just_pressed(KeyCode::KeyF) { if keyboard_input.just_pressed(KeyCode::KeyF) {
*action = PlayerAction::ToggleFlashlight; *action = PlayerAction::ToggleFlashlight;
} }
if keyboard_input.just_pressed(KeyCode::KeyK) { if keyboard_input.just_pressed(KeyCode::KeyT) {
*action = PlayerAction::OpenDoor; *action = PlayerAction::OpenDoor;
} }
@@ -413,7 +424,7 @@ pub fn handle_flashlight(
let Ok(action) = player_query.get_single() else { let Ok(action) = player_query.get_single() else {
return; return;
}; };
if *action != PlayerAction::ToggleFlashlight { if *action != PlayerAction::ToggleFlashlight {
return; return;
} }
if let Ok(flashlight) = flashlight_query.get_single() { if let Ok(flashlight) = flashlight_query.get_single() {
@@ -435,19 +446,15 @@ pub fn handle_spotlight(
mut spotlight_query: Query<&mut SpotLight, With<SpotlightFlashlight>>, mut spotlight_query: Query<&mut SpotLight, With<SpotlightFlashlight>>,
mut flashlight_query: Query<&mut Flashlight>, mut flashlight_query: Query<&mut Flashlight>,
) { ) {
if let (Ok(mut spotlight), Ok(flashlight)) = (spotlight_query.get_single_mut(), flashlight_query.get_single_mut()) { if let (Ok(mut spotlight), Ok(flashlight)) = (
spotlight.intensity = if !flashlight.is_on { spotlight_query.get_single_mut(),
0.0 flashlight_query.get_single_mut(),
} else { ) {
320_000.0 spotlight.intensity = if !flashlight.is_on { 0.0 } else { 320_000.0 };
};
} }
} }
pub fn update_flashlight_charge( pub fn update_flashlight_charge(time: Res<Time>, mut flashlight_query: Query<&mut Flashlight>) {
time: Res<Time>,
mut flashlight_query: Query<&mut Flashlight>,
) {
for mut flashlight in flashlight_query.iter_mut() { for mut flashlight in flashlight_query.iter_mut() {
if flashlight.is_on { if flashlight.is_on {
flashlight.charge = flashlight.charge - time.delta_secs() * 0.1; flashlight.charge = flashlight.charge - time.delta_secs() * 0.1;
@@ -479,7 +486,7 @@ pub fn update_flashlight_button_animation(
pub fn update_flashlight_sprite( pub fn update_flashlight_sprite(
mut query: Query<(&FlashlightButtonAnimation, &mut Sprite, &Flashlight)>, mut query: Query<(&FlashlightButtonAnimation, &mut Sprite, &Flashlight)>,
flashlights: Res<FlashlightAssets>, flashlights: Res<FlashlightAssets>,
){ ) {
for (animation, mut image, flashlight) in query.iter_mut() { for (animation, mut image, flashlight) in query.iter_mut() {
let charge = flashlight.charge.round() as i32; let charge = flashlight.charge.round() as i32;
let sprite_image = match (charge, animation.is_pressed) { let sprite_image = match (charge, animation.is_pressed) {
@@ -493,7 +500,7 @@ pub fn update_flashlight_sprite(
(1, false) => flashlights.flash_hold_1.clone(), (1, false) => flashlights.flash_hold_1.clone(),
(0, true) => flashlights.flash_hold_0_pressed.clone(), (0, true) => flashlights.flash_hold_0_pressed.clone(),
(0, false) => flashlights.flash_hold_0.clone(), (0, false) => flashlights.flash_hold_0.clone(),
_ => flashlights.flash_hold_0.clone() _ => flashlights.flash_hold_0.clone(),
}; };
*image = Sprite::from_image(sprite_image); *image = Sprite::from_image(sprite_image);
} }

View File

@@ -13,6 +13,10 @@ impl Item {
pub fn take(&mut self) -> Option<Entity> { pub fn take(&mut self) -> Option<Entity> {
self.0.take() self.0.take()
} }
pub fn inner(&self) -> Option<Entity> {
self.0
}
} }
#[derive(Component, Default, Debug)] #[derive(Component, Default, Debug)]