pickup hanlder

This commit is contained in:
LorrensP-2158466
2025-04-06 15:08:25 +02:00
parent c7ed475278
commit 4bd1f1343e
9 changed files with 216 additions and 139 deletions

View File

@@ -5,4 +5,5 @@
"library": Folder ( "library": Folder (
path: "meshes/library", path: "meshes/library",
), ),
"id_card": File(path: "meshes/id_card.glb")
}) })

BIN
assets/meshes/id_card.glb Normal file

Binary file not shown.

View File

@@ -33,6 +33,8 @@ pub(crate) struct AudioAssets {}
pub(crate) struct GltfAssets { pub(crate) struct GltfAssets {
#[asset(key = "library", collection(typed, mapped))] #[asset(key = "library", collection(typed, mapped))]
pub(crate) library: HashMap<String, Handle<Gltf>>, pub(crate) library: HashMap<String, Handle<Gltf>>,
#[asset(key = "id_card")]
pub(crate) card: Handle<Gltf>,
} }
#[derive(AssetCollection, Resource, Clone)] #[derive(AssetCollection, Resource, Clone)]

View File

@@ -3,6 +3,9 @@ use bevy::prelude::*;
mod objects; mod objects;
mod ui; mod ui;
#[derive(Component)]
pub struct Interact;
pub fn plugin(app: &mut App) { pub fn plugin(app: &mut App) {
app.add_plugins((ui::plugin, objects::plugin)); app.add_plugins((ui::plugin, objects::plugin));
} }

View File

@@ -1,40 +1,3 @@
use bevy::{prelude::*}; use bevy::prelude::*;
use bevy_rapier3d::prelude::{Collider, RigidBody};
use crate::{GameState, asset_loading::GltfAssets}; pub fn plugin(_app: &mut App) {}
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<Assets<Gltf>>, gltf_assets: Res<GltfAssets>) {
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
}

View File

@@ -1,18 +1,25 @@
use crate::GameState; use crate::GameState;
use crate::player::toolbar::Item;
use crate::player::{Player, PlayerAction}; use crate::player::{Player, PlayerAction};
use crate::util::{single, single_mut}; use crate::util::{single, single_mut};
use bevy::{prelude::*, window::PrimaryWindow}; use bevy::{prelude::*, window::PrimaryWindow};
use bevy_egui::{EguiContexts, EguiPlugin, egui}; 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) { pub(super) fn plugin(app: &mut App) {
app.add_plugins(EguiPlugin) app.add_plugins(EguiPlugin)
.init_resource::<InteractionOpportunity>() .init_resource::<InteractionOpportunity>()
.add_systems( .add_systems(
Update, Update,
(update_interaction_opportunities, display_interaction_prompt) (
update_interaction_opportunities,
display_interaction_prompt,
handle_pick_up,
)
.chain() .chain()
.run_if(in_state(GameState::Playing)), .run_if(in_state(GameState::Playing)),
); );
@@ -22,57 +29,80 @@ pub(super) fn plugin(app: &mut App) {
struct InteractionOpportunity(Option<Entity>); struct InteractionOpportunity(Option<Entity>);
fn update_interaction_opportunities( fn update_interaction_opportunities(
player_query: Query<&GlobalTransform, With<Player>>, mut collision_events: EventReader<CollisionEvent>,
player_query: Query<Entity, With<Player>>,
parents: Query<&Parent>, parents: Query<&Parent>,
target_query: Query< target_query: Query<Entity, (Without<Player>, With<Interact>)>,
(Entity, &GlobalTransform),
(Without<Player>, Without<Camera>, With<Interact>),
>,
mut interaction_opportunity: ResMut<InteractionOpportunity>, mut interaction_opportunity: ResMut<InteractionOpportunity>,
) { ) {
interaction_opportunity.0 = None; let player = single!(player_query);
let player_transform = 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 poss_target = match player {
let target_translation = target_transform.translation(); p if p == e1 => e2,
if player_translation.distance(target_translation) <= 2.0 { p if p == e2 => e1,
interaction_opportunity.0.replace(target_entity); _ => 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( fn display_interaction_prompt(
interaction_opportunity: Res<InteractionOpportunity>, interaction_opportunity: Res<InteractionOpportunity>,
mut egui_contexts: EguiContexts, mut egui_contexts: EguiContexts,
action: Query<&PlayerAction, With<Player>>,
primary_windows: Query<&Window, With<PrimaryWindow>>, primary_windows: Query<&Window, With<PrimaryWindow>>,
names: Query<&Name, 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
.get(opportunity)
.map(|name| name.as_str())
.expect("A named Interactable object");
egui::Window::new("Interaction") egui::Window::new("Interaction")
.collapsible(false) .collapsible(false)
.title_bar(false) .title_bar(false)
.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("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<Player>>,
// current interactable
interaction_opportunity: Res<InteractionOpportunity>,
) {
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);
}
} }

View File

@@ -1,48 +1,69 @@
use bevy::prelude::*; use bevy::{prelude::*, reflect::DynamicTypePath};
use bevy_rapier3d::prelude::{Collider, RigidBody}; 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) { 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( fn spawn_level(mut commands: Commands, models: Res<Assets<Gltf>>, gltf_assets: Res<GltfAssets>) {
mut commands: Commands,
models: Res<Assets<Gltf>>,
gltf_assets: Res<GltfAssets>
) {
println!("LIBRARY: {:?}", gltf_assets.library); 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 shapes = mesh_names.map(|mesh_name| {
let collider: Vec<(Collider, Transform)> = match mesh_name { let collider: Vec<(Collider, Transform)> = match mesh_name {
"corner_inside" => vec![ "corner_inside" => vec![(
(Collider::cuboid(1.0, 0.1, 1.0), Transform::from_xyz(0.0, 0.0, 0.0)) 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)) "corner_outside" => vec![(
], Collider::cuboid(1.0, 0.1, 1.0),
"wall" => vec![ Transform::from_xyz(0.0, 0.0, 0.0),
(Collider::cuboid(1.0, 1.0, 0.1), Transform::from_xyz(0.0, 0.5, -1.0)) )],
], "wall" => vec![(
"door" => vec![ Collider::cuboid(1.0, 1.0, 0.1),
(Collider::cuboid(1.0, 0.1, 1.0), Transform::from_xyz(0.0, 0.0, 0.0)) Transform::from_xyz(0.0, 0.5, -1.0),
], )],
"round_door" => vec![ "door" => vec![(
(Collider::cuboid(1.0, 0.1, 1.0), Transform::from_xyz(0.0, 0.0, 0.0)) 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)) "round_door" => vec![(
], Collider::cuboid(1.0, 0.1, 1.0),
_ => vec![ Transform::from_xyz(0.0, 0.0, 0.0),
(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 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 handle = gltf_assets
let gltf = models.get(handle).expect(&format!("No model for {}", mesh_name)); .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) (SceneRoot(asset.clone()), collider)
}); });
let [ let [
@@ -51,33 +72,29 @@ fn spawn_level(
wall, wall,
door, door,
round_door, round_door,
round_hole round_hole,
] = shapes; ] = shapes;
for i in 0..30 { for i in 0..30 {
commands.spawn(( commands
.spawn((
wall.0.clone(), wall.0.clone(),
Transform::from_xyz(i as f32 * 2.0, 0.0, 0.0), Transform::from_xyz(i as f32 * 2.0, 0.0, 0.0),
)).with_children(|parent| { ))
.with_children(|parent| {
for ele in wall.1.clone() { for ele in wall.1.clone() {
parent.spawn(( parent.spawn((RigidBody::Fixed, ele.0, ele.1));
RigidBody::Fixed,
ele.0,
ele.1,
));
} }
}); });
commands.spawn(( commands
.spawn((
wall.0.clone(), wall.0.clone(),
Transform::from_xyz(i as f32 * 2.0, 0.0, 0.0) Transform::from_xyz(i as f32 * 2.0, 0.0, 0.0)
.with_rotation(Quat::from_rotation_y(std::f32::consts::PI)), .with_rotation(Quat::from_rotation_y(std::f32::consts::PI)),
)).with_children(|parent| { ))
.with_children(|parent| {
for ele in wall.1.clone() { for ele in wall.1.clone() {
parent.spawn(( parent.spawn((RigidBody::Fixed, ele.0, ele.1));
RigidBody::Fixed,
ele.0,
ele.1,
));
} }
}); });
} }
@@ -94,3 +111,60 @@ fn spawn_level(
Transform::from_xyz(-500.0, 3.0, -500.0), Transform::from_xyz(-500.0, 3.0, -500.0),
)); ));
} }
fn spawn_objects(mut commands: Commands, models: Res<Assets<Gltf>>, gltf_assets: Res<GltfAssets>) {
// 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,
));
});
}

View File

@@ -1,15 +1,15 @@
use asset_loading::ImageAssets; use asset_loading::ImageAssets;
use bevy::{prelude::*}; use bevy::prelude::*;
use bevy_rapier3d::prelude::*; use bevy_rapier3d::prelude::*;
mod asset_loading; mod asset_loading;
mod bevy_plugin; mod bevy_plugin;
mod debugging;
mod interaction; mod interaction;
mod level_instantiation; mod level_instantiation;
mod main_menu; mod main_menu;
mod player; mod player;
mod util; mod util;
mod debugging;
fn main() { fn main() {
App::new() App::new()
@@ -22,7 +22,7 @@ fn main() {
RapierPhysicsPlugin::<NoUserData>::default(), RapierPhysicsPlugin::<NoUserData>::default(),
RapierDebugRenderPlugin::default(), RapierDebugRenderPlugin::default(),
player::plugin, player::plugin,
debugging::plugin debugging::plugin,
)) ))
.init_state::<GameState>() .init_state::<GameState>()
.add_systems(OnEnter(GameState::Playing), setup) .add_systems(OnEnter(GameState::Playing), setup)
@@ -44,7 +44,7 @@ fn setup(
mut commands: Commands, mut commands: Commands,
mut meshes: ResMut<Assets<Mesh>>, mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<StandardMaterial>>, mut materials: ResMut<Assets<StandardMaterial>>,
image: Res<ImageAssets> image: Res<ImageAssets>,
) { ) {
// circular base // circular base
commands.spawn(( commands.spawn((

View File

@@ -13,6 +13,11 @@ impl Item {
pub fn none() -> Self { pub fn none() -> Self {
Default::default() Default::default()
} }
// set item and return the current item
pub fn set_item(&mut self, item: Entity) -> Option<Entity> {
self.0.replace(item)
}
} }
pub fn plugin(app: &mut App) { 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<Player>>) { fn bottom_panel(mut egui_ctx: EguiContexts, item_query: Query<&Item, With<Player>>) {
dbg!(single!(item_query));
egui::TopBottomPanel::bottom("inventory_toolbar") egui::TopBottomPanel::bottom("inventory_toolbar")
.frame(egui::Frame { .frame(egui::Frame {
fill: egui::Color32::from_rgba_premultiplied(0, 0, 0, 0), fill: egui::Color32::from_rgba_premultiplied(0, 0, 0, 0),