diff --git a/assets/main.assets.ron b/assets/main.assets.ron index a89d4f7..1a7db2c 100644 --- a/assets/main.assets.ron +++ b/assets/main.assets.ron @@ -5,4 +5,5 @@ "library": Folder ( path: "meshes/library", ), + "id_card": File(path: "meshes/id_card.glb") }) 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 f2ff0f8..5e89abf 100644 --- a/src/asset_loading/mod.rs +++ b/src/asset_loading/mod.rs @@ -16,10 +16,10 @@ pub(super) fn plugin(app: &mut App) { .load_collection::() .load_collection::() .load_collection::(), - // .load_collection::() - // .load_collection::() - // .load_collection::() - // .load_collection::(), + // .load_collection::() + // .load_collection::() + // .load_collection::() + // .load_collection::(), ); } @@ -33,6 +33,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)] 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..32838db 100644 --- a/src/interaction/objects.rs +++ b/src/interaction/objects.rs @@ -1,40 +1,3 @@ -use bevy::{prelude::*}; -use bevy_rapier3d::prelude::{Collider, RigidBody}; +use bevy::prelude::*; -use crate::{GameState, asset_loading::GltfAssets}; - -pub fn plugin(app: &mut App) { - app.add_systems(OnEnter(GameState::Playing), spawn); -} - -#[derive(Component)] -pub struct Interact; - -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 -} +pub fn plugin(_app: &mut App) {} diff --git a/src/interaction/ui.rs b/src/interaction/ui.rs index 708bd87..b126b90 100644 --- a/src/interaction/ui.rs +++ b/src/interaction/ui.rs @@ -1,18 +1,25 @@ use crate::GameState; +use crate::player::toolbar::Item; use crate::player::{Player, PlayerAction}; use crate::util::{single, single_mut}; 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; 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, + handle_pick_up, + ) .chain() .run_if(in_state(GameState::Playing)), ); @@ -22,57 +29,80 @@ pub(super) fn plugin(app: &mut App) { struct InteractionOpportunity(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() { + dbg!(event); + 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 poss_target = match player { + p if p == e1 => e2, + p if p == e2 => e1, + _ => continue, + }; + let mut ancestors = iter::once(poss_target).chain(parents.iter_ancestors(poss_target)); + + let Some(target) = ancestors.find_map(|entity| target_query.get(entity).ok()) else { + continue; + }; + + if started { + interaction_opportunity.0.replace(target); + } else { + interaction_opportunity.0.take_if(|t| *t == target); + } } } -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"); + 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); +} + +fn handle_pick_up( + // current action + mut action: Query<(&PlayerAction, &mut Item), With>, + // current interactable + interaction_opportunity: Res, +) { + let Some(target) = interaction_opportunity.0 else { + return; + }; + let (action, mut item) = single_mut!(action); + if *action == PlayerAction::Interact { + let _replaced = item.set_item(target); + } } diff --git a/src/level_instantiation/mod.rs b/src/level_instantiation/mod.rs index 30a92d9..a7fdb7b 100644 --- a/src/level_instantiation/mod.rs +++ b/src/level_instantiation/mod.rs @@ -1,48 +1,69 @@ -use bevy::prelude::*; -use bevy_rapier3d::prelude::{Collider, RigidBody}; +use bevy::{prelude::*, reflect::DynamicTypePath}; +use bevy_rapier3d::prelude::*; -use crate::{asset_loading::GltfAssets, GameState}; +use crate::{GameState, asset_loading::GltfAssets, interaction::Interact}; 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).chain(), + ); } -fn spawn_level( - mut commands: Commands, - models: Res>, - gltf_assets: Res -) { +fn spawn_level(mut commands: Commands, models: 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(1.0, 0.1, 1.0), Transform::from_xyz(0.0, 0.0, 0.0)) - ], - "corner_outside" => vec![ - (Collider::cuboid(1.0, 0.1, 1.0), Transform::from_xyz(0.0, 0.0, 0.0)) - ], - "wall" => vec![ - (Collider::cuboid(1.0, 1.0, 0.1), Transform::from_xyz(0.0, 0.5, -1.0)) - ], - "door" => vec![ - (Collider::cuboid(1.0, 0.1, 1.0), Transform::from_xyz(0.0, 0.0, 0.0)) - ], - "round_door" => vec![ - (Collider::cuboid(1.0, 0.1, 1.0), Transform::from_xyz(0.0, 0.0, 0.0)) - ], - "round_hole" => vec![ - (Collider::cuboid(1.0, 0.1, 1.0), Transform::from_xyz(0.0, 0.0, 0.0)) - ], - _ => vec![ - (Collider::cuboid(1.0, 0.1, 1.0), Transform::from_xyz(0.0, 0.0, 0.0)) - ], + "corner_inside" => vec![( + Collider::cuboid(1.0, 0.1, 1.0), + Transform::from_xyz(0.0, 0.0, 0.0), + )], + "corner_outside" => vec![( + Collider::cuboid(1.0, 0.1, 1.0), + Transform::from_xyz(0.0, 0.0, 0.0), + )], + "wall" => vec![( + Collider::cuboid(1.0, 1.0, 0.1), + Transform::from_xyz(0.0, 0.5, -1.0), + )], + "door" => vec![( + Collider::cuboid(1.0, 0.1, 1.0), + Transform::from_xyz(0.0, 0.0, 0.0), + )], + "round_door" => vec![( + Collider::cuboid(1.0, 0.1, 1.0), + Transform::from_xyz(0.0, 0.0, 0.0), + )], + "round_hole" => vec![( + Collider::cuboid(1.0, 0.1, 1.0), + Transform::from_xyz(0.0, 0.0, 0.0), + )], + _ => 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 [ @@ -51,35 +72,31 @@ fn spawn_level( wall, door, round_door, - round_hole + round_hole, ] = shapes; for i in 0..30 { - commands.spawn(( - wall.0.clone(), - Transform::from_xyz(i as f32 * 2.0, 0.0, 0.0), - )).with_children(|parent| { - for ele in wall.1.clone() { - parent.spawn(( - RigidBody::Fixed, - ele.0, - ele.1, - )); - } - }); + commands + .spawn(( + wall.0.clone(), + Transform::from_xyz(i as f32 * 2.0, 0.0, 0.0), + )) + .with_children(|parent| { + for ele in wall.1.clone() { + parent.spawn((RigidBody::Fixed, ele.0, ele.1)); + } + }); - commands.spawn(( - wall.0.clone(), - Transform::from_xyz(i as f32 * 2.0, 0.0, 0.0) - .with_rotation(Quat::from_rotation_y(std::f32::consts::PI)), - )).with_children(|parent| { - for ele in wall.1.clone() { - parent.spawn(( - RigidBody::Fixed, - ele.0, - ele.1, - )); - } - }); + commands + .spawn(( + wall.0.clone(), + Transform::from_xyz(i as f32 * 2.0, 0.0, 0.0) + .with_rotation(Quat::from_rotation_y(std::f32::consts::PI)), + )) + .with_children(|parent| { + for ele in wall.1.clone() { + parent.spawn((RigidBody::Fixed, ele.0, ele.1)); + } + }); } // huge floor @@ -94,3 +111,60 @@ fn spawn_level( Transform::from_xyz(-500.0, 3.0, -500.0), )); } + +fn spawn_objects(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)) + // .insert(Transform::from_xyz(0.0, 4.2, 1.0)); + // }); + + // id card + let card = models.get(&gltf_assets.card).unwrap(); + let asset = card.default_scene.as_ref().unwrap(); + + commands + .spawn(( + Transform::from_xyz(0.0, 2.0, 2.0), + Interact, + RigidBody::Dynamic, + Name::new("Id Card"), + 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, + )); + }); +} diff --git a/src/main.rs b/src/main.rs index cf1bac4..2b4f1b4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,15 +1,15 @@ use asset_loading::ImageAssets; -use bevy::{prelude::*}; +use bevy::prelude::*; use bevy_rapier3d::prelude::*; mod asset_loading; mod bevy_plugin; +mod debugging; mod interaction; mod level_instantiation; mod main_menu; mod player; mod util; -mod debugging; fn main() { App::new() @@ -22,7 +22,7 @@ fn main() { RapierPhysicsPlugin::::default(), RapierDebugRenderPlugin::default(), player::plugin, - debugging::plugin + debugging::plugin, )) .init_state::() .add_systems(OnEnter(GameState::Playing), setup) @@ -44,7 +44,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/toolbar.rs b/src/player/toolbar.rs index 5bdf79c..9dcfe31 100644 --- a/src/player/toolbar.rs +++ b/src/player/toolbar.rs @@ -13,6 +13,11 @@ impl Item { pub fn none() -> Self { Default::default() } + + // set item and return the current item + pub fn set_item(&mut self, item: Entity) -> Option { + self.0.replace(item) + } } pub fn plugin(app: &mut App) { @@ -20,7 +25,6 @@ pub fn plugin(app: &mut App) { } fn bottom_panel(mut egui_ctx: EguiContexts, item_query: Query<&Item, With>) { - dbg!(single!(item_query)); egui::TopBottomPanel::bottom("inventory_toolbar") .frame(egui::Frame { fill: egui::Color32::from_rgba_premultiplied(0, 0, 0, 0),