Merge pull request #3 from Back777space/toolbar

Toolbar
This commit is contained in:
lorrens
2025-04-06 19:56:03 +02:00
committed by GitHub
11 changed files with 388 additions and 141 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -1,10 +1,12 @@
({ ({
"lebron": File (path: "images/KingLebron.png"), "lebron": File (path: "images/KingLebron.png"),
"flash_hold_4": File (path: "images/pixelart/Flashlight_hold_4.png"),
"flash_hold_4_pressed": File (path: "images/pixelart/Flashlight_click_4.png"),
"house": File (path: "meshes/House.glb"), "house": File (path: "meshes/House.glb"),
"flashlight_click": File (path: "audio/flashlight-switch.ogg"),
"library": Folder ( "library": Folder (
path: "meshes/library", path: "meshes/library",
), ),
"id_card": File(path: "meshes/id_card.glb"),
"id_card_toolbar": File(path: "images/id_card_toolbar.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"),
}) })

BIN
assets/meshes/id_card.glb Normal file

Binary file not shown.

View File

@@ -9,8 +9,7 @@ use bevy_kira_audio::{AudioPlugin, AudioSource};
/// Loads resources and assets for the game. /// Loads resources and assets for the game.
/// See assets/main.assets.ron for the actual paths used. /// See assets/main.assets.ron for the actual paths used.
pub(super) fn plugin(app: &mut App) { pub(super) fn plugin(app: &mut App) {
app.add_plugins(AudioPlugin) app.add_plugins(AudioPlugin).add_loading_state(
.add_loading_state(
LoadingState::new(GameState::Loading) LoadingState::new(GameState::Loading)
.continue_to_state(GameState::Menu) .continue_to_state(GameState::Menu)
.with_dynamic_assets_file::<StandardDynamicAssetCollection>("main.assets.ron") .with_dynamic_assets_file::<StandardDynamicAssetCollection>("main.assets.ron")
@@ -37,6 +36,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)]
@@ -52,6 +53,8 @@ pub(crate) struct ConfigAssets {}
pub(crate) struct ImageAssets { pub(crate) struct ImageAssets {
#[asset(key = "lebron")] #[asset(key = "lebron")]
pub(crate) king: Handle<Image>, pub(crate) king: Handle<Image>,
#[asset(key = "id_card_toolbar")]
pub(crate) id_card: Handle<Image>,
} }
#[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,80 @@
use bevy::{prelude::*}; use bevy::prelude::*;
use bevy_rapier3d::prelude::{Collider, RigidBody}; use bevy_rapier3d::prelude::ColliderDisabled;
use crate::{GameState, asset_loading::GltfAssets}; use crate::{
player::{Player, PlayerAction, toolbar::Item},
util::single_mut,
};
pub fn plugin(app: &mut App) { use super::{Interact, ui::InteractionOpportunity};
app.add_systems(OnEnter(GameState::Playing), spawn);
pub fn plugin(_app: &mut App) {}
pub fn handle_pick_up(
mut commands: Commands,
// current action
mut action: Query<(&PlayerAction, &mut Item), With<Player>>,
mut vis_query: Query<&mut Visibility>,
children: Query<&mut Children>,
// current interactable
mut interaction_opportunity: ResMut<InteractionOpportunity>,
) {
let (action, mut item) = single_mut!(action);
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::<ColliderDisabled>();
}
}
}
}
} }
#[derive(Component)] pub fn handle_drop(
pub struct Interact; mut commands: Commands,
// current action
fn spawn(mut commands: Commands, models: Res<Assets<Gltf>>, gltf_assets: Res<GltfAssets>) { mut action: Query<(&PlayerAction, &Transform, &mut Item), With<Player>>,
let hammer = gltf_assets mut vis_query: Query<&mut Visibility>,
.library mut item_transform: Query<&mut Transform, With<Interact>>,
.get("meshes/library/hammer.glb") children: Query<&mut Children>,
.unwrap(); ) {
// let (action, transform, mut item) = single_mut!(action);
let hammer = models.get(hammer).unwrap(); // if *action == PlayerAction::Drop {
let asset = hammer.default_scene.as_ref().unwrap(); // if let Ok(mut vis) = vis_query.get_mut(target) {
// hammer // *vis = Visibility::Hidden;
commands // }
.spawn(( // if let Ok(colliders) = children.get(target) {
Transform::from_xyz(0.0, 100.0, 0.0).with_scale(Vec3::splat(0.1)), // for &collider in colliders {
Interact, // commands.entity(collider).insert(ColliderDisabled);
RigidBody::Dynamic, // }
SceneRoot(asset.clone()), // }
)) // if let Some(dropped) = replaced {
.with_children(|parent| { // if let Ok(mut vis) = vis_query.get_mut(dropped) {
parent // *vis = Visibility::Visible;
.spawn(Collider::cuboid(0.8, 10f32, 0.8)) // }
.insert(Transform::from_xyz(0.0, -5.0, 0.0)); // if let Ok(colliders) = children.get(dropped) {
parent // for &collider in colliders {
.spawn(Collider::cuboid(1.0, 1.0, 4.5)) // commands.entity(collider).remove::<ColliderDisabled>();
// Position the collider relative to the rigid-body. // }
.insert(Transform::from_xyz(0.0, 4.2, 1.0)); // }
}); // }
// }
//tools
} }

View File

@@ -1,78 +1,93 @@
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, objects};
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,
(objects::handle_pick_up, objects::handle_drop),
)
.chain() .chain()
.run_if(in_state(GameState::Playing)), .run_if(in_state(GameState::Playing)),
); );
} }
#[derive(Debug, Clone, Eq, PartialEq, Resource, Default)] #[derive(Debug, Clone, Eq, PartialEq, Resource, Default)]
struct InteractionOpportunity(Option<Entity>); pub struct InteractionOpportunity(pub 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() {
let (e1, e2, started) = match event {
let player_translation = player_transform.translation(); CollisionEvent::Started(e1, e2, CollisionEventFlags::SENSOR) => (*e1, *e2, true),
let target_translation = target_transform.translation(); CollisionEvent::Stopped(e1, e2, CollisionEventFlags::SENSOR) => (*e1, *e2, false),
if player_translation.distance(target_translation) <= 2.0 { _ => {
interaction_opportunity.0.replace(target_entity); continue;
} }
} };
fn is_facing_target( let sensor = match player {
player: Vec3, p if p == e1 => e2,
target: Vec3, p if p == e2 => e1,
camera_transform: Transform, _ => continue,
camera: &Camera, };
) -> bool { let mut ancestors = iter::once(sensor).chain(parents.iter_ancestors(sensor));
let camera_to_player = camera_transform.forward();
let player_to_target = target - player; let Some(interactable) = ancestors.find_map(|entity| target_query.get(entity).ok()) else {
let angle = camera_to_player.angle_between(player_to_target); continue;
angle < TAU / 8. };
if started {
interaction_opportunity.0.replace(interactable);
} else {
interaction_opportunity.0.take_if(|t| *t == interactable);
}
}
} }
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");
// objective or item
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);
} }

View File

@@ -1,15 +1,24 @@
use std::collections::HashMap; use std::collections::HashMap;
use bevy::prelude::*; use bevy::prelude::*;
use bevy_rapier3d::prelude::{Collider, RigidBody}; use bevy_rapier3d::prelude::*;
use rand::Rng; use rand::Rng;
use crate::{asset_loading::GltfAssets, GameState}; use crate::{
GameState,
asset_loading::{GltfAssets, ImageAssets},
interaction::Interact,
player::toolbar::ItemIcon,
};
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, mut commands: Commands,
mut meshes: ResMut<Assets<Mesh>>, mut meshes: ResMut<Assets<Mesh>>,
@@ -18,39 +27,74 @@ fn spawn_level(
gltf_assets: Res<GltfAssets> 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(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())),
)],
"corner_outside" => vec![ "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) Collider::cuboid(1.0, 1.0, 0.1),
.with_rotation(Quat::from_rotation_y(90.0_f32.to_radians()))), Transform::from_xyz(0.0, 1.0, -1.0),
], ),
"wall" => 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)
"door" => vec![ .with_rotation(Quat::from_rotation_y(90.0_f32.to_radians())),
(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))
], ],
"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![ "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()))) Collider::cuboid(0.2, 1.0, 0.2),
], Transform::from_xyz(1.0, 1.0, -1.0)
_ => vec![ .with_rotation(Quat::from_rotation_y(45.0_f32.to_radians())),
(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())),
),
], ],
_ => 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 [
@@ -59,10 +103,9 @@ fn spawn_level(
wall, wall,
door, door,
round_door, round_door,
round_hole round_hole,
] = shapes.clone(); ] = shapes.clone();
// huge floor // huge floor
commands.spawn(( commands.spawn((
RigidBody::Fixed, RigidBody::Fixed,
@@ -215,6 +258,7 @@ fn spawn_level(
} }
} }
fn create_maps(n: i32) -> Vec<GameMap> { fn create_maps(n: i32) -> Vec<GameMap> {
let mut maps = Vec::new(); let mut maps = Vec::new();
let mut initial_node = MapNode::new(); let mut initial_node = MapNode::new();
@@ -298,7 +342,6 @@ impl GameMap {
m.generate_map(); m.generate_map();
m m
} }
fn generate_map(&mut self) { fn generate_map(&mut self) {
let mut first_point = self.initial_point.clone(); let mut first_point = self.initial_point.clone();
first_point.1 += 1; first_point.1 += 1;
@@ -316,7 +359,11 @@ impl GameMap {
if self.pos_within_boundaries(pos) { if self.pos_within_boundaries(pos) {
Side::Closed Side::Closed
} else { } 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
}
} }
} }
@@ -419,3 +466,44 @@ impl GameMap {
} }
} }
} }
fn spawn_objects(
mut commands: Commands,
models: Res<Assets<Gltf>>,
gltf_assets: Res<GltfAssets>,
image_assets: Res<ImageAssets>,
) {
// 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 1"),
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,
));
});
}

View File

@@ -4,12 +4,12 @@ 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()
@@ -49,7 +49,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

@@ -1,4 +1,3 @@
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}
}; };
@@ -7,7 +6,10 @@ use bevy_rapier3d::prelude::*;
pub mod toolbar; pub mod toolbar;
use crate::{asset_loading::{AudioAssets, FlashlightAssets}, GameState}; use crate::{
GameState,
asset_loading::{AudioAssets, FlashlightAssets},
};
#[derive(Debug, Component, Default)] #[derive(Debug, Component, Default)]
pub struct Player { pub struct Player {
@@ -70,7 +72,6 @@ impl Default for FlashlightButtonAnimation {
} }
} }
pub fn plugin(app: &mut App) { pub fn plugin(app: &mut App) {
app.add_plugins(toolbar::plugin) app.add_plugins(toolbar::plugin)
.add_systems(OnEnter(GameState::Playing), (init_player, hide_cursor)) .add_systems(OnEnter(GameState::Playing), (init_player, hide_cursor))
@@ -83,7 +84,8 @@ pub fn plugin(app: &mut App) {
on_resize_system, on_resize_system,
handle_flashlight, handle_flashlight,
update_flashlight_button_animation, update_flashlight_button_animation,
).run_if(in_state(GameState::Playing)), )
.run_if(in_state(GameState::Playing)),
) )
.add_systems( .add_systems(
FixedUpdate, FixedUpdate,
@@ -196,9 +198,11 @@ fn flashlight_base_transform(window_width: f32, window_height: f32) -> BaseTrans
let xoffset = window_size.x / 4.0 - 40.0; let xoffset = window_size.x / 4.0 - 40.0;
let yoffset = 15.0; let yoffset = 15.0;
let mut transform = Transform::from_translation( let mut transform = Transform::from_translation(Vec3::new(
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) 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); transform.scale = Vec3::new(scale, scale, 1.0);
return BaseTransform(transform); return BaseTransform(transform);
} }
@@ -237,8 +241,10 @@ pub(crate) enum PlayerAction {
Move, Move,
Sprint, Sprint,
Jump, Jump,
Interact, ToggleFlashlight,
ToggleFlashlight // Objects and stuff
PickUp,
Drop,
} }
pub fn handle_input( pub fn handle_input(
@@ -276,7 +282,10 @@ pub fn handle_input(
player.speed_factor = 1.0; player.speed_factor = 1.0;
} }
if keyboard_input.just_pressed(KeyCode::KeyE) { 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) { if keyboard_input.just_pressed(KeyCode::KeyF) {
*action = PlayerAction::ToggleFlashlight; *action = PlayerAction::ToggleFlashlight;
@@ -286,7 +295,9 @@ pub fn handle_input(
} }
} }
pub fn apply_player_movement(mut player_query: Query<(&PlayerInput, &mut Velocity, &Player), With<Player>>) { pub fn apply_player_movement(
mut player_query: Query<(&PlayerInput, &mut Velocity, &Player), With<Player>>,
) {
const SPEED: f32 = 2.6; const SPEED: f32 = 2.6;
const JUMP_FORCE: f32 = 4.0; const JUMP_FORCE: f32 = 4.0;
@@ -311,14 +322,18 @@ pub fn apply_head_bob(
time: Res<Time>, time: Res<Time>,
mut query: Query<(&PlayerInput, &mut HeadBob, &Player), With<Player>>, mut query: Query<(&PlayerInput, &mut HeadBob, &Player), With<Player>>,
mut camera_query: Query<&mut Transform, (With<WorldModelCamera>, Without<Player>)>, mut camera_query: Query<&mut Transform, (With<WorldModelCamera>, Without<Player>)>,
mut sprite_query: Query<(&mut Transform, &Sprite, &BaseTransform), (With<Sprite>, Without<WorldModelCamera>, Without<Player>)>, mut sprite_query: Query<
(&mut Transform, &Sprite, &BaseTransform),
(With<Sprite>, Without<WorldModelCamera>, Without<Player>),
>,
) { ) {
let Ok((input, mut head_bob, player)) = query.get_single_mut() else { let Ok((input, mut head_bob, player)) = query.get_single_mut() else {
return; return;
}; };
// bob when moving horizontally // bob when moving horizontally
let horizontal_movement = Vec3::new(input.movement_direction.x, 0.0, input.movement_direction.z); let horizontal_movement =
Vec3::new(input.movement_direction.x, 0.0, input.movement_direction.z);
let is_moving = horizontal_movement.length_squared() > 0.01; let is_moving = horizontal_movement.length_squared() > 0.01;
let bobbing_speed = head_bob.speed * player.speed_factor; let bobbing_speed = head_bob.speed * player.speed_factor;
@@ -359,17 +374,17 @@ pub fn apply_head_bob(
let scale_factor = 40.0 * player.speed_factor; let scale_factor = 40.0 * player.speed_factor;
if is_moving { if is_moving {
transform.translation.x = base_transform.0.translation.x + horizontal_offset * scale_factor; transform.translation.x =
transform.translation.y = base_transform.0.translation.y + vertical_offset * scale_factor; base_transform.0.translation.x + horizontal_offset * scale_factor;
transform.translation.y =
base_transform.0.translation.y + vertical_offset * scale_factor;
transform.rotation = Quat::from_euler( transform.rotation =
EulerRot::XYZ, Quat::from_euler(EulerRot::XYZ, 0.0, 0.0, horizontal_offset * 0.1);
0.0,
0.0,
horizontal_offset * 0.1,
);
} else { } else {
transform.translation = transform.translation.lerp(base_transform.0.translation, 0.1); transform.translation = transform
.translation
.lerp(base_transform.0.translation, 0.1);
transform.rotation = transform.rotation.slerp(Quat::IDENTITY, 0.1); transform.rotation = transform.rotation.slerp(Quat::IDENTITY, 0.1);
} }
} }

View File

@@ -1,21 +1,102 @@
use bevy::prelude::*; use bevy::prelude::*;
use bevy_egui::{EguiContexts, egui};
use std::default::Default; use std::default::Default;
use crate::GameState; use crate::{GameState, interaction::Interact, util::single};
use super::Player; use super::Player;
#[derive(Component, Default, Debug)] #[derive(Component, Default, Debug)]
pub struct Item(Option<Entity>); pub struct Item(Option<Entity>);
#[derive(Component, Default, Debug)]
pub struct ItemIcon(Handle<Image>);
impl ItemIcon {
pub fn new(h: Handle<Image>) -> Self {
Self(h)
}
}
impl Item { 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) {
app.add_systems(Update, show_toolbar.run_if(in_state(GameState::Playing))); app.add_systems(Update, bottom_panel.run_if(in_state(GameState::Playing)));
} }
fn show_toolbar(player_tool_query: Query<&Item, With<Player>>) {} fn bottom_panel(
mut egui_ctx: EguiContexts,
player_item_query: Query<&Item, With<Player>>,
item_query: Query<(&Name, &ItemIcon), With<Interact>>,
) {
let item = single!(player_item_query);
let item = item.0.and_then(|id| item_query.get(id).ok());
let (name, icon) = item.map_or((Name::new(""), None), |(name, handle)| {
(
name.clone(),
Some(egui_ctx.add_image(handle.0.clone_weak())),
)
});
egui::TopBottomPanel::bottom("inventory_toolbar")
.frame(egui::Frame {
fill: egui::Color32::from_rgba_premultiplied(0, 0, 0, 0),
// Removed the stroke/border by setting it to none
stroke: egui::Stroke::NONE,
outer_margin: egui::epaint::Margin::same(0),
inner_margin: egui::epaint::Margin::same(0),
..Default::default()
})
.show(egui_ctx.ctx_mut(), |ui| {
ui.vertical_centered(|ui| {
// Create a single centered slot
ui.horizontal(|ui| {
// Add flexible space before the slot to center it
ui.add_space(ui.available_width() / 2.0 - 25.0);
// Create a frame for the slot
let slot_frame = egui::Frame {
fill: egui::Color32::from_rgba_premultiplied(75, 75, 75, 100),
stroke: egui::Stroke::new(2.0, egui::Color32::WHITE),
..Default::default()
};
slot_frame.show(ui, |ui| {
ui.add_sized([50.0, 50.0], |ui: &mut egui::Ui| {
// Display the item
ui.vertical_centered(|ui| {
ui.horizontal_centered(|ui| {
match icon {
Some(image) => {
ui.add(egui::widgets::Image::new(
egui::load::SizedTexture::new(image, [50.0, 30.0]),
));
}
None => {
ui.label(egui::RichText::new("Empty").size(12.0));
return;
}
}
ui.label(
egui::RichText::new(name)
.color(egui::Color32::WHITE)
.size(12.0),
);
});
});
ui.allocate_response(ui.available_size(), egui::Sense::click())
});
});
});
});
});
}