Compare commits

..

13 Commits

BIN
assets/bevy.png (Stored with Git LFS)

Binary file not shown.

BIN
assets/flappy/background-gradient.png (Stored with Git LFS)

Binary file not shown.

BIN
assets/flappy/background-gradient.xcf (Stored with Git LFS)

Binary file not shown.

BIN
assets/flappy/play.png (Stored with Git LFS)

Binary file not shown.

BIN
assets/flappy/play.xcf (Stored with Git LFS)

Binary file not shown.

BIN
assets/flappy/rewind.png (Stored with Git LFS)

Binary file not shown.

BIN
assets/flappy/rewind.xcf (Stored with Git LFS)

Binary file not shown.

@ -9,23 +9,66 @@ fn main() {
},))
.add_systems(Startup, spawn_background)
.add_systems(Update, move_camera)
.add_systems(Update, parallax_gizmos)
.run();
}
fn spawn_background(
mut commands: Commands,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<ColorMaterial>>,
server: Res<AssetServer>,
) {
commands.spawn((Parallax(1.0), children![(Text2d("1.0".into()), Transform::from_xyz(0.0, 35.0, 0.0))]));
commands.spawn((Parallax(2.0), children![(Text2d("2.0".into()), Transform::from_xyz(0.0, 35.0, 0.0))]));
commands.spawn((Parallax(4.0), children![(Text2d("4.0".into()), Transform::from_xyz(0.0, 35.0, 0.0))]));
commands.spawn((Parallax(8.0), children![(Text2d("8.0".into()), Transform::from_xyz(0.0, 35.0, 0.0))]));
commands.spawn((
Name::new("Depth: 1.0"),
Transform::default().with_translation(Vec3::new(0.0, 0.0, 0.0)).with_scale(Vec3::splat(50.0)),
Mesh2d(meshes.add(Circle::new(1.0))),
MeshMaterial2d(materials.add(ColorMaterial {
texture: Some(server.load("bevy.png")),
color: RED.into(),
..default()
})),
ParallaxDepth(1.0),
Visibility::Inherited,
));
commands.spawn((
Name::new("Depth: 2.0"),
Transform::default().with_translation(Vec3::new(0.0, 0.0, -1.0)).with_scale(Vec3::splat(25.0)),
Mesh2d(meshes.add(Circle::new(2.0))),
MeshMaterial2d(materials.add(ColorMaterial {
texture: Some(server.load("bevy.png")),
color: GREEN.into(),
..default()
})),
ParallaxDepth(2.0),
Visibility::Inherited,
));
commands.spawn((
Name::new("Depth: 4.0"),
Transform::default().with_translation(Vec3::new(0.0, 0.0, -2.0)).with_scale(Vec3::splat(12.5)),
Mesh2d(meshes.add(Circle::new(4.0))),
MeshMaterial2d(materials.add(ColorMaterial {
texture: Some(server.load("bevy.png")),
color: BLUE.into(),
..default()
})),
ParallaxDepth(4.0),
Visibility::Inherited,
));
commands.spawn((
Name::new("Depth: 8.0"),
Transform::default().with_translation(Vec3::new(0.0, 0.0, -3.0)).with_scale(Vec3::splat(6.25)),
Mesh2d(meshes.add(Circle::new(8.0))),
MeshMaterial2d(materials.add(ColorMaterial {
texture: Some(server.load("bevy.png")),
color: YELLOW.into(),
..default()
})),
ParallaxDepth(8.0),
Visibility::Inherited,
));
}
fn move_camera(
mut t: Single<&mut Transform, With<Camera2d>>,
keys: Res<ButtonInput<KeyCode>>,
) {
fn move_camera(mut t: Single<&mut Transform, With<Camera2d>>, keys: Res<ButtonInput<KeyCode>>) {
if keys.pressed(KeyCode::ArrowLeft) {
t.translation.x -= 5.0;
} else if keys.pressed(KeyCode::ArrowRight) {
@ -38,18 +81,3 @@ fn move_camera(
t.translation.y += 5.0;
}
}
fn parallax_gizmos(
mut gizmos: Gizmos,
q: Query<&Transform, With<Parallax>>,
) {
// Closest to camera
// Parallax(1)
q.iter().for_each(|t| {
gizmos.grid_2d(
t.translation.truncate(),
UVec2::new(5, 5),
Vec2::splat(10.),
RED,
).outer_edges();
});
}

@ -2,6 +2,7 @@
#![allow(clippy::type_complexity)]
use bevy::audio::PlaybackMode;
use bevy::image::{ImageLoaderSettings, ImageSampler};
use bevy::render::view::ColorGrading;
use games::physics2d::*;
use games::*;
@ -35,6 +36,7 @@ fn main() {
init_bird.after(init_assets),
init_first_batches.after(init_assets),
init_ui,
init_background,
tweak_camera.after(create_camera_2d),
),
)
@ -90,7 +92,7 @@ fn main() {
(update_tooltip, debug_trail).run_if(in_state(DebuggingState::On)),
// TODO: Add run_if to this system
update_batch_position.run_if(any_component_changed::<Batch>),
move_batches.run_if(on_event::<CollisionStarted>),
move_batches.run_if(on_event::<CollisionStarted>.or(on_event::<CollisionEnded>)),
manage_score.run_if(on_event::<CollisionStarted>.or(on_event::<CollisionEnded>)),
shimmer_button::<RewindButton>.run_if(in_state(PlayerState::Stasis)),
shimmer_button::<FlapButton>.run_if(in_state(PlayerState::Pause)),
@ -99,6 +101,7 @@ fn main() {
.add_observer(flap)
.add_observer(populate_batch)
.add_observer(populate_pipe)
.add_observer(move_pipe)
.add_observer(populate_ground)
.add_observer(populate_ceiling)
.add_observer(populate_hitbox)
@ -108,7 +111,7 @@ fn main() {
fn tweak_camera(camera: Single<(Entity, &mut Camera)>, mut commands: Commands) {
debug!("Tweaking camera");
let (e, mut c) = camera.into_inner();
c.clear_color = ClearColorConfig::Custom(WHITE.into());
c.clear_color = ClearColorConfig::Custom(SKY_BLUE.into());
commands.entity(e).insert(ColorGrading { ..default() });
}
@ -127,17 +130,72 @@ enum PlayerState {
}
// A tape tracking the bird's state every frame
#[derive(Component, Default)]
#[derive(Component)]
struct Tape {
accumulated_translations: Vec<AccumulatedTranslation>,
linear_velocities: Vec<LinearVelocity>,
angular_velocities: Vec<AngularVelocity>,
external_angular_impulses: Vec<ExternalAngularImpulse>,
external_forces: Vec<ExternalForce>,
external_impulses: Vec<ExternalImpulse>,
external_torques: Vec<ExternalTorque>,
positions: Vec<Position>,
rotations: Vec<Rotation>,
capacity: usize,
linear_velocities: VecDeque<LinearVelocity>,
angular_velocities: VecDeque<AngularVelocity>,
external_impulses: VecDeque<ExternalImpulse>,
positions: VecDeque<Position>,
rotations: VecDeque<Rotation>,
}
impl Tape {
fn new_with_capacity(capacity: usize) -> Self {
Tape {
capacity,
linear_velocities: VecDeque::with_capacity(capacity),
angular_velocities: VecDeque::with_capacity(capacity),
external_impulses: VecDeque::with_capacity(capacity),
positions: VecDeque::with_capacity(capacity),
rotations: VecDeque::with_capacity(capacity),
}
}
fn push(
&mut self,
lv: LinearVelocity,
av: AngularVelocity,
ei: ExternalImpulse,
p: Position,
r: Rotation,
) {
// If we are at capacity, make room
if self.linear_velocities.len() == self.capacity {
self.linear_velocities.pop_front().unwrap();
self.angular_velocities.pop_front().unwrap();
self.external_impulses.pop_front().unwrap();
self.positions.pop_front().unwrap();
self.rotations.pop_front().unwrap();
}
self.linear_velocities.push_back(lv);
self.angular_velocities.push_back(av);
self.external_impulses.push_back(ei);
self.positions.push_back(p);
self.rotations.push_back(r);
}
fn pop(
&mut self,
) -> Option<(
LinearVelocity,
AngularVelocity,
ExternalImpulse,
Position,
Rotation,
)> {
if self.linear_velocities.is_empty() {
None
} else {
let lv = self.linear_velocities.pop_back().unwrap();
let av = self.angular_velocities.pop_back().unwrap();
let ei = self.external_impulses.pop_back().unwrap();
let p = self.positions.pop_back().unwrap();
let r = self.rotations.pop_back().unwrap();
Some((lv, av, ei, p, r))
}
}
}
fn init_bird(mut commands: Commands, bird_assets: Res<BirdAssets>) {
@ -153,7 +211,9 @@ fn init_bird(mut commands: Commands, bird_assets: Res<BirdAssets>) {
MaxLinearSpeed(500.0),
);
let tape = Tape::default();
// 60fps * 5 seconds
const REWIND_SECONDS: usize = 5;
let tape = Tape::new_with_capacity(60 * REWIND_SECONDS);
commands.spawn((
name,
@ -240,7 +300,7 @@ fn update_batch_position(
mut root_changes: Query<(&mut Transform, &Batch), (Changed<Batch>, Without<ChildOf>)>,
) {
root_changes.iter_mut().for_each(|(mut t, Batch(idx))| {
info!("Updating batch {:?}", idx);
debug!("Updating batch {:?}", idx);
t.translation.x = 500.0 * (*idx) as f32;
});
}
@ -281,18 +341,49 @@ fn populate_ceiling(
));
}
fn move_pipe(
trigger: Trigger<OnInsert, Batch>,
mut pipes: Query<(&Batch, &Pipe, &mut Transform)>,
rand: Res<Rand>,
) {
if let Ok((Batch(id), pipe, mut pipe_t)) = pipes.get_mut(trigger.target()) {
*pipe_t = {
let offset = {
let val = rand.0.hash_one(id);
let option = val % 3;
match option {
0 => 100.0,
1 => 0.0,
2 => -100.0,
_ => panic!("Can only pick 1 of 3 pipe offsets"),
}
};
match pipe {
Pipe::Top => {
Transform::from_xyz(0.0, 300.0 + offset, -2.0).with_scale(Vec3::splat(100.0))
}
Pipe::Bottom => {
Transform::from_xyz(0.0, -300.0 + offset, -2.0).with_scale(Vec3::splat(100.0))
}
}
};
}
}
/// Based on if this is a Top or Bottom pipe the placement changes
/// Otherwise this just spawns in the center of the batch.
fn populate_pipe(
trigger: Trigger<OnAdd, Pipe>,
pipes: Query<(&Pipe, &Batch)>,
pipes: Query<(&Batch, &Pipe)>,
pipe_assets: Res<PipeAssets>,
mut commands: Commands,
rand: Res<Rand>,
) {
let pipe_t = {
let (pipe, Batch(id)) = pipes.get(trigger.target()).unwrap();
let (Batch(id), pipe) = pipes.get(trigger.target()).unwrap();
let offset = {
let val = rand.0.hash_one(id);
@ -315,6 +406,7 @@ fn populate_pipe(
}
}
};
commands.entity(trigger.target()).insert((
pipe_t,
pipe_assets.material.clone(),
@ -381,14 +473,14 @@ fn init_assets(
ground_assets.material = MeshMaterial2d(materials.add(ColorMaterial {
texture: Some(server.load("flappy/ground.png")),
color: BLACK.into(),
color: Srgba::new(0.1, 0.1, 0.1, 1.0).into(),
..default()
}));
ground_assets.mesh = Mesh2d(meshes.add(Rectangle::new(1.0, 1.0)));
ceiling_assets.material = MeshMaterial2d(materials.add(ColorMaterial {
texture: Some(server.load("flappy/ceiling.png")),
color: BLACK.into(),
color: Srgba::new(0.1, 0.1, 0.1, 1.0).into(),
..default()
}));
ceiling_assets.mesh = Mesh2d(meshes.add(Rectangle::new(1.0, 0.777)));
@ -417,7 +509,7 @@ struct RewindSfx;
#[derive(Component)]
struct RewindButton;
fn init_ui(mut commands: Commands) {
fn init_ui(mut commands: Commands, server: Res<AssetServer>) {
commands
.spawn((
Node {
@ -524,10 +616,6 @@ fn init_ui(mut commands: Commands) {
})
.observe(hide_credits);
fn start_game(_trigger: Trigger<Pointer<Click>>, mut next: ResMut<NextState<PlayerState>>) {
next.set(PlayerState::Alive);
}
commands.spawn((
Node {
align_self: AlignSelf::Center,
@ -550,6 +638,14 @@ fn init_ui(mut commands: Commands) {
..default()
})
.with_children(|parent| {
let rewind_image = server.load_with_settings(
"flappy/rewind.png",
|settings: &mut ImageLoaderSettings| {
// Need to use nearest filtering to avoid bleeding between the slices with tiling
settings.sampler = ImageSampler::nearest();
},
);
parent
.spawn((
Node {
@ -562,13 +658,34 @@ fn init_ui(mut commands: Commands) {
BackgroundColor::default(),
RewindButton,
children![
(
ImageNode {
color: BLACK.into(),
image: rewind_image,
..default()
},
Node {
height: Val::Px(50.0),
..default()
},
),
(
Text::new("Rewind! (R)"),
TextFont::from_font_size(30.0),
TextLayout::new_with_justify(JustifyText::Center)
),
],
))
.observe(start_rewind)
.observe(end_rewind);
let play_image = server.load_with_settings(
"flappy/play.png",
|settings: &mut ImageLoaderSettings| {
// Need to use nearest filtering to avoid bleeding between the slices with tiling
settings.sampler = ImageSampler::nearest();
},
);
parent
.spawn((
Node {
@ -580,9 +697,22 @@ fn init_ui(mut commands: Commands) {
Button,
FlapButton,
children![
(
Text::new("Flap! (Spacebar)"),
TextFont::from_font_size(30.0),
TextLayout::new_with_justify(JustifyText::Center)
),
(
ImageNode {
color: BLACK.into(),
image: play_image,
..default()
},
Node {
height: Val::Px(50.0),
..default()
},
),
],
))
.observe(flap_button);
@ -599,10 +729,53 @@ fn init_ui(mut commands: Commands) {
SyncResource::<Score>::default(),
Text::default(),
TextLayout::new_with_justify(JustifyText::Center),
)]
)],
));
}
fn init_background(
mut commands: Commands,
server: Res<AssetServer>,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<ColorMaterial>>,
) {
{
let material = MeshMaterial2d(materials.add(ColorMaterial {
texture: Some(server.load("flappy/background-city.png")),
color: Srgba::new(0.3, 0.3, 0.3, 1.0).into(),
alpha_mode: AlphaMode2d::Blend,
..default()
}));
let mesh = Mesh2d(meshes.add(Rectangle::new(1.0, 1.0)));
{
let t = Transform::from_xyz(-325.0, 0.0, -32.0).with_scale(Vec3::splat(650.0));
commands.spawn((ParallaxDepth(4.0), mesh.clone(), material.clone(), t));
}
{
let t = Transform::from_xyz(325.0, 0.0, -32.0).with_scale(Vec3::splat(650.0));
commands.spawn((ParallaxDepth(4.0), mesh.clone(), material.clone(), t));
}
}
{
let material = MeshMaterial2d(materials.add(ColorMaterial {
texture: Some(server.load("flappy/background-clouds.png")),
color: Srgba::new(0.2, 0.2, 0.2, 1.0).into(),
alpha_mode: AlphaMode2d::Blend,
..default()
}));
let mesh = Mesh2d(meshes.add(Rectangle::new(1.0, 1.0)));
{
let t = Transform::from_xyz(-325.0, 0.0, -64.0).with_scale(Vec3::splat(650.0));
commands.spawn((ParallaxDepth(8.0), mesh.clone(), material.clone(), t));
}
{
let t = Transform::from_xyz(325.0, 0.0, -64.0).with_scale(Vec3::splat(650.0));
commands.spawn((ParallaxDepth(8.0), mesh.clone(), material.clone(), t));
}
}
}
fn start_rewind(_trigger: Trigger<Pointer<Pressed>>, mut next: ResMut<NextState<PlayerState>>) {
next.set(PlayerState::Rewind);
}
@ -709,13 +882,9 @@ fn record(
#[cfg(debug_assertions)] state: Res<State<PlayerState>>,
mut birds: Query<
(
&AccumulatedTranslation,
&LinearVelocity,
&AngularVelocity,
&ExternalAngularImpulse,
&ExternalForce,
&ExternalImpulse,
&ExternalTorque,
&Position,
&Rotation,
&mut Tape,
@ -729,18 +898,8 @@ fn record(
"Only record in the alive state"
);
birds
.iter_mut()
.for_each(|(at, lv, av, eai, ef, ei, et, p, r, mut tape)| {
tape.accumulated_translations.push(*at);
tape.linear_velocities.push(*lv);
tape.angular_velocities.push(*av);
tape.external_angular_impulses.push(*eai);
tape.external_forces.push(*ef);
tape.external_impulses.push(*ei);
tape.external_torques.push(*et);
tape.positions.push(*p);
tape.rotations.push(*r);
birds.iter_mut().for_each(|(lv, av, ei, p, r, mut tape)| {
tape.push(*lv, *av, *ei, *p, *r);
});
}
@ -749,13 +908,9 @@ fn rewind(
mut next: ResMut<NextState<PlayerState>>,
mut birds: Query<
(
&mut AccumulatedTranslation,
&mut LinearVelocity,
&mut AngularVelocity,
&mut ExternalAngularImpulse,
&mut ExternalForce,
&mut ExternalImpulse,
&mut ExternalTorque,
&mut Position,
&mut Rotation,
&mut Tape,
@ -770,31 +925,20 @@ fn rewind(
"Only rewind in the rewinding state"
);
birds.iter_mut().for_each(
|(mut at, mut lv, mut av, mut eai, mut ef, mut ei, mut et, mut p, mut r, mut tape)| {
if tape.positions.is_empty() {
next.set(PlayerState::Pause);
} else {
// TODO: Only record/restore variables that we manage!
at.0 = tape.accumulated_translations.pop().unwrap().0;
lv.0 = tape.linear_velocities.pop().unwrap().0;
av.0 = tape.angular_velocities.pop().unwrap().0;
eai.set_impulse(tape.external_angular_impulses.pop().unwrap().impulse());
ef.set_force(tape.external_forces.pop().unwrap().force());
ei.set_impulse(tape.external_impulses.pop().unwrap().impulse());
et.set_torque(tape.external_torques.pop().unwrap().torque());
p.0 = tape.positions.pop().unwrap().0;
*r = {
let curr = tape.rotations.pop().unwrap();
Rotation {
cos: curr.cos,
sin: curr.sin,
}
};
birds
.iter_mut()
.for_each(|(mut lv, mut av, mut ei, mut p, mut r, mut tape)| {
if let Some((new_lv, new_av, new_ei, new_p, new_r)) = tape.pop() {
lv.0 = new_lv.0;
av.0 = new_av.0;
ei.set_impulse(new_ei.impulse());
p.0 = new_p.0;
*r = new_r;
frames.0 += 1;
} else {
next.set(PlayerState::Pause);
}
},
);
});
}
// PERF: May run more than necessary, should be event-driven on aabb intersection
@ -939,12 +1083,16 @@ fn manage_score(
/// This includes root batch entities as well as pipes and hitboxes
fn move_batches(
mut start: EventReader<CollisionStarted>,
mut end: EventReader<CollisionEnded>,
hitboxes: Query<Entity, With<Hitbox>>,
batches: Query<(Entity, &Batch)>,
state: Res<State<PlayerState>>,
mut commands: Commands,
) {
start.read().for_each(|CollisionStarted(a, b)| {
let s = start.read().map(|CollisionStarted(a, b)| (a, b));
let e = end.read().map(|CollisionEnded(a, b)| (a, b));
let c = s.chain(e);
c.for_each(|(a, b)| {
debug!("[batches] Collision {a} -> {b}");
let target = {
@ -959,7 +1107,7 @@ fn move_batches(
let (_, Batch(curr)) = batches.get(target).unwrap();
info!("[batches] Current: {curr}");
debug!("[batches] Current: {curr}");
let (old_batch, new_batch) = match state.get() {
PlayerState::Alive => (curr.saturating_sub(2), curr.saturating_add(2)),
PlayerState::Rewind => (curr.saturating_add(2), curr.saturating_sub(2)),
@ -972,7 +1120,7 @@ fn move_batches(
// Filter to just entities with this batch ID
.filter_map(|(e, Batch(id))| (*id == old_batch).then_some(e))
.for_each(|old| {
info!("Moving batch {old_batch}({old}) -> {new_batch}");
debug!("Moving batch {old_batch}({old}) -> {new_batch}");
commands.entity(old).insert(Batch(new_batch));
})
}

@ -4,12 +4,12 @@
mod base_game;
mod debug;
mod loading;
mod parallax;
pub mod physics2d;
pub mod physics3d;
mod scheduling;
mod ui;
mod version;
mod parallax;
// Rust stdlib
pub use std::collections::VecDeque;
@ -42,7 +42,7 @@ pub use thiserror::Error;
pub use base_game::*;
pub use debug::*;
pub use loading::*;
pub use parallax::*;
pub use scheduling::*;
pub use ui::*;
pub use version::*;
pub use parallax::*;

@ -1,27 +1,122 @@
use bevy::render::primitives::Aabb;
use bevy::render::mesh::MeshAabb;
use super::*;
pub struct ParallaxPlugin;
impl Plugin for ParallaxPlugin {
fn build(&self, app: &mut App) {
app.add_systems(Update, move_parallax_items.run_if(any_component_changed::<Transform>));
app.add_systems(
Update,
(
move_parallax_items.run_if(any_component_changed::<Transform>),
wrap_parallax_items.run_if(any_component_changed::<ViewVisibility>),
)
);
}
}
///
/// ParallaxDepth describes how far from the camera something is
///
/// A parallax depth of 1 means it moves 1:1 with camera movement;
/// If the camera moves 1 pixel to the left, the background moves 1px to the right
///
/// A parallax depth of 2 means the movement is 1:2
/// Camera moves 2px, the element moves 1px
///
#[derive(Component)]
#[require(Transform)]
pub struct Parallax(pub f32);
pub struct ParallaxDepth(pub f32);
fn move_parallax_items(
mut q: Query<(&mut Transform, &Parallax), Without<Camera2d>>,
cam_t: Single<&Transform, With<Camera2d>>,
mut q: Query<(&mut Transform, &ParallaxDepth), Without<Camera2d>>,
camera: Single<&Transform, (With<Camera2d>, Changed<Transform>)>,
mut prev_camera_pos: Local<Vec2>,
) {
let base = cam_t.translation.truncate();
// Unpack the camera data
let cam_t = *camera;
// Calculate how far the camera moved since the last update
let delta = cam_t.translation.truncate() - *prev_camera_pos;
debug!("Cam Delta: {:?}", delta);
// For each object
q.iter_mut().for_each(|(mut t, pd)| {
// Update ParallaxPosition
let depth_movement = delta / pd.0;
q.iter_mut().for_each(|(mut t, p)| {
let val = base * (1.0 - (1.0 / p.0));
t.translation.x = val.x;
t.translation.y = val.y;
debug!("Depth: {:?} | Move: {:?}", pd.0, depth_movement);
// Update actual position
t.translation.x -= depth_movement.x;
t.translation.y -= depth_movement.y;
});
*prev_camera_pos = cam_t.translation.truncate();
}
fn wrap_parallax_items(
mut items: Query<(&mut Transform, &GlobalTransform, &ViewVisibility, &Mesh2d), (With<ParallaxDepth>, Without<Camera2d>, Changed<ViewVisibility>)>,
camera: Single<(&Camera, &GlobalTransform), With<Camera2d>>,
window: Single<&Window>,
meshes: Res<Assets<Mesh>>,
) {
if !items.is_empty() {
let (cam, cam_gt) = *camera;
// get the window size in world space
let window_size_in_world_space = {
let top_left = cam.viewport_to_world_2d(cam_gt, Vec2::ZERO).unwrap();
let bottom_right = cam.viewport_to_world_2d(cam_gt, window.size()).unwrap();
Vec2::abs(Vec2::new(bottom_right.x - top_left.x, bottom_right.y - top_left.y))
};
// for each item in the paralax items
items.iter_mut().for_each(|(mut t, gt, v, Mesh2d(m))| {
if !v.get() {
debug!("Item is not visible");
// Get the total size (window + scale)
let half_extents = {
let Aabb { half_extents, .. } = meshes.get(m).unwrap().compute_aabb().unwrap();
half_extents.truncate()
};
let object_size = t.scale.truncate() * 2.0;
let total_size = window_size_in_world_space + (object_size * half_extents);
debug!("Sizes:\n\twindow {window_size_in_world_space}\n\tobject size: {object_size}\n\tAabb Half extents: {half_extents}\n\tTotal size: {total_size}");
// Double check that item is out of bounds
let bounds_check = Vec2::abs(cam_gt.translation().truncate() - gt.translation().truncate());
let out_of_bounds = (total_size / 2.0) - bounds_check;
debug!("Bounds check {bounds_check} | Out of bounds: {out_of_bounds:?}");
debug!("Starting position: {:?}", t.translation);
if out_of_bounds.x < 0.0 {
debug!("Object is actually out of bounds horizontally");
// determine if should move to the left or right relative to camera
let move_right = cam_gt.translation().x > gt.translation().x;
// move left or right
if move_right {
t.translation.x += total_size.x;
} else {
t.translation.x -= total_size.x;
}
}
if out_of_bounds.y < 0.0 {
debug!("Object is actually out of bounds vertically");
let move_up = cam_gt.translation().y > gt.translation().y;
// move up or down
if move_up {
t.translation.y += total_size.y;
} else {
t.translation.y -= total_size.y;
}
}
debug!("Moved to {:?}", t.translation);
}
});
}
}

@ -133,6 +133,7 @@ fn add_ui_node(
mut added: Query<(Entity, &mut Node), Added<Node>>,
style: Res<Style>,
text: Query<Entity, With<Text>>,
images: Query<Entity, With<ImageNode>>,
mut commands: Commands,
) {
added.iter_mut().for_each(|(e, mut n)| {
@ -146,6 +147,8 @@ fn add_ui_node(
if text.contains(e) {
// Only change text color for text
this.insert(TextColor(style.secondary));
} else if images.contains(e) {
// Do nothing
} else {
// Add extra stuff for non-text nodes
this.insert(BackgroundColor(style.primary));

Loading…
Cancel
Save