Tnua and dolly (#2)
* character controller + camera rig * make tnua work * cash collect
This commit is contained in:
189
src/player.rs
Normal file
189
src/player.rs
Normal file
@@ -0,0 +1,189 @@
|
||||
use crate::{camera::GameCameraRig, cash::Cash, tb_entities::SpawnPoint};
|
||||
use avian3d::prelude::*;
|
||||
use bevy::{
|
||||
input::mouse::MouseMotion,
|
||||
prelude::*,
|
||||
window::{CursorGrabMode, PrimaryWindow},
|
||||
};
|
||||
use bevy_dolly::prelude::Rig;
|
||||
use bevy_tnua::{TnuaUserControlsSystemSet, prelude::*};
|
||||
use bevy_tnua_avian3d::TnuaAvian3dSensorShape;
|
||||
|
||||
#[derive(Component, Default)]
|
||||
pub struct Player;
|
||||
|
||||
#[derive(Resource, Default)]
|
||||
struct PlayerSpawned {
|
||||
spawned: bool,
|
||||
}
|
||||
|
||||
pub fn plugin(app: &mut App) {
|
||||
app.init_resource::<PlayerSpawned>();
|
||||
app.add_systems(Startup, initial_grab_cursor);
|
||||
app.add_systems(Update, (spawn, update_camera, cursor_events, collect_cash));
|
||||
app.add_systems(
|
||||
FixedUpdate,
|
||||
apply_controls.in_set(TnuaUserControlsSystemSet),
|
||||
);
|
||||
}
|
||||
|
||||
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"));
|
||||
|
||||
commands
|
||||
.spawn((
|
||||
Name::from("player"),
|
||||
Player,
|
||||
transform,
|
||||
Visibility::default(),
|
||||
RigidBody::Dynamic,
|
||||
Collider::capsule(1.2, 1.5),
|
||||
LockedAxes::ROTATION_LOCKED,
|
||||
TnuaController::default(),
|
||||
TnuaAvian3dSensorShape(Collider::cylinder(1.0, 0.0)),
|
||||
))
|
||||
.with_child((
|
||||
Transform::from_translation(Vec3::new(0., 1.0, 0.))
|
||||
.with_rotation(Quat::from_rotation_y(std::f32::consts::PI)),
|
||||
SceneRoot(mesh),
|
||||
));
|
||||
|
||||
commands.spawn(AudioPlayer::new(
|
||||
asset_server.load("sfx/heads/angry demonstrator.ogg"),
|
||||
));
|
||||
|
||||
player_spawned.spawned = true;
|
||||
}
|
||||
|
||||
fn cursor_events(
|
||||
mut mouse: EventReader<MouseMotion>,
|
||||
mut player: Query<&mut Transform, With<Player>>,
|
||||
) {
|
||||
let Ok(mut player) = player.get_single_mut() else {
|
||||
return;
|
||||
};
|
||||
|
||||
for ev in mouse.read() {
|
||||
player.rotate_y(ev.delta.x * -0.001);
|
||||
}
|
||||
}
|
||||
|
||||
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 initial_grab_cursor(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 `initial_grab_cursor`!");
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_controls(
|
||||
keyboard: Res<ButtonInput<KeyCode>>,
|
||||
mut query: Query<&mut TnuaController>,
|
||||
player: Query<&Transform, With<Player>>,
|
||||
) {
|
||||
let Ok(mut controller) = query.get_single_mut() else {
|
||||
return;
|
||||
};
|
||||
|
||||
let Ok(player) = player.get_single() else {
|
||||
return;
|
||||
};
|
||||
|
||||
let mut direction = Vec3::ZERO;
|
||||
|
||||
if keyboard.pressed(KeyCode::KeyW) {
|
||||
direction = player.forward().as_vec3();
|
||||
}
|
||||
if keyboard.pressed(KeyCode::KeyS) {
|
||||
direction = player.forward().as_vec3() * -1.;
|
||||
}
|
||||
if keyboard.pressed(KeyCode::KeyA) {
|
||||
direction += player.left().as_vec3();
|
||||
}
|
||||
if keyboard.pressed(KeyCode::KeyD) {
|
||||
direction += player.right().as_vec3();
|
||||
}
|
||||
|
||||
controller.basis(TnuaBuiltinWalk {
|
||||
// The `desired_velocity` determines how the character will move.
|
||||
desired_velocity: direction.normalize_or_zero() * 10.0,
|
||||
// The `float_height` must be greater (even if by little) from the distance between the
|
||||
// character's center and the lowest point of its collider.
|
||||
float_height: 3.0,
|
||||
// `TnuaBuiltinWalk` has many other fields for customizing the movement - but they have
|
||||
// sensible defaults. Refer to the `TnuaBuiltinWalk`'s documentation to learn what they do.
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
// Feed the jump action every frame as long as the player holds the jump button. If the player
|
||||
// stops holding the jump button, simply stop feeding the action.
|
||||
if keyboard.pressed(KeyCode::Space) {
|
||||
controller.action(TnuaBuiltinJump {
|
||||
// The height is the only mandatory field of the jump button.
|
||||
height: 4.0,
|
||||
// `TnuaBuiltinJump` also has customization fields with sensible defaults.
|
||||
..Default::default()
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn update_camera(player: Query<&Transform, With<Player>>, mut rig: Single<&mut Rig>) {
|
||||
let Some(player) = player.iter().next() else {
|
||||
return;
|
||||
};
|
||||
|
||||
rig.driver_mut::<GameCameraRig>()
|
||||
.set_position_target(player.translation, player.rotation);
|
||||
}
|
||||
|
||||
fn collect_cash(
|
||||
mut commands: Commands,
|
||||
mut collision_event_reader: EventReader<CollisionStarted>,
|
||||
query_player: Query<&Player>,
|
||||
query_cash: Query<&Cash>,
|
||||
asset_server: Res<AssetServer>,
|
||||
) {
|
||||
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.spawn(AudioPlayer::new(asset_server.load("sfx/effects/cash.ogg")));
|
||||
commands.entity(cash).despawn();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user