custom camera rig
* should not go into level geometry anymore
This commit is contained in:
12
Cargo.lock
generated
12
Cargo.lock
generated
@@ -697,17 +697,6 @@ dependencies = [
|
|||||||
"sysinfo",
|
"sysinfo",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "bevy_dolly"
|
|
||||||
version = "0.0.5"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "68783929254625d3ffa54a6fed7ff7216abf90296eea5e3885cde419d5daead0"
|
|
||||||
dependencies = [
|
|
||||||
"bevy",
|
|
||||||
"bevy_math",
|
|
||||||
"bevy_transform",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bevy_ecs"
|
name = "bevy_ecs"
|
||||||
version = "0.15.3"
|
version = "0.15.3"
|
||||||
@@ -2816,7 +2805,6 @@ dependencies = [
|
|||||||
"bevy-tnua",
|
"bevy-tnua",
|
||||||
"bevy-tnua-avian3d",
|
"bevy-tnua-avian3d",
|
||||||
"bevy_asset_loader",
|
"bevy_asset_loader",
|
||||||
"bevy_dolly",
|
|
||||||
"bevy_sprite3d",
|
"bevy_sprite3d",
|
||||||
"bevy_trenchbroom",
|
"bevy_trenchbroom",
|
||||||
"nil",
|
"nil",
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ nil = "0.14.0"
|
|||||||
bevy-inspector-egui = "0.29.1"
|
bevy-inspector-egui = "0.29.1"
|
||||||
bevy-tnua = "0.21.0"
|
bevy-tnua = "0.21.0"
|
||||||
bevy-tnua-avian3d = "0.2.0"
|
bevy-tnua-avian3d = "0.2.0"
|
||||||
bevy_dolly = { version = "0.0.5", default-features = false }
|
|
||||||
bevy_asset_loader = "0.22.0"
|
bevy_asset_loader = "0.22.0"
|
||||||
bevy_sprite3d = "4.0.0"
|
bevy_sprite3d = "4.0.0"
|
||||||
rand = "0.8.5"
|
rand = "0.8.5"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
billboards::Billboard,
|
billboards::Billboard,
|
||||||
player::{Player, PlayerHead},
|
player::{Player, PlayerRig},
|
||||||
tb_entities::EnemySpawn,
|
tb_entities::EnemySpawn,
|
||||||
};
|
};
|
||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
@@ -84,7 +84,7 @@ fn update(
|
|||||||
mut state: ResMut<AimState>,
|
mut state: ResMut<AimState>,
|
||||||
query: Query<(Entity, &Transform), With<EnemySpawn>>,
|
query: Query<(Entity, &Transform), With<EnemySpawn>>,
|
||||||
player_pos: Query<&Transform, With<Player>>,
|
player_pos: Query<&Transform, With<Player>>,
|
||||||
player_rot: Query<&Transform, With<PlayerHead>>,
|
player_rot: Query<&Transform, With<PlayerRig>>,
|
||||||
) {
|
) {
|
||||||
let Some(player_pos) = player_pos.iter().next().map(|t| t.translation) else {
|
let Some(player_pos) = player_pos.iter().next().map(|t| t.translation) else {
|
||||||
return;
|
return;
|
||||||
|
|||||||
101
src/camera.rs
101
src/camera.rs
@@ -1,38 +1,77 @@
|
|||||||
|
use avian3d::prelude::*;
|
||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
use bevy_dolly::prelude::*;
|
|
||||||
|
|
||||||
impl GameCameraRig {
|
use crate::physics_layers::GameLayer;
|
||||||
pub fn new_with_arm(arm: Vec3) -> Self {
|
|
||||||
Self(
|
|
||||||
CameraRig::builder()
|
|
||||||
.with(Position::new(Vec3::ZERO))
|
|
||||||
.with(Rotation::new(Quat::IDENTITY))
|
|
||||||
.with(Smooth::new_position(1.25).predictive(true))
|
|
||||||
.with(Smooth::new_rotation(0.5))
|
|
||||||
.with(Arm::new(arm))
|
|
||||||
.with(
|
|
||||||
LookAt::new(Vec3::ZERO + Vec3::Y)
|
|
||||||
.tracking_smoothness(0.2)
|
|
||||||
.tracking_predictive(true),
|
|
||||||
)
|
|
||||||
.build(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_position_target(&mut self, target_position: Vec3, target_rotation: Quat) {
|
#[derive(Component, Reflect, Debug)]
|
||||||
self.driver_mut::<Position>().position = target_position;
|
pub struct CameraTarget;
|
||||||
self.driver_mut::<Rotation>().rotation = target_rotation;
|
|
||||||
self.driver_mut::<LookAt>().target = target_position + Vec3::Y;
|
#[derive(Component, Reflect, Debug)]
|
||||||
|
pub struct CameraArmRotation;
|
||||||
|
|
||||||
|
#[derive(Component, Reflect, Debug)]
|
||||||
|
pub struct MainCamera {
|
||||||
|
arm: Vec3,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MainCamera {
|
||||||
|
fn new(arm: Vec3) -> Self {
|
||||||
|
Self { arm }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A custom camera rig which combines smoothed movement with a look-at driver.
|
pub fn plugin(app: &mut App) {
|
||||||
#[derive(Component, Debug, Deref, DerefMut)]
|
app.add_systems(Startup, startup);
|
||||||
pub struct GameCameraRig(CameraRig);
|
app.add_systems(Update, update);
|
||||||
|
}
|
||||||
// Turn the nested rig into a driver, so it can be used in another rig.
|
|
||||||
impl RigDriver for GameCameraRig {
|
fn startup(mut commands: Commands) {
|
||||||
fn update(&mut self, params: RigUpdateParams) -> Transform {
|
commands.spawn((
|
||||||
self.0.update(params.delta_time_seconds)
|
Camera3d::default(),
|
||||||
}
|
MainCamera::new(Vec3::new(0., 1., -12.)),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(
|
||||||
|
mut cam: Query<
|
||||||
|
(&MainCamera, &mut Transform),
|
||||||
|
(Without<CameraTarget>, Without<CameraArmRotation>),
|
||||||
|
>,
|
||||||
|
target: Query<&GlobalTransform, (With<CameraTarget>, Without<CameraArmRotation>)>,
|
||||||
|
arm_rotation: Query<&Transform, With<CameraArmRotation>>,
|
||||||
|
spatial_query: SpatialQuery,
|
||||||
|
) {
|
||||||
|
let Ok(target) = target.get_single().map(|t| t.translation()) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let Ok(rotation) = arm_rotation.get_single() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let Ok((camera, mut cam_transform)) = cam.get_single_mut() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let target_transform = Transform::from_translation(target)
|
||||||
|
* Transform::from_rotation(rotation.rotation)
|
||||||
|
* Transform::from_translation(camera.arm);
|
||||||
|
|
||||||
|
let ideal_cam_pos = target_transform.translation;
|
||||||
|
|
||||||
|
let max_distance = camera.arm.length();
|
||||||
|
let Ok(direction) = Dir3::new(ideal_cam_pos - target) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let filter = SpatialQueryFilter::from_mask(LayerMask(GameLayer::Level.to_bits()));
|
||||||
|
let cam_pos = if let Some(first_hit) =
|
||||||
|
spatial_query.cast_ray(target, direction, max_distance, false, &filter)
|
||||||
|
{
|
||||||
|
target + (direction * first_hit.distance)
|
||||||
|
} else {
|
||||||
|
ideal_cam_pos
|
||||||
|
};
|
||||||
|
|
||||||
|
*cam_transform = Transform::from_translation(cam_pos).looking_at(target, Vec3::Y);
|
||||||
}
|
}
|
||||||
|
|||||||
27
src/main.rs
27
src/main.rs
@@ -10,6 +10,7 @@ mod heads_ui;
|
|||||||
mod keys;
|
mod keys;
|
||||||
mod movables;
|
mod movables;
|
||||||
mod npc;
|
mod npc;
|
||||||
|
mod physics_layers;
|
||||||
mod platforms;
|
mod platforms;
|
||||||
mod player;
|
mod player;
|
||||||
mod shooting;
|
mod shooting;
|
||||||
@@ -25,11 +26,10 @@ use bevy::core_pipeline::tonemapping::Tonemapping;
|
|||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
use bevy::render::view::ColorGrading;
|
use bevy::render::view::ColorGrading;
|
||||||
use bevy::scene::SceneInstanceReady;
|
use bevy::scene::SceneInstanceReady;
|
||||||
use bevy_dolly::prelude::*;
|
|
||||||
use bevy_tnua::prelude::TnuaControllerPlugin;
|
use bevy_tnua::prelude::TnuaControllerPlugin;
|
||||||
use bevy_tnua_avian3d::TnuaAvian3dPlugin;
|
use bevy_tnua_avian3d::TnuaAvian3dPlugin;
|
||||||
use bevy_trenchbroom::prelude::*;
|
use bevy_trenchbroom::prelude::*;
|
||||||
use camera::GameCameraRig;
|
use physics_layers::GameLayer;
|
||||||
|
|
||||||
#[derive(Resource, Reflect, Debug)]
|
#[derive(Resource, Reflect, Debug)]
|
||||||
#[reflect(Resource)]
|
#[reflect(Resource)]
|
||||||
@@ -41,9 +41,6 @@ struct DebugVisuals {
|
|||||||
pub cam_follow: bool,
|
pub cam_follow: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Component, Debug)]
|
|
||||||
pub struct MainCamera;
|
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let mut app = App::new();
|
let mut app = App::new();
|
||||||
|
|
||||||
@@ -73,8 +70,6 @@ fn main() {
|
|||||||
// app.add_plugins(PhysicsDebugPlugin::default());
|
// app.add_plugins(PhysicsDebugPlugin::default());
|
||||||
// app.add_plugins(bevy_inspector_egui::quick::WorldInspectorPlugin::default());
|
// app.add_plugins(bevy_inspector_egui::quick::WorldInspectorPlugin::default());
|
||||||
|
|
||||||
app.add_systems(Update, Dolly::<MainCamera>::update_active);
|
|
||||||
|
|
||||||
app.add_plugins(alien::plugin);
|
app.add_plugins(alien::plugin);
|
||||||
app.add_plugins(cash::plugin);
|
app.add_plugins(cash::plugin);
|
||||||
app.add_plugins(player::plugin);
|
app.add_plugins(player::plugin);
|
||||||
@@ -91,6 +86,7 @@ fn main() {
|
|||||||
app.add_plugins(cutscene::plugin);
|
app.add_plugins(cutscene::plugin);
|
||||||
app.add_plugins(controls::plugin);
|
app.add_plugins(controls::plugin);
|
||||||
app.add_plugins(sounds::plugin);
|
app.add_plugins(sounds::plugin);
|
||||||
|
app.add_plugins(camera::plugin);
|
||||||
|
|
||||||
app.insert_resource(AmbientLight {
|
app.insert_resource(AmbientLight {
|
||||||
color: Color::WHITE,
|
color: Color::WHITE,
|
||||||
@@ -99,7 +95,7 @@ fn main() {
|
|||||||
|
|
||||||
app.add_plugins(TrenchBroomPlugin(TrenchBroomConfig::new("hedz")));
|
app.add_plugins(TrenchBroomPlugin(TrenchBroomConfig::new("hedz")));
|
||||||
|
|
||||||
app.add_systems(Startup, (setup_cam, write_trenchbroom_config, music));
|
app.add_systems(Startup, (write_trenchbroom_config, music));
|
||||||
app.add_systems(PostStartup, setup_scene);
|
app.add_systems(PostStartup, setup_scene);
|
||||||
app.add_systems(
|
app.add_systems(
|
||||||
Update,
|
Update,
|
||||||
@@ -109,19 +105,12 @@ fn main() {
|
|||||||
app.run();
|
app.run();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn setup_cam(mut commands: Commands) {
|
|
||||||
commands.spawn((
|
|
||||||
Camera3d::default(),
|
|
||||||
MainCamera,
|
|
||||||
Rig::builder()
|
|
||||||
.with(GameCameraRig::new_with_arm(Vec3::new(0., 5., 14.)))
|
|
||||||
.build(),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
fn setup_scene(mut commands: Commands, asset_server: Res<AssetServer>) {
|
fn setup_scene(mut commands: Commands, asset_server: Res<AssetServer>) {
|
||||||
commands
|
commands
|
||||||
.spawn(SceneRoot(asset_server.load("maps/map1.map#Scene")))
|
.spawn((
|
||||||
|
CollisionLayers::new(LayerMask(GameLayer::Level.to_bits()), LayerMask::ALL),
|
||||||
|
SceneRoot(asset_server.load("maps/map1.map#Scene")),
|
||||||
|
))
|
||||||
.observe(|_t: Trigger<SceneInstanceReady>| {
|
.observe(|_t: Trigger<SceneInstanceReady>| {
|
||||||
//TODO: use for state driven map loading
|
//TODO: use for state driven map loading
|
||||||
info!("map loaded");
|
info!("map loaded");
|
||||||
|
|||||||
8
src/physics_layers.rs
Normal file
8
src/physics_layers.rs
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
use avian3d::prelude::PhysicsLayer;
|
||||||
|
|
||||||
|
#[derive(PhysicsLayer, Clone, Copy, Debug, Default)]
|
||||||
|
pub enum GameLayer {
|
||||||
|
#[default]
|
||||||
|
Level,
|
||||||
|
Player,
|
||||||
|
}
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
DebugVisuals,
|
|
||||||
alien::{ALIEN_ASSET_PATH, Animations},
|
alien::{ALIEN_ASSET_PATH, Animations},
|
||||||
camera::GameCameraRig,
|
camera::{CameraArmRotation, CameraTarget},
|
||||||
cash::{Cash, CashCollectEvent},
|
cash::{Cash, CashCollectEvent},
|
||||||
controls::Controls,
|
controls::Controls,
|
||||||
heads_ui::HeadChanged,
|
heads_ui::HeadChanged,
|
||||||
|
physics_layers::GameLayer,
|
||||||
tb_entities::SpawnPoint,
|
tb_entities::SpawnPoint,
|
||||||
};
|
};
|
||||||
use avian3d::prelude::*;
|
use avian3d::prelude::*;
|
||||||
@@ -12,10 +12,9 @@ use bevy::{
|
|||||||
prelude::*,
|
prelude::*,
|
||||||
window::{CursorGrabMode, PrimaryWindow},
|
window::{CursorGrabMode, PrimaryWindow},
|
||||||
};
|
};
|
||||||
use bevy_dolly::prelude::Rig;
|
|
||||||
use bevy_tnua::{TnuaUserControlsSystemSet, prelude::*};
|
use bevy_tnua::{TnuaUserControlsSystemSet, prelude::*};
|
||||||
use bevy_tnua_avian3d::TnuaAvian3dSensorShape;
|
use bevy_tnua_avian3d::TnuaAvian3dSensorShape;
|
||||||
use std::{f32::consts::PI, time::Duration};
|
use std::time::Duration;
|
||||||
|
|
||||||
#[derive(Component, Default)]
|
#[derive(Component, Default)]
|
||||||
pub struct Player;
|
pub struct Player;
|
||||||
@@ -24,7 +23,7 @@ pub struct Player;
|
|||||||
struct PlayerAnimations;
|
struct PlayerAnimations;
|
||||||
|
|
||||||
#[derive(Component, Default)]
|
#[derive(Component, Default)]
|
||||||
pub struct PlayerHead;
|
struct PlayerHead;
|
||||||
|
|
||||||
#[derive(Component, Default)]
|
#[derive(Component, Default)]
|
||||||
pub struct PlayerRig;
|
pub struct PlayerRig;
|
||||||
@@ -47,7 +46,6 @@ pub fn plugin(app: &mut App) {
|
|||||||
Update,
|
Update,
|
||||||
(
|
(
|
||||||
spawn,
|
spawn,
|
||||||
update_camera,
|
|
||||||
collect_cash,
|
collect_cash,
|
||||||
toggle_animation,
|
toggle_animation,
|
||||||
setup_animations_marker_for_player,
|
setup_animations_marker_for_player,
|
||||||
@@ -86,30 +84,39 @@ fn spawn(
|
|||||||
.spawn((
|
.spawn((
|
||||||
Name::from("player"),
|
Name::from("player"),
|
||||||
Player,
|
Player,
|
||||||
|
CameraTarget,
|
||||||
transform,
|
transform,
|
||||||
TransformInterpolation,
|
TransformInterpolation,
|
||||||
|
TransformExtrapolation,
|
||||||
Visibility::default(),
|
Visibility::default(),
|
||||||
RigidBody::Dynamic,
|
RigidBody::Dynamic,
|
||||||
Collider::capsule(1.2, 1.5),
|
Collider::capsule(1.2, 1.5),
|
||||||
|
CollisionLayers::new(LayerMask(GameLayer::Player.to_bits()), LayerMask::ALL),
|
||||||
LockedAxes::ROTATION_LOCKED,
|
LockedAxes::ROTATION_LOCKED,
|
||||||
TnuaController::default(),
|
TnuaController::default(),
|
||||||
TnuaAvian3dSensorShape(Collider::cylinder(0.8, 0.0)),
|
TnuaAvian3dSensorShape(Collider::cylinder(0.8, 0.0)),
|
||||||
))
|
))
|
||||||
|
.with_children(|parent| {
|
||||||
|
parent
|
||||||
|
.spawn((
|
||||||
|
Name::from("body rig"),
|
||||||
|
PlayerRig,
|
||||||
|
CameraArmRotation,
|
||||||
|
Transform::from_translation(Vec3::new(0., -3., 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((
|
.with_child((
|
||||||
Name::from("head"),
|
Name::from("head"),
|
||||||
PlayerHead,
|
PlayerHead,
|
||||||
Transform::from_translation(Vec3::new(0., -0.5, 0.))
|
Transform::from_translation(Vec3::new(0., 1.6, 0.))
|
||||||
.with_rotation(Quat::from_rotation_y(std::f32::consts::PI)),
|
.with_scale(Vec3::splat(0.7)),
|
||||||
SceneRoot(mesh),
|
SceneRoot(mesh),
|
||||||
))
|
|
||||||
.with_child((
|
|
||||||
Name::from("body rig"),
|
|
||||||
PlayerRig,
|
|
||||||
Transform::from_translation(Vec3::new(0., -3., 0.))
|
|
||||||
.with_rotation(Quat::from_rotation_y(std::f32::consts::PI))
|
|
||||||
.with_scale(Vec3::splat(1.5)),
|
|
||||||
SceneRoot(asset_server.load(GltfAssetLabel::Scene(0).from_asset(ALIEN_ASSET_PATH))),
|
|
||||||
));
|
));
|
||||||
|
});
|
||||||
|
|
||||||
commands.spawn((
|
commands.spawn((
|
||||||
AudioPlayer::new(asset_server.load("sfx/heads/angry demonstrator.ogg")),
|
AudioPlayer::new(asset_server.load("sfx/heads/angry demonstrator.ogg")),
|
||||||
@@ -122,7 +129,7 @@ fn spawn(
|
|||||||
fn rotate_view(
|
fn rotate_view(
|
||||||
mut controls: ResMut<Controls>,
|
mut controls: ResMut<Controls>,
|
||||||
// todo: Put the player head as a child of the rig to avoid this mess:
|
// todo: Put the player head as a child of the rig to avoid this mess:
|
||||||
mut player: Query<&mut Transform, Or<(With<PlayerRig>, With<PlayerHead>)>>,
|
mut player: Query<&mut Transform, With<PlayerRig>>,
|
||||||
) {
|
) {
|
||||||
for mut tr in &mut player {
|
for mut tr in &mut player {
|
||||||
tr.rotate_y(controls.keyboard_state.look_dir.x * -0.001);
|
tr.rotate_y(controls.keyboard_state.look_dir.x * -0.001);
|
||||||
@@ -207,25 +214,6 @@ fn apply_controls(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_camera(
|
|
||||||
player: Query<&GlobalTransform, With<PlayerRig>>,
|
|
||||||
mut rig: Single<&mut Rig>,
|
|
||||||
res: Res<DebugVisuals>,
|
|
||||||
) {
|
|
||||||
let Some(player) = player.iter().next() else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
if !res.cam_follow {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
rig.driver_mut::<GameCameraRig>().set_position_target(
|
|
||||||
player.translation(),
|
|
||||||
player.rotation() * Quat::from_rotation_y(PI),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn collect_cash(
|
fn collect_cash(
|
||||||
mut commands: Commands,
|
mut commands: Commands,
|
||||||
mut collision_event_reader: EventReader<CollisionStarted>,
|
mut collision_event_reader: EventReader<CollisionStarted>,
|
||||||
|
|||||||
Reference in New Issue
Block a user