Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

✨ camera / look at #7

Merged
merged 6 commits into from
Nov 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
181 changes: 140 additions & 41 deletions camera/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,22 @@
use bevy::{
app::{PostStartup, Update},
ecs::system::ResMut,
asset::Assets,
ecs::{
bundle::Bundle,
component::Component,
system::{Commands, Res, ResMut},
},
gizmos::{gizmos::Gizmos, GizmoConfig},
math::Vec2,
math::{Vec2, Vec3},
prelude::{
App, Bundle, Camera2dBundle, Component, Entity, Plugin, PostUpdate, Query, Transform, With,
Without,
default, App, Camera2dBundle, Entity, Plugin, PostUpdate, Query, Time, Timer, TimerMode,
Transform, With, Without,
},
render::{
color::Color,
mesh::{shape, Mesh},
},
render::color::Color,
sprite::{ColorMaterial, MaterialMesh2dBundle},
};

#[derive(Component)]
Expand All @@ -17,17 +26,37 @@ pub struct Target;
pub struct Camera {
target: Entity, // TODO: find a way to have multiple targets per camera, but also being able to have multi cameras (n-n)
dead_zone: Vec2,
target_prev_translation: Vec3,
// look at this position, this is the player + velocity + factor
// it allow us to place the camera a bit ahead of time
look_at: Vec3,
ahead_factor: Vec3,
traveling: bool,
center_after: Timer,
}

impl Camera {
pub fn new(target: Entity, dead_zone: Vec2) -> Self {
Self { target, dead_zone }
pub fn new(target: Entity, dead_zone: Vec2, ahead_factor: Vec3) -> Self {
Self {
target,
dead_zone,
target_prev_translation: Vec3::ZERO,
look_at: Vec3::ZERO,
ahead_factor,
traveling: false,
center_after: Timer::from_seconds(0.4, TimerMode::Once),
}
}

pub fn new_default(target: Entity) -> Self {
Self {
target,
dead_zone: Vec2::new(30.0, 15.0),
target_prev_translation: Vec3::ZERO,
look_at: Vec3::ZERO,
ahead_factor: Vec3::ONE,
traveling: false,
center_after: Timer::from_seconds(0.4, TimerMode::Once),
}
}
}
Expand All @@ -39,11 +68,8 @@ pub struct CameraBundle {
}

impl CameraBundle {
pub fn new(target: Entity, bundle: Camera2dBundle) -> Self {
Self {
camera: Camera::new_default(target),
bundle,
}
pub fn new(camera: Camera, bundle: Camera2dBundle) -> Self {
Self { camera, bundle }
}

pub fn new_with_default_bundle(target: Entity) -> Self {
Expand All @@ -64,10 +90,10 @@ impl Plugin for CameraPlugin {
}

fn center(
mut query_camera: Query<(&mut Transform, &Camera), Without<Target>>,
mut query_camera: Query<(&mut Transform, &mut Camera), Without<Target>>,
query_targets: Query<(&Transform, Entity), With<Target>>,
) {
for (mut camera_transform, camera) in &mut query_camera {
for (mut camera_transform, mut camera) in &mut query_camera {
for (target_transform, target_entity) in &query_targets {
if camera.target != target_entity {
continue;
Expand All @@ -77,46 +103,67 @@ fn center(
if camera.target == target_entity {
camera_transform.translation.x = target_transform.translation.x;
camera_transform.translation.y = target_transform.translation.y;
camera.target_prev_translation = target_transform.translation;
camera.look_at = target_transform.translation;
break;
}
}
}
}

fn cameraman(
mut query_camera: Query<(&mut Transform, &Camera), Without<Target>>,
mut query_camera: Query<(&mut Transform, &mut Camera), Without<Target>>,
query_targets: Query<(&Transform, Entity), With<Target>>,
time: Res<Time>,
) {
for (mut camera_transform, camera) in &mut query_camera {
for (mut camera_transform, mut camera) in &mut query_camera {
for (target_transform, target_entity) in &query_targets {
if camera.target != target_entity {
continue;
}

// TODO: for now we follow the first target but we could think of doing an average positions of all the targets
if camera.target == target_entity {
let diff = camera_transform.translation - target_transform.translation;
let diff_abs = diff.abs();

if diff_abs.x > camera.dead_zone.x {
camera_transform.translation.x = target_transform.translation.x
- if diff.x > 0. {
-camera.dead_zone.x
} else {
camera.dead_zone.x
};
}
if diff_abs.y > camera.dead_zone.y {
camera_transform.translation.y = target_transform.translation.y
- if diff.y > 0. {
-camera.dead_zone.y
} else {
camera.dead_zone.y
};
}
// process target velocity
let mut target_velocity = target_transform.translation - camera.target_prev_translation;
let target_moving = target_velocity != Vec3::ZERO;
if target_moving {
target_velocity /= time.delta_seconds();
}
camera.look_at = target_transform.translation + (target_velocity * camera.ahead_factor);
camera.target_prev_translation = target_transform.translation;

// process dead zone
let diff_pos_abs = (target_transform.translation - camera_transform.translation)
.truncate()
.abs();
let dead_zone =
diff_pos_abs.x <= camera.dead_zone.x && diff_pos_abs.y <= camera.dead_zone.y;
let centered = diff_pos_abs.x < 3.0 && diff_pos_abs.y < 3.0;

// center after some time in the dead zone
if dead_zone && !centered && !target_moving && !camera.traveling {
camera.center_after.tick(time.delta());
} else {
camera.center_after.reset();
}

break;
// triggers travelling if we are out of dead zone
if !dead_zone {
camera.traveling = true;
}

// once the camera is moving we keep moving until we reach the center
if camera.traveling || camera.center_after.finished() {
let next_pos = camera.look_at - camera_transform.translation;
camera_transform.translation.x += next_pos.x * 0.02;
camera_transform.translation.y += next_pos.y * 0.02;

// we arrived
if centered && !target_moving {
camera.traveling = false;
}
}

// if the target is in the dead zone, do nothing on camera
}
}
}
Expand All @@ -125,23 +172,75 @@ pub struct CameraDebugPlugin;

impl Plugin for CameraDebugPlugin {
fn build(&self, app: &mut App) {
app.add_systems(Update, debug);
app.add_systems(PostStartup, setup_debug)
.add_systems(Update, debug);
}
}

#[derive(Component)]
pub struct CameraDebug(Entity);

fn setup_debug(
mut commands: Commands,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<ColorMaterial>>,
query_cameras: Query<(&Transform, &Camera, Entity)>,
) {
for (camera_transform, _camera, entity) in &query_cameras {
commands.spawn((
MaterialMesh2dBundle {
mesh: meshes.add(shape::Circle::new(2.).into()).into(),
material: materials.add(ColorMaterial::from(Color::GREEN)),
transform: Transform::from_translation(Vec3::new(
camera_transform.translation.x,
camera_transform.translation.y,
100.0,
)),
..default()
},
CameraDebug(entity),
));
}
}

#[allow(clippy::type_complexity)] // Because of query_targets
fn debug(
mut gizmos: Gizmos,
mut config: ResMut<GizmoConfig>,
query_cameras: Query<(&Transform, &Camera)>,
query_cameras: Query<(&Transform, &Camera, Entity)>,
query_targets: Query<(&Transform, Entity), (With<Target>, Without<CameraDebug>)>,
mut query_camera_debug: Query<(&mut Transform, &CameraDebug), Without<Camera>>,
) {
config.line_width = 1.0;

for (camera_transform, camera) in &query_cameras {
// TODO: Unspawn camera debug object if camera do not exist anymore

for (camera_transform, camera, entity) in &query_cameras {
gizmos.rect_2d(
camera_transform.translation.truncate(),
0.,
camera.dead_zone * 2.0,
Color::RED,
);

for (target_transform, target_entity) in &query_targets {
if camera.target != target_entity {
continue;
}

gizmos.line_2d(
target_transform.translation.truncate(),
camera.look_at.truncate(),
Color::GREEN,
);
}

for (mut transform, camera_debug) in &mut query_camera_debug {
if camera_debug.0 != entity {
continue;
}
transform.translation.x = camera.look_at.x;
transform.translation.y = camera.look_at.y;
}
}
}
5 changes: 4 additions & 1 deletion src/player.rs
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,10 @@ fn setup(
})
.id();

commands.spawn(CameraBundle::new_with_default_bundle(entity));
commands.spawn(CameraBundle::new(
Camera::new(entity, Vec2::new(50.0, 20.0), Vec3::ONE * 0.8),
Camera2dBundle::default(),
));
}

fn setup_ui(mut commands: Commands, asset_server: Res<AssetServer>) {
Expand Down