implement 3rd person view, flexible attachment of camera to actors
This commit is contained in:
parent
bb73b1ed1d
commit
b78340dd1a
6 changed files with 127 additions and 67 deletions
|
@ -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
|
||||
|
|
11
src/actor.rs
11
src/actor.rs
|
@ -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 {
|
||||
|
|
|
@ -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();
|
||||
|
|
11
src/defs.txt
11
src/defs.txt
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
69
src/world.rs
69
src/world.rs
|
@ -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] => {
|
||||
|
|
Loading…
Add table
Reference in a new issue