use std::time::Duration;
use crate::{LOGICAL_HEIGHT, LOGICAL_WIDTH};
use bevy::prelude::*;
use bevy_egui::{egui, EguiContexts};
use bevy_smooth_pixel_camera::components::PixelCamera;
use bevy_smooth_pixel_camera::viewport::ViewportSize;
pub struct CameraPlugin;
impl Plugin for CameraPlugin {
fn build(&self, app: &mut App) {
app.add_plugins(bevy_smooth_pixel_camera::PixelCameraPlugin)
.init_resource::<CameraRig>()
.add_systems(Startup, setup_camera)
.add_systems(Update, (update_camera, camera_rig_debug));
}
}
#[derive(Resource)]
pub struct CameraRig {
/// Which [`CameraTarget`](s) does the camera target?
pub targetting: usize,
precision: f32,
/// how long should the lerp take?
pub snap_duration: Duration,
}
impl CameraRig {
/// Camera lag - [0.0, 1.0]
///
/// The camera will nominally be within this percentage of the
/// target within [`self::snap_duration`]
pub fn lag(&self) -> f32 {
self.precision
}
pub fn set_lag(&mut self, lag: f32) {
self.precision = lag.clamp(0.0, 1.0);
}
}
impl Default for CameraRig {
fn default() -> Self {
Self {
targetting: 0,
precision: 0.7,
snap_duration: Duration::from_secs_f32(10f32.recip()),
}
}
}
/// The maximum value used for [`CameraRig::targetting`]
pub const MAX_TARGET: usize = 3;
fn camera_rig_debug(mut rig: ResMut<CameraRig>, mut contexts: EguiContexts) {
egui::Window::new("Camera Rig").show(contexts.ctx_mut(), |ui| {
ui.horizontal(|ui| {
ui.label("Targetting");
ui.add(egui::Slider::new(&mut rig.targetting, 0..=MAX_TARGET));
});
ui.horizontal(|ui| {
ui.label("Precision");
ui.add(egui::Slider::new(&mut rig.precision, 0.0..=1.0));
});
ui.horizontal(|ui| {
ui.label("Snap Inverse Halflife");
let mut recip_halflife = rig.snap_duration.as_secs_f32().recip();
if ui
.add(egui::Slider::new(&mut recip_halflife, 1.0..=100.0))
.changed()
{
rig.snap_duration = Duration::from_secs_f32(recip_halflife.recip());
}
});
});
}
/// Attach this to something to make it the target of the camera
#[derive(Component, Default)]
pub struct CameraTarget(pub usize);
#[derive(Component, Default)]
struct Camera;
#[derive(Bundle)]
struct CameraBundle {
camera: Camera,
camera_2d: Camera2dBundle,
pixel_camera: PixelCamera,
// chroma_aberration: post_process::ChromaticAberattionSettings,
}
fn setup_camera(mut commands: Commands) {
commands.insert_resource(ClearColor(Color::rgb(0.07, 0.05, 0.05)));
commands.spawn(CameraBundle {
camera: Camera,
camera_2d: Camera2dBundle::default(),
pixel_camera: PixelCamera::from_size(ViewportSize::AutoMax {
max_width: LOGICAL_WIDTH,
max_height: LOGICAL_HEIGHT,
}),
// chroma_aberration: post_process::ChromaticAberattionSettings {
// // intensity: 0.02,
// ..default()
// },
});
}
fn update_camera(
mut camera: Query<(&mut PixelCamera, &GlobalTransform), With<Camera>>,
targets: Query<(&GlobalTransform, &CameraTarget)>,
rig: Res<CameraRig>,
time: Res<Time>,
) {
// This is only 1 camera...
let (mut camera, cam_transform) = camera.single_mut();
// ...but soooo many targets
let targeted = targets
.iter()
.filter(|(_, t)| t.0 == rig.targetting)
.collect::<Vec<_>>();
// Get the midpoint of all targeted items
let goal = targeted
.iter()
.map(|(transform, _)| transform.translation())
.reduce(|total, e| total + e)
.map(|total| total / targeted.len() as f32);
if let Some(goal) = goal {
// Lerp the camera to player's global translation point
let source = cam_transform.translation();
// Taken from https://mastodon.social/@acegikmo/111931613710775864
let lambda = -rig.snap_duration.as_secs_f32() / rig.precision.log2();
let alpha = 2.0f32.powf(-time.delta_seconds() / lambda);
let lerped = source * alpha + goal * (1.0 - alpha);
camera.subpixel_pos = lerped.xy();
}
}