Skip to content

Commit

Permalink
Add camera smoothing preferences and impl Reflect (#27)
Browse files Browse the repository at this point in the history
* Customizable camera smoothing
Defaults much smoother than before, more like a platformer than an FPS

* Prefs can be modified by the debug WorldInspector
  • Loading branch information
Waridley authored Feb 8, 2024
1 parent e9041d7 commit c26eaa7
Show file tree
Hide file tree
Showing 10 changed files with 226 additions and 59 deletions.
43 changes: 37 additions & 6 deletions rs/src/anim.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,20 +20,20 @@ pub struct BuiltinAnimations;
impl Plugin for BuiltinAnimations {
fn build(&self, app: &mut App) {
app.add_plugins((
AnimationPlugin::<Transform>::default(),
AnimationPlugin::<GlobalTransform>::default(),
AnimationPlugin::<Transform>::PLUGIN,
AnimationPlugin::<GlobalTransform>::PLUGIN,
))
.add_systems(
PostUpdate,
BlendTargets::animate
.before(apply_animations::<Transform>)
.in_set(AnimationSet::<Transform>::default()),
.in_set(AnimationSet::<Transform>::SET),
)
.configure_sets(
PostUpdate,
(
AnimationSet::<Transform>::default().before(TransformPropagate),
AnimationSet::<GlobalTransform>::default().before(TransformPropagate),
AnimationSet::<Transform>::SET.before(TransformPropagate),
AnimationSet::<GlobalTransform>::SET.before(TransformPropagate),
),
);
}
Expand All @@ -42,6 +42,10 @@ impl Plugin for BuiltinAnimations {
#[derive(Default, Debug)]
pub struct AnimationPlugin<T: Component>(PhantomData<T>);

impl<T: Component> AnimationPlugin<T> {
pub const PLUGIN: Self = Self(PhantomData::<T>);
}

impl<T: Component> Plugin for AnimationPlugin<T> {
fn build(&self, app: &mut App) {
app.add_event::<ComponentDelta<T>>().add_systems(
Expand Down Expand Up @@ -73,7 +77,9 @@ impl<T: Resource> Plugin for ResAnimationPlugin<T> {

#[derive(SystemSet)]
pub struct AnimationSet<T>(PhantomData<T>);

impl<T> AnimationSet<T> {
pub const SET: Self = Self(PhantomData);
}
impl<T> Copy for AnimationSet<T> {}
impl<T> Clone for AnimationSet<T> {
fn clone(&self) -> Self {
Expand Down Expand Up @@ -220,6 +226,8 @@ impl<T: Component> ComponentDelta<T> {
})
}

/// A delta for an animation with an indefinite progress. `progress` will always be `f32::NAN`,
/// so the animation will be non-blendable and always run before all blendable animations.
pub fn indefinite(target: Entity, apply: impl FnOnce(Mut<T>) + Send + Sync + 'static) -> Self {
Self {
target,
Expand All @@ -230,6 +238,20 @@ impl<T: Component> ComponentDelta<T> {
}
}

/// A delta for a "default" animation. `progress` will always be `0.0`, so the animation will be blended,
/// but will only contribute any change if there are no running animations with `progress > 0.0`. The
/// coefficient passed to `apply` will be nonzero if and only if no other animations have made any progress.
///
/// Usually there will be only one default animation for a component on an entity, so the coefficient will
/// be either `0.0` or `1.0`. However, multiple "default" animations technically can be blended together.
pub fn default_animation(
target: Entity,
apply: impl FnOnce(Mut<T>, f32) + Send + Sync + 'static,
) -> Self {
Self::new(target, 0.0, apply)
}

/// A delta for a component that implements `Diff`.
pub fn diffable(target: Entity, progress: f32, new_value: T) -> Self
where
T: Diff + Clone + Add<<T as Diff>::Delta, Output = T>,
Expand All @@ -242,6 +264,15 @@ impl<T: Component> ComponentDelta<T> {
}
})
}

/// See [Self::default_animation] and [Self::diffable].
pub fn default_diffable(target: Entity, new_value: T) -> Self
where
T: Diff + Clone + Add<<T as Diff>::Delta, Output = T>,
<T as Diff>::Delta: Mul<f32, Output = <T as Diff>::Delta> + Default + PartialEq,
{
Self::diffable(target, 0.0, new_value)
}
}

pub struct Delta<T> {
Expand Down
10 changes: 5 additions & 5 deletions rs/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,13 @@ use player::ctrl::CtrlVel;
use std::{f32::consts::*, fmt::Debug, time::Duration};
use util::{IntoFnPlugin, RonReflectAssetLoader};

use player::abilities::AbilitiesPlugin;
#[allow(unused_imports, clippy::single_component_path_imports)]
#[cfg(all(debug_assertions, not(target_arch = "wasm32")))]
use bevy_dylib;
use bevy_rapier3d::plugin::PhysicsSet::StepSimulation;
use offloading::OffloadingPlugin;
use planet::sky::SkyPlugin;
use player::abilities::AbilitiesPlugin;

pub mod anim;
pub mod enemies;
Expand Down Expand Up @@ -116,7 +116,7 @@ pub fn game_plugin(app: &mut App) -> &mut App {
OffloadingPlugin,
SkyPlugin,
anim::BuiltinAnimations,
anim::AnimationPlugin::<Spewer>::default(),
anim::AnimationPlugin::<Spewer>::PLUGIN,
enemies::plugin.plugfn(),
player::plugin.plugfn(),
pickups::plugin.plugfn(),
Expand All @@ -125,9 +125,9 @@ pub fn game_plugin(app: &mut App) -> &mut App {
ui::plugin.plugfn(),
))
.insert_resource(PkvStore::new_with_qualifier("studio", "sonday", "has"))
.add_plugins((
MaterialPlugin::<ExtendedMaterial<StandardMaterial, BubbleMaterial>>::default(),
))
.add_plugins((MaterialPlugin::<
ExtendedMaterial<StandardMaterial, BubbleMaterial>,
>::default(),))
.add_systems(Startup, startup)
.add_systems(
Update,
Expand Down
2 changes: 1 addition & 1 deletion rs/src/pickups.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::{player::abilities::Hurt, mats::BubbleMaterial, pickups::pickup::PickupItem};
use crate::{mats::BubbleMaterial, pickups::pickup::PickupItem, player::abilities::Hurt};
use bevy::{
math::Vec3Swizzles,
pbr::ExtendedMaterial,
Expand Down
49 changes: 37 additions & 12 deletions rs/src/player.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::{terminal_velocity, NeverDespawn, TerminalVelocity};
use crate::{anim, terminal_velocity, NeverDespawn, TerminalVelocity};

use crate::{pickups::MissSfx, settings::Settings, util::IntoFnPlugin};
use bevy::{
Expand All @@ -12,6 +12,7 @@ use bevy::{
};
use bevy_common_assets::ron::RonAssetPlugin;
use bevy_kira_audio::{Audio, AudioControl};
use bevy_pkv::PkvStore;
use bevy_rapier3d::{
dynamics::{CoefficientCombineRule::Min, Velocity},
geometry::{Collider, Friction},
Expand Down Expand Up @@ -55,9 +56,10 @@ pub enum InterpolatedXforms {

pub fn plugin(app: &mut App) -> &mut App {
app.add_plugins((
prefs::PrefsPlugin,
input::plugin.plugfn(),
RonAssetPlugin::<PlayerParams>::new(&["ron"]),
crate::anim::AnimationPlugin::<RotVel>::default(),
crate::anim::AnimationPlugin::<RotVel>::PLUGIN,
))
.insert_resource(PlayerRespawnTimers::default())
.add_systems(Startup, setup)
Expand Down Expand Up @@ -246,9 +248,11 @@ pub enum PlayerEntity {
Orb(PlayerArm),
}
use crate::{
player::abilities::{BoosterCharge, HurtboxFilter, Sfx, WeaponCharge},
anim::ComponentDelta,
player::tune::PlayerParams,
player::{
abilities::{BoosterCharge, HurtboxFilter, Sfx, WeaponCharge},
tune::PlayerParams,
},
util::{Diff, Prev, TransformDelta},
};
use player_entity::*;
Expand Down Expand Up @@ -284,6 +288,7 @@ pub fn spawn_players(
spawn_data: Res<PlayerSpawnData>,
params: Res<PlayerParams>,
mut events: ResMut<Events<PlayerSpawnEvent>>,
mut pkv: ResMut<PkvStore>,
) {
for event in events.drain() {
let PlayerSpawnData {
Expand Down Expand Up @@ -334,9 +339,10 @@ pub fn spawn_players(
let id = event.id;
let owner = BelongsToPlayer::with_id(id);
let char_collider = Collider::from(SharedShape::new(params.phys.collider));
let username = format!("Player{}", owner.0.get());
let mut root = cmds
.spawn((
Name::new(format!("Player{}", owner.0.get())),
Name::new(username.clone()),
owner,
TerminalVelocity(Velocity {
linvel: Vect::splat(96.0),
Expand All @@ -353,6 +359,20 @@ pub fn spawn_players(
))
.with_enum(Root);

let prefs_key = format!("{username}.prefs");
let prefs = match pkv.get(&prefs_key) {
Ok(prefs) => prefs,
Err(e) => {
if !matches!(e, bevy_pkv::GetError::NotFound) {
error!("{e}");
}
let prefs = PlayerPrefs::default();
pkv.set(prefs_key, &prefs).unwrap_or_else(|e| error!("{e}"));
prefs
}
};
info!("Loaded: {prefs:#?}");

build_player_scene(
&mut root,
owner,
Expand All @@ -366,6 +386,7 @@ pub fn spawn_players(
],
arm_particle_mesh,
(crosshair_mesh, crosshair_mat),
prefs,
);
}
}
Expand All @@ -379,8 +400,9 @@ fn build_player_scene(
arm_meshes: [(MaterialMeshBundle<StandardMaterial>, PlayerArm); 3],
arm_particle_mesh: Handle<Mesh>,
crosshair: (Handle<Mesh>, Handle<StandardMaterial>),
prefs: PlayerPrefs,
) {
player_controller(root, owner, char_collider);
player_controller(root, owner, char_collider, prefs);
player_vis(
root,
owner,
Expand All @@ -392,14 +414,17 @@ fn build_player_scene(
);
}

fn player_controller(root: &mut EntityCommands, owner: BelongsToPlayer, char_collider: Collider) {
fn player_controller(
root: &mut EntityCommands,
owner: BelongsToPlayer,
char_collider: Collider,
prefs: PlayerPrefs,
) {
root.with_children(|builder| {
let PlayerPrefs {
invert_camera,
fov,
camera: cam_prefs,
input_map,
sens,
} = PlayerPrefs::default();
} = prefs;
builder
.spawn((
Name::new(format!("Player{}.Controller", owner.0.get())),
Expand All @@ -417,7 +442,7 @@ fn player_controller(root: &mut EntityCommands, owner: BelongsToPlayer, char_col
CtrlState::default(),
BoosterCharge::default(),
WeaponCharge::default(),
(invert_camera, fov, sens),
cam_prefs,
))
.with_enum(Controller);
});
Expand Down
6 changes: 4 additions & 2 deletions rs/src/player/abilities.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ impl Plugin for AbilitiesPlugin {
.add_systems(
PostUpdate,
(hit_stuff
.after(AnimationSet::<Transform>::default())
.after(AnimationSet::<Transform>::SET)
.after(TransformPropagate),),
)
.add_event::<Hurt>();
Expand Down Expand Up @@ -487,6 +487,9 @@ pub fn hit_stuff(
let xform = xform.compute_transform();
let prev = prev.compute_transform();
let vel = xform.translation - prev.translation;
// TODO: Get all hits along cast, not just the first.
// Maybe use `intersections_with_shape` with an extruded shape somehow?
// Would be easy with spheres (capsule) but not all shapes.
let Some((other, toi)) = ctx.cast_shape(
prev.translation.into(),
prev.rotation.slerp(xform.rotation, 0.5).into(), // Average I guess? *shrugs*
Expand All @@ -498,7 +501,6 @@ pub fn hit_stuff(
) else {
continue;
};
dbg!(&toi);
events.send(Hurt {
hurtbox: id,
toi,
Expand Down
38 changes: 25 additions & 13 deletions rs/src/player/camera.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use super::{
};
use crate::{planet::sky::SkyShader, player::PlayerId, settings::Settings, NeverDespawn};

use crate::{anim::ComponentDelta, player::prefs::CamSmoothing, util::LerpSlerp};
use bevy::{
core_pipeline::{
bloom::BloomSettings, clear_color::ClearColorConfig, fxaa::Fxaa, tonemapping::Tonemapping,
Expand All @@ -30,9 +31,9 @@ use bevy_rapier3d::{
use enum_components::{ERef, EntityEnumCommands};
use std::f32::consts::FRAC_PI_2;

pub const CAM_ACCEL: f32 = 12.0;
const MAX_CAM_DIST: f32 = 24.0;
const MIN_CAM_DIST: f32 = 6.4;
const CAM_SMOOTHING: f32 = 0.33;
const MAX_CAM_DIST: f32 = 32.0;
const MIN_CAM_DIST: f32 = 9.6;

pub fn spawn_camera<'w, 's, 'a>(
cmds: &'a mut Commands<'w, 's>,
Expand Down Expand Up @@ -171,7 +172,7 @@ pub fn spawn_pivot<'w, 's, 'a>(
owner,
CameraVertSlider(0.4),
TransformBundle::from_transform(Transform {
translation: Vect::new(0.0, 0.0, 6.4),
translation: Vect::new(0.0, 0.0, MIN_CAM_DIST),
..default()
}),
))
Expand Down Expand Up @@ -255,15 +256,26 @@ pub fn position_target(
}
}

pub fn follow_target(mut cam_q: Query<(&mut Transform, &CamTarget), ERef<Cam>>, t: Res<Time>) {
pub fn follow_target(
player_q: Query<(&CamSmoothing, &BelongsToPlayer)>,
mut cam_q: Query<(Entity, &mut Transform, &CamTarget, &BelongsToPlayer), ERef<Cam>>,
t: Res<Time>,
mut sender: EventWriter<ComponentDelta<Transform>>,
) {
let dt = t.delta_seconds();
for (mut cam_xform, target_xform) in &mut cam_q {
// TODO: Maybe always aim towards pivot, rather than immediately assuming final rotation
cam_xform.translation = cam_xform
.translation
.lerp(target_xform.translation, CAM_ACCEL * dt);
cam_xform.rotation = cam_xform
.rotation
.slerp(target_xform.rotation, CAM_ACCEL * dt);
for (id, mut cam_xform, target_xform, owner) in &mut cam_q {
let Some(smoothing) = player_q
.iter()
.find_map(|(smoothing, id)| (*id == *owner).then_some(*smoothing))
else {
continue;
};
let s = if *smoothing <= dt {
1.0
} else {
(1.0 / *smoothing) * dt
};
let new = cam_xform.lerp_slerp(**target_xform, s);
sender.send(ComponentDelta::<Transform>::default_diffable(id, new))
}
}
4 changes: 2 additions & 2 deletions rs/src/player/ctrl.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::{
player::abilities::BoosterCharge,
player::{
abilities::BoosterCharge,
player_entity::{Controller, Root, ShipCenter},
tune::PlayerParams,
BelongsToPlayer, G1,
Expand Down Expand Up @@ -431,7 +431,7 @@ pub fn move_player(
let end = vis_interp.end.unwrap();
vis_interp.start = Some(Isometry::new(
end.translation.vector - Vector3::from(body_xform.rotation.inverse() * result),
(Quat::from(end.rotation) * rot.inverse())
(Quat::from(end.rotation).inverse() * rot.inverse())
.to_scaled_axis()
.into(),
));
Expand Down
Loading

0 comments on commit c26eaa7

Please sign in to comment.