diff --git a/assets/images/id_card_toolbar.png b/assets/images/id_card_toolbar.png new file mode 100644 index 0000000..c4bb27a Binary files /dev/null and b/assets/images/id_card_toolbar.png differ diff --git a/assets/main.assets.ron b/assets/main.assets.ron index 99637d0..43af53b 100644 --- a/assets/main.assets.ron +++ b/assets/main.assets.ron @@ -4,8 +4,20 @@ "flash_hold_4_pressed": File (path: "images/pixelart/Flashlight_click_4.png"), "monster_footsteps": File (path: "audio/monster-footsteps.ogg"), "house": File (path: "meshes/House.glb"), - "flashlight_click": File (path: "audio/flashlight-switch.ogg"), "library": Folder ( path: "meshes/library", ), + "id_card": File(path: "meshes/id_card.glb"), + "id_card_toolbar": File(path: "images/id_card_toolbar.png"), + "flash_hold_0": File (path: "images/pixelart/Flashlight_hold_0.png"), + "flash_hold_0_pressed": File (path: "images/pixelart/Flashlight_click_0.png"), + "flash_hold_1": File (path: "images/pixelart/Flashlight_hold_1.png"), + "flash_hold_1_pressed": File (path: "images/pixelart/Flashlight_click_1.png"), + "flash_hold_2": File (path: "images/pixelart/Flashlight_hold_2.png"), + "flash_hold_2_pressed": File (path: "images/pixelart/Flashlight_click_2.png"), + "flash_hold_3": File (path: "images/pixelart/Flashlight_hold_3.png"), + "flash_hold_3_pressed": File (path: "images/pixelart/Flashlight_click_3.png"), + "flash_hold_4": File (path: "images/pixelart/Flashlight_hold_4.png"), + "flash_hold_4_pressed": File (path: "images/pixelart/Flashlight_click_4.png"), + "flashlight_click": File (path: "audio/flashlight-switch.ogg"), }) diff --git a/assets/meshes/id_card.glb b/assets/meshes/id_card.glb new file mode 100644 index 0000000..61c4824 Binary files /dev/null and b/assets/meshes/id_card.glb differ diff --git a/src/asset_loading/mod.rs b/src/asset_loading/mod.rs index 0efc560..ffc942c 100644 --- a/src/asset_loading/mod.rs +++ b/src/asset_loading/mod.rs @@ -9,19 +9,18 @@ use bevy_kira_audio::{AudioPlugin, AudioSource}; /// Loads resources and assets for the game. /// See assets/main.assets.ron for the actual paths used. pub(super) fn plugin(app: &mut App) { - app.add_plugins(AudioPlugin) - .add_loading_state( - LoadingState::new(GameState::Loading) - .continue_to_state(GameState::Menu) - .with_dynamic_assets_file::("main.assets.ron") - .load_collection::() - .load_collection::() - .load_collection::() - .load_collection::(), - // .load_collection::() - // .load_collection::() - // .load_collection::(), - ); + app.add_plugins(AudioPlugin).add_loading_state( + LoadingState::new(GameState::Loading) + .continue_to_state(GameState::Menu) + .with_dynamic_assets_file::("main.assets.ron") + .load_collection::() + .load_collection::() + .load_collection::() + .load_collection::(), + // .load_collection::() + // .load_collection::() + // .load_collection::(), + ); } // the following asset collections will be loaded during the State `GameState::InitialLoading` @@ -39,6 +38,8 @@ pub(crate) struct AudioAssets { pub(crate) struct GltfAssets { #[asset(key = "library", collection(typed, mapped))] pub(crate) library: HashMap>, + #[asset(key = "id_card")] + pub(crate) card: Handle, } #[derive(AssetCollection, Resource, Clone)] @@ -54,10 +55,28 @@ pub(crate) struct ConfigAssets {} pub(crate) struct ImageAssets { #[asset(key = "lebron")] pub(crate) king: Handle, + #[asset(key = "id_card_toolbar")] + pub(crate) id_card: Handle, } #[derive(AssetCollection, Resource, Clone)] pub(crate) struct FlashlightAssets { + #[asset(key = "flash_hold_0")] + pub(crate) flash_hold_0: Handle, + #[asset(key = "flash_hold_0_pressed")] + pub(crate) flash_hold_0_pressed: Handle, + #[asset(key = "flash_hold_1")] + pub(crate) flash_hold_1: Handle, + #[asset(key = "flash_hold_1_pressed")] + pub(crate) flash_hold_1_pressed: Handle, + #[asset(key = "flash_hold_2")] + pub(crate) flash_hold_2: Handle, + #[asset(key = "flash_hold_2_pressed")] + pub(crate) flash_hold_2_pressed: Handle, + #[asset(key = "flash_hold_3")] + pub(crate) flash_hold_3: Handle, + #[asset(key = "flash_hold_3_pressed")] + pub(crate) flash_hold_3_pressed: Handle, #[asset(key = "flash_hold_4")] pub(crate) flash_hold_4: Handle, #[asset(key = "flash_hold_4_pressed")] diff --git a/src/interaction/mod.rs b/src/interaction/mod.rs index d175524..031e8ba 100644 --- a/src/interaction/mod.rs +++ b/src/interaction/mod.rs @@ -3,6 +3,9 @@ use bevy::prelude::*; mod objects; mod ui; +#[derive(Component)] +pub struct Interact; + pub fn plugin(app: &mut App) { app.add_plugins((ui::plugin, objects::plugin)); } diff --git a/src/interaction/objects.rs b/src/interaction/objects.rs index fefcdd1..9e390e8 100644 --- a/src/interaction/objects.rs +++ b/src/interaction/objects.rs @@ -1,40 +1,95 @@ -use bevy::{prelude::*}; -use bevy_rapier3d::prelude::{Collider, RigidBody}; +use bevy::prelude::*; +use bevy_rapier3d::prelude::ColliderDisabled; -use crate::{GameState, asset_loading::GltfAssets}; +use crate::{ + level_instantiation::Door, player::{toolbar::Item, Player, PlayerAction}, util::single_mut +}; -pub fn plugin(app: &mut App) { - app.add_systems(OnEnter(GameState::Playing), spawn); +use super::{Interact, ui::InteractionOpportunity}; + +pub fn plugin(_app: &mut App) {} + +pub fn handle_pick_up( + mut commands: Commands, + // current action + mut player_query: Query<(&PlayerAction, &Transform, &mut Item), With>, + mut vis_query: Query<&mut Visibility>, + children: Query<&mut Children>, + + mut item_transform: Query<&mut Transform, (With, Without)>, + // current interactable + mut interaction_opportunity: ResMut, +) { + let (action, transform, mut item) = single_mut!(player_query); + if *action == PlayerAction::PickUp { + // take out the interaction, because we are picking it up + 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) { + *vis = Visibility::Hidden; + } + if let Ok(colliders) = children.get(target) { + for &collider in colliders { + commands.entity(collider).insert(ColliderDisabled); + } + } + 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 { + commands.entity(collider).remove::(); + } + } + // 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; + } + } } -#[derive(Component)] -pub struct Interact; +pub fn handle_drop( + mut commands: Commands, + // current action + mut player_query: Query<(&PlayerAction, &Transform, &mut Item), With>, + mut vis_query: Query<&mut Visibility>, + mut item_transform: Query<&mut Transform, (With, Without)>, + children: Query<&mut Children>, +) { + let (action, transform, mut item) = single_mut!(player_query); + if *action == PlayerAction::Drop { + let Some(item) = item.take() else { + return; + }; + 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::(); + } + } -fn spawn(mut commands: Commands, models: Res>, gltf_assets: Res) { - let hammer = gltf_assets - .library - .get("meshes/library/hammer.glb") - .unwrap(); - - let hammer = models.get(hammer).unwrap(); - let asset = hammer.default_scene.as_ref().unwrap(); - // hammer - commands - .spawn(( - Transform::from_xyz(0.0, 100.0, 0.0).with_scale(Vec3::splat(0.1)), - Interact, - RigidBody::Dynamic, - SceneRoot(asset.clone()), - )) - .with_children(|parent| { - parent - .spawn(Collider::cuboid(0.8, 10f32, 0.8)) - .insert(Transform::from_xyz(0.0, -5.0, 0.0)); - parent - .spawn(Collider::cuboid(1.0, 1.0, 4.5)) - // Position the collider relative to the rigid-body. - .insert(Transform::from_xyz(0.0, 4.2, 1.0)); - }); - - //tools + // 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( + player_query: Query<&PlayerAction, With>, mut doors: Query<&mut Door> +) { + let Ok(action) = player_query.get_single() else { + return; + }; + if *action != PlayerAction::OpenDoor { + return; + } + + for mut door in doors.iter_mut() { + door.is_open = !door.is_open; + } } diff --git a/src/interaction/ui.rs b/src/interaction/ui.rs index 708bd87..1b84f3a 100644 --- a/src/interaction/ui.rs +++ b/src/interaction/ui.rs @@ -1,78 +1,92 @@ use crate::GameState; -use crate::player::{Player, PlayerAction}; -use crate::util::{single, single_mut}; +use crate::player::Player; +use crate::util::single; use bevy::{prelude::*, window::PrimaryWindow}; use bevy_egui::{EguiContexts, EguiPlugin, egui}; -use std::f32::consts::TAU; +use bevy_rapier3d::prelude::*; +use bevy_rapier3d::rapier::prelude::CollisionEventFlags; +use std::iter; -use super::objects::Interact; +use super::{Interact, objects}; pub(super) fn plugin(app: &mut App) { app.add_plugins(EguiPlugin) .init_resource::() .add_systems( Update, - (update_interaction_opportunities, display_interaction_prompt) + ( + update_interaction_opportunities, + display_interaction_prompt, + (objects::handle_pick_up, objects::handle_drop, objects::handle_door_interaction), + ) .chain() .run_if(in_state(GameState::Playing)), ); } #[derive(Debug, Clone, Eq, PartialEq, Resource, Default)] -struct InteractionOpportunity(Option); +pub struct InteractionOpportunity(pub Option); fn update_interaction_opportunities( - player_query: Query<&GlobalTransform, With>, + mut collision_events: EventReader, + player_query: Query>, parents: Query<&Parent>, - target_query: Query< - (Entity, &GlobalTransform), - (Without, Without, With), - >, + target_query: Query, With)>, mut interaction_opportunity: ResMut, ) { - interaction_opportunity.0 = None; - let player_transform = single!(player_query); + let player = single!(player_query); - let (target_entity, target_transform) = single!(target_query); + for event in collision_events.read() { + let (e1, e2, started) = match event { + CollisionEvent::Started(e1, e2, CollisionEventFlags::SENSOR) => (*e1, *e2, true), + CollisionEvent::Stopped(e1, e2, CollisionEventFlags::SENSOR) => (*e1, *e2, false), + _ => { + continue; + } + }; - let player_translation = player_transform.translation(); - let target_translation = target_transform.translation(); - if player_translation.distance(target_translation) <= 2.0 { - interaction_opportunity.0.replace(target_entity); + let sensor = match player { + p if p == e1 => e2, + p if p == e2 => e1, + _ => continue, + }; + let mut ancestors = iter::once(sensor).chain(parents.iter_ancestors(sensor)); + + let Some(interactable) = ancestors.find_map(|entity| target_query.get(entity).ok()) else { + continue; + }; + + if started { + interaction_opportunity.0.replace(interactable); + } else { + interaction_opportunity.0.take_if(|t| *t == interactable); + } } } -fn is_facing_target( - player: Vec3, - target: Vec3, - camera_transform: Transform, - camera: &Camera, -) -> bool { - let camera_to_player = camera_transform.forward(); - let player_to_target = target - player; - let angle = camera_to_player.angle_between(player_to_target); - angle < TAU / 8. -} - fn display_interaction_prompt( interaction_opportunity: Res, mut egui_contexts: EguiContexts, - action: Query<&PlayerAction, With>, primary_windows: Query<&Window, With>, + names: Query<&Name, With>, // only the interactables ofcourse ) { let Some(opportunity) = interaction_opportunity.0 else { return; }; let window = single!(primary_windows); + let entity_name = names + .get(opportunity) + .map(|name| name.as_str()) + .expect("A named Interactable object"); + + // objective or item egui::Window::new("Interaction") .collapsible(false) .title_bar(false) .auto_sized() .fixed_pos(egui::Pos2::new(window.width() / 2., window.height() / 2.)) .show(egui_contexts.ctx_mut(), |ui| { - ui.label("E: Pick Up"); + ui.label(format!("E: Pick Up {entity_name}")); }); - - let action = single!(action); } diff --git a/src/level_instantiation/mod.rs b/src/level_instantiation/mod.rs index 828a395..41e9564 100644 --- a/src/level_instantiation/mod.rs +++ b/src/level_instantiation/mod.rs @@ -1,13 +1,19 @@ use std::collections::HashMap; use bevy::prelude::*; -use bevy_rapier3d::prelude::{Collider, RigidBody}; -use rand::Rng; +use bevy_rapier3d::prelude::*; +use rand::{Rng, seq::IteratorRandom}; -use crate::{asset_loading::GltfAssets, GameState}; +use crate::{ + asset_loading::{GltfAssets, ImageAssets}, interaction::Interact, player::{toolbar::ItemIcon, Player, PlayerAction}, GameState +}; pub fn map_plugin(app: &mut App) { - app.add_systems(OnEnter(GameState::Playing), spawn_level); + app.add_systems( + OnEnter(GameState::Playing), + (spawn_level, spawn_objects, spawn_doors).chain(), + ); + app.add_systems(Update, handle_door_pos); } fn spawn_level( @@ -15,42 +21,77 @@ fn spawn_level( mut meshes: ResMut>, mut materials: ResMut>, models: Res>, - gltf_assets: Res + gltf_assets: Res, ) { println!("LIBRARY: {:?}", gltf_assets.library); - let mesh_names = ["corner_inside", "corner_outside", "wall", "door", "round_door", "round_hole"]; + let mesh_names = [ + "corner_inside", + "corner_outside", + "wall", + "door", + "round_door", + "round_hole", + ]; let shapes = mesh_names.map(|mesh_name| { let collider: Vec<(Collider, Transform)> = match mesh_name { - "corner_inside" => vec![ - (Collider::cuboid(0.2, 1.0, 0.2), Transform::from_xyz(1.0, 1.0, 1.0).with_rotation(Quat::from_rotation_y(45.0_f32.to_radians()))) - ], + "corner_inside" => vec![( + Collider::cuboid(0.2, 1.0, 0.2), + Transform::from_xyz(1.0, 1.0, 1.0) + .with_rotation(Quat::from_rotation_y(45.0_f32.to_radians())), + )], "corner_outside" => vec![ - (Collider::cuboid(1.0, 1.0, 0.1), Transform::from_xyz(0.0, 1.0, -1.0)), - (Collider::cuboid(1.0, 1.0, 0.1), Transform::from_xyz(-1.0, 1.0, 0.0) - .with_rotation(Quat::from_rotation_y(90.0_f32.to_radians()))), - ], - "wall" => vec![ - (Collider::cuboid(1.0, 1.0, 0.1), Transform::from_xyz(0.0, 1.0, -1.0)) - ], - "door" => vec![ - (Collider::cuboid(1.0, 1.0, 0.1), Transform::from_xyz(0.0, 1.0, -1.0)) - ], - "round_door" => vec![ - (Collider::cuboid(1.0, 1.0, 0.1), Transform::from_xyz(0.0, 1.0, -1.0)) + ( + Collider::cuboid(1.0, 1.0, 0.1), + Transform::from_xyz(0.0, 1.0, -1.0), + ), + ( + Collider::cuboid(1.0, 1.0, 0.1), + Transform::from_xyz(-1.0, 1.0, 0.0) + .with_rotation(Quat::from_rotation_y(90.0_f32.to_radians())), + ), ], + "wall" => vec![( + Collider::cuboid(1.0, 1.0, 0.1), + Transform::from_xyz(0.0, 1.0, -1.0), + )], + "door" => vec![( + Collider::cuboid(1.0, 1.0, 0.1), + Transform::from_xyz(0.0, 1.0, -1.0), + )], + "round_door" => vec![( + Collider::cuboid(1.0, 1.0, 0.1), + Transform::from_xyz(0.0, 1.0, -1.0), + )], "round_hole" => vec![ - (Collider::cuboid(0.2, 1.0, 0.2), Transform::from_xyz(1.0, 1.0, -1.0).with_rotation(Quat::from_rotation_y(45.0_f32.to_radians()))), - (Collider::cuboid(0.2, 1.0, 0.2), Transform::from_xyz(-1.0, 1.0, -1.0).with_rotation(Quat::from_rotation_y(45.0_f32.to_radians()))) - ], - _ => vec![ - (Collider::cuboid(1.0, 0.1, 1.0), Transform::from_xyz(0.0, 0.0, 0.0)) + ( + Collider::cuboid(0.2, 1.0, 0.2), + Transform::from_xyz(1.0, 1.0, -1.0) + .with_rotation(Quat::from_rotation_y(45.0_f32.to_radians())), + ), + ( + Collider::cuboid(0.2, 1.0, 0.2), + Transform::from_xyz(-1.0, 1.0, -1.0) + .with_rotation(Quat::from_rotation_y(45.0_f32.to_radians())), + ), ], + _ => vec![( + Collider::cuboid(1.0, 0.1, 1.0), + Transform::from_xyz(0.0, 0.0, 0.0), + )], }; let path = format!("meshes/library/space_{}.glb", mesh_name); - let handle = gltf_assets.library.get(&path).expect(&format!("Couldn't find {} in library", mesh_name)); - let gltf = models.get(handle).expect(&format!("No model for {}", mesh_name)); + let handle = gltf_assets + .library + .get(&path) + .expect(&format!("Couldn't find {} in library", mesh_name)); + let gltf = models + .get(handle) + .expect(&format!("No model for {}", mesh_name)); - let asset = gltf.default_scene.as_ref().expect(&format!("No scene in {}", mesh_name)); + let asset = gltf + .default_scene + .as_ref() + .expect(&format!("No scene in {}", mesh_name)); (SceneRoot(asset.clone()), collider) }); let [ @@ -59,10 +100,9 @@ fn spawn_level( wall, door, round_door, - round_hole + round_hole, ] = shapes.clone(); - // huge floor commands.spawn(( RigidBody::Fixed, @@ -77,147 +117,239 @@ fn spawn_level( // )); // let map = GameMap::test(); - let maps = create_maps(3); + let levels = create_levels(3); - for map in maps { - for ((x, z), node) in map.nodes.into_iter() { + for level in &levels { + for ((x, z), node) in level.nodes.iter() { let values = { // corners are handled later, for now just a wall to test let node = node.clone(); - let pos = Transform::from_xyz(2.0*x as f32, 0.0, -2.0*z as f32); - use Side::{Connection, Closed}; + let pos = Transform::from_xyz(2.0 * (*x) as f32, 0.0, -2.0 * (*z) as f32); + use Side::{Closed, Connection}; match (node.north, node.east, node.south, node.west) { // hallway horizontal (Closed, Connection, Closed, Connection) => { vec![ (wall.clone(), pos), - (wall.clone(), pos.with_rotation(Quat::from_rotation_y(180.0_f32.to_radians()))) + ( + wall.clone(), + pos.with_rotation(Quat::from_rotation_y(180.0_f32.to_radians())), + ), ] - }, + } // hallway vertical (Connection, Closed, Connection, Closed) => { vec![ - (wall.clone(), pos.with_rotation(Quat::from_rotation_y(90.0_f32.to_radians()))), - (wall.clone(), pos.with_rotation(Quat::from_rotation_y(270.0_f32.to_radians()))) + ( + wall.clone(), + pos.with_rotation(Quat::from_rotation_y(90.0_f32.to_radians())), + ), + ( + wall.clone(), + pos.with_rotation(Quat::from_rotation_y(270.0_f32.to_radians())), + ), ] } // dead ends (Connection, Closed, Closed, Closed) => { vec![ - (wall.clone(), pos.with_rotation(Quat::from_rotation_y(90.0_f32.to_radians()))), - (round_door.clone(), pos.with_rotation(Quat::from_rotation_y(180.0_f32.to_radians()))), - (wall.clone(), pos.with_rotation(Quat::from_rotation_y(270.0_f32.to_radians()))) + ( + wall.clone(), + pos.with_rotation(Quat::from_rotation_y(90.0_f32.to_radians())), + ), + ( + round_door.clone(), + pos.with_rotation(Quat::from_rotation_y(180.0_f32.to_radians())), + ), + ( + wall.clone(), + pos.with_rotation(Quat::from_rotation_y(270.0_f32.to_radians())), + ), ] } (Closed, Closed, Connection, Closed) => { vec![ - (wall.clone(), pos.with_rotation(Quat::from_rotation_y(90.0_f32.to_radians()))), - (round_door.clone(), pos.with_rotation(Quat::from_rotation_y(0.0_f32.to_radians()))), - (wall.clone(), pos.with_rotation(Quat::from_rotation_y(270.0_f32.to_radians()))) + ( + wall.clone(), + pos.with_rotation(Quat::from_rotation_y(90.0_f32.to_radians())), + ), + ( + round_door.clone(), + pos.with_rotation(Quat::from_rotation_y(0.0_f32.to_radians())), + ), + ( + wall.clone(), + pos.with_rotation(Quat::from_rotation_y(270.0_f32.to_radians())), + ), ] } (Closed, Connection, Closed, Closed) => { vec![ (wall.clone(), pos), - (round_door.clone(), pos.with_rotation(Quat::from_rotation_y(90.0_f32.to_radians()))), - (wall.clone(), pos.with_rotation(Quat::from_rotation_y(180.0_f32.to_radians()))) + ( + round_door.clone(), + pos.with_rotation(Quat::from_rotation_y(90.0_f32.to_radians())), + ), + ( + wall.clone(), + pos.with_rotation(Quat::from_rotation_y(180.0_f32.to_radians())), + ), ] - }, + } (Closed, Closed, Closed, Connection) => { vec![ (wall.clone(), pos), - (round_door.clone(), pos.with_rotation(Quat::from_rotation_y(270.0_f32.to_radians()))), - (wall.clone(), pos.with_rotation(Quat::from_rotation_y(180.0_f32.to_radians()))) + ( + round_door.clone(), + pos.with_rotation(Quat::from_rotation_y(270.0_f32.to_radians())), + ), + ( + wall.clone(), + pos.with_rotation(Quat::from_rotation_y(180.0_f32.to_radians())), + ), ] - }, + } // T hallways (Closed, Connection, Connection, Connection) => { vec![ (wall.clone(), pos), - (round_hole.clone(), pos.with_rotation(Quat::from_rotation_y(180.0_f32.to_radians()))) + ( + round_hole.clone(), + pos.with_rotation(Quat::from_rotation_y(180.0_f32.to_radians())), + ), ] - }, + } (Connection, Closed, Connection, Connection) => { vec![ - (round_hole.clone(), pos.with_rotation(Quat::from_rotation_y(90.0_f32.to_radians()))), - (wall.clone(), pos.with_rotation(Quat::from_rotation_y(270.0_f32.to_radians()))) + ( + round_hole.clone(), + pos.with_rotation(Quat::from_rotation_y(90.0_f32.to_radians())), + ), + ( + wall.clone(), + pos.with_rotation(Quat::from_rotation_y(270.0_f32.to_radians())), + ), ] - }, + } (Connection, Connection, Closed, Connection) => { vec![ (round_hole.clone(), pos), - (wall.clone(), pos.with_rotation(Quat::from_rotation_y(180.0_f32.to_radians()))) + ( + wall.clone(), + pos.with_rotation(Quat::from_rotation_y(180.0_f32.to_radians())), + ), ] - }, + } (Connection, Connection, Connection, Closed) => { vec![ - (wall.clone(), pos.with_rotation(Quat::from_rotation_y(90.0_f32.to_radians()))), - (round_hole.clone(), pos.with_rotation(Quat::from_rotation_y(270.0_f32.to_radians()))) + ( + wall.clone(), + pos.with_rotation(Quat::from_rotation_y(90.0_f32.to_radians())), + ), + ( + round_hole.clone(), + pos.with_rotation(Quat::from_rotation_y(270.0_f32.to_radians())), + ), ] - }, + } // fourway (Connection, Connection, Connection, Connection) => { vec![ - (corner_inside.clone(), pos.with_rotation(Quat::from_rotation_y(0.0_f32.to_radians()))), - (corner_inside.clone(), pos.with_rotation(Quat::from_rotation_y(90.0_f32.to_radians()))), - (corner_inside.clone(), pos.with_rotation(Quat::from_rotation_y(180.0_f32.to_radians()))), - (corner_inside.clone(), pos.with_rotation(Quat::from_rotation_y(270.0_f32.to_radians()))) + ( + corner_inside.clone(), + pos.with_rotation(Quat::from_rotation_y(0.0_f32.to_radians())), + ), + ( + corner_inside.clone(), + pos.with_rotation(Quat::from_rotation_y(90.0_f32.to_radians())), + ), + ( + corner_inside.clone(), + pos.with_rotation(Quat::from_rotation_y(180.0_f32.to_radians())), + ), + ( + corner_inside.clone(), + pos.with_rotation(Quat::from_rotation_y(270.0_f32.to_radians())), + ), ] - }, + } // corners (Connection, Connection, Closed, Closed) => { vec![ - (corner_outside.clone(), pos.with_rotation(Quat::from_rotation_y(90.0_f32.to_radians()))), - (corner_inside.clone(), pos.with_rotation(Quat::from_rotation_y(90.0_f32.to_radians()))), - ] - }, - (Closed, Connection, Connection, Closed) => { - vec![ - (corner_outside.clone(), pos.with_rotation(Quat::from_rotation_y(0.0_f32.to_radians()))), - (corner_inside.clone(), pos.with_rotation(Quat::from_rotation_y(0.0_f32.to_radians()))), - ] - }, - (Closed, Closed, Connection, Connection) => { - vec![ - (corner_outside.clone(), pos.with_rotation(Quat::from_rotation_y(270.0_f32.to_radians()))), - (corner_inside.clone(), pos.with_rotation(Quat::from_rotation_y(270.0_f32.to_radians()))), - ] - }, - (Connection, Closed, Closed, Connection) => { - vec![ - (corner_outside.clone(), pos.with_rotation(Quat::from_rotation_y(180.0_f32.to_radians()))), - (corner_inside.clone(), pos.with_rotation(Quat::from_rotation_y(180.0_f32.to_radians()))), + ( + corner_outside.clone(), + pos.with_rotation(Quat::from_rotation_y(90.0_f32.to_radians())), + ), + ( + corner_inside.clone(), + pos.with_rotation(Quat::from_rotation_y(90.0_f32.to_radians())), + ), ] } - _ => vec![] + (Closed, Connection, Connection, Closed) => { + vec![ + ( + corner_outside.clone(), + pos.with_rotation(Quat::from_rotation_y(0.0_f32.to_radians())), + ), + ( + corner_inside.clone(), + pos.with_rotation(Quat::from_rotation_y(0.0_f32.to_radians())), + ), + ] + } + (Closed, Closed, Connection, Connection) => { + vec![ + ( + corner_outside.clone(), + pos.with_rotation(Quat::from_rotation_y(270.0_f32.to_radians())), + ), + ( + corner_inside.clone(), + pos.with_rotation(Quat::from_rotation_y(270.0_f32.to_radians())), + ), + ] + } + (Connection, Closed, Closed, Connection) => { + vec![ + ( + corner_outside.clone(), + pos.with_rotation(Quat::from_rotation_y(180.0_f32.to_radians())), + ), + ( + corner_inside.clone(), + pos.with_rotation(Quat::from_rotation_y(180.0_f32.to_radians())), + ), + ] + } + _ => vec![], } }; for ((scene_root, colliders), pos) in values { - commands.spawn(( - RigidBody::Fixed, - scene_root, - pos, - )).with_children(|parent| { - for (collider, transform) in colliders { - parent.spawn((collider, transform)); - } - }); + commands + .spawn((RigidBody::Fixed, scene_root, pos)) + .with_children(|parent| { + for (collider, transform) in colliders { + parent.spawn((collider, transform)); + } + }); } } - let (x,z) = map.end_node; + let (x, z) = level.end_node; commands.spawn(( Mesh3d(meshes.add(Cuboid::new(1.0, 20.0, 1.0))), MeshMaterial3d(materials.add(Color::srgb_u8(255, 0, 0))), - 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), )); } + commands.insert_resource(GameLevels { levels }); } -fn create_maps(n: i32) -> Vec { +fn create_levels(n: i32) -> Vec { let mut maps = Vec::new(); - let mut initial_node = MapNode::new(); + let mut initial_node = LevelNode::new(); initial_node.east = Side::Closed; initial_node.south = Side::Closed; initial_node.west = Side::Closed; @@ -225,15 +357,15 @@ fn create_maps(n: i32) -> Vec { let pos = (0, 0); - let map = GameMap::new(pos, initial_node.clone(), 5); + let map = GameLevel::new(pos, initial_node.clone(), 5); maps.push(map); - for _ in 0..n-1 { + for _ in 0..n - 1 { let map = maps.last().unwrap(); let mut pos = map.end_node.clone(); - let mut next_node = MapNode::new(); + let mut next_node = LevelNode::new(); next_node.east = Side::Connection; next_node.south = Side::Connection; next_node.west = Side::Connection; @@ -250,7 +382,7 @@ fn create_maps(n: i32) -> Vec { } initial_node = next_node; - let map = GameMap::new(pos, initial_node.clone(), 5); + let map = GameLevel::new(pos, initial_node.clone(), 5); maps.push(map); } @@ -265,16 +397,16 @@ enum Side { } #[derive(Clone, Debug)] -struct MapNode { +struct LevelNode { north: Side, south: Side, east: Side, west: Side, } -impl MapNode { +impl LevelNode { fn new() -> Self { - MapNode { + LevelNode { north: Side::Empty, south: Side::Empty, east: Side::Empty, @@ -283,60 +415,70 @@ impl MapNode { } } -struct GameMap { - nodes: HashMap<(i32, i32), MapNode>, - end_node: (i32, i32), - grid_size: i32, - initial_point: (i32, i32) +#[derive(Resource)] +struct GameLevels { + pub levels: Vec, } -impl GameMap { - fn new(initial_point: (i32, i32), node: MapNode, grid_size: i32) -> Self { +struct GameLevel { + nodes: HashMap<(i32, i32), LevelNode>, + end_node: (i32, i32), + grid_size: i32, + initial_point: (i32, i32), +} + +impl GameLevel { + fn new(initial_point: (i32, i32), node: LevelNode, grid_size: i32) -> Self { let mut nodes = HashMap::new(); nodes.insert(initial_point, node); - let mut m = GameMap { initial_point, nodes, grid_size, end_node: initial_point}; + let mut m = GameLevel { + initial_point, + nodes, + grid_size, + end_node: initial_point, + }; m.generate_map(); m } - fn generate_map(&mut self) { let mut first_point = self.initial_point.clone(); first_point.1 += 1; - self.create_node(first_point); // North + self.create_node(first_point); // North } fn pos_within_boundaries(&self, pos: (i32, i32)) -> bool { pos.0 > self.grid_size + self.initial_point.0 - || pos.1 > self.grid_size + self.initial_point.1 - || pos.0 < self.initial_point.0 - || pos.1 < self.initial_point.1 + || pos.1 > self.grid_size + self.initial_point.1 + || pos.0 < self.initial_point.0 + || pos.1 < self.initial_point.1 } fn choose_side(&self, pos: (i32, i32)) -> Side { if self.pos_within_boundaries(pos) { Side::Closed } else { - if rand::rng().random_bool(0.5) { Side::Connection } else { Side::Closed } + if rand::rng().random_bool(0.5) { + Side::Connection + } else { + Side::Closed + } } } - fn ensure_connection(&self, node: &mut MapNode, (x, y): (i32, i32)) { - let am_connections = (node.north == Side::Connection) as u8 + - (node.south == Side::Connection) as u8 + - (node.east == Side::Connection) as u8 + - (node.west == Side::Connection) as u8; + fn ensure_connection(&self, node: &mut LevelNode, (x, y): (i32, i32)) { + let am_connections = (node.north == Side::Connection) as u8 + + (node.south == Side::Connection) as u8 + + (node.east == Side::Connection) as u8 + + (node.west == Side::Connection) as u8; if am_connections <= 2 { if node.north != Side::Connection && !self.nodes.contains_key(&(x, y + 1)) { node.north = Side::Connection - } - else if node.south != Side::Connection && !self.nodes.contains_key(&(x, y - 1)) { + } else if node.south != Side::Connection && !self.nodes.contains_key(&(x, y - 1)) { node.south = Side::Connection - } - else if node.east != Side::Connection && !self.nodes.contains_key(&(x + 1, y)) { + } else if node.east != Side::Connection && !self.nodes.contains_key(&(x + 1, y)) { node.east = Side::Connection - } - else if node.west != Side::Connection && !self.nodes.contains_key(&(x - 1, y)) { + } else if node.west != Side::Connection && !self.nodes.contains_key(&(x - 1, y)) { node.west = Side::Connection } } @@ -351,7 +493,7 @@ impl GameMap { } let (x, y) = current_idx; - let mut new_node = MapNode::new(); + let mut new_node = LevelNode::new(); if x >= self.end_node.0 && y >= self.end_node.1 { self.end_node = (x, y); @@ -389,9 +531,10 @@ impl GameMap { let end_node = self.nodes.get_mut(&self.end_node).unwrap(); if self.end_node.1 >= self.end_node.0 { end_node.north = Side::Connection; - } - else if self.end_node.0 >= self.end_node.1 { + end_node.south = Side::Connection; + } else { end_node.east = Side::Connection; + end_node.west = Side::Connection; } } @@ -419,3 +562,128 @@ impl GameMap { } } } + +fn spawn_objects( + mut commands: Commands, + levels: Res, + models: Res>, + gltf_assets: Res, + image_assets: Res, +) { + // id card + let card = models.get(&gltf_assets.card).unwrap(); + let asset = card.default_scene.as_ref().unwrap(); + + for (i, level) in levels.levels.iter().enumerate() { + let begin_node = level.initial_point; + let end_node = level.end_node; + // take a random position that is not the beginning or end_node + 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, + RigidBody::Dynamic, + 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, + )); + }); + } +} + +#[derive(Component)] +pub struct Door { + pub is_open: bool, + pub open_direction: (i32, i32), + pub position: (i32, i32) +} + +fn spawn_doors( + mut commands: Commands, + levels: Res, + models: Res>, + gltf_assets: Res, +) { + let collider: Vec<(Collider, Transform)> = vec![( + Collider::cuboid(1.0, 1.0, 0.1), + Transform::from_xyz(0.0, 1.0, -1.0), + )]; + let path = format!("meshes/library/space_round_door.glb",); + let handle = gltf_assets.library.get(&path).unwrap(); + let gltf = models.get(handle).unwrap(); + + let asset = gltf.default_scene.as_ref().unwrap(); + let scene_root = SceneRoot(asset.clone()); + + for level in levels.levels.iter() { + let (x, z) = level.end_node; + let (direction, transform) = if x >= z { + ( + (0, 1), + 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())) + ) + } else { + ( + (1, 0), + Transform::from_xyz(2.0 * x as f32, 0.0, -2.0 * z as f32) + ) + }; + commands.spawn(( + RigidBody::Fixed, + Door { + is_open: false, + open_direction: direction, + position: (x, z) + }, + transform, + scene_root.clone(), + )).with_children(|parent| { + for (collider, transform) in collider.clone() { + parent.spawn((collider, transform)); + } + }); + } +} + +fn handle_door_pos(mut query: Query<(&Door, &mut Transform)>) { + for (door, mut transform) in query.iter_mut() { + if door.is_open { + transform.translation.y = 2.0 + } + else { + transform.translation.y = 0.0; + } + } +} diff --git a/src/main.rs b/src/main.rs index ddaf90c..9c6e7a3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,6 +4,7 @@ use bevy_rapier3d::prelude::*; mod asset_loading; mod bevy_plugin; +mod debugging; mod interaction; mod level_instantiation; mod main_menu; @@ -51,7 +52,7 @@ fn setup( mut commands: Commands, mut meshes: ResMut>, mut materials: ResMut>, - image: Res + image: Res, ) { // circular base commands.spawn(( diff --git a/src/player.rs b/src/player.rs index 97e84be..53a4792 100644 --- a/src/player.rs +++ b/src/player.rs @@ -1,4 +1,3 @@ - use bevy::{ input::mouse::AccumulatedMouseMotion, pbr::VolumetricFog, prelude::*, render::view::RenderLayers, window::{PrimaryWindow, WindowResized} }; @@ -7,7 +6,10 @@ use bevy_rapier3d::prelude::*; pub mod toolbar; -use crate::{asset_loading::{AudioAssets, FlashlightAssets}, GameState}; +use crate::{ + GameState, + asset_loading::{AudioAssets, FlashlightAssets}, +}; #[derive(Debug, Component, Default)] pub struct Player { @@ -53,7 +55,15 @@ impl Default for HeadBob { pub struct BaseTransform(pub Transform); #[derive(Debug, Component)] -pub struct Flashlight; +pub struct Flashlight { + // 0 - 4 + pub charge: f32, + pub is_on: bool, +} + +#[derive(Debug, Component)] +pub struct SpotlightFlashlight { +} #[derive(Component)] pub struct FlashlightButtonAnimation { @@ -70,7 +80,6 @@ impl Default for FlashlightButtonAnimation { } } - pub fn plugin(app: &mut App) { app.add_plugins(toolbar::plugin) .add_systems(OnEnter(GameState::Playing), (init_player, hide_cursor)) @@ -82,8 +91,9 @@ pub fn plugin(app: &mut App) { apply_head_bob, on_resize_system, handle_flashlight, - update_flashlight_button_animation, - ).run_if(in_state(GameState::Playing)), + (update_flashlight_button_animation, update_flashlight_charge, update_flashlight_sprite).chain(), + ) + .run_if(in_state(GameState::Playing)), ) .add_systems( FixedUpdate, @@ -128,10 +138,10 @@ pub fn init_player( ..default() }), DistanceFog { - color: Color::srgba(0.12, 0.08, 0.08, 0.65), + color: Color::srgba(0.12, 0.08, 0.08, 0.65), falloff: FogFalloff::Linear { - start: 3.0, - end: 12.0, + start: 3.0, + end: 12.0, }, ..default() }, @@ -149,6 +159,7 @@ pub fn init_player( let window = window.single(); let transform = flashlight_base_transform(window.width(), window.height()); parent.spawn(( + Flashlight { charge: 4.0, is_on: false }, Sprite::from_image(flashlights.flash_hold_4.clone()), transform.0.clone(), transform, @@ -158,7 +169,7 @@ pub fn init_player( // feitelijke pitslamp parent.spawn(( - Flashlight, + SpotlightFlashlight{}, SpotLight { intensity: 0.0, color: Color::srgba(0.9, 0.628, 0.392, 1.0), @@ -196,9 +207,11 @@ fn flashlight_base_transform(window_width: f32, window_height: f32) -> BaseTrans let xoffset = window_size.x / 4.0 - 40.0; let yoffset = 15.0; - let mut transform = Transform::from_translation( - Vec3::new(window_size.x / 2.0 - world_size.x / 2.0 - xoffset, -window_size.y / 2.0 + world_size.y / 2.0 - yoffset, 0.0) - ); + let mut transform = Transform::from_translation(Vec3::new( + window_size.x / 2.0 - world_size.x / 2.0 - xoffset, + -window_size.y / 2.0 + world_size.y / 2.0 - yoffset, + 0.0, + )); transform.scale = Vec3::new(scale, scale, 1.0); return BaseTransform(transform); } @@ -237,8 +250,11 @@ pub(crate) enum PlayerAction { Move, Sprint, Jump, - Interact, - ToggleFlashlight + ToggleFlashlight, + OpenDoor, + // Objects and stuff + PickUp, + Drop, } pub fn handle_input( @@ -276,17 +292,25 @@ pub fn handle_input( player.speed_factor = 1.0; } if keyboard_input.just_pressed(KeyCode::KeyE) { - *action = PlayerAction::Interact + *action = PlayerAction::PickUp; + } + if keyboard_input.just_pressed(KeyCode::KeyQ) { + *action = PlayerAction::Drop; } if keyboard_input.just_pressed(KeyCode::KeyF) { *action = PlayerAction::ToggleFlashlight; } + if keyboard_input.just_pressed(KeyCode::KeyK) { + *action = PlayerAction::OpenDoor; + } input.movement_direction = movement_direction.normalize_or_zero(); } } -pub fn apply_player_movement(mut player_query: Query<(&PlayerInput, &mut Velocity, &Player), With>) { +pub fn apply_player_movement( + mut player_query: Query<(&PlayerInput, &mut Velocity, &Player), With>, +) { const SPEED: f32 = 2.6; const JUMP_FORCE: f32 = 4.0; @@ -311,14 +335,18 @@ pub fn apply_head_bob( time: Res