Files
HEDZReloaded/src/player.rs
GitGhillie f6b640d06c Refactor controls (#14)
* Move controls

* Use system sets

* Use control states for controller

* Unite controls

* move collisions part

* Right deadzone as well

* Remove comment
2025-03-22 20:32:09 +01:00

296 lines
8.1 KiB
Rust

use crate::{
alien::{ALIEN_ASSET_PATH, Animations},
camera::{CameraArmRotation, CameraTarget},
cash::{Cash, CashCollectEvent},
control::Controls,
control::controller::{CharacterControllerBundle, PlayerMovement},
heads_ui::HeadChanged,
physics_layers::GameLayer,
tb_entities::SpawnPoint,
};
use avian3d::{
math::{Scalar, Vector},
prelude::*,
};
use bevy::{
input::common_conditions::input_just_pressed,
prelude::*,
window::{CursorGrabMode, PrimaryWindow},
};
use std::time::Duration;
#[derive(Component, Default)]
pub struct Player;
#[derive(Component, Default)]
struct PlayerAnimations;
#[derive(Component, Default)]
struct PlayerHead;
#[derive(Component, Default)]
pub struct PlayerRig;
#[derive(Resource, Default)]
struct PlayerSpawned {
spawned: bool,
}
pub fn plugin(app: &mut App) {
app.init_resource::<PlayerSpawned>();
app.add_systems(Startup, (toggle_cursor_system, cursor_recenter));
app.add_systems(
Update,
(
spawn,
collect_cash,
toggle_animation,
setup_animations_marker_for_player,
toggle_cursor_system.run_if(input_just_pressed(KeyCode::Escape)),
),
);
app.add_systems(Update, (rotate_view_keyboard, rotate_view_gamepad));
app.add_observer(update_head);
}
fn spawn(
mut commands: Commands,
asset_server: Res<AssetServer>,
query: Query<&Transform, With<SpawnPoint>>,
mut player_spawned: ResMut<PlayerSpawned>,
) {
if player_spawned.spawned {
return;
}
let Some(spawn) = query.iter().next() else {
return;
};
let transform = Transform::from_translation(spawn.translation + Vec3::new(0., 3., 0.));
let mesh = asset_server
.load(GltfAssetLabel::Scene(0).from_asset("models/heads/angry demonstrator.glb"));
let gravity = Vector::NEG_Y * 40.0;
let collider = Collider::capsule(0.9, 1.2);
let acceleration = 30.0;
let damping = 0.95;
let jump_impulse = 18.0;
let max_slope_angle = (60.0 as Scalar).to_radians();
commands
.spawn((
Name::from("player"),
Player,
CameraTarget,
transform,
Visibility::default(),
// LockedAxes::ROTATION_LOCKED, todo
CollisionLayers::new(LayerMask(GameLayer::Player.to_bits()), LayerMask::ALL),
CharacterControllerBundle::new(collider, gravity).with_movement(
acceleration,
damping,
jump_impulse,
max_slope_angle,
),
))
.with_children(|parent| {
parent
.spawn((
Name::from("body rig"),
PlayerRig,
CameraArmRotation,
Transform::from_translation(Vec3::new(0., -1.45, 0.))
.with_rotation(Quat::from_rotation_y(std::f32::consts::PI))
.with_scale(Vec3::splat(1.4)),
SceneRoot(
asset_server.load(GltfAssetLabel::Scene(0).from_asset(ALIEN_ASSET_PATH)),
),
))
.with_child((
Name::from("head"),
PlayerHead,
Transform::from_translation(Vec3::new(0., 1.6, 0.))
.with_scale(Vec3::splat(0.7)),
SceneRoot(mesh),
));
});
commands.spawn((
AudioPlayer::new(asset_server.load("sfx/heads/angry demonstrator.ogg")),
PlaybackSettings::DESPAWN,
));
player_spawned.spawned = true;
}
fn rotate_view_gamepad(
controls: Res<Controls>,
mut player: Query<&mut Transform, With<PlayerRig>>,
) {
let Some(gamepad) = controls.gamepad_state else {
return;
};
if gamepad.view_mode {
return;
}
for mut tr in &mut player {
tr.rotate_y(gamepad.look_dir.x * -0.001);
}
}
fn rotate_view_keyboard(
mut controls: ResMut<Controls>,
mut player: Query<&mut Transform, With<PlayerRig>>,
) {
if controls.keyboard_state.view_mode {
return;
}
for mut tr in &mut player {
tr.rotate_y(controls.keyboard_state.look_dir.x * -0.001);
}
controls.keyboard_state.look_dir = Vec2::ZERO;
}
fn cursor_recenter(mut q_windows: Query<&mut Window, With<PrimaryWindow>>) {
let mut primary_window = q_windows.single_mut();
let center = Vec2::new(primary_window.width() / 2.0, primary_window.height() / 2.0);
primary_window.set_cursor_position(Some(center));
}
fn toggle_grab_cursor(window: &mut Window) {
match window.cursor_options.grab_mode {
CursorGrabMode::None => {
window.cursor_options.grab_mode = CursorGrabMode::Confined;
window.cursor_options.visible = false;
}
_ => {
window.cursor_options.grab_mode = CursorGrabMode::None;
window.cursor_options.visible = true;
}
}
}
fn toggle_cursor_system(mut primary_window: Query<&mut Window, With<PrimaryWindow>>) {
if let Ok(mut window) = primary_window.get_single_mut() {
toggle_grab_cursor(&mut window);
} else {
warn!("Primary window not found for `toggle_cursor_system`!");
}
}
fn collect_cash(
mut commands: Commands,
mut collision_event_reader: EventReader<CollisionStarted>,
query_player: Query<&Player>,
query_cash: Query<&Cash>,
) {
for CollisionStarted(e1, e2) in collision_event_reader.read() {
let collect = if query_player.contains(*e1) && query_cash.contains(*e2) {
Some(*e2)
} else if query_player.contains(*e2) && query_cash.contains(*e1) {
Some(*e1)
} else {
None
};
if let Some(cash) = collect {
commands.trigger(CashCollectEvent);
commands.entity(cash).despawn();
}
}
}
fn setup_animations_marker_for_player(
mut commands: Commands,
animation_handles: Query<Entity, Added<AnimationGraphHandle>>,
parent_query: Query<&Parent>,
player: Query<&PlayerRig>,
) {
for entity in animation_handles.iter() {
for ancestor in parent_query.iter_ancestors(entity) {
if player.contains(ancestor) {
commands.entity(entity).insert(PlayerAnimations);
}
}
}
}
fn toggle_animation(
animations: Res<Animations>,
mut transitions: Query<
(&mut AnimationTransitions, &mut AnimationPlayer),
With<PlayerAnimations>,
>,
movement: Res<PlayerMovement>,
) {
if movement.is_changed() {
let index = if movement.any_direction { 0 } else { 1 };
for (mut transition, mut player) in &mut transitions {
transition
.play(
&mut player,
animations.animations[index],
Duration::from_millis(100),
)
.repeat();
}
}
}
fn update_head(
trigger: Trigger<HeadChanged>,
mut commands: Commands,
asset_server: Res<AssetServer>,
head: Query<Entity, With<PlayerHead>>,
) {
let Ok(head) = head.get_single() else {
return;
};
let head_str = head_id_to_str(trigger.0);
commands.spawn((
AudioPlayer::new(asset_server.load(format!("sfx/heads/{}.ogg", head_str))),
PlaybackSettings::DESPAWN,
));
let mesh = asset_server
.load(GltfAssetLabel::Scene(0).from_asset(format!("models/heads/{}.glb", head_str)));
commands.entity(head).insert(SceneRoot(mesh));
}
pub fn head_id_to_str(head: usize) -> &'static str {
match head {
0 => "angry demonstrator",
1 => "carnival knife thrower",
2 => "chicago gangster",
3 => "commando",
4 => "field medic",
5 => "geisha",
6 => "goblin",
7 => "green grocer",
8 => "highland hammer thrower",
9 => "legionnaire",
10 => "mig pilot",
11 => "nanny",
12 => "panic attack",
13 => "salty sea dog",
14 => "snow plough operator",
15 => "soldier ant",
16 => "super market shopper",
17 => "troll",
_ => unimplemented!(),
}
}