implement 3rd person view, flexible attachment of camera to actors

This commit is contained in:
yuni 2024-03-29 19:41:46 +01:00
parent bb73b1ed1d
commit b78340dd1a
6 changed files with 127 additions and 67 deletions

View file

@ -24,6 +24,7 @@ Key features:
- t: toggle music (NOTE: currently no music is included in the git repo)
- m: mute sound effects
- q: enter/exit vehicle
- f: toggle 3rd person view
- TAB: toggle augmented reality overlay (HUD, low-light amplifier)
# System Requirements

View file

@ -64,6 +64,7 @@ pub struct Actor {
pub v: Vec3, // velocity
pub angular_momentum: Quat,
pub inside_entity: u32,
pub camdistance: f32,
}
impl Default for Actor {
@ -75,12 +76,14 @@ impl Default for Actor {
v: Vec3::ZERO,
inside_entity: NO_RIDE,
angular_momentum: Quat::from_euler(EulerRot::XYZ, 0.001, 0.01, 0.003),
camdistance: 10.0,
}
}
}
#[derive(Component)] pub struct Player;
#[derive(Component)] pub struct PlayerDrivesThis;
#[derive(Component)] pub struct Player; // Attached to the suit of the player
#[derive(Component)] pub struct PlayerDrivesThis; // Attached to the entered vehicle
#[derive(Component)] pub struct PlayerCamera; // Attached to the actor to use as point of view
#[derive(Component)] pub struct PlayerInConversation;
#[derive(Component)] pub struct InConversationWithPlayer;
#[derive(Component)] pub struct ActorEnteringVehicle;
@ -310,6 +313,8 @@ pub fn handle_vehicle_enter_exit(
//player_actor.inside_entity = entity.index();
*vehicle_vis = Visibility::Hidden;
driver_actor.v = vehicle_actor.v;
commands.entity(driver).remove::<PlayerCamera>();
commands.entity(vehicle).insert(PlayerCamera);
commands.entity(vehicle).insert(PlayerDrivesThis);
}
}
@ -322,6 +327,8 @@ pub fn handle_vehicle_enter_exit(
vehicle_actor.v = driver_actor.v;
vehicle_actor.angular_momentum = driver_actor.angular_momentum;
*vehicle_trans = driver_trans.clone();
commands.entity(vehicle).remove::<PlayerCamera>();
commands.entity(driver).insert(PlayerCamera);
commands.entity(vehicle).remove::<PlayerDrivesThis>();
}
else {

View file

@ -1,6 +1,7 @@
use bevy::prelude::*;
use bevy::input::mouse::MouseMotion;
use bevy::window::PrimaryWindow;
use bevy::core_pipeline::bloom::{BloomCompositeMode, BloomSettings};
use bevy_xpbd_3d::prelude::*;
use std::f32::consts::*;
use crate::{settings, audio, actor};
@ -9,6 +10,9 @@ pub struct CameraControllerPlugin;
impl Plugin for CameraControllerPlugin {
fn build(&self, app: &mut App) {
app.add_systems(Startup, setup_camera);
app.add_systems(Update, handle_input);
app.add_systems(Update, manage_player_actor);
app.add_systems(Update, run_camera_controller);
}
}
@ -39,6 +43,50 @@ impl Default for CameraController {
}
}
fn setup_camera(
mut commands: Commands,
) {
// Add player
commands.spawn((
Camera3dBundle {
camera: Camera {
hdr: true, // HDR is required for bloom
..default()
},
transform: Transform::from_xyz(0.0, 0.0, 8.0).looking_at(Vec3::ZERO, Vec3::Y),
..default()
},
CameraController::default(),
BloomSettings {
composite_mode: BloomCompositeMode::EnergyConserving,
..default()
},
));
}
pub fn handle_input(
keyboard_input: Res<ButtonInput<KeyCode>>,
mut settings: ResMut<settings::Settings>,
) {
if keyboard_input.just_pressed(settings.key_camera) {
settings.third_person ^= true;
}
}
fn manage_player_actor(
settings: Res<settings::Settings>,
mut q_playercam: Query<&mut Visibility, With<actor::PlayerCamera>>
) {
for mut vis in &mut q_playercam {
if settings.third_person {
*vis = Visibility::Inherited;
}
else {
*vis = Visibility::Hidden;
}
}
}
#[allow(clippy::too_many_arguments)]
fn run_camera_controller(
time: Res<Time>,
@ -50,15 +98,15 @@ fn run_camera_controller(
rocket_sound_controller: Query<&AudioSink, With<audio::ComponentRocketSound>>,
ion_sound_controller: Query<&AudioSink, With<audio::ComponentIonSound>>,
mut q_engine: Query<&mut actor::Engine, With<actor::PlayerDrivesThis>>,
mut query: Query<(
mut q_camera: Query<(&mut Transform, &mut CameraController, &mut Projection)>,
q_player: Query<&actor::LifeForm, With<actor::Player>>,
mut q_playercam: Query<(
&actor::Actor,
&mut Transform,
&mut CameraController,
&mut Projection,
&actor::LifeForm,
&mut actor::Engine,
&mut AngularVelocity,
&mut LinearVelocity,
), (With<Camera>, Without<actor::PlayerDrivesThis>)>,
), (With<actor::PlayerCamera>, Without<actor::PlayerDrivesThis>, Without<CameraController>)>,
) {
let dt = time.delta_seconds();
let mut play_thruster_sound = false;
@ -69,12 +117,11 @@ fn run_camera_controller(
focused = window_result.unwrap().focused;
}
if let Ok((mut transform, mut controller, mut projection, lifeform, player_engine, mut angularvelocity, mut v)) = query.get_single_mut() {
if !controller.initialized {
controller.initialized = true;
transform.rotation =
Quat::from_euler(EulerRot::ZYX, 0.0, controller.yaw, controller.pitch);
}
if let (
Ok((mut camera_transform, mut controller, mut projection)),
Ok(lifeform),
Ok((actor, mut player_transform, player_engine, mut angularvelocity, mut v)),
) = (q_camera.get_single_mut(), q_player.get_single(), q_playercam.get_single_mut()) {
if !controller.enabled {
mouse_events.clear();
return;
@ -93,16 +140,16 @@ fn run_camera_controller(
let mut axis_input = Vec3::ZERO;
if focused {
if key_input.pressed(settings.key_forward) {
axis_input.z -= 1.2;
}
if key_input.pressed(settings.key_back) {
axis_input.z += 1.2;
}
if key_input.pressed(settings.key_back) {
axis_input.z -= 1.2;
}
if key_input.pressed(settings.key_right) {
axis_input.x += 1.2;
axis_input.x -= 1.2;
}
if key_input.pressed(settings.key_left) {
axis_input.x -= 1.2;
axis_input.x += 1.2;
}
if key_input.pressed(settings.key_up) {
axis_input.y += 1.2;
@ -113,7 +160,7 @@ fn run_camera_controller(
if key_input.pressed(settings.key_stop) {
let stop_direction = -v.normalize();
if stop_direction.length_squared() > 0.3 {
axis_input += 1.0 * (transform.rotation.inverse() * stop_direction);
axis_input += 1.0 * (player_transform.rotation.inverse() * stop_direction);
}
}
}
@ -137,7 +184,7 @@ fn run_camera_controller(
let factor = Vec3::new(right_factor, up_factor, forward_factor);
if axis_input.length_squared() > 0.003 {
let acceleration_global = transform.rotation * (axis_input * factor);
let acceleration_global = player_transform.rotation * (axis_input * factor);
let mut acceleration_total = actor::ENGINE_SPEED_FACTOR * dt * acceleration_global;
let threshold = 1e-5;
if key_input.pressed(settings.key_stop) {
@ -169,11 +216,18 @@ fn run_camera_controller(
if mouse_delta != Vec2::ZERO {
// Apply look update
controller.pitch = (controller.pitch
- mouse_delta.y * RADIANS_PER_DOT * controller.sensitivity)
+ mouse_delta.y * RADIANS_PER_DOT * controller.sensitivity)
.clamp(-PI / 2., PI / 2.);
controller.yaw -= mouse_delta.x * RADIANS_PER_DOT * controller.sensitivity;
transform.rotation =
Quat::from_euler(EulerRot::ZYX, 0.0, controller.yaw, controller.pitch);
controller.yaw += mouse_delta.x * RADIANS_PER_DOT * controller.sensitivity;
player_transform.rotation = Quat::from_euler(EulerRot::ZYX, 0.0, -controller.yaw, controller.pitch);
camera_transform.rotation = Quat::from_euler(EulerRot::ZYX, 0.0, PI-controller.yaw, -controller.pitch);
}
if settings.third_person {
camera_transform.translation = player_transform.translation + camera_transform.rotation * Vec3::new(0.0, 0.0, actor.camdistance);
}
else {
camera_transform.translation = player_transform.translation;
}
let fov = (lifeform.adrenaline * lifeform.adrenaline * lifeform.adrenaline * 45.0 + 45.0).to_radians();

View file

@ -1,3 +1,13 @@
actor 0 0 0 suit
player yes
mass 200.0
scale 1
oxygen 0.008
health 0.3
collider capsule 2 1
thrust 1.2 1 1 1 1.5
engine monopropellant
actor 300000 0 500000 jupiter
scale 200000
rotationy -1.40
@ -103,6 +113,7 @@ actor 10 -30 20 MeteorAceGT
thrust 70 13.7 9.4 0.5 20
engine ion
collider sphere 2
camdistance 50
mass 500
actor 10 0 70 suit

View file

@ -10,6 +10,7 @@ pub struct Settings {
pub font_size_hud: f32,
pub font_size_conversations: f32,
pub hud_active: bool,
pub third_person: bool,
pub key_togglehud: KeyCode,
pub key_exit: KeyCode,
pub key_restart: KeyCode,
@ -24,6 +25,7 @@ pub struct Settings {
pub key_stop: KeyCode,
pub key_interact: KeyCode,
pub key_vehicle: KeyCode,
pub key_camera: KeyCode,
pub key_reply1: KeyCode,
pub key_reply2: KeyCode,
pub key_reply3: KeyCode,
@ -56,6 +58,7 @@ impl Default for Settings {
font_size_hud: 32.0,
font_size_conversations: 32.0,
hud_active: false,
third_person: false,
key_togglehud: KeyCode::Tab,
key_exit: KeyCode::Escape,
key_restart: KeyCode::F12,
@ -70,6 +73,7 @@ impl Default for Settings {
key_stop: KeyCode::Space,
key_interact: KeyCode::KeyE,
key_vehicle: KeyCode::KeyQ,
key_camera: KeyCode::KeyF,
key_reply1: KeyCode::Digit1,
key_reply2: KeyCode::Digit2,
key_reply3: KeyCode::Digit3,

View file

@ -1,12 +1,11 @@
extern crate regex;
use crate::{actor, camera, nature};
use crate::{actor, nature};
use regex::Regex;
use bevy::prelude::*;
//use bevy::core_pipeline::Skybox;
//use bevy::asset::LoadState;
//use bevy::render::render_resource::{TextureViewDescriptor, TextureViewDimension};
use bevy::pbr::CascadeShadowConfigBuilder;
use bevy::core_pipeline::bloom::{BloomCompositeMode, BloomSettings};
use bevy_xpbd_3d::prelude::*;
use std::f32::consts::PI;
@ -81,47 +80,6 @@ pub fn setup(
// cubemap_ar_handle: asset_server.load(ASSET_CUBEMAP_AR),
// });
// Add player
commands.spawn((
actor::Player,
RigidBody::Dynamic,
AngularVelocity(Vec3::new(0.0, 0.0, 0.0)),
Collider::cuboid(1.0, 1.0, 1.0),
ColliderDensity(200.0),
actor::Actor {
angular_momentum: Quat::IDENTITY,
..default()
},
actor::LifeForm::default(),
actor::Suit {
oxygen: nature::OXY_H,
integrity: 0.3,
..default()
},
actor::Engine {
thrust_forward: 1.2,
..default()
},
Visibility::Visible,
Camera3dBundle {
camera: Camera {
hdr: true, // HDR is required for bloom
..default()
},
transform: Transform::from_xyz(0.0, 0.0, 8.0).looking_at(Vec3::ZERO, Vec3::Y),
..default()
},
camera::CameraController::default(),
// Skybox {
// image: cubemap_handle,
// brightness: SKYBOX_BRIGHTNESS,
// },
BloomSettings {
composite_mode: BloomCompositeMode::EnergyConserving,
..default()
},
));
// Generate a bunch of asteriods
let maxdist = 10;
for i in -maxdist..maxdist {
@ -245,6 +203,7 @@ struct ParserState {
rotation: Quat,
angular_momentum: Quat,
pronoun: String,
is_player: bool,
is_lifeform: bool,
is_alive: bool,
is_suited: bool,
@ -258,6 +217,7 @@ struct ParserState {
oxygen: f32,
mass: f32,
collider: Collider,
camdistance: f32,
// Chat fields
delay: f64,
@ -287,6 +247,7 @@ impl Default for ParserState {
rotation: Quat::IDENTITY,
angular_momentum: default_actor.angular_momentum,
pronoun: "they/them".to_string(),
is_player: false,
is_lifeform: false,
is_alive: false,
is_suited: false,
@ -300,6 +261,7 @@ impl Default for ParserState {
oxygen: nature::OXY_D,
mass: 1.0,
collider: Collider::sphere(1.0),
camdistance: default_actor.camdistance,
delay: 0.0,
text: "".to_string(),
@ -360,6 +322,7 @@ impl ParserState {
actor.insert(actor::Actor {
angular_momentum: self.angular_momentum,
id: self.id.clone(),
camdistance: self.camdistance,
..default()
});
actor.insert(SceneBundle {
@ -378,6 +341,11 @@ impl ParserState {
actor.insert(self.collider.clone());
actor.insert(ColliderDensity(self.mass * fix_scale));
// Optional Components
if self.is_player {
actor.insert(actor::Player);
actor.insert(actor::PlayerCamera);
}
if self.is_lifeform {
actor.insert(actor::LifeForm::default());
actor.insert(actor::Suit {
@ -394,6 +362,8 @@ impl ParserState {
}
if self.is_vehicle {
actor.insert(actor::Vehicle);
}
if self.is_vehicle || self.is_suited {
actor.insert(actor::Engine {
thrust_forward: self.thrust_forward,
thrust_back: self.thrust_back,
@ -587,6 +557,19 @@ pub fn load_defs(
continue;
}
}
["player", "yes"] => {
state.is_player = true;
state.is_alive = true;
}
["camdistance", value] => {
if let Ok(value_float) = value.parse::<f32>() {
state.camdistance = value_float;
}
else {
error!("Can't parse float: {line}");
continue;
}
}
// Parsing chats
["chat", chat_name] => {