Files
HEDZReloaded/src/camera.rs
PROMETHIA-27 e1abb02cb8 Fix character controller jitter (#43)
* fix jitter while moving character controller

* added transform interpolation to character controller
2025-06-19 09:27:59 +02:00

162 lines
4.4 KiB
Rust

use avian3d::prelude::*;
use bevy::prelude::*;
use crate::{
GameState, control::ControlState, loading_assets::UIAssets, physics_layers::GameLayer,
};
#[derive(Component, Reflect, Debug)]
pub struct CameraTarget;
#[derive(Component, Reflect, Debug)]
pub struct CameraArmRotation;
/// Requested camera rotation based on various input sources (keyboard, gamepad)
#[derive(Component, Reflect, Debug, Default, Deref, DerefMut)]
#[reflect(Component)]
pub struct CameraRotationInput(pub Vec2);
#[derive(Resource, Reflect, Debug, Default)]
#[reflect(Resource)]
pub struct CameraState {
pub cutscene: bool,
pub look_around: bool,
}
#[derive(Component, Reflect, Debug, Default)]
struct CameraUi;
#[derive(Component, Reflect, Debug)]
#[reflect(Component)]
pub struct MainCamera {
dir: Dir3,
distance: f32,
}
impl MainCamera {
fn new(arm: Vec3) -> Self {
let (dir, distance) = Dir3::new_and_length(arm).expect("invalid arm length");
Self { dir, distance }
}
}
pub fn plugin(app: &mut App) {
app.register_type::<CameraRotationInput>();
app.register_type::<CameraState>();
app.register_type::<MainCamera>();
app.init_resource::<CameraState>();
app.add_systems(OnEnter(GameState::Playing), startup);
app.add_systems(
RunFixedMainLoop,
(update, update_ui, update_look_around, rotate_view)
.after(RunFixedMainLoopSystem::AfterFixedMainLoop)
.run_if(in_state(GameState::Playing)),
);
}
fn startup(mut commands: Commands) {
commands.spawn((
Camera3d::default(),
MainCamera::new(Vec3::new(0., 2.5, -13.)),
CameraRotationInput::default(),
));
}
fn update_look_around(controls: Res<ControlState>, mut cam_state: ResMut<CameraState>) {
let look_around = controls.view_mode;
if look_around != cam_state.look_around {
cam_state.look_around = look_around;
}
}
fn update_ui(
mut commands: Commands,
cam_state: Res<CameraState>,
assets: Res<UIAssets>,
query: Query<Entity, With<CameraUi>>,
) {
if cam_state.is_changed() {
let show_ui = cam_state.look_around || cam_state.cutscene;
if show_ui {
commands.spawn((
CameraUi,
Node {
margin: UiRect::top(Val::Px(20.))
.with_left(Val::Auto)
.with_right(Val::Auto),
justify_content: JustifyContent::Center,
..default()
},
children![(
Node {
display: Display::Block,
position_type: PositionType::Absolute,
..default()
},
ImageNode::new(assets.camera.clone()),
)],
));
} else {
for entity in query.iter() {
commands.entity(entity).despawn();
}
}
}
}
fn update(
mut cam: Query<
(&MainCamera, &mut Transform, &CameraRotationInput),
(Without<CameraTarget>, Without<CameraArmRotation>),
>,
target_q: Single<&Transform, (With<CameraTarget>, Without<CameraArmRotation>)>,
arm_rotation: Single<&Transform, With<CameraArmRotation>>,
spatial_query: SpatialQuery,
cam_state: Res<CameraState>,
) {
if cam_state.cutscene {
return;
}
let target = target_q.translation;
let arm_tf = arm_rotation;
let Ok((camera, mut cam_transform, cam_rotation_input)) = cam.single_mut() else {
return;
};
let direction = arm_tf.rotation * Quat::from_rotation_y(cam_rotation_input.x) * camera.dir;
let max_distance = camera.distance;
let filter = SpatialQueryFilter::from_mask(LayerMask(GameLayer::Level.to_bits()));
let cam_pos = if let Some(first_hit) = spatial_query.cast_shape(
&Collider::sphere(0.5),
target,
Quat::IDENTITY,
direction,
&ShapeCastConfig::from_max_distance(max_distance),
&filter,
) {
let distance = first_hit.distance;
target + (direction * distance)
} else {
target + (direction * camera.distance)
};
*cam_transform = Transform::from_translation(cam_pos).looking_at(target, Vec3::Y);
}
fn rotate_view(controls: Res<ControlState>, mut cam: Single<&mut CameraRotationInput>) {
if !controls.view_mode {
cam.x = 0.;
return;
}
cam.0 += controls.look_dir * -0.001;
}