fixes head switching (#96)

This commit is contained in:
extrawurst
2025-12-21 18:43:13 +01:00
committed by GitHub
parent c3c5ae6dfb
commit 0735c429ca
12 changed files with 205 additions and 76 deletions

View File

@@ -1,6 +1,7 @@
use crate::GameState;
#[cfg(feature = "client")]
use crate::control::Inputs;
use crate::control::ViewMode;
#[cfg(feature = "client")]
use crate::physics_layers::GameLayer;
#[cfg(feature = "client")]
@@ -31,7 +32,7 @@ pub struct CameraRotationInput(pub Vec2);
#[reflect(Resource)]
pub struct CameraState {
pub cutscene: bool,
pub look_around: bool,
pub view_mode: ViewMode,
}
#[derive(Component, Reflect, Debug, Default)]
@@ -85,10 +86,10 @@ fn update_look_around(
inputs: Single<&Inputs, With<LocalPlayer>>,
mut cam_state: ResMut<CameraState>,
) {
let look_around = inputs.view_mode;
let view_mode = inputs.view_mode;
if look_around != cam_state.look_around {
cam_state.look_around = look_around;
if view_mode != cam_state.view_mode {
cam_state.view_mode = view_mode;
}
}
@@ -100,9 +101,9 @@ fn update_ui(
query: Query<Entity, With<CameraUi>>,
) {
if cam_state.is_changed() {
let show_ui = cam_state.look_around || cam_state.cutscene;
let show_free_cam_ui = cam_state.view_mode.is_free() || cam_state.cutscene;
if show_ui {
if show_free_cam_ui {
commands.spawn((
CameraUi,
Node {
@@ -194,7 +195,7 @@ fn rotate_view(
look_dir: Res<LookDirMovement>,
mut cam: Single<&mut CameraRotationInput>,
) {
if !inputs.view_mode {
if !inputs.view_mode.is_free() {
cam.x = 0.0;
return;
}

View File

@@ -1,6 +1,6 @@
use crate::{
GameState,
control::{ControllerSet, Inputs, LookDirMovement},
control::{ControllerSet, Inputs, LookDirMovement, SelectedController},
player::{LocalPlayer, PlayerBodyMesh},
};
use bevy::prelude::*;
@@ -19,14 +19,20 @@ pub fn plugin(app: &mut App) {
fn rotate_rig(
inputs: Single<&Inputs, With<LocalPlayer>>,
look_dir: Res<LookDirMovement>,
local_player: Single<&Children, With<LocalPlayer>>,
local_player: Single<(&Children, &SelectedController), With<LocalPlayer>>,
mut player_mesh: Query<&mut Transform, With<PlayerBodyMesh>>,
) {
if inputs.view_mode {
if inputs.view_mode.is_free() {
return;
}
local_player.iter().find(|&child| {
let (local_player_childer, selected_controller) = *local_player;
if !matches!(selected_controller, SelectedController::Flying) {
return;
}
local_player_childer.iter().find(|&child| {
if let Ok(mut rig_transform) = player_mesh.get_mut(child) {
let look_dir = look_dir.0;

View File

@@ -3,7 +3,7 @@ use crate::{
client::control::CharacterInputEnabled,
control::{
BackpackButtonPress, CashHealPressed, ClientInputs, ControllerSet, Inputs, LocalInputs,
LookDirMovement, SelectLeftPressed, SelectRightPressed,
LookDirMovement, SelectLeftPressed, SelectRightPressed, ViewMode,
},
player::{LocalPlayer, PlayerBodyMesh},
};
@@ -146,8 +146,11 @@ fn gamepad_controls(
inputs.0.move_dir += move_dir.clamp_length_max(1.0);
inputs.0.jump |= gamepad.pressed(GamepadButton::South);
inputs.0.view_mode |= gamepad.pressed(GamepadButton::LeftTrigger2);
inputs.0.trigger |= gamepad.pressed(GamepadButton::RightTrigger2);
inputs
.0
.view_mode
.merge_input(gamepad.pressed(GamepadButton::LeftTrigger2));
if gamepad.just_pressed(GamepadButton::DPadUp) {
backpack_inputs.write(BackpackButtonPress::Toggle);
@@ -212,8 +215,8 @@ fn keyboard_controls(
inputs.0.move_dir = direction;
inputs.0.jump = keyboard.pressed(KeyCode::Space);
inputs.0.view_mode = keyboard.pressed(KeyCode::Tab);
inputs.0.trigger = mouse.pressed(MouseButton::Left);
inputs.0.view_mode = ViewMode::from_input(keyboard.pressed(KeyCode::Tab));
if keyboard.just_pressed(KeyCode::KeyB) {
backpack_inputs.write(BackpackButtonPress::Toggle);

View File

@@ -2,7 +2,7 @@ use crate::{
global_observer,
heads_database::{HeadControls, HeadsDatabase},
loading_assets::AudioAssets,
player::{LocalPlayer, PlayerBodyMesh},
player::{LocalPlayer, Player, PlayerBodyMesh},
protocol::{ClientHeadChanged, PlaySound, PlayerId, messages::AssignClientPlayer},
};
use bevy::prelude::*;
@@ -59,23 +59,38 @@ pub enum PlayerAssignmentState {
Confirmed,
}
// TODO: currently a networked message.
// can be done by just using local change detection on `ActiveHead`?
fn on_client_update_head_mesh(
trigger: On<ClientHeadChanged>,
mut commands: Commands,
body_mesh: Single<(Entity, &Children), With<PlayerBodyMesh>>,
player: Query<(&Children, &PlayerId), With<Player>>,
body_mesh: Query<(Entity, &Children), With<PlayerBodyMesh>>,
head_db: Res<HeadsDatabase>,
audio_assets: Res<AudioAssets>,
sfx: Query<&AudioPlayer>,
) -> Result {
let head = trigger.0 as usize;
let (body_mesh, mesh_children) = *body_mesh;
let (player_children, _) = player
.iter()
.find(|(_, player_id)| **player_id == trigger.player)
.unwrap();
let (body_mesh, body_mesh_children) = player_children
.iter()
.find_map(|child| body_mesh.get(child).ok())
.unwrap();
let head = trigger.head;
let head_str = head_db.head_key(head);
commands.trigger(PlaySound::Head(head_str.to_string()));
//TODO: make part of full character mesh later
for child in mesh_children.iter().filter(|child| sfx.contains(*child)) {
for child in body_mesh_children
.iter()
.filter(|child| sfx.contains(*child))
{
commands.entity(child).despawn();
}
if head_db.head_stats(head).controls == HeadControls::Plane {

View File

@@ -72,12 +72,22 @@ fn set_animation_flags(
pub fn reset_upon_switch(
mut c: Commands,
mut event_controller_switch: MessageReader<ControllerSwitchEvent>,
selected_controller: Res<SelectedController>,
mut rig_transforms: Query<&mut Transform, With<PlayerBodyMesh>>,
mut controllers: Query<(&mut KinematicVelocity, &Children, &Inputs), With<Player>>,
mut controllers: Query<
(
&mut KinematicVelocity,
&Children,
&Inputs,
&SelectedController,
),
With<Player>,
>,
) {
for &ControllerSwitchEvent { controller } in event_controller_switch.read() {
let (mut velocity, children, inputs) = controllers.get_mut(controller).unwrap();
let (mut velocity, children, inputs, selected_controller) =
controllers.get_mut(controller).unwrap();
info!("resetting controller");
velocity.0 = Vec3::ZERO;
@@ -165,6 +175,7 @@ impl Default for MovementSpeedFactor {
MoveInput,
MovementSpeedFactor,
TransformInterpolation,
SelectedController::Running,
CharacterMovement = RUNNING_MOVEMENT_CONFIG.movement,
ControllerSettings = RUNNING_MOVEMENT_CONFIG.settings,
CharacterGravity = RUNNING_MOVEMENT_CONFIG.gravity,

View File

@@ -1,7 +1,7 @@
use super::ControllerSet;
use crate::{
GameState,
control::{Inputs, controller_common::MovementSpeedFactor},
control::{Inputs, SelectedController, controller_common::MovementSpeedFactor},
};
use bevy::prelude::*;
use happy_feet::prelude::MoveInput;
@@ -19,8 +19,17 @@ impl Plugin for CharacterControllerPlugin {
}
}
pub fn apply_controls(mut query: Query<(&mut MoveInput, &MovementSpeedFactor, &Inputs)>) {
for (mut char_input, factor, inputs) in query.iter_mut() {
char_input.set(inputs.look_dir * factor.0);
pub fn apply_controls(
mut query: Query<(
&mut MoveInput,
&MovementSpeedFactor,
&Inputs,
&SelectedController,
)>,
) {
for (mut move_input, factor, inputs, selected_controller) in query.iter_mut() {
if *selected_controller == SelectedController::Flying {
move_input.set(inputs.look_dir * factor.0);
}
}
}

View File

@@ -1,7 +1,10 @@
use crate::{
GameState,
animation::AnimationFlags,
control::{ControllerSet, ControllerSettings, Inputs, controller_common::MovementSpeedFactor},
control::{
ControllerSet, ControllerSettings, Inputs, SelectedController,
controller_common::MovementSpeedFactor,
},
protocol::is_server,
};
#[cfg(feature = "client")]
@@ -41,7 +44,7 @@ fn rotate_view(
) {
let (inputs, children) = controller.into_inner();
if inputs.view_mode {
if inputs.view_mode.is_free() {
return;
}
@@ -64,11 +67,24 @@ fn apply_controls(
&ControllerSettings,
&MovementSpeedFactor,
&Inputs,
&SelectedController,
)>,
) {
for (mut move_input, mut grounding, mut velocity, mut flags, settings, move_factor, inputs) in
query.iter_mut()
for (
mut move_input,
mut grounding,
mut velocity,
mut flags,
settings,
move_factor,
inputs,
selected_controller,
) in query.iter_mut()
{
if *selected_controller != SelectedController::Running {
continue;
}
let ground_normal = *grounding.normal().unwrap_or(Dir3::Y);
let mut direction = inputs.move_dir.extend(0.0).xzy();

View File

@@ -20,7 +20,8 @@ pub enum ControllerSet {
ApplyControlsRun,
}
#[derive(Resource, Debug, Clone, Copy, PartialEq, Default)]
#[derive(Component, Reflect, Debug, Clone, Copy, PartialEq, Eq, Default)]
#[reflect(Component)]
pub enum SelectedController {
Flying,
#[default]
@@ -35,8 +36,9 @@ pub fn plugin(app: &mut App) {
#[cfg(feature = "client")]
app.register_type::<LocalInputs>();
app.register_type::<SelectedController>();
app.init_resource::<LookDirMovement>();
app.init_resource::<SelectedController>();
app.add_message::<ControllerSwitchEvent>()
.add_message::<BackpackButtonPress>();
@@ -48,8 +50,8 @@ pub fn plugin(app: &mut App) {
app.configure_sets(
FixedUpdate,
(
ControllerSet::ApplyControlsFly.run_if(resource_equals(SelectedController::Flying)),
ControllerSet::ApplyControlsRun.run_if(resource_equals(SelectedController::Running)),
ControllerSet::ApplyControlsFly,
ControllerSet::ApplyControlsRun,
)
.chain()
.run_if(in_state(GameState::Playing)),
@@ -64,6 +66,35 @@ pub fn plugin(app: &mut App) {
app.add_systems(Update, head_change.run_if(in_state(GameState::Playing)));
}
#[derive(Reflect, Default, Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq)]
pub enum ViewMode {
#[default]
Default,
FreeMode,
}
impl ViewMode {
pub fn from_input(button: bool) -> Self {
if button {
Self::FreeMode
} else {
Self::Default
}
}
pub fn merge_input(&mut self, button: bool) {
let new = Self::from_input(button);
*self = match (*self, new) {
(Self::FreeMode, _) | (_, Self::FreeMode) => Self::FreeMode,
_ => Self::Default,
};
}
pub fn is_free(&self) -> bool {
matches!(self, Self::FreeMode)
}
}
/// The continuous inputs of a client for a tick. The instant inputs are sent via messages like `BackpackTogglePressed`.
#[derive(Component, Clone, Copy, Debug, Serialize, Deserialize, Reflect)]
#[reflect(Component, Default)]
@@ -75,7 +106,7 @@ pub struct Inputs {
pub look_dir: Vec3,
pub jump: bool,
/// Determines if the camera can rotate freely around the player
pub view_mode: bool,
pub view_mode: ViewMode,
pub trigger: bool,
}
@@ -154,23 +185,24 @@ fn collect_player_inputs(
}
fn head_change(
//TODO: needs a 'LocalPlayer' at some point for multiplayer
query: Query<(Entity, &ActiveHead), (Changed<ActiveHead>, With<Player>)>,
mut commands: Commands,
query: Query<(Entity, &ActiveHead, &SelectedController), (Changed<ActiveHead>, With<Player>)>,
heads_db: Res<HeadsDatabase>,
mut selected_controller: ResMut<SelectedController>,
mut event_controller_switch: MessageWriter<ControllerSwitchEvent>,
) {
for (entity, head) in query.iter() {
for (entity, head, selected_controller) in query.iter() {
let stats = heads_db.head_stats(head.0);
let controller = match stats.controls {
HeadControls::Plane => SelectedController::Flying,
HeadControls::Walk => SelectedController::Running,
};
info!("player head changed: {} ({:?})", head.0, controller);
if *selected_controller != controller {
event_controller_switch.write(ControllerSwitchEvent { controller: entity });
*selected_controller = controller;
commands.entity(entity).insert(controller);
}
}
}

View File

@@ -2,7 +2,8 @@ use super::{ActiveHeads, HEAD_SLOTS};
#[cfg(feature = "client")]
use crate::heads::HeadsImages;
use crate::{
GameState, backpack::UiHeadState, loading_assets::UIAssets, player::Player, protocol::is_server,
GameState, backpack::UiHeadState, loading_assets::UIAssets, player::LocalPlayer,
protocol::is_server,
};
use bevy::{ecs::spawn::SpawnIter, prelude::*};
use serde::{Deserialize, Serialize};
@@ -239,14 +240,10 @@ fn update_health(
}
fn sync(
active_heads: Query<Ref<ActiveHeads>, With<Player>>,
active_heads: Single<Ref<ActiveHeads>, With<LocalPlayer>>,
mut state: Single<&mut UiActiveHeads>,
time: Res<Time>,
) {
let Ok(active_heads) = active_heads.single() else {
return;
};
if active_heads.is_changed() || active_heads.reloading() {
state.selected_slot = active_heads.selected_slot;

View File

@@ -177,8 +177,11 @@ impl ActiveHeads {
}
}
#[derive(Event)]
pub struct HeadChanged(pub usize);
#[derive(EntityEvent)]
pub struct HeadChanged {
pub entity: Entity,
pub head: usize,
}
pub fn plugin(app: &mut App) {
app.add_plugins(heads_ui::plugin);
@@ -217,11 +220,10 @@ fn sync_hp(mut query: Query<(&mut ActiveHeads, &Hitpoints)>) {
fn reload(
mut commands: Commands,
mut active: Query<&mut ActiveHeads>,
mut active: Query<(&mut ActiveHeads, &mut AnimationFlags), With<Player>>,
time: Res<Time>,
mut flags: Single<&mut AnimationFlags, With<Player>>,
) {
for mut active in active.iter_mut() {
for (mut active, mut flags) in active.iter_mut() {
if !active.reloading() {
continue;
}
@@ -249,6 +251,7 @@ fn reload(
fn on_select_active_head(
mut commands: Commands,
mut query: Query<(&mut ActiveHeads, &mut Hitpoints), With<Player>>,
// TODO: unify into just one message
mut select_lefts: MessageReader<FromClient<SelectLeftPressed>>,
mut select_rights: MessageReader<FromClient<SelectRightPressed>>,
controllers: ClientToController,
@@ -270,9 +273,10 @@ fn on_select_active_head(
active_heads.current_slot = active_heads.selected_slot;
hp.set_health(active_heads.current().unwrap().health);
commands.trigger(HeadChanged(
active_heads.heads[active_heads.current_slot].unwrap().head,
));
commands.trigger(HeadChanged {
entity: player,
head: active_heads.heads[active_heads.current_slot].unwrap().head,
});
}
}
@@ -293,9 +297,10 @@ fn on_select_active_head(
active_heads.current_slot = active_heads.selected_slot;
hp.set_health(active_heads.current().unwrap().health);
commands.trigger(HeadChanged(
active_heads.heads[active_heads.current_slot].unwrap().head,
));
commands.trigger(HeadChanged {
entity: player,
head: active_heads.heads[active_heads.current_slot].unwrap().head,
});
}
}
}
@@ -303,11 +308,11 @@ fn on_select_active_head(
fn on_swap_backpack(
trigger: On<FromClient<BackpackSwapEvent>>,
mut commands: Commands,
mut query: Query<(&mut ActiveHeads, &mut Hitpoints, &mut Backpack), With<Player>>,
mut query: Query<(Entity, &mut ActiveHeads, &mut Hitpoints, &mut Backpack), With<Player>>,
) {
let backpack_slot = trigger.event().0;
let Ok((mut active_heads, mut hp, mut backpack)) = query.single_mut() else {
let Ok((player, mut active_heads, mut hp, mut backpack)) = query.single_mut() else {
return;
};
@@ -326,7 +331,8 @@ fn on_swap_backpack(
hp.set_health(active_heads.current().unwrap().health);
commands.trigger(HeadChanged(
active_heads.heads[active_heads.selected_slot].unwrap().head,
));
commands.trigger(HeadChanged {
entity: player,
head: active_heads.heads[active_heads.selected_slot].unwrap().head,
});
}

View File

@@ -132,9 +132,16 @@ pub fn spawn(
fn on_kill(
trigger: On<Kill>,
mut commands: Commands,
mut query: Query<(&Transform, &ActiveHead, &mut ActiveHeads, &mut Hitpoints)>,
mut query: Query<(
Entity,
&Transform,
&ActiveHead,
&mut ActiveHeads,
&mut Hitpoints,
)>,
) {
let Ok((transform, active, mut heads, mut hp)) = query.get_mut(trigger.event().entity) else {
let Ok((player, transform, active, mut heads, mut hp)) = query.get_mut(trigger.event().entity)
else {
return;
};
@@ -143,31 +150,51 @@ fn on_kill(
if let Some(new_head) = heads.loose_current() {
hp.set_health(heads.current().unwrap().health);
commands.trigger(HeadChanged(new_head));
commands.trigger(HeadChanged {
entity: player,
head: new_head,
});
}
}
fn on_update_head_mesh(
trigger: On<HeadChanged>,
mut commands: Commands,
mesh_children: Single<&Children, With<PlayerBodyMesh>>,
player_id: Query<&PlayerId, With<Player>>,
children: Query<&Children>,
player_body_mesh: Query<&PlayerBodyMesh>,
animated_characters: Query<&AnimatedCharacter>,
mut player: Single<&mut ActiveHead, With<Player>>,
mut active_head: Query<&mut ActiveHead>,
) -> Result {
let animated_char = mesh_children
.iter()
.find(|child| animated_characters.contains(*child))
.ok_or("tried to update head mesh before AnimatedCharacter was readded")?;
let player_id = player_id.get(trigger.entity)?.clone();
player.0 = trigger.0;
let player_body_mesh = children
.get(trigger.entity)?
.iter()
.find(|child| player_body_mesh.get(*child).is_ok())
.unwrap();
let animated_character = children
.get(player_body_mesh)?
.iter()
.find(|child| animated_characters.get(*child).is_ok())
.unwrap();
{
let mut active_head = active_head.get_mut(trigger.entity)?;
active_head.0 = trigger.head;
}
commands
.entity(animated_char)
.insert(AnimatedCharacter::new(trigger.0));
.entity(animated_character)
.insert(AnimatedCharacter::new(trigger.head));
commands.server_trigger(ToClients {
mode: SendMode::Broadcast,
message: ClientHeadChanged(trigger.0 as u64),
message: ClientHeadChanged {
player: player_id,
head: trigger.head,
},
});
Ok(())

View File

@@ -1,8 +1,14 @@
use bevy::prelude::*;
use serde::{Deserialize, Serialize};
use crate::protocol::PlayerId;
// TODO: remove in favour of client side change detection
#[derive(Clone, Event, Serialize, Deserialize, PartialEq)]
pub struct ClientHeadChanged(pub u64);
pub struct ClientHeadChanged {
pub player: PlayerId,
pub head: usize,
}
#[derive(Event, Clone, Debug, Serialize, Deserialize)]
pub enum PlaySound {