Compare commits

..

6 Commits

Author SHA1 Message Date
Elijah Voigt 50c802fdab Refactor: change how shapes are represented. 1 month ago
Elijah Voigt 882aac6e6e clippy 1 month ago
Elijah Voigt acbf89a077 Everything compiles 1 month ago
Elijah Voigt c3cd7f87b6 Further migration to 0.17. Saving my place again 1 month ago
Elijah Voigt 602d6a923e Tetris works on 0.17 1 month ago
Elijah Voigt 1e2d37ed60 bevy 0.16 -> 0.17 saving my place 1 month ago

1288
Cargo.lock generated

File diff suppressed because it is too large Load Diff

@ -23,13 +23,13 @@ version = "1.0.219"
features = ["derive"] features = ["derive"]
[dependencies.avian3d] [dependencies.avian3d]
version = "0.3.1" version = "0.4.1"
[dependencies.avian2d] [dependencies.avian2d]
version = "0.3.1" version = "0.4.1"
[dependencies.bevy] [dependencies.bevy]
version = "0.16.1" version = "0.17.2"
features = ["wayland", "dynamic_linking"] features = ["wayland", "dynamic_linking"]
[dev-dependencies] [dev-dependencies]

@ -2,9 +2,9 @@ use bevy::{color::palettes::css::*, prelude::*};
fn main() { fn main() {
App::new() App::new()
.add_plugins(DefaultPlugins) .add_plugins(DefaultPlugins {})
.insert_resource(ClearColor(WHITE.into())) .insert_resource(ClearColor(WHITE.into()))
.add_event::<CameraMovement>() .add_message::<CameraMovement>()
.add_systems(Startup, setup_2d) .add_systems(Startup, setup_2d)
.add_systems(Update, (move_camera, track_camera_movement, parallax)) .add_systems(Update, (move_camera, track_camera_movement, parallax))
.run(); .run();
@ -78,11 +78,11 @@ fn move_camera(keys: Res<ButtonInput<KeyCode>>, mut camera: Single<&mut Transfor
} }
} }
#[derive(Event)] #[derive(Message)]
struct CameraMovement(Vec3); struct CameraMovement(Vec3);
fn track_camera_movement( fn track_camera_movement(
mut events: EventWriter<CameraMovement>, mut events: MessageWriter<CameraMovement>,
camera: Query<&Transform, (With<Camera>, Changed<Transform>)>, camera: Query<&Transform, (With<Camera>, Changed<Transform>)>,
mut last: Local<Vec3>, mut last: Local<Vec3>,
) { ) {
@ -93,7 +93,7 @@ fn track_camera_movement(
} }
fn parallax( fn parallax(
mut events: EventReader<CameraMovement>, mut events: MessageReader<CameraMovement>,
mut query: Query<(&mut Transform, &ParallaxDistance)>, mut query: Query<(&mut Transform, &ParallaxDistance)>,
) { ) {
events.read().for_each(|CameraMovement(v)| { events.read().for_each(|CameraMovement(v)| {

@ -4,7 +4,7 @@ fn main() {
App::new() App::new()
.add_plugins(DefaultPlugins.set(WindowPlugin { .add_plugins(DefaultPlugins.set(WindowPlugin {
primary_window: Some(Window { primary_window: Some(Window {
resolution: (640.0, 480.0).into(), resolution: (640, 480).into(),
..default() ..default()
}), }),
..default() ..default()

@ -3,6 +3,7 @@ use games::*;
fn main() { fn main() {
App::new() App::new()
.add_plugins((BaseGamePlugin { .add_plugins((BaseGamePlugin {
target_resolution: (640, 480).into(),
name: "parallax example".into(), name: "parallax example".into(),
title: "Parallax".into(), title: "Parallax".into(),
game_type: GameType::Two, game_type: GameType::Two,

@ -1,5 +1,3 @@
use avian3d::math::Vector;
// TEMP
use avian2d::prelude::*; use avian2d::prelude::*;
use bevy::{color::palettes::css::*, prelude::*}; use bevy::{color::palettes::css::*, prelude::*};
@ -14,7 +12,7 @@ fn main() {
.add_systems(Update, draw_gizmos) .add_systems(Update, draw_gizmos)
.add_systems( .add_systems(
Update, Update,
event_detection.run_if(on_event::<CollisionStarted>.or(on_event::<CollisionEnded>)), event_detection.run_if(on_message::<CollisionStart>.or(on_message::<CollisionEnd>)),
) )
.add_observer(set_tree_position) .add_observer(set_tree_position)
.run(); .run();
@ -63,7 +61,7 @@ fn setup(
commands.spawn(( commands.spawn((
Player, Player,
RigidBody::Kinematic, RigidBody::Kinematic,
ExternalImpulse::default().with_persistence(true), ConstantForce::default(),
Collider::rectangle(50.0, 50.0), Collider::rectangle(50.0, 50.0),
Mesh2d(meshes.add(Rectangle::new(50.0, 50.0))), Mesh2d(meshes.add(Rectangle::new(50.0, 50.0))),
MeshMaterial2d(materials.add(Color::from(RED))), MeshMaterial2d(materials.add(Color::from(RED))),
@ -87,30 +85,26 @@ fn setup(
)); ));
} }
fn set_tree_position( fn set_tree_position(event: On<Add, TreePos>, tree_pos: Query<&TreePos>, mut commands: Commands) {
trigger: Trigger<OnAdd, TreePos>, let TreePos(pos) = tree_pos.get(event.entity).unwrap();
tree_pos: Query<&TreePos>,
mut commands: Commands,
) {
let TreePos(pos) = tree_pos.get(trigger.target()).unwrap();
let x = if (*pos) > 0 { let x = if (*pos) > 0 {
(200.0 * (*pos) as f32) - 100.0 (200.0 * (*pos) as f32) - 100.0
} else { } else {
(200.0 * (*pos) as f32) + 100.0 (200.0 * (*pos) as f32) + 100.0
}; };
let t = Transform::from_xyz(x, 0.0, 0.0); let t = Transform::from_xyz(x, 0.0, 0.0);
commands.entity(trigger.target()).insert(t); commands.entity(event.entity).insert(t);
} }
fn move_player( fn move_player(
keyboard_input: Res<ButtonInput<KeyCode>>, keyboard_input: Res<ButtonInput<KeyCode>>,
mut player: Single<&mut ExternalImpulse, With<Player>>, mut player: Single<Forces, With<Player>>,
) { ) {
const SPEED: f32 = 5.0; const SPEED: f32 = 5.0;
if keyboard_input.pressed(KeyCode::ArrowLeft) { if keyboard_input.pressed(KeyCode::ArrowLeft) {
player.set_impulse(Vec2::NEG_X * SPEED); player.apply_local_force(Vec2::NEG_X * SPEED);
} else if keyboard_input.pressed(KeyCode::ArrowRight) { } else if keyboard_input.pressed(KeyCode::ArrowRight) {
player.set_impulse(Vec2::X * SPEED); player.apply_local_force(Vec2::X * SPEED);
} }
} }
@ -132,15 +126,26 @@ fn draw_gizmos(mut gizmos: Gizmos, play_area: Single<&GlobalTransform, With<Rend
} }
fn event_detection( fn event_detection(
mut start_events: EventReader<CollisionStarted>, mut start_events: MessageReader<CollisionStart>,
mut end_events: EventReader<CollisionEnded>, mut end_events: MessageReader<CollisionEnd>,
player: Single<&Player>,
trees: Query<&TreePos>, trees: Query<&TreePos>,
keyboard_input: Res<ButtonInput<KeyCode>>, keyboard_input: Res<ButtonInput<KeyCode>>,
) { ) {
debug_assert!(!(start_events.is_empty() && end_events.is_empty())); debug_assert!(!(start_events.is_empty() && end_events.is_empty()));
let s = start_events.read().map(|CollisionStarted(a, b)| (a, b)); let s = start_events.read().map(
let e = end_events.read().map(|CollisionEnded(a, b)| (a, b)); |CollisionStart {
collider1,
collider2,
..
}| (collider1, collider2),
);
let e = end_events.read().map(
|CollisionEnd {
collider1,
collider2,
..
}| (collider1, collider2),
);
let mut events = s.chain(e); let mut events = s.chain(e);
let current_tree = events.find_map(|(a, b)| { let current_tree = events.find_map(|(a, b)| {
info!("{a:?}, {b:?}"); info!("{a:?}, {b:?}");

@ -1,222 +0,0 @@
use bevy::{
color::palettes::css::{BLUE, GREEN},
render::mesh::{SphereKind, SphereMeshBuilder},
};
use bevy_rapier3d::rapier::prelude::CollisionEventFlags;
use games::*;
/// Example showing using Rapier3d to do area intersection
///
/// Have you ever wanted to detect if a character is within some bounds?
/// This shows how to do that.
fn main() {
App::new()
.add_plugins(BaseGamePlugin::default())
.init_resource::<InsideArea>()
.add_systems(
Startup,
(
setup_physics_scene,
setup_ui,
position_camera.after(create_camera_3d),
),
)
.add_systems(
Update,
(
control_ball,
process_events,
sync_resource_to_ui::<InsideArea>.run_if(resource_changed::<InsideArea>),
)
.run_if(in_state(LoadingState::Idle)),
)
.run();
}
/// Which area the sphere is in/touching
#[derive(Component, Debug, Clone)]
enum AreaMarker {
Green,
Blue,
}
/// Setup a basic scene with:
/// * Floor
/// * A light for dramatic shadows
/// * A sphere that can move
/// * Two cubes that designate area intersection
fn setup_physics_scene(
mut commands: Commands,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<StandardMaterial>>,
) {
// Create the ground.
// Make this a physical looking object
commands.spawn((
Collider::cuboid(100.0, 0.1, 100.0),
Transform::from_xyz(0.0, -1.0, 0.0),
Pickable::IGNORE,
Mesh3d(meshes.add(Plane3d::new(Vec3::Y, Vec2::splat(50.0)))),
MeshMaterial3d(materials.add(StandardMaterial {
base_color: RED.into(),
..Default::default()
})),
));
commands.spawn((
SpotLight {
color: WHITE.into(),
intensity: 10_000_000.0,
shadows_enabled: true,
..default()
},
Transform::from_xyz(0.0, 10.0, 10.0).looking_at(Vec3::ZERO, Vec3::Y),
));
// Create the bouncing ball.
commands.spawn((
RigidBody::Dynamic,
GravityScale(1.0),
Collider::ball(0.5),
ColliderMassProperties::Mass(2.0),
Restitution::coefficient(0.7),
ExternalImpulse::default(),
Transform::from_xyz(0.0, 4.0, 0.0),
ActiveEvents::all(),
Name::new("Ball"),
Mesh3d(meshes.add(SphereMeshBuilder::new(
0.5,
SphereKind::Uv {
sectors: 10,
stacks: 10,
},
))),
MeshMaterial3d(materials.add(StandardMaterial {
base_color: WHITE.into(),
..Default::default()
})),
));
// Create a box that the ball can pass into/through
commands.spawn((
Sensor,
Collider::cuboid(1.0, 1.0, 1.0),
Transform::from_xyz(0.0, 0.0, 2.0),
Mesh3d(meshes.add(Cuboid::new(2.0, 2.0, 2.0))),
Name::new("Green Area"),
MeshMaterial3d(materials.add(StandardMaterial {
base_color: GREEN.with_alpha(0.5).into(),
alpha_mode: AlphaMode::Blend,
..Default::default()
})),
AreaMarker::Green,
));
// Create a box that the ball can pass into/through
commands.spawn((
Sensor,
Collider::cuboid(1.0, 1.0, 1.0),
Transform::from_xyz(0.0, 0.0, -2.0),
Mesh3d(meshes.add(Cuboid::new(2.0, 2.0, 2.0))),
Name::new("Blue Area"),
MeshMaterial3d(materials.add(StandardMaterial {
base_color: BLUE.with_alpha(0.5).into(),
alpha_mode: AlphaMode::Blend,
..Default::default()
})),
AreaMarker::Blue,
));
}
/// Setup the UI for this example that indicates if the game "sees" the sphere
/// is inside the cube
fn setup_ui(mut commands: Commands) {
commands.spawn((
Node {
align_self: AlignSelf::Start,
justify_self: JustifySelf::Center,
..default()
},
Text("Placeholder".into()),
SyncResource::<InsideArea>::default(),
));
}
/// Position the world camera to get a better view
fn position_camera(mut query: Query<&mut Transform, (With<Camera>, With<Camera3d>)>) {
query.iter_mut().for_each(|mut t| {
*t = Transform::from_xyz(10.0, 10.0, 10.0).looking_at(Vec3::ZERO, Vec3::Y);
})
}
/// Control the ball based on arrow keys
fn control_ball(keys: Res<ButtonInput<KeyCode>>, mut ball: Single<&mut ExternalImpulse>) {
if keys.pressed(KeyCode::ArrowUp) {
ball.torque_impulse = Vec3::new(0.0, 0.0, 0.1);
}
if keys.pressed(KeyCode::ArrowDown) {
ball.torque_impulse = Vec3::new(0.0, 0.0, -0.1);
}
if keys.pressed(KeyCode::ArrowLeft) {
ball.torque_impulse = Vec3::new(0.1, 0.0, 0.0);
}
if keys.pressed(KeyCode::ArrowRight) {
ball.torque_impulse = Vec3::new(-0.1, 0.0, 0.0);
}
}
/// Maps CollisionEvent to a game-specific construct (did X enter/exit an area)
#[derive(Debug)]
enum WithinBounds {
Enter(Entity, Entity),
Exit(Entity, Entity),
}
/// Resource tracking what box the sphere is in
#[derive(Resource, Debug, Default, Clone)]
struct InsideArea(Option<AreaMarker>);
impl Display for InsideArea {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self.0 {
Some(AreaMarker::Blue) => write!(f, "Ball IS touching the BLUE box"),
Some(AreaMarker::Green) => write!(f, "Ball IS touching the GREEN box"),
None => write!(f, "Ball is NOT touching ANY box"),
}
}
}
/// Read collision events and procss them into resource updates to be displayed to the user
fn process_events(
mut events: EventReader<CollisionEvent>,
areas: Query<&AreaMarker>,
mut inside: ResMut<InsideArea>,
) {
events
.read()
.filter_map(|collision_event| match collision_event {
CollisionEvent::Started(a, b, flags) => {
(*flags == CollisionEventFlags::SENSOR).then_some(WithinBounds::Enter(*a, *b))
}
CollisionEvent::Stopped(a, b, flags) => {
(*flags == CollisionEventFlags::SENSOR).then_some(WithinBounds::Exit(*a, *b))
}
})
.for_each(|within_bounds| match within_bounds {
WithinBounds::Enter(a, b) => {
if let Ok(x) = areas.get(a) {
inside.0 = Some(x.clone());
} else if let Ok(x) = areas.get(b) {
inside.0 = Some(x.clone());
}
}
WithinBounds::Exit(a, b) => {
if areas.contains(a) || areas.contains(b) {
inside.0 = None;
}
}
})
}

@ -9,7 +9,7 @@ fn main() {
Update, Update,
( (
sync_resource_to_ui::<Thing>.run_if(resource_changed::<Thing>), sync_resource_to_ui::<Thing>.run_if(resource_changed::<Thing>),
update_foo.run_if(on_event::<MouseMotion>), update_foo.run_if(on_message::<MouseMotion>),
), ),
) )
.run(); .run();
@ -44,7 +44,7 @@ fn init_ui(mut commands: Commands) {
)); ));
} }
fn update_foo(mut events: EventReader<MouseMotion>, mut thing: ResMut<Thing>) { fn update_foo(mut events: MessageReader<MouseMotion>, mut thing: ResMut<Thing>) {
events.read().for_each(|_| { events.read().for_each(|_| {
thing.0 = thing.0.overflowing_add(1).0; thing.0 = thing.0.overflowing_add(1).0;
}); });

@ -15,7 +15,7 @@ fn main() {
Update, Update,
( (
hide_menu, hide_menu,
control_menu.run_if(on_event::<Pointer<Over>>.or(on_event::<Pointer<Out>>)), control_menu.run_if(on_message::<Pointer<Over>>.or(on_message::<Pointer<Out>>)),
), ),
); );
app.run(); app.run();
@ -85,10 +85,12 @@ fn setup_nav_tree(mut commands: Commands) {
)) ))
.with_children(|parent| { .with_children(|parent| {
(0..5).for_each(|_| { (0..5).for_each(|_| {
let title: String = lipsum_title_with_rng(thread_rng()) let title: String = Iterator::intersperse(
lipsum_title_with_rng(thread_rng())
.split_whitespace() .split_whitespace()
.take(2) .take(2),
.intersperse(" ") " ",
)
.collect(); .collect();
parent.spawn(( parent.spawn((
children![Text::new(title),], children![Text::new(title),],
@ -129,15 +131,15 @@ fn hide_menu(mut nodes: Query<(&mut Visibility, &NavState), Changed<NavState>>)
// When you pointer goes off of the '+' or any of it's children make the entire menu invisible // When you pointer goes off of the '+' or any of it's children make the entire menu invisible
fn control_menu( fn control_menu(
mut over_events: EventReader<Pointer<Over>>, mut over_events: MessageReader<Pointer<Over>>,
mut out_events: EventReader<Pointer<Out>>, mut out_events: MessageReader<Pointer<Out>>,
children: Query<&ChildOf>, children: Query<&ChildOf>,
parents: Query<&Children>, parents: Query<&Children>,
mut nav: Query<&mut NavState>, mut nav: Query<&mut NavState>,
hover_map: Res<HoverMap>, hover_map: Res<HoverMap>,
) { ) {
over_events.read().for_each(|over| { over_events.read().for_each(|over| {
let root = children.root_ancestor(over.target); let root = children.root_ancestor(over.entity);
parents.iter_descendants(root).for_each(|child| { parents.iter_descendants(root).for_each(|child| {
if let Ok(mut n) = nav.get_mut(child) { if let Ok(mut n) = nav.get_mut(child) {
@ -157,9 +159,9 @@ fn control_menu(
// For all pointer out events // For all pointer out events
out_events.read().for_each(|out| { out_events.read().for_each(|out| {
// If a relative of out.target is hovered, do nothing // If a relative of out.entity is hovered, do nothing
// Otherwise set to closed // Otherwise set to closed
let root = children.root_ancestor(out.target); let root = children.root_ancestor(out.entity);
let tree_still_hovered = parents let tree_still_hovered = parents
.iter_descendants(root) .iter_descendants(root)
.any(|child| is_hovered.contains(&&child)); .any(|child| is_hovered.contains(&&child));

@ -3,7 +3,7 @@ use games::*;
fn main() { fn main() {
App::new() App::new()
.add_plugins(BaseGamePlugin { .add_plugins(BaseGamePlugin {
target_resolution: (360.0, 640.0).into(), target_resolution: (360, 640).into(),
..default() ..default()
}) })
.add_systems(Startup, init_window_info) .add_systems(Startup, init_window_info)

@ -15,8 +15,7 @@ fn main() {
title: "flappy bird (with rewind)".into(), title: "flappy bird (with rewind)".into(),
name: "flappy".into(), name: "flappy".into(),
game_type: GameType::Two, game_type: GameType::Two,
target_resolution: (360.0, 640.0).into(), target_resolution: (360, 640).into(),
..default()
}, },
Physics2dPlugin, Physics2dPlugin,
)) ))
@ -99,8 +98,8 @@ fn main() {
(update_tooltip, debug_trail).run_if(in_state(DebuggingState::On)), (update_tooltip, debug_trail).run_if(in_state(DebuggingState::On)),
// TODO: Add run_if to this system // TODO: Add run_if to this system
update_batch_position.run_if(any_component_changed::<Batch>), update_batch_position.run_if(any_component_changed::<Batch>),
move_batches.run_if(on_event::<CollisionStarted>.or(on_event::<CollisionEnded>)), move_batches.run_if(on_message::<CollisionStart>.or(on_message::<CollisionEnd>)),
manage_score.run_if(on_event::<CollisionStarted>.or(on_event::<CollisionEnded>)), manage_score.run_if(on_message::<CollisionStart>.or(on_message::<CollisionEnd>)),
shimmer_button::<RewindButton>.run_if(in_state(PlayerState::Stasis)), shimmer_button::<RewindButton>.run_if(in_state(PlayerState::Stasis)),
shimmer_button::<FlapButton>.run_if(in_state(PlayerState::Pause)), shimmer_button::<FlapButton>.run_if(in_state(PlayerState::Pause)),
), ),
@ -142,7 +141,7 @@ struct Tape {
capacity: usize, capacity: usize,
linear_velocities: VecDeque<LinearVelocity>, linear_velocities: VecDeque<LinearVelocity>,
angular_velocities: VecDeque<AngularVelocity>, angular_velocities: VecDeque<AngularVelocity>,
external_impulses: VecDeque<ExternalImpulse>, constant_forces: VecDeque<ConstantForce>,
positions: VecDeque<Position>, positions: VecDeque<Position>,
rotations: VecDeque<Rotation>, rotations: VecDeque<Rotation>,
} }
@ -153,7 +152,7 @@ impl Tape {
capacity, capacity,
linear_velocities: VecDeque::with_capacity(capacity), linear_velocities: VecDeque::with_capacity(capacity),
angular_velocities: VecDeque::with_capacity(capacity), angular_velocities: VecDeque::with_capacity(capacity),
external_impulses: VecDeque::with_capacity(capacity), constant_forces: VecDeque::with_capacity(capacity),
positions: VecDeque::with_capacity(capacity), positions: VecDeque::with_capacity(capacity),
rotations: VecDeque::with_capacity(capacity), rotations: VecDeque::with_capacity(capacity),
} }
@ -163,7 +162,7 @@ impl Tape {
&mut self, &mut self,
lv: LinearVelocity, lv: LinearVelocity,
av: AngularVelocity, av: AngularVelocity,
ei: ExternalImpulse, ei: ConstantForce,
p: Position, p: Position,
r: Rotation, r: Rotation,
) { ) {
@ -171,14 +170,14 @@ impl Tape {
if self.linear_velocities.len() == self.capacity { if self.linear_velocities.len() == self.capacity {
self.linear_velocities.pop_front().unwrap(); self.linear_velocities.pop_front().unwrap();
self.angular_velocities.pop_front().unwrap(); self.angular_velocities.pop_front().unwrap();
self.external_impulses.pop_front().unwrap(); self.constant_forces.pop_front().unwrap();
self.positions.pop_front().unwrap(); self.positions.pop_front().unwrap();
self.rotations.pop_front().unwrap(); self.rotations.pop_front().unwrap();
} }
self.linear_velocities.push_back(lv); self.linear_velocities.push_back(lv);
self.angular_velocities.push_back(av); self.angular_velocities.push_back(av);
self.external_impulses.push_back(ei); self.constant_forces.push_back(ei);
self.positions.push_back(p); self.positions.push_back(p);
self.rotations.push_back(r); self.rotations.push_back(r);
} }
@ -188,7 +187,7 @@ impl Tape {
) -> Option<( ) -> Option<(
LinearVelocity, LinearVelocity,
AngularVelocity, AngularVelocity,
ExternalImpulse, ConstantForce,
Position, Position,
Rotation, Rotation,
)> { )> {
@ -197,10 +196,10 @@ impl Tape {
} else { } else {
let lv = self.linear_velocities.pop_back().unwrap(); let lv = self.linear_velocities.pop_back().unwrap();
let av = self.angular_velocities.pop_back().unwrap(); let av = self.angular_velocities.pop_back().unwrap();
let ei = self.external_impulses.pop_back().unwrap(); let cf = self.constant_forces.pop_back().unwrap();
let p = self.positions.pop_back().unwrap(); let p = self.positions.pop_back().unwrap();
let r = self.rotations.pop_back().unwrap(); let r = self.rotations.pop_back().unwrap();
Some((lv, av, ei, p, r)) Some((lv, av, cf, p, r))
} }
} }
} }
@ -214,7 +213,7 @@ fn init_bird(mut commands: Commands, bird_assets: Res<BirdAssets>) {
RigidBody::Static, RigidBody::Static,
Collider::rectangle(1.0, 1.0), Collider::rectangle(1.0, 1.0),
Mass(10.0), Mass(10.0),
ExternalImpulse::default().with_persistence(false), ConstantForce::default(),
MaxLinearSpeed(500.0), MaxLinearSpeed(500.0),
); );
@ -265,20 +264,20 @@ fn init_first_batches(mut commands: Commands) {
/// * The pipe obstacle /// * The pipe obstacle
/// * The pipe scoring sensor /// * The pipe scoring sensor
/// ///
/// These are all populated via `OnAdd` observers for the respected components. /// These are all populated via `On<Add, >` observers for the respected components.
/// This makes it much more concise to spawn each part of the environment. /// This makes it much more concise to spawn each part of the environment.
fn populate_batch( fn populate_batch(
trigger: Trigger<OnAdd, Batch>, event: On<Add, Batch>,
batches: Query<&Batch>, batches: Query<&Batch>,
children: Query<&ChildOf>, children: Query<&ChildOf>,
mut commands: Commands, mut commands: Commands,
) { ) {
// Only run this for top level batch entities, // Only run this for top level batch entities,
// not children containing a reference to their batch, like hitboxes // not children containing a reference to their batch, like hitboxes
if !children.contains(trigger.target()) { if !children.contains(event.entity) {
let Batch(batch_id) = batches.get(trigger.target()).unwrap(); let Batch(batch_id) = batches.get(event.entity).unwrap();
commands commands
.entity(trigger.target()) .entity(event.entity)
.insert(( .insert((
Transform::from_xyz(500.0 * (*batch_id) as f32, 0.0, 0.0), Transform::from_xyz(500.0 * (*batch_id) as f32, 0.0, 0.0),
Visibility::Inherited, Visibility::Inherited,
@ -313,14 +312,14 @@ fn update_batch_position(
} }
fn populate_ground( fn populate_ground(
trigger: Trigger<OnAdd, Ground>, event: On<Add, Ground>,
grounds: Query<&Ground>, grounds: Query<&Ground>,
ground_assets: Res<GroundAssets>, ground_assets: Res<GroundAssets>,
mut commands: Commands, mut commands: Commands,
) { ) {
let Ground(idx) = grounds.get(trigger.target()).unwrap(); let Ground(idx) = grounds.get(event.entity).unwrap();
debug!("populating ground {:?}", idx); debug!("populating ground {:?}", idx);
commands.entity(trigger.target()).insert(( commands.entity(event.entity).insert((
ground_assets.material.clone(), ground_assets.material.clone(),
ground_assets.mesh.clone(), ground_assets.mesh.clone(),
Name::new("ground"), Name::new("ground"),
@ -331,14 +330,14 @@ fn populate_ground(
} }
fn populate_ceiling( fn populate_ceiling(
trigger: Trigger<OnAdd, Ceiling>, event: On<Add, Ceiling>,
ceiling: Query<&Ceiling>, ceiling: Query<&Ceiling>,
ceiling_assets: Res<CeilingAssets>, ceiling_assets: Res<CeilingAssets>,
mut commands: Commands, mut commands: Commands,
) { ) {
let Ceiling(idx) = ceiling.get(trigger.target()).unwrap(); let Ceiling(idx) = ceiling.get(event.entity).unwrap();
debug!("populating ceiling{:?}", idx); debug!("populating ceiling{:?}", idx);
commands.entity(trigger.target()).insert(( commands.entity(event.entity).insert((
ceiling_assets.material.clone(), ceiling_assets.material.clone(),
ceiling_assets.mesh.clone(), ceiling_assets.mesh.clone(),
Name::new("ceiling"), Name::new("ceiling"),
@ -349,11 +348,11 @@ fn populate_ceiling(
} }
fn move_pipe( fn move_pipe(
trigger: Trigger<OnInsert, Batch>, event: On<Insert, Batch>,
mut pipes: Query<(&Batch, &Pipe, &mut Transform)>, mut pipes: Query<(&Batch, &Pipe, &mut Transform)>,
rand: Res<Rand>, rand: Res<Rand>,
) { ) {
if let Ok((Batch(id), pipe, mut pipe_t)) = pipes.get_mut(trigger.target()) { if let Ok((Batch(id), pipe, mut pipe_t)) = pipes.get_mut(event.entity) {
*pipe_t = *pipe_t =
{ {
let offset = { let offset = {
@ -382,14 +381,14 @@ fn move_pipe(
/// Based on if this is a Top or Bottom pipe the placement changes /// Based on if this is a Top or Bottom pipe the placement changes
/// Otherwise this just spawns in the center of the batch. /// Otherwise this just spawns in the center of the batch.
fn populate_pipe( fn populate_pipe(
trigger: Trigger<OnAdd, Pipe>, event: On<Add, Pipe>,
pipes: Query<(&Batch, &Pipe)>, pipes: Query<(&Batch, &Pipe)>,
pipe_assets: Res<PipeAssets>, pipe_assets: Res<PipeAssets>,
mut commands: Commands, mut commands: Commands,
rand: Res<Rand>, rand: Res<Rand>,
) { ) {
let pipe_t = { let pipe_t = {
let (Batch(id), pipe) = pipes.get(trigger.target()).unwrap(); let (Batch(id), pipe) = pipes.get(event.entity).unwrap();
let offset = { let offset = {
let val = rand.0.hash_one(id); let val = rand.0.hash_one(id);
@ -413,7 +412,7 @@ fn populate_pipe(
} }
}; };
commands.entity(trigger.target()).insert(( commands.entity(event.entity).insert((
pipe_t, pipe_t,
pipe_assets.material.clone(), pipe_assets.material.clone(),
pipe_assets.mesh.clone(), pipe_assets.mesh.clone(),
@ -425,8 +424,8 @@ fn populate_pipe(
/// The hitbox should cover the entire height of the screen so if the player /// The hitbox should cover the entire height of the screen so if the player
/// passes between the pipes it registers a point /// passes between the pipes it registers a point
fn populate_hitbox(trigger: Trigger<OnAdd, Hitbox>, mut commands: Commands) { fn populate_hitbox(event: On<Add, Hitbox>, mut commands: Commands) {
commands.entity(trigger.target()).insert(( commands.entity(event.entity).insert((
RigidBody::Static, RigidBody::Static,
Collider::rectangle(1.0, 10.0), Collider::rectangle(1.0, 10.0),
Sensor, Sensor,
@ -517,49 +516,49 @@ fn init_ui(mut commands: Commands, server: Res<AssetServer>) {
}, },
BorderRadius::all(Val::Px(5.0)), BorderRadius::all(Val::Px(5.0)),
BackgroundColor(Color::WHITE), BackgroundColor(Color::WHITE),
BorderColor(BLACK.into()), BorderColor::all(BLACK),
PlayerState::Stasis, PlayerState::Stasis,
)) ))
.with_children(|parent| { .with_children(|parent| {
parent.spawn(( parent.spawn((
Text::new("Game Over...?"), Text::new("Game Over...?"),
TextColor(BLACK.into()), TextColor(BLACK.into()),
TextLayout::new_with_justify(JustifyText::Center), TextLayout::new_with_justify(Justify::Center),
)); ));
parent.spawn(( parent.spawn((
SyncResource::<Score>::default(), SyncResource::<Score>::default(),
Text::default(), Text::default(),
TextColor(BLACK.into()), TextColor(BLACK.into()),
TextLayout::new_with_justify(JustifyText::Center), TextLayout::new_with_justify(Justify::Center),
)); ));
parent.spawn(( parent.spawn((
SyncResource::<LongestRun>::default(), SyncResource::<LongestRun>::default(),
Text::default(), Text::default(),
TextColor(BLACK.into()), TextColor(BLACK.into()),
TextLayout::new_with_justify(JustifyText::Center), TextLayout::new_with_justify(Justify::Center),
)); ));
parent.spawn(( parent.spawn((
SyncResource::<Deaths>::default(), SyncResource::<Deaths>::default(),
Text::default(), Text::default(),
TextColor(BLACK.into()), TextColor(BLACK.into()),
TextLayout::new_with_justify(JustifyText::Center), TextLayout::new_with_justify(Justify::Center),
)); ));
parent.spawn(( parent.spawn((
SyncResource::<Flaps>::default(), SyncResource::<Flaps>::default(),
Text::default(), Text::default(),
TextColor(BLACK.into()), TextColor(BLACK.into()),
TextLayout::new_with_justify(JustifyText::Center), TextLayout::new_with_justify(Justify::Center),
)); ));
parent.spawn(( parent.spawn((
SyncResource::<RewindFrames>::default(), SyncResource::<RewindFrames>::default(),
Text::default(), Text::default(),
TextColor(BLACK.into()), TextColor(BLACK.into()),
TextLayout::new_with_justify(JustifyText::Center), TextLayout::new_with_justify(Justify::Center),
)); ));
parent.spawn(( parent.spawn((
Text::new("Press R to Rewind"), Text::new("Press R to Rewind"),
TextColor(BLACK.into()), TextColor(BLACK.into()),
TextLayout::new_with_justify(JustifyText::Center), TextLayout::new_with_justify(Justify::Center),
)); ));
parent parent
.spawn((Node { .spawn((Node {
@ -569,7 +568,7 @@ fn init_ui(mut commands: Commands, server: Res<AssetServer>) {
},)) },))
.with_children(|parent| { .with_children(|parent| {
fn show_credits( fn show_credits(
_trigger: Trigger<Pointer<Click>>, _event: On<Pointer<Click>>,
mut state: ResMut<NextState<PlayerState>>, mut state: ResMut<NextState<PlayerState>>,
) { ) {
state.set(PlayerState::Credits); state.set(PlayerState::Credits);
@ -579,7 +578,7 @@ fn init_ui(mut commands: Commands, server: Res<AssetServer>) {
.spawn(( .spawn((
Button, Button,
BorderRadius::all(Val::Px(5.0)), BorderRadius::all(Val::Px(5.0)),
BorderColor(BLACK.into()), BorderColor::all(BLACK),
Node { ..default() }, Node { ..default() },
children![(TextColor(BLACK.into()), Text::new("Credits")),], children![(TextColor(BLACK.into()), Text::new("Credits")),],
)) ))
@ -587,10 +586,7 @@ fn init_ui(mut commands: Commands, server: Res<AssetServer>) {
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]
{ {
fn quit_game( fn quit_game(_event: On<Pointer<Click>>, mut exit: MessageWriter<AppExit>) {
_trigger: Trigger<Pointer<Click>>,
mut exit: EventWriter<AppExit>,
) {
warn!("Quitting game"); warn!("Quitting game");
exit.write(AppExit::Success); exit.write(AppExit::Success);
} }
@ -598,7 +594,7 @@ fn init_ui(mut commands: Commands, server: Res<AssetServer>) {
parent parent
.spawn(( .spawn((
BorderRadius::all(Val::Px(5.0)), BorderRadius::all(Val::Px(5.0)),
BorderColor(BLACK.into()), BorderColor::all(BLACK),
Button, Button,
Node { ..default() }, Node { ..default() },
children![(Text::new("Quit"), TextColor(BLACK.into()))], children![(Text::new("Quit"), TextColor(BLACK.into()))],
@ -608,7 +604,7 @@ fn init_ui(mut commands: Commands, server: Res<AssetServer>) {
}); });
}); });
fn hide_credits(_trigger: Trigger<Pointer<Click>>, mut state: ResMut<NextState<PlayerState>>) { fn hide_credits(_event: On<Pointer<Click>>, mut state: ResMut<NextState<PlayerState>>) {
state.set(PlayerState::Stasis) state.set(PlayerState::Stasis)
} }
@ -624,12 +620,12 @@ fn init_ui(mut commands: Commands, server: Res<AssetServer>) {
}, },
BorderRadius::all(Val::Px(5.0)), BorderRadius::all(Val::Px(5.0)),
BackgroundColor(WHITE.into()), BackgroundColor(WHITE.into()),
BorderColor(BLACK.into()), BorderColor::all(BLACK),
PlayerState::Credits, PlayerState::Credits,
children![( children![(
Text::new(credits_str), Text::new(credits_str),
TextColor(BLACK.into()), TextColor(BLACK.into()),
TextLayout::new_with_justify(JustifyText::Center) TextLayout::new_with_justify(Justify::Center)
)], )],
)) ))
.with_children(|parent| { .with_children(|parent| {
@ -639,7 +635,7 @@ fn init_ui(mut commands: Commands, server: Res<AssetServer>) {
..default() ..default()
}, },
BorderRadius::all(Val::Px(5.0)), BorderRadius::all(Val::Px(5.0)),
BorderColor(BLACK.into()), BorderColor::all(BLACK),
Button, Button,
children![(Text::new("Close"), TextColor(BLACK.into()))], children![(Text::new("Close"), TextColor(BLACK.into()))],
)); ));
@ -679,7 +675,7 @@ fn init_ui(mut commands: Commands, server: Res<AssetServer>) {
..default() ..default()
}, },
BorderRadius::all(Val::Px(5.0)), BorderRadius::all(Val::Px(5.0)),
BorderColor(BLACK.into()), BorderColor::all(BLACK),
Text::new("(paused)"), Text::new("(paused)"),
TextColor(BLACK.into()), TextColor(BLACK.into()),
) )
@ -714,7 +710,7 @@ fn init_ui(mut commands: Commands, server: Res<AssetServer>) {
..default() ..default()
}, },
BorderRadius::all(Val::Px(5.0)), BorderRadius::all(Val::Px(5.0)),
BorderColor(BLACK.into()), BorderColor::all(BLACK),
BackgroundColor(Color::WHITE.with_alpha(0.9)), BackgroundColor(Color::WHITE.with_alpha(0.9)),
Button, Button,
RewindButton, RewindButton,
@ -734,7 +730,7 @@ fn init_ui(mut commands: Commands, server: Res<AssetServer>) {
Text::new("Rewind!\n(Hold R)"), Text::new("Rewind!\n(Hold R)"),
TextColor(BLACK.into()), TextColor(BLACK.into()),
TextFont::from_font_size(20.0), TextFont::from_font_size(20.0),
TextLayout::new_with_justify(JustifyText::Center) TextLayout::new_with_justify(Justify::Center)
), ),
], ],
)) ))
@ -757,7 +753,7 @@ fn init_ui(mut commands: Commands, server: Res<AssetServer>) {
..default() ..default()
}, },
BorderRadius::all(Val::Px(5.0)), BorderRadius::all(Val::Px(5.0)),
BorderColor(BLACK.into()), BorderColor::all(BLACK),
BackgroundColor(Color::WHITE.with_alpha(0.9)), BackgroundColor(Color::WHITE.with_alpha(0.9)),
Button, Button,
FlapButton, FlapButton,
@ -766,7 +762,7 @@ fn init_ui(mut commands: Commands, server: Res<AssetServer>) {
Text::new("Flap!\n(Spacebar)"), Text::new("Flap!\n(Spacebar)"),
TextColor(BLACK.into()), TextColor(BLACK.into()),
TextFont::from_font_size(20.0), TextFont::from_font_size(20.0),
TextLayout::new_with_justify(JustifyText::Center) TextLayout::new_with_justify(Justify::Center)
), ),
( (
ImageNode { ImageNode {
@ -792,12 +788,12 @@ fn init_ui(mut commands: Commands, server: Res<AssetServer>) {
}, },
BorderRadius::all(Val::Px(5.0)), BorderRadius::all(Val::Px(5.0)),
BackgroundColor(Color::WHITE), BackgroundColor(Color::WHITE),
BorderColor(BLACK.into()), BorderColor::all(BLACK),
children![( children![(
SyncResource::<Score>::default(), SyncResource::<Score>::default(),
Text::default(), Text::default(),
TextColor(BLACK.into()), TextColor(BLACK.into()),
TextLayout::new_with_justify(JustifyText::Center), TextLayout::new_with_justify(Justify::Center),
)], )],
)); ));
} }
@ -845,16 +841,16 @@ fn init_background(
} }
} }
fn start_rewind(_trigger: Trigger<Pointer<Pressed>>, mut next: ResMut<NextState<PlayerState>>) { fn start_rewind(_event: On<Pointer<Press>>, mut next: ResMut<NextState<PlayerState>>) {
next.set(PlayerState::Rewind); next.set(PlayerState::Rewind);
} }
fn end_rewind(_trigger: Trigger<Pointer<Released>>, mut next: ResMut<NextState<PlayerState>>) { fn end_rewind(_event: On<Pointer<Release>>, mut next: ResMut<NextState<PlayerState>>) {
next.set(PlayerState::Alive); next.set(PlayerState::Alive);
} }
fn flap_button( fn flap_button(
_trigger: Trigger<Pointer<Pressed>>, _event: On<Pointer<Press>>,
mut commands: Commands, mut commands: Commands,
bird: Single<Entity, With<Bird>>, bird: Single<Entity, With<Bird>>,
curr: Res<State<PlayerState>>, curr: Res<State<PlayerState>>,
@ -865,7 +861,7 @@ fn flap_button(
} }
let e = *bird; let e = *bird;
debug!("Flapping {:?}", e); debug!("Flapping {:?}", e);
commands.trigger_targets(Flap, e); commands.trigger(Flap { entity: e });
} }
/// Pause the game when the player presses "Escape" /// Pause the game when the player presses "Escape"
@ -877,22 +873,20 @@ fn un_pause_game(mut next: ResMut<NextState<PlayerState>>) {
next.set(PlayerState::Alive); next.set(PlayerState::Alive);
} }
#[derive(Component, Clone, Event)] #[derive(Component, Clone, Message, EntityEvent)]
struct Flap; struct Flap {
entity: Entity,
}
// Observer for flapping // Observer for flapping
fn flap( fn flap(event: On<Flap>, mut bird: Query<Forces, With<Bird>>, mut flaps: ResMut<Flaps>) {
trigger: Trigger<Flap>, debug!("real flap for {:?}", event.entity);
mut bird: Query<&mut ExternalImpulse, With<Bird>>,
mut flaps: ResMut<Flaps>,
) {
debug!("real flap for {:?}", trigger.target());
// Increment flap stat // Increment flap stat
flaps.0 += 1; flaps.0 += 1;
// Flap birds wings // Flap birds wings
if let Ok(mut f) = bird.get_mut(trigger.target()) { if let Ok(mut f) = bird.get_mut(event.entity) {
f.apply_impulse(Vec2::Y * 5000.0 + Vec2::X * 1000.0); f.apply_local_linear_impulse(Vec2::Y * 5000.0 + Vec2::X * 1000.0);
} }
} }
@ -916,7 +910,7 @@ fn flap_kb(
birds.iter().for_each(|e| { birds.iter().for_each(|e| {
debug!("Flapping {:?}", e); debug!("Flapping {:?}", e);
commands.trigger_targets(Flap, e); commands.trigger(Flap { entity: e });
}); });
} }
@ -941,7 +935,7 @@ fn record(
( (
&LinearVelocity, &LinearVelocity,
&AngularVelocity, &AngularVelocity,
&ExternalImpulse, &ConstantForce,
&Position, &Position,
&Rotation, &Rotation,
&mut Tape, &mut Tape,
@ -956,7 +950,7 @@ fn record(
); );
birds.iter_mut().for_each(|(lv, av, ei, p, r, mut tape)| { birds.iter_mut().for_each(|(lv, av, ei, p, r, mut tape)| {
tape.push(*lv, *av, *ei, *p, *r); tape.push(*lv, *av, ei.clone(), *p, *r);
}); });
} }
@ -967,7 +961,7 @@ fn rewind(
( (
&mut LinearVelocity, &mut LinearVelocity,
&mut AngularVelocity, &mut AngularVelocity,
&mut ExternalImpulse, // TODO: Need to re-add impulse here
&mut Position, &mut Position,
&mut Rotation, &mut Rotation,
&mut Tape, &mut Tape,
@ -984,11 +978,11 @@ fn rewind(
birds birds
.iter_mut() .iter_mut()
.for_each(|(mut lv, mut av, mut ei, mut p, mut r, mut tape)| { .for_each(|(mut lv, mut av, mut p, mut r, mut tape)| {
if let Some((new_lv, new_av, new_ei, new_p, new_r)) = tape.pop() { if let Some((new_lv, new_av, _new_ei, new_p, new_r)) = tape.pop() {
lv.0 = new_lv.0; lv.0 = new_lv.0;
av.0 = new_av.0; av.0 = new_av.0;
ei.set_impulse(new_ei.impulse()); // TODO: Need to re-add impulse here
p.0 = new_p.0; p.0 = new_p.0;
*r = new_r; *r = new_r;
frames.0 += 1; frames.0 += 1;
@ -1016,15 +1010,16 @@ fn detect_dead(
} }
} }
fn alive_bird(mut bird: Single<&mut RigidBody, With<Bird>>) { fn alive_bird(bird: Single<Entity, With<Bird>>, mut commands: Commands) {
debug!("Setting bird to Dynamic"); debug!("Setting bird to Dynamic");
**bird = RigidBody::Dynamic; commands.entity(*bird).remove::<RigidBodyDisabled>();
} }
fn pause_bird( fn pause_bird(
state: Res<State<PlayerState>>, state: Res<State<PlayerState>>,
mut bird: Single<&mut RigidBody, With<Bird>>, bird: Single<Entity, With<Bird>>,
mut deaths: ResMut<Deaths>, mut deaths: ResMut<Deaths>,
mut commands: Commands,
) { ) {
// Increment death count // Increment death count
if state.get() == &PlayerState::Stasis { if state.get() == &PlayerState::Stasis {
@ -1032,7 +1027,7 @@ fn pause_bird(
} }
debug!("Setting bird to Static"); debug!("Setting bird to Static");
**bird = RigidBody::Static; commands.entity(*bird).insert(RigidBodyDisabled);
} }
fn camera_follow_bird( fn camera_follow_bird(
@ -1138,36 +1133,48 @@ impl Display for Deaths {
} }
fn manage_score( fn manage_score(
mut start: EventReader<CollisionStarted>, mut start: MessageReader<CollisionStart>,
mut end: EventReader<CollisionEnded>, mut end: MessageReader<CollisionEnd>,
state: Res<State<PlayerState>>, state: Res<State<PlayerState>>,
hitboxes: Query<&Batch, With<Hitbox>>, hitboxes: Query<&Batch, With<Hitbox>>,
mut score: ResMut<Score>, mut score: ResMut<Score>,
) { ) {
match state.get() { match state.get() {
PlayerState::Rewind => { PlayerState::Rewind => {
start.read().for_each(|CollisionStarted(a, b)| { start.read().for_each(
|CollisionStart {
collider1,
collider2,
..
}| {
// Set score to collided hitbox // Set score to collided hitbox
if let Ok(Batch(this)) = hitboxes.get(*a) { if let Ok(Batch(this)) = hitboxes.get(*collider1) {
debug!("[Rewind] Setting score to {this}"); debug!("[Rewind] Setting score to {this}");
score.0 = this.saturating_sub(1); score.0 = this.saturating_sub(1);
} else if let Ok(Batch(this)) = hitboxes.get(*b) { } else if let Ok(Batch(this)) = hitboxes.get(*collider2) {
debug!("[Rewind] Setting score to {this}"); debug!("[Rewind] Setting score to {this}");
score.0 = this.saturating_sub(1); score.0 = this.saturating_sub(1);
} }
}) },
)
} }
_ => { _ => {
end.read().for_each(|CollisionEnded(a, b)| { end.read().for_each(
|CollisionEnd {
collider1,
collider2,
..
}| {
// Set score to collided hitbox // Set score to collided hitbox
if let Ok(Batch(this)) = hitboxes.get(*b) { if let Ok(Batch(this)) = hitboxes.get(*collider2) {
debug!("[Alive] Setting score to {this}"); debug!("[Alive] Setting score to {this}");
score.0 = *this; score.0 = *this;
} else if let Ok(Batch(this)) = hitboxes.get(*a) { } else if let Ok(Batch(this)) = hitboxes.get(*collider1) {
debug!("[Alive] Setting score to {this}"); debug!("[Alive] Setting score to {this}");
score.0 = *this; score.0 = *this;
} }
}) },
)
} }
} }
} }
@ -1182,15 +1189,27 @@ fn manage_score(
/// Finally we iterate over all entities with the old batch ID and upsert the new batch ID /// Finally we iterate over all entities with the old batch ID and upsert the new batch ID
/// This includes root batch entities as well as pipes and hitboxes /// This includes root batch entities as well as pipes and hitboxes
fn move_batches( fn move_batches(
mut start: EventReader<CollisionStarted>, mut start: MessageReader<CollisionStart>,
mut end: EventReader<CollisionEnded>, mut end: MessageReader<CollisionEnd>,
hitboxes: Query<Entity, With<Hitbox>>, hitboxes: Query<Entity, With<Hitbox>>,
batches: Query<(Entity, &Batch)>, batches: Query<(Entity, &Batch)>,
state: Res<State<PlayerState>>, state: Res<State<PlayerState>>,
mut commands: Commands, mut commands: Commands,
) { ) {
let s = start.read().map(|CollisionStarted(a, b)| (a, b)); let s = start.read().map(
let e = end.read().map(|CollisionEnded(a, b)| (a, b)); |CollisionStart {
collider1,
collider2,
..
}| (collider1, collider2),
);
let e = end.read().map(
|CollisionEnd {
collider1,
collider2,
..
}| (collider1, collider2),
);
let c = s.chain(e); let c = s.chain(e);
c.for_each(|(a, b)| { c.for_each(|(a, b)| {
debug!("[batches] Collision {a} -> {b}"); debug!("[batches] Collision {a} -> {b}");

@ -1,20 +1,8 @@
#![feature(try_blocks)] #![feature(try_blocks)]
// Bevy basically forces "complex types" with Querys // Bevy basically forces "complex types" with Querys
#![allow(clippy::type_complexity)] #![allow(clippy::type_complexity)]
use bevy::{
asset::RenderAssetUsages,
math::{FloatOrd},
render::{
mesh::MeshAabb,
camera::{ImageRenderTarget, RenderTarget},
render_resource::{Extent3d, TextureDimension, TextureFormat, TextureUsages},
view::RenderLayers,
},
};
use games::*; use games::*;
use itertools::Itertools;
#[cfg(test)] #[cfg(test)]
mod test; mod test;
@ -30,7 +18,7 @@ fn main() {
App::new() App::new()
.add_plugins(BaseGamePlugin { .add_plugins(BaseGamePlugin {
name: "falling-block-adventure".into(), name: "falling-block-adventure".into(),
target_resolution: (640.0, 480.0).into(), target_resolution: (640, 480).into(),
game_type: GameType::Two, game_type: GameType::Two,
..default() ..default()
}) })
@ -53,7 +41,7 @@ fn main() {
.add_systems( .add_systems(
Update, Update,
( (
kb_input.run_if(on_event::<KeyboardInput>), kb_input.run_if(on_message::<KeyboardInput>),
toggle_state_visibility::<GameState>.run_if(state_changed::<GameState>), toggle_state_visibility::<GameState>.run_if(state_changed::<GameState>),
), ),
) )
@ -85,7 +73,7 @@ fn main() {
.run_if(any_component_changed::<Health>.or(any_component_added::<Health>)), .run_if(any_component_changed::<Health>.or(any_component_added::<Health>)),
damage_on_place_shape.run_if(any_component_removed::<Shape>), damage_on_place_shape.run_if(any_component_removed::<Shape>),
damage_on_clear_line.run_if(any_component_removed::<LineBlock>), damage_on_clear_line.run_if(any_component_removed::<LineBlock>),
damage_over_time.run_if(clock_cycle(5.0)) damage_over_time.run_if(clock_cycle(5.0)),
), ),
) )
// UI systems // UI systems
@ -151,16 +139,16 @@ struct GridPosition {
} }
impl GridPosition { impl GridPosition {
fn with_offset(self, other_x: isize, other_y: isize) -> Result<GridPosition, GameError> { fn with_offset(self, other_x: isize, other_y: isize) -> Result<GridPosition, OutOfBoundsError> {
let x = self.x as isize + other_x; let x = self.x as isize + other_x;
let y = self.y as isize + other_y; let y = self.y as isize + other_y;
if x >= X_MAX as isize { if x >= X_MAX as isize {
Err(GameError::OutOfBoundsLeft) Err(OutOfBoundsError::Left)
} else if x < 0 { } else if x < 0 {
Err(GameError::OutOfBoundsRight) Err(OutOfBoundsError::Right)
} else if y < 0 { } else if y < 0 {
Err(GameError::OutOfBoundsDown) Err(OutOfBoundsError::Down)
} else { } else {
Ok(GridPosition { Ok(GridPosition {
x: x as usize, x: x as usize,
@ -177,15 +165,13 @@ impl Display for GridPosition {
} }
#[derive(Error, Debug, PartialEq)] #[derive(Error, Debug, PartialEq)]
enum GameError { enum OutOfBoundsError {
#[error("Coordinates are out of bounds: Left")] #[error("Coordinates are out of bounds: Left")]
OutOfBoundsLeft, Left,
#[error("Coordinates are out of bounds: Right")] #[error("Coordinates are out of bounds: Right")]
OutOfBoundsRight, Right,
#[error("Coordinates are out of bounds: Down")] #[error("Coordinates are out of bounds: Down")]
OutOfBoundsDown, Down,
#[error("Coordiante collision")]
Collision,
} }
impl Default for GridPosition { impl Default for GridPosition {
@ -278,7 +264,7 @@ struct ShapeStore(Option<Shape>);
impl Display for ShapeStore { impl Display for ShapeStore {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self.0 { match &self.0 {
Some(inner) => write!(f, "{}", inner.as_ascii()), Some(inner) => write!(f, "{}", inner.as_ascii()),
None => write!(f, "---"), None => write!(f, "---"),
} }
@ -510,7 +496,7 @@ fn init_ui(mut commands: Commands, output_images: Res<OutputImages>, images: Res
border: UiRect::all(Val::Px(5.0)), border: UiRect::all(Val::Px(5.0)),
..default() ..default()
}, },
BorderColor(WHITE.into()), BorderColor::all(WHITE),
)) ))
.with_children(|parent| { .with_children(|parent| {
let img = images.get(&output_images.tetris).unwrap(); let img = images.get(&output_images.tetris).unwrap();
@ -536,7 +522,7 @@ fn init_ui(mut commands: Commands, output_images: Res<OutputImages>, images: Res
border: UiRect::all(Val::Px(5.0)), border: UiRect::all(Val::Px(5.0)),
..default() ..default()
}, },
BorderColor(WHITE.into()), BorderColor::all(WHITE),
)) ))
.with_children(|parent| { .with_children(|parent| {
let img = images.get(&output_images.battler).unwrap(); let img = images.get(&output_images.battler).unwrap();
@ -586,10 +572,9 @@ fn init_debug_ui(mut commands: Commands) {
}); });
} }
#[derive(Component, Debug, Clone, Copy)] #[derive(Component, Debug, Clone)]
enum Shape { struct Shape {
M4(Mat4), layout: ShapeBlockLayout,
M3(Mat3),
} }
impl Default for Shape { impl Default for Shape {
@ -600,180 +585,164 @@ impl Default for Shape {
impl Display for Shape { impl Display for Shape {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.as_ascii()) write!(f, "{}", self.layout.as_ascii())
} }
} }
impl Shape { impl From<Vec<Vec<u8>>> for Shape {
fn from_mat4(input: Mat4) -> Self { fn from(inner: Vec<Vec<u8>>) -> Shape {
Self::M4(input) Shape {
layout: ShapeBlockLayout { inner },
}
} }
fn from_mat3(input: Mat3) -> Self {
Self::M3(input)
} }
impl Shape {
fn new_o() -> Self { fn new_o() -> Self {
Self::from_mat4(Mat4::from_cols_array_2d(&[ vec![vec![1, 1], vec![1, 1]].into()
[0., 0., 0., 0.],
[0., 1., 1., 0.],
[0., 1., 1., 0.],
[0., 0., 0., 0.],
]))
} }
fn new_t() -> Self { fn new_t() -> Self {
Self::from_mat3(Mat3::from_cols_array_2d(&[ vec![
[0., 1., 0.], vec![0, 1, 0],
[1., 1., 1.], vec![1, 1, 1],
[0., 0., 0.], vec![0, 0, 0]
])) ].into()
} }
fn new_l() -> Self { fn new_l() -> Self {
Self::from_mat4(Mat4::from_cols_array_2d(&[ vec![vec![1, 0], vec![1, 0], vec![1, 1]].into()
[0., 0., 0., 0.],
[0., 1., 0., 0.],
[0., 1., 0., 0.],
[0., 1., 1., 0.],
]))
} }
fn new_j() -> Self { fn new_j() -> Self {
Self::from_mat4(Mat4::from_cols_array_2d(&[ vec![vec![0, 1], vec![0, 1], vec![1, 1]].into()
[0., 0., 0., 0.],
[0., 0., 1., 0.],
[0., 0., 1., 0.],
[0., 1., 1., 0.],
]))
} }
fn new_s() -> Self { fn new_s() -> Self {
Self::from_mat4(Mat4::from_cols_array_2d(&[ vec![vec![0, 1, 1], vec![1, 1, 0]].into()
[0., 0., 0., 0.],
[0., 1., 1., 0.],
[1., 1., 0., 0.],
[0., 0., 0., 0.],
]))
} }
fn new_z() -> Self { fn new_z() -> Self {
Self::from_mat4(Mat4::from_cols_array_2d(&[ vec![vec![1, 1, 0], vec![0, 1, 1]].into()
[0., 0., 0., 0.],
[1., 1., 0., 0.],
[0., 1., 1., 0.],
[0., 0., 0., 0.],
]))
} }
fn new_i() -> Self { fn new_i() -> Self {
Self::from_mat4(Mat4::from_cols_array_2d(&[ vec![vec![1], vec![1], vec![1], vec![1]].into()
[0., 0., 1., 0.],
[0., 0., 1., 0.],
[0., 0., 1., 0.],
[0., 0., 1., 0.],
]))
} }
// Rotates 90 degrees to the right // Rotates 90 degrees to the right
// https://stackoverflow.com/a/8664879 // https://stackoverflow.com/a/8664879
fn rotated(&self) -> Self { fn rotated(&self) -> Self {
match self { Self {
Self::M4(inner) => { layout: self.layout.rotated(),
let mut new_self = inner.transpose();
for i in 0..4 {
let col = new_self.col_mut(i);
*col = Vec4::new(col[3], col[2], col[1], col[0]);
}
Self::M4(new_self)
}
Self::M3(inner) => {
let mut new_self = inner.transpose();
for i in 0..3 {
let col = new_self.col_mut(i);
*col = Vec3::new(col[2], col[1], col[0]);
}
Self::M3(new_self)
} }
} }
fn reposition(
(x_offset, y_offset): (isize, isize),
center: &GridPosition,
) -> Result<GridPosition, OutOfBoundsError> {
center.with_offset(x_offset, y_offset)
} }
fn coordinates( fn coordinates(
&self, &self,
center: &GridPosition, center: &GridPosition,
) -> impl Iterator<Item = Result<GridPosition, GameError>> { ) -> impl Iterator<Item = Result<GridPosition, OutOfBoundsError>> {
let mut v: Vec<Result<GridPosition, GameError>> = Vec::new(); self.layout
match self { .coordinates()
Self::M4(inner) => { .map(|(x, y)| Shape::reposition((x, y), center))
for (i, y) in (-1..3).rev().enumerate() {
let c = inner.col(i);
for (j, x) in (-1..3).enumerate() {
if c[j] == 1.0 {
v.push(center.with_offset(x, y));
} }
fn as_ascii(&self) -> String {
self.layout.as_ascii()
} }
fn height(&self) -> usize {
self.layout.height()
} }
} }
Self::M3(inner) => {
for (i, y) in (-1..2).rev().enumerate() { #[derive(Debug, Clone, PartialEq, Eq)]
let c = inner.col(i); struct ShapeBlockLayout {
for (j, x) in (-1..2).enumerate() { inner: Vec<Vec<u8>>,
if c[j] == 1.0 {
v.push(center.with_offset(x, y));
} }
impl ShapeBlockLayout {
fn rotated(&self) -> Self {
let mut inner = vec![];
for _ in 0..self.inner[0].len() {
inner.push(vec![]);
} }
for y in self.inner.iter() {
for (j, x) in y.iter().enumerate() {
inner[j].insert(0, *x);
} }
} }
}; ShapeBlockLayout { inner }
v.into_iter()
} }
fn as_ascii(&self) -> String { fn center(&self) -> (usize, usize) {
let mut output = String::default(); let mid_x = match self.inner[0].len() % 2 {
0 => (self.inner[0].len() - 1) / 2,
1 => self.inner[0].len() / 2,
match self { _ => panic!("That's not how mod works!"),
Self::M4(this) => {
for i in 0..4 {
let col = this.col(i).to_array();
output += format!("{}{}{}{}\n", col[0], col[1], col[2], col[3]).as_str();
}
}
Self::M3(this) => {
for i in 0..3 {
let col = this.col(i).to_array();
output += format!("{}{}{}\n", col[0], col[1], col[2]).as_str();
}
}
}; };
output let mid_y = match self.inner.len() % 2 {
} // If we have an even number of elements
0 => self.inner.len().div_ceil(2),
// If we have an odd number of elements
1 => self.inner.len() / 2,
fn height(&self) -> usize { _ => panic!("That's not how mod works!"),
let mut x = 0; };
match self { (mid_x, mid_y)
Self::M4(this) => {
for i in 0..4 {
if this.col(i).to_array().contains(&1.0) {
x += 1
} }
fn coordinates(&self) -> impl Iterator<Item = (isize, isize)> {
let (mid_x, mid_y) = self.center();
// Loop over outer vec (i)
self.inner
.iter()
.rev()
.enumerate()
.flat_map(move |(i, ys)| {
ys.iter().enumerate().filter_map(move |(j, val)| {
// this_x = j - mid_x
let x = j as isize - mid_x as isize;
// this_y = i - mid_y
let y = i as isize - mid_y as isize;
(*val > 0).then_some((x, y))
})
})
} }
// TODO: Make fnctional with chaining
fn as_ascii(&self) -> String {
let mut s = String::new();
for y in self.inner.iter() {
for x in y {
match x {
0 => s.push('0'),
1 => s.push('1'),
_ => panic!("aur nau"),
} }
Self::M3(this) => {
for i in 0..3 {
if this.col(i).to_array().contains(&1.0) {
x += 1
} }
s.push('\n');
} }
s
} }
};
x fn height(&self) -> usize {
self.inner.len()
} }
} }
// TODO: move to trigger // TODO: move to event
fn update_position( fn update_position(
mut changed: Query< mut changed: Query<
(Entity, &GridPosition, &mut Transform), (Entity, &GridPosition, &mut Transform),
@ -792,7 +761,7 @@ fn update_position(
}); });
} }
// TODO: Move to trigger // TODO: Move to event
fn update_shape_blocks( fn update_shape_blocks(
query: Query< query: Query<
(Entity, &Shape, &GridPosition), (Entity, &Shape, &GridPosition),
@ -832,7 +801,7 @@ fn update_shape_blocks(
} }
fn kb_input( fn kb_input(
mut events: EventReader<KeyboardInput>, mut events: MessageReader<KeyboardInput>,
mut query: Query<(Entity, &mut Shape)>, mut query: Query<(Entity, &mut Shape)>,
curr: Res<State<GameState>>, curr: Res<State<GameState>>,
mut next: ResMut<NextState<GameState>>, mut next: ResMut<NextState<GameState>>,
@ -848,22 +817,37 @@ fn kb_input(
// Up arrow should rotate if in falling mode // Up arrow should rotate if in falling mode
// Only move up if in falling::off mode // Only move up if in falling::off mode
KeyCode::ArrowUp => { KeyCode::ArrowUp => {
commands.entity(e).trigger(Movement::Rotate); commands.entity(e).trigger(|entity| Movement {
entity,
direction: MovementDirection::Rotate,
});
} }
KeyCode::ArrowDown => { KeyCode::ArrowDown => {
commands.entity(e).trigger(Movement::Down); commands.entity(e).trigger(|entity| Movement {
entity,
direction: MovementDirection::Down,
});
} }
KeyCode::ArrowLeft => { KeyCode::ArrowLeft => {
commands.entity(e).trigger(Movement::Left); commands.entity(e).trigger(|entity| Movement {
entity,
direction: MovementDirection::Left,
});
} }
KeyCode::ArrowRight => { KeyCode::ArrowRight => {
commands.entity(e).trigger(Movement::Right); commands.entity(e).trigger(|entity| Movement {
entity,
direction: MovementDirection::Right,
});
} }
KeyCode::Enter => { KeyCode::Enter => {
commands.entity(e).trigger(Movement::Skip); commands.entity(e).trigger(|entity| Movement {
entity,
direction: MovementDirection::Skip,
});
} }
KeyCode::Space => { KeyCode::Space => {
commands.entity(e).trigger(Swap); commands.entity(e).trigger(|entity| Swap { entity });
} }
KeyCode::Escape => next.set(match curr.get() { KeyCode::Escape => next.set(match curr.get() {
GameState::Falling => GameState::Pause, GameState::Falling => GameState::Pause,
@ -887,7 +871,10 @@ fn kb_input(
fn falling(mut shape: Query<Entity, With<Shape>>, mut commands: Commands) { fn falling(mut shape: Query<Entity, With<Shape>>, mut commands: Commands) {
shape.iter_mut().for_each(|e| { shape.iter_mut().for_each(|e| {
debug!("Making {:?} fall", e); debug!("Making {:?} fall", e);
commands.entity(e).trigger(Movement::Down); commands.entity(e).trigger(|entity| Movement {
entity,
direction: MovementDirection::Down,
});
}); });
} }
@ -1003,12 +990,20 @@ fn adjust_block_lines(
} }
/// Swap the current piece out /// Swap the current piece out
#[derive(Event, Copy, Clone, PartialEq)] #[derive(EntityEvent, Message, Copy, Clone, PartialEq)]
struct Swap; struct Swap {
entity: Entity,
}
/// Movement events evented on the piece
#[derive(Message, EntityEvent, Copy, Clone, PartialEq)]
struct Movement {
entity: Entity,
direction: MovementDirection,
}
/// Movement events triggered on the piece #[derive(Clone, Copy, Debug, PartialEq)]
#[derive(Event, Copy, Clone, PartialEq)] enum MovementDirection {
enum Movement {
Down, Down,
Left, Left,
Right, Right,
@ -1018,7 +1013,7 @@ enum Movement {
// TODO: When out of bounds left/right, try to move piece away from wall // TODO: When out of bounds left/right, try to move piece away from wall
fn movement( fn movement(
trigger: Trigger<Movement>, event: On<Movement>,
mut grid_positions: Query< mut grid_positions: Query<
&mut GridPosition, &mut GridPosition,
Or<(With<ShapeBlock>, With<ShapeBlocks>, Without<LineBlock>)>, Or<(With<ShapeBlock>, With<ShapeBlocks>, Without<LineBlock>)>,
@ -1028,21 +1023,24 @@ fn movement(
mut commands: Commands, mut commands: Commands,
) { ) {
if let (Ok(this_shape), Ok(center)) = ( if let (Ok(this_shape), Ok(center)) = (
shape.get_mut(trigger.target()), shape.get_mut(event.entity),
grid_positions.get(trigger.target()), grid_positions.get(event.entity),
) { ) {
let new_positions = match trigger.event() { let new_positions = match event.event().direction {
Movement::Down => vec![center.with_offset(0, -1)], MovementDirection::Down => vec![center.with_offset(0, -1)],
Movement::Left => vec![center.with_offset(-1, 0)], MovementDirection::Left => vec![center.with_offset(-1, 0)],
Movement::Right => vec![center.with_offset(1, 0)], MovementDirection::Right => vec![center.with_offset(1, 0)],
Movement::Rotate => vec![Ok(*center)], MovementDirection::Rotate => vec![Ok(*center)],
Movement::Skip => (1..=center.y) MovementDirection::Skip => (1..=center.y)
.map(|i| center.with_offset(0, -(i as isize))) .map(|i| center.with_offset(0, -(i as isize)))
.collect(), .collect(),
}; };
let new_shape = match trigger.event() { let new_shape = match event.event().direction {
Movement::Down | Movement::Left | Movement::Right | Movement::Skip => *this_shape, MovementDirection::Down
Movement::Rotate => this_shape.rotated(), | MovementDirection::Left
| MovementDirection::Right
| MovementDirection::Skip => this_shape.clone(),
MovementDirection::Rotate => this_shape.rotated(),
}; };
debug!( debug!(
"Proposed change: {:?}\n{}", "Proposed change: {:?}\n{}",
@ -1051,31 +1049,29 @@ fn movement(
); );
for position in new_positions { for position in new_positions {
match position { match position {
Err(GameError::OutOfBoundsLeft) | Err(GameError::OutOfBoundsRight) => (), // Do nothing Err(OutOfBoundsError::Left) | Err(OutOfBoundsError::Right) => (), // Do nothing
Err(GameError::OutOfBoundsDown) => { Err(OutOfBoundsError::Down) => {
commands.entity(trigger.target()).remove::<Shape>(); commands.entity(event.entity).remove::<Shape>();
} }
Err(GameError::Collision) => panic!("This shouldn't happen!"),
Ok(new_center) => { Ok(new_center) => {
let new_blocks = new_shape.coordinates(&new_center); let new_blocks = new_shape.coordinates(&new_center);
for block_gp in new_blocks { for block_gp in new_blocks {
match block_gp { match block_gp {
Err(GameError::OutOfBoundsLeft) | Err(GameError::OutOfBoundsRight) => { Err(OutOfBoundsError::Left) | Err(OutOfBoundsError::Right) => {
return; return;
} // Do nothing } // Do nothing
Err(GameError::OutOfBoundsDown) => { Err(OutOfBoundsError::Down) => {
commands.entity(trigger.target()).remove::<Shape>(); commands.entity(event.entity).remove::<Shape>();
return; return;
} }
Err(GameError::Collision) => panic!("This shouldn't happen!"),
Ok(gp) => { Ok(gp) => {
for other_gp in inactive.iter() { for other_gp in inactive.iter() {
// If there would be a collision between blocks // If there would be a collision between blocks
if gp == *other_gp { if gp == *other_gp {
// And we are moving down // And we are moving down
if *trigger.event() == Movement::Down { if event.event().direction == MovementDirection::Down {
// De-activate this piece // De-activate this piece
commands.entity(trigger.target()).remove::<Shape>(); commands.entity(event.entity).remove::<Shape>();
} }
// Regardless, cancel the move // Regardless, cancel the move
return; return;
@ -1087,22 +1083,22 @@ fn movement(
debug!("Checks passed for {position:?}, committing change"); debug!("Checks passed for {position:?}, committing change");
// Update center // Update center
let mut gp = grid_positions.get_mut(trigger.target()).unwrap(); let mut gp = grid_positions.get_mut(event.entity).unwrap();
*gp = new_center; *gp = new_center;
// Update shape/rotation // Update shape/rotation
let mut s = shape.get_mut(trigger.target()).unwrap(); let mut s = shape.get_mut(event.entity).unwrap();
*s = new_shape; *s = new_shape.clone();
} }
} }
} }
} else { } else {
warn!("Triggered movement on non-shape entity"); warn!("Oned movement on non-shape entity");
} }
} }
fn swap( fn swap(
trigger: Trigger<Swap>, event: On<Swap>,
mut shapes: Query<&mut Shape>, mut shapes: Query<&mut Shape>,
mut store: ResMut<ShapeStore>, mut store: ResMut<ShapeStore>,
mut commands: Commands, mut commands: Commands,
@ -1112,16 +1108,16 @@ fn swap(
None => { None => {
// Copy current shape into store // Copy current shape into store
// De-activate entity // De-activate entity
store.0 = Some(*shapes.get(trigger.target()).unwrap()); store.0 = Some(shapes.get(event.entity).unwrap().clone());
commands commands
.entity(trigger.target()) .entity(event.entity)
.despawn_related::<ShapeBlocks>() .despawn_related::<ShapeBlocks>()
.despawn(); .despawn();
} }
Some(inner) => { Some(inner) => {
// Copy current shape into store // Copy current shape into store
// Copy old shape into entity // Copy old shape into entity
let mut curr = shapes.get_mut(trigger.target()).unwrap(); let mut curr = shapes.get_mut(event.entity).unwrap();
std::mem::swap(&mut (*inner), &mut (*curr)); std::mem::swap(&mut (*inner), &mut (*curr));
} }
} }
@ -1209,16 +1205,14 @@ fn sync_health(
}) })
} }
#[derive(Event)] #[derive(Message, EntityEvent)]
struct Damage { struct Damage {
quantity: f32 entity: Entity,
quantity: f32,
} }
fn deal_damage( fn deal_damage(event: On<Damage>, mut healths: Query<&mut Health>) {
trigger: Trigger<Damage>, healths.get_mut(event.entity).unwrap().0 -= event.event().quantity
mut healths: Query<&mut Health>
) {
healths.get_mut(trigger.target()).unwrap().0 -= trigger.event().quantity
} }
fn damage_on_place_shape( fn damage_on_place_shape(
@ -1228,7 +1222,10 @@ fn damage_on_place_shape(
) { ) {
events.read().for_each(|_| { events.read().for_each(|_| {
enemies.iter().for_each(|e| { enemies.iter().for_each(|e| {
commands.entity(e).trigger(Damage { quantity: 1.0 }); commands.entity(e).trigger(|entity| Damage {
entity,
quantity: 1.0,
});
}); });
}); });
} }
@ -1240,14 +1237,17 @@ fn damage_on_clear_line(
) { ) {
events.read().for_each(|_| { events.read().for_each(|_| {
enemies.iter().for_each(|e| { enemies.iter().for_each(|e| {
commands.entity(e).trigger(Damage { quantity: 1.0 }); commands.entity(e).trigger(|entity| Damage {
entity,
quantity: 1.0,
});
}); });
}); });
} }
fn damage_over_time( fn damage_over_time(protagonist: Single<Entity, With<Protagonist>>, mut commands: Commands) {
protagonist: Single<Entity, With<Protagonist>>, commands.entity(*protagonist).trigger(|entity| Damage {
mut commands: Commands, entity,
) { quantity: 1.0,
commands.entity(*protagonist).trigger(Damage { quantity: 1.0 }); });
} }

@ -35,25 +35,19 @@ fn test_shape_t() {
fn test_shape_i() { fn test_shape_i() {
let mut shape = Shape::new_i(); let mut shape = Shape::new_i();
let expected_up = "0010\n\ let expected_up = "1\n\
0010\n\ 1\n\
0010\n\ 1\n\
0010\n"; 1\n";
let expected_right = "0000\n\ let expected_right = "1111\n";
0000\n\
1111\n\ let expected_down = "1\n\
0000\n"; 1\n\
1\n\
let expected_down = "0100\n\ 1\n";
0100\n\
0100\n\ let expected_left = "1111\n";
0100\n";
let expected_left = "0000\n\
1111\n\
0000\n\
0000\n";
assert_eq!(shape.as_ascii(), expected_up); assert_eq!(shape.as_ascii(), expected_up);
shape = shape.rotated(); shape = shape.rotated();
@ -72,21 +66,155 @@ fn test_coordinates() {
let center = GridPosition { x: 5, y: 5 }; let center = GridPosition { x: 5, y: 5 };
let expected: Vec<Result<GridPosition, GameError>> = vec![ let actual: Vec<Result<GridPosition, OutOfBoundsError>> = shape.coordinates(&center).collect();
Ok((5, 6).into()),
let expected: Vec<Result<GridPosition, OutOfBoundsError>> = vec![
Ok((4, 5).into()), Ok((4, 5).into()),
Ok((5, 5).into()), Ok((5, 5).into()),
Ok((6, 5).into()), Ok((6, 5).into()),
Ok((5, 6).into()),
]; ];
let actual: Vec<Result<GridPosition, GameError>> = shape.coordinates(&center).collect(); assert_eq!(shape.layout.center(), (1, 1));
assert_eq!(actual, expected); assert_eq!(actual, expected);
} }
#[test] #[test]
fn test_height() { fn test_height() {
assert_eq!(Shape::new_t().height(), 2); // todo: should be 2 for t piece
assert_eq!(Shape::new_t().height(), 3);
assert_eq!(Shape::new_i().height(), 4); assert_eq!(Shape::new_i().height(), 4);
assert_eq!(Shape::new_l().height(), 3); assert_eq!(Shape::new_l().height(), 3);
} }
#[test]
fn test_shape_block_layout_rotation() {
{
let actual = ShapeBlockLayout {
inner: vec![vec![0, 0, 1]],
}
.rotated();
let expected = ShapeBlockLayout {
inner: vec![vec![0], vec![0], vec![1]],
};
assert_eq!(expected, actual);
}
{
let actual = ShapeBlockLayout {
inner: vec![vec![1, 2, 3], vec![4, 5, 6]],
}
.rotated();
let expected = ShapeBlockLayout {
inner: vec![vec![4, 1], vec![5, 2], vec![6, 3]],
};
assert_eq!(expected, actual);
}
{
let actual = ShapeBlockLayout {
inner: vec![vec![1, 2, 3], vec![4, 5, 6]],
}
.rotated();
let expected = ShapeBlockLayout {
inner: vec![vec![4, 1], vec![5, 2], vec![6, 3]],
};
assert_eq!(expected, actual);
}
{
let actual = ShapeBlockLayout {
inner: vec![
vec![1, 2, 3, 4],
vec![5, 6, 7, 8],
vec![9, 10, 11, 12],
vec![13, 14, 15, 16],
],
}
.rotated();
let expected = ShapeBlockLayout {
inner: vec![
vec![13, 9, 5, 1],
vec![14, 10, 6, 2],
vec![15, 11, 7, 3],
vec![16, 12, 8, 4],
],
};
assert_eq!(expected, actual);
}
}
#[test]
fn test_shape_block_center() {
{
let actual = ShapeBlockLayout {
inner: vec![
vec![0],
vec![0],
vec![0],
]
}.center();
let expected = (0, 1);
assert_eq!(actual, expected);
}
{
let actual = ShapeBlockLayout {
inner: vec![
vec![0],
vec![0],
vec![0],
vec![0],
]
}.center();
let expected = (0, 2);
assert_eq!(actual, expected);
}
{
let actual = ShapeBlockLayout {
inner: vec![
vec![0, 0],
vec![0, 0],
]
}.center();
let expected = (0, 1);
assert_eq!(actual, expected);
}
{
let actual = ShapeBlockLayout {
inner: vec![
vec![0, 0, 0],
vec![0, 0, 0],
vec![0, 0, 0],
]
}.center();
let expected = (1, 1);
assert_eq!(actual, expected);
}
{
let actual = ShapeBlockLayout {
inner: vec![
vec![0, 0, 0, 0],
vec![0, 0, 0, 0],
vec![0, 0, 0, 0],
vec![0, 0, 0, 0],
]
}.center();
let expected = (1, 2);
assert_eq!(actual, expected);
}
}

@ -8,15 +8,16 @@ impl Plugin for TreesDebugPlugin {
.add_systems( .add_systems(
Update, Update,
( (
(spawn_debug_buttons.run_if(on_event::<AssetEvent<Monologue>>),), (spawn_debug_buttons.run_if(on_message::<AssetEvent<Monologue>>),),
( (
monologue_asset_tooltip monologue_asset_tooltip
.run_if(on_event::<Pointer<Over>>.or(on_event::<Pointer<Out>>)), .run_if(on_message::<Pointer<Over>>.or(on_message::<Pointer<Out>>)),
hide_menu.run_if(any_component_changed::<NavState>), hide_menu.run_if(any_component_changed::<NavState>),
clear_monologue.run_if(any_component_changed::<NavState>), clear_monologue.run_if(any_component_changed::<NavState>),
control_menu.run_if(on_event::<Pointer<Over>>.or(on_event::<Pointer<Out>>)), control_menu
delete_tree.run_if(on_event::<Pointer<Click>>), .run_if(on_message::<Pointer<Over>>.or(on_message::<Pointer<Out>>)),
drag_tree.run_if(on_event::<Pointer<Drag>>), delete_tree.run_if(on_message::<Pointer<Click>>),
drag_tree.run_if(on_message::<Pointer<Drag>>),
) )
.run_if(in_state(DebuggingState::On)), .run_if(in_state(DebuggingState::On)),
), ),
@ -35,7 +36,7 @@ struct MonologuesList;
struct MonologuePreview; struct MonologuePreview;
fn drag_tree( fn drag_tree(
mut events: EventReader<Pointer<Drag>>, mut events: MessageReader<Pointer<Drag>>,
state: Res<State<DebuggingState>>, state: Res<State<DebuggingState>>,
mut query: Query<&mut Transform, With<Tree>>, mut query: Query<&mut Transform, With<Tree>>,
camera: Single<(&Camera, &GlobalTransform), With<Camera>>, camera: Single<(&Camera, &GlobalTransform), With<Camera>>,
@ -44,7 +45,7 @@ fn drag_tree(
debug_assert_eq!(*state.get(), DebuggingState::On); debug_assert_eq!(*state.get(), DebuggingState::On);
events.read().for_each(|event| { events.read().for_each(|event| {
if let Ok(mut t) = query.get_mut(event.target) { if let Ok(mut t) = query.get_mut(event.entity) {
let world_position = window let world_position = window
.cursor_position() .cursor_position()
.and_then(|cursor| camera.0.viewport_to_world(camera.1, cursor).ok()) .and_then(|cursor| camera.0.viewport_to_world(camera.1, cursor).ok())
@ -154,8 +155,8 @@ fn init_debug_ui(mut commands: Commands) {
// When you pointer goes off of the '+' or any of it's children make the entire menu invisible // When you pointer goes off of the '+' or any of it's children make the entire menu invisible
fn control_menu( fn control_menu(
mut over_events: EventReader<Pointer<Over>>, mut over_events: MessageReader<Pointer<Over>>,
mut out_events: EventReader<Pointer<Out>>, mut out_events: MessageReader<Pointer<Out>>,
nav_children: Query<&NavParent>, nav_children: Query<&NavParent>,
children: Query<&ChildOf>, children: Query<&ChildOf>,
nav_parents: Query<&NavChildren>, nav_parents: Query<&NavChildren>,
@ -164,7 +165,7 @@ fn control_menu(
hover_map: Res<HoverMap>, hover_map: Res<HoverMap>,
) { ) {
over_events.read().for_each(|over| { over_events.read().for_each(|over| {
let root = nav_children.root_ancestor(over.target); let root = nav_children.root_ancestor(over.entity);
nav_parents.iter_descendants(root).for_each(|child| { nav_parents.iter_descendants(root).for_each(|child| {
if let Ok(mut n) = nav.get_mut(child) { if let Ok(mut n) = nav.get_mut(child) {
@ -184,9 +185,9 @@ fn control_menu(
// For all pointer out events // For all pointer out events
out_events.read().for_each(|out| { out_events.read().for_each(|out| {
// If a relative of out.target is hovered, do nothing // If a relative of out.entity is hovered, do nothing
// Otherwise set to closed // Otherwise set to closed
let root = children.root_ancestor(out.target); let root = children.root_ancestor(out.entity);
let tree_still_hovered = parents let tree_still_hovered = parents
.iter_descendants(root) .iter_descendants(root)
.any(|child| is_hovered.contains(&&child)); .any(|child| is_hovered.contains(&&child));
@ -207,15 +208,15 @@ fn control_menu(
/// 1. Add the Button component /// 1. Add the Button component
/// 2. Change the color to Orange /// 2. Change the color to Orange
/// 3. Add observers for click (select) and hover (change color) /// 3. Add observers for click (select) and hover (change color)
fn add_dialog_option(trigger: Trigger<OnAdd, DialogOption>, mut commands: Commands) { fn add_dialog_option(event: On<Add, DialogOption>, mut commands: Commands) {
commands commands
.entity(trigger.target()) .entity(event.entity)
.insert(Button) .insert(Button)
.insert(Node { .insert(Node {
width: Val::Percent(100.0), width: Val::Percent(100.0),
..default() ..default()
}) })
.insert(TextLayout::new_with_justify(JustifyText::Center)) .insert(TextLayout::new_with_justify(Justify::Center))
.insert(TextColor(ORANGE.into())) .insert(TextColor(ORANGE.into()))
.observe(choose_dialog_option) .observe(choose_dialog_option)
.observe(hover_dialog_option_over) .observe(hover_dialog_option_over)
@ -223,16 +224,16 @@ fn add_dialog_option(trigger: Trigger<OnAdd, DialogOption>, mut commands: Comman
} }
fn assign_monologue_event( fn assign_monologue_event(
trigger: Trigger<Pointer<Click>>, event: On<Pointer<Click>>,
mut events: EventWriter<AssignMonologue>, mut events: MessageWriter<AssignMonologue>,
monologues: Query<&TreeMonologue>, monologues: Query<&TreeMonologue>,
) { ) {
let TreeMonologue(handle) = monologues.get(trigger.target()).unwrap(); let TreeMonologue(handle) = monologues.get(event.entity).unwrap();
events.write(AssignMonologue(handle.clone())); events.write(AssignMonologue(*handle));
} }
/// Observer for the "Plant a new tree" button in the debug UI /// Observer for the "Plant a new tree" button in the debug UI
fn spawn_tree(_trigger: Trigger<Pointer<Click>>, mut events: EventWriter<PlantTree>) { fn spawn_tree(_event: On<Pointer<Click>>, mut events: MessageWriter<PlantTree>) {
events.write(PlantTree(None)); events.write(PlantTree(None));
} }
@ -261,37 +262,38 @@ fn hide_menu(mut nodes: Query<(&mut Visibility, &NavState), Changed<NavState>>)
} }
fn delete_tree( fn delete_tree(
mut events: EventReader<Pointer<Click>>, mut events: MessageReader<Pointer<Click>>,
mut commands: Commands, mut commands: Commands,
query: Query<Entity, With<Tree>>, query: Query<Entity, With<Tree>>,
) { ) {
events.read().for_each(|event| { events.read().for_each(|event| {
if matches!(event.event.button, PointerButton::Middle) && query.contains(event.target) { if matches!(event.event.button, PointerButton::Middle) && query.contains(event.entity) {
debug!("Middle Click -> Despawning {}", event.target); debug!("Middle Click -> Despawning {}", event.entity);
commands.entity(event.target).despawn(); commands.entity(event.entity).despawn();
} }
}) })
} }
/// Add the "script: path/to/file.mono" tooltip info /// Add the "script: path/to/file.mono" tooltip info
fn monologue_asset_tooltip( fn monologue_asset_tooltip(
mut over_events: EventReader<Pointer<Over>>, mut over_events: MessageReader<Pointer<Over>>,
mut out_events: EventReader<Pointer<Out>>, mut out_events: MessageReader<Pointer<Out>>,
mut tooltip: ResMut<ToolTip>, mut tooltip: ResMut<ToolTip>,
server: Res<AssetServer>,
trees: Query<(&Tree, Option<&TreeMonologue>)>, trees: Query<(&Tree, Option<&TreeMonologue>)>,
) { ) {
out_events out_events
.read() .read()
.filter_map(|Pointer { target, .. }| trees.contains(*target).then_some(*target)) .filter_map(|Pointer { entity, .. }| trees.contains(*entity).then_some(*entity))
.for_each(|_| { .for_each(|_| {
tooltip.remove("Script"); tooltip.remove("Script");
}); });
over_events over_events
.read() .read()
.filter_map(|Pointer { target, .. }| trees.contains(*target).then_some(*target)) .filter_map(|Pointer { entity, .. }| trees.contains(*entity).then_some(*entity))
.for_each(|e| match trees.get(e) { .for_each(|e| match trees.get(e) {
Ok((_tree, Some(TreeMonologue(handle)))) => match handle.path() { Ok((_tree, Some(TreeMonologue(id)))) => match server.get_path(*id) {
Some(p) => tooltip.insert("Script", format!("{p}")), Some(p) => tooltip.insert("Script", format!("{p}")),
None => tooltip.insert("Script", "A".into()), None => tooltip.insert("Script", "A".into()),
}, },
@ -304,14 +306,14 @@ fn monologue_asset_tooltip(
/// When a dialog option is chosen (clicked on) we do the following: /// When a dialog option is chosen (clicked on) we do the following:
fn choose_dialog_option( fn choose_dialog_option(
trigger: Trigger<Pointer<Click>>, event: On<Pointer<Click>>,
mut dialog_events: EventWriter<DialogEvent>, mut dialog_events: MessageWriter<DialogEvent>,
mut commands: Commands, mut commands: Commands,
texts: Query<&Text>, texts: Query<&Text>,
options: Query<Entity, With<DialogOption>>, options: Query<Entity, With<DialogOption>>,
dialog_box: Single<Entity, With<DialogBox>>, dialog_box: Single<Entity, With<DialogBox>>,
) { ) {
debug!("Choosing dialog {:?}", trigger.target()); debug!("Choosing dialog {:?}", event.entity);
debug!("Despawning dialog options"); debug!("Despawning dialog options");
options.iter().for_each(|e| { options.iter().for_each(|e| {
@ -319,27 +321,27 @@ fn choose_dialog_option(
}); });
debug!("Inserting dialog line"); debug!("Inserting dialog line");
if let Ok(t) = texts.get(trigger.target()) { if let Ok(t) = texts.get(event.entity) {
commands.entity(*dialog_box).with_children(|parent| { commands.entity(*dialog_box).with_children(|parent| {
parent.spawn((t.clone(), DialogLine)); parent.spawn((t.clone(), DialogLine));
}); });
} }
// trigger the next dialog line // event the next dialog line
dialog_events.write(DialogEvent::NextBatch); dialog_events.write(DialogEvent::NextBatch);
} }
fn preview_monologue( fn preview_monologue(
trigger: Trigger<Pointer<Over>>, event: On<Pointer<Over>>,
container: Single<Entity, (With<MonologuePreview>, Without<Button>, Without<Text>)>, container: Single<Entity, (With<MonologuePreview>, Without<Button>, Without<Text>)>,
tree_monologue: Query<&TreeMonologue, With<Button>>, tree_monologue: Query<&TreeMonologue, With<Button>>,
monologues: Res<Assets<Monologue>>, monologues: Res<Assets<Monologue>>,
mut commands: Commands, mut commands: Commands,
) { ) {
// Get the handle for this button's monologuie // Get the handle for this button's monologuie
if let Ok(TreeMonologue(handle)) = tree_monologue.get(trigger.target()) { if let Ok(TreeMonologue(id)) = tree_monologue.get(event.entity) {
// Get the monologue data // Get the monologue data
if let Some(monologue) = monologues.get(handle) { if let Some(monologue) = monologues.get(*id) {
commands.entity(*container).despawn_related::<Children>(); commands.entity(*container).despawn_related::<Children>();
// Spawn the monologue // Spawn the monologue
@ -366,7 +368,7 @@ fn preview_monologue(
} }
fn spawn_debug_buttons( fn spawn_debug_buttons(
mut events: EventReader<AssetEvent<Monologue>>, mut events: MessageReader<AssetEvent<Monologue>>,
mut commands: Commands, mut commands: Commands,
container: Single<Entity, (With<MonologuesList>, Without<Button>)>, container: Single<Entity, (With<MonologuesList>, Without<Button>)>,
server: Res<AssetServer>, server: Res<AssetServer>,
@ -396,9 +398,9 @@ fn spawn_debug_buttons(
.trim_prefix("trees/") .trim_prefix("trees/")
.trim_suffix(".mono"), .trim_suffix(".mono"),
), ),
TextLayout::new(JustifyText::Left, LineBreak::WordBoundary), TextLayout::new(Justify::Left, LineBreak::WordBoundary),
], ],
TreeMonologue(handle.clone()), TreeMonologue(handle.id()),
MonologuesList, MonologuesList,
)) ))
.observe(assign_monologue_event) .observe(assign_monologue_event)

@ -20,9 +20,9 @@ fn main() {
}) })
.add_plugins(MonologueAssetsPlugin) .add_plugins(MonologueAssetsPlugin)
.add_plugins(TreesDebugPlugin) .add_plugins(TreesDebugPlugin)
.add_event::<DialogEvent>() .add_message::<DialogEvent>()
.add_event::<PlantTree>() .add_message::<PlantTree>()
.add_event::<AssignMonologue>() .add_message::<AssignMonologue>()
.init_state::<DialogState>() .init_state::<DialogState>()
.insert_resource(ClearColor(WHITE.into())) .insert_resource(ClearColor(WHITE.into()))
.add_systems( .add_systems(
@ -43,20 +43,20 @@ fn main() {
start_dialog start_dialog
.run_if(in_state(DebuggingState::Off)) .run_if(in_state(DebuggingState::Off))
.run_if(in_state(DialogState::None)) .run_if(in_state(DialogState::None))
.run_if(on_event::<Pointer<Click>>), .run_if(on_message::<Pointer<Click>>),
// Close the dialog box if it is idle (not choosing) // Close the dialog box if it is idle (not choosing)
end_dialog end_dialog
.run_if(in_state(DebuggingState::Off)) .run_if(in_state(DebuggingState::Off))
.run_if(in_state(DialogState::Idle)) .run_if(in_state(DialogState::Idle))
.run_if(on_event::<Pointer<Click>>), .run_if(on_message::<Pointer<Click>>),
handle_plant_tree.run_if(on_event::<PlantTree>), handle_plant_tree.run_if(on_message::<PlantTree>),
assign_monologue_to_tree assign_monologue_to_tree
.run_if(on_event::<AssignMonologue>) .run_if(on_message::<AssignMonologue>)
.after(handle_plant_tree), .after(handle_plant_tree),
dialog_engine.run_if(on_event::<DialogEvent>), dialog_engine.run_if(on_message::<DialogEvent>),
auto_scroll.run_if(any_component_added::<DialogOption>), auto_scroll.run_if(any_component_added::<DialogOption>),
dialog_box_visibility.run_if(state_changed::<DialogState>), dialog_box_visibility.run_if(state_changed::<DialogState>),
scale_window.run_if(on_event::<WindowResized>), scale_window.run_if(on_message::<WindowResized>),
), ),
) )
.add_observer(add_tree_monologue) .add_observer(add_tree_monologue)
@ -69,7 +69,7 @@ fn main() {
struct Tree; struct Tree;
#[derive(Component, PartialEq, Clone)] #[derive(Component, PartialEq, Clone)]
struct TreeMonologue(Handle<Monologue>); struct TreeMonologue(AssetId<Monologue>);
/// Initialize the trees, currently placeholders /// Initialize the trees, currently placeholders
/// Trees are 2d cards in a 3d world for flexibility /// Trees are 2d cards in a 3d world for flexibility
@ -110,19 +110,19 @@ fn init_ui(mut commands: Commands) {
} }
fn hover_dialog_box_over( fn hover_dialog_box_over(
trigger: Trigger<Pointer<Over>>, event: On<Pointer<Over>>,
mut query: Query<&mut BackgroundColor, With<DialogBox>>, mut query: Query<&mut BackgroundColor, With<DialogBox>>,
) { ) {
if let Ok(mut bg) = query.get_mut(trigger.target()) { if let Ok(mut bg) = query.get_mut(event.entity) {
bg.0.set_alpha(0.95); bg.0.set_alpha(0.95);
} }
} }
fn hover_dialog_box_out( fn hover_dialog_box_out(
trigger: Trigger<Pointer<Out>>, event: On<Pointer<Out>>,
mut query: Query<&mut BackgroundColor, With<DialogBox>>, mut query: Query<&mut BackgroundColor, With<DialogBox>>,
) { ) {
if let Ok(mut bg) = query.get_mut(trigger.target()) { if let Ok(mut bg) = query.get_mut(event.entity) {
bg.0.set_alpha(0.9); bg.0.set_alpha(0.9);
} }
} }
@ -149,7 +149,7 @@ fn auto_scroll(
); );
scroll_positions.iter_mut().for_each(|mut sp| { scroll_positions.iter_mut().for_each(|mut sp| {
sp.offset_y = f32::MAX; sp.y = f32::MAX;
}); });
} }
@ -162,9 +162,9 @@ struct DialogOption;
struct DialogLine; struct DialogLine;
/// Events that drive the dialog engine /// Events that drive the dialog engine
#[derive(Event, PartialEq)] #[derive(Message, PartialEq)]
enum DialogEvent { enum DialogEvent {
Start(Entity, Handle<Monologue>), Start(Entity, AssetId<Monologue>),
NextBatch, NextBatch,
End, End,
} }
@ -183,15 +183,15 @@ enum DialogState {
/// Start dialog /// Start dialog
fn start_dialog( fn start_dialog(
mut click_events: EventReader<Pointer<Click>>, mut click_events: MessageReader<Pointer<Click>>,
mut dialog_events: EventWriter<DialogEvent>, mut dialog_events: MessageWriter<DialogEvent>,
query: Query<&TreeMonologue, With<Tree>>, query: Query<&TreeMonologue, With<Tree>>,
) { ) {
click_events.read().for_each(|event| { click_events.read().for_each(|event| {
debug!("Click event detected in start dialog systme"); debug!("Click event detected in start dialog systme");
if let Ok(TreeMonologue(handle)) = query.get(event.target) { if let Ok(TreeMonologue(id)) = query.get(event.entity) {
debug!("Tree Monologue received, sending start dialog event"); debug!("Tree Monologue received, sending start dialog event");
dialog_events.write(DialogEvent::Start(event.target, handle.clone())); dialog_events.write(DialogEvent::Start(event.entity, *id));
dialog_events.write(DialogEvent::NextBatch); dialog_events.write(DialogEvent::NextBatch);
} }
}) })
@ -199,13 +199,13 @@ fn start_dialog(
/// When dialog is complete and you click away from the dialog box, close it out /// When dialog is complete and you click away from the dialog box, close it out
fn end_dialog( fn end_dialog(
mut click_events: EventReader<Pointer<Click>>, mut click_events: MessageReader<Pointer<Click>>,
mut dialog_events: EventWriter<DialogEvent>, mut dialog_events: MessageWriter<DialogEvent>,
query: Query<Entity, Or<(With<TreeMonologue>, With<DialogBox>, With<DialogOption>)>>, query: Query<Entity, Or<(With<TreeMonologue>, With<DialogBox>, With<DialogOption>)>>,
) { ) {
click_events.read().for_each(|event| { click_events.read().for_each(|event| {
debug!("Click even triggered end of dialog: {:?}", event.target); debug!("Click even evented end of dialog: {:?}", event.entity);
if !query.contains(event.target) { if !query.contains(event.entity) {
dialog_events.write(DialogEvent::End); dialog_events.write(DialogEvent::End);
} }
}); });
@ -214,13 +214,13 @@ fn end_dialog(
/// System which puts DialogOptions into the DialogBox /// System which puts DialogOptions into the DialogBox
fn dialog_engine( fn dialog_engine(
// React to dialog events // React to dialog events
mut events: EventReader<DialogEvent>, mut events: MessageReader<DialogEvent>,
// Reference to DialogBox // Reference to DialogBox
dialog_box: Single<Entity, With<DialogBox>>, dialog_box: Single<Entity, With<DialogBox>>,
// EntityCommands for Dialog Box // EntityCommands for Dialog Box
mut commands: Commands, mut commands: Commands,
// Handle to "active" monologue // Handle to "active" monologue
mut handle: Local<Handle<Monologue>>, mut asset_id: Local<AssetId<Monologue>>,
// Track active entity as well as the monologue // Track active entity as well as the monologue
mut tree_entity: Local<Option<Entity>>, mut tree_entity: Local<Option<Entity>>,
// Index into "active" monologue // Index into "active" monologue
@ -234,19 +234,19 @@ fn dialog_engine(
) { ) {
debug_assert!( debug_assert!(
!events.is_empty(), !events.is_empty(),
"Dialog engine is triggered by Dialog Events" "Dialog engine is evented by Dialog Events"
); );
events.read().for_each(|event| { events.read().for_each(|event| {
match event { match event {
DialogEvent::Start(e, h) => { DialogEvent::Start(e, id) => {
debug!("Dialog start: {:?}", h); debug!("Dialog start: {:?}", id);
// Set state to "Active" // Set state to "Active"
next_state.set(DialogState::Ongoing); next_state.set(DialogState::Ongoing);
// Copy monologue asset into local // Copy monologue asset into local
*handle = h.clone(); *asset_id = *id;
*tree_entity = Some(*e); *tree_entity = Some(*e);
} }
DialogEvent::NextBatch => { DialogEvent::NextBatch => {
@ -254,7 +254,7 @@ fn dialog_engine(
commands.entity(*dialog_box).with_children(|parent| { commands.entity(*dialog_box).with_children(|parent| {
// Fetch this monologue from the assets // Fetch this monologue from the assets
if let Some(monologue) = monologues.get(handle.clone().id()) { if let Some(monologue) = monologues.get(*asset_id) {
// Fetch this batch of options // Fetch this batch of options
if let Some(batch) = monologue.get(*idx) { if let Some(batch) = monologue.get(*idx) {
// Spawn the dialog options in the dialog box // Spawn the dialog options in the dialog box
@ -262,7 +262,7 @@ fn dialog_engine(
parent.spawn(( parent.spawn((
Text::new(line.clone()), Text::new(line.clone()),
DialogOption, DialogOption,
TextLayout::new(JustifyText::Left, LineBreak::NoWrap), TextLayout::new(Justify::Left, LineBreak::NoWrap),
)); ));
}); });
*idx += 1; *idx += 1;
@ -290,7 +290,7 @@ fn dialog_engine(
*idx = 0; *idx = 0;
// Wipe the current handle from context // Wipe the current handle from context
*handle = Handle::default(); *asset_id = AssetId::default();
// Set state to "Active" // Set state to "Active"
next_state.set(DialogState::None); next_state.set(DialogState::None);
@ -300,20 +300,20 @@ fn dialog_engine(
} }
fn hover_dialog_option_over( fn hover_dialog_option_over(
trigger: Trigger<Pointer<Over>>, event: On<Pointer<Over>>,
mut query: Query<(&mut TextColor, &mut BackgroundColor)>, mut query: Query<(&mut TextColor, &mut BackgroundColor)>,
) { ) {
if let Ok((mut tc, mut bg)) = query.get_mut(trigger.target()) { if let Ok((mut tc, mut bg)) = query.get_mut(event.entity) {
*tc = TextColor(DARK_ORANGE.into()); *tc = TextColor(DARK_ORANGE.into());
bg.0.set_alpha(1.0); bg.0.set_alpha(1.0);
} }
} }
fn hover_dialog_option_out( fn hover_dialog_option_out(
trigger: Trigger<Pointer<Out>>, event: On<Pointer<Out>>,
mut query: Query<(&mut TextColor, &mut BackgroundColor)>, mut query: Query<(&mut TextColor, &mut BackgroundColor)>,
) { ) {
if let Ok((mut tc, mut bg)) = query.get_mut(trigger.target()) { if let Ok((mut tc, mut bg)) = query.get_mut(event.entity) {
*tc = TextColor(ORANGE.into()); *tc = TextColor(ORANGE.into());
bg.0.set_alpha(0.0); bg.0.set_alpha(0.0);
} }
@ -331,12 +331,12 @@ fn dialog_box_visibility(
} }
fn add_tree_monologue( fn add_tree_monologue(
trigger: Trigger<OnAdd, TreeMonologue>, event: On<Add, TreeMonologue>,
query: Query<&MeshMaterial3d<StandardMaterial>>, query: Query<&MeshMaterial3d<StandardMaterial>>,
mut materials: ResMut<Assets<StandardMaterial>>, mut materials: ResMut<Assets<StandardMaterial>>,
) { ) {
// Get the affected entity's MeshMaterial3d // Get the affected entity's MeshMaterial3d
if let Ok(handle) = query.get(trigger.target()) { if let Ok(handle) = query.get(event.entity) {
// Get the concrete StandardMaterial // Get the concrete StandardMaterial
if let Some(material) = materials.get_mut(handle) { if let Some(material) = materials.get_mut(handle) {
material.base_color = WHITE.with_alpha(1.0).into(); material.base_color = WHITE.with_alpha(1.0).into();
@ -345,12 +345,12 @@ fn add_tree_monologue(
} }
fn remove_tree_monologue( fn remove_tree_monologue(
trigger: Trigger<OnRemove, TreeMonologue>, event: On<Remove, TreeMonologue>,
query: Query<&MeshMaterial3d<StandardMaterial>>, query: Query<&MeshMaterial3d<StandardMaterial>>,
mut materials: ResMut<Assets<StandardMaterial>>, mut materials: ResMut<Assets<StandardMaterial>>,
) { ) {
// Get the affected entity's MeshMaterial3d // Get the affected entity's MeshMaterial3d
if let Ok(handle) = query.get(trigger.target()) { if let Ok(handle) = query.get(event.entity) {
// Get the concrete StandardMaterial // Get the concrete StandardMaterial
if let Some(material) = materials.get_mut(handle) { if let Some(material) = materials.get_mut(handle) {
// Make it dull // Make it dull
@ -359,7 +359,7 @@ fn remove_tree_monologue(
} }
} }
fn scale_window(events: EventReader<WindowResized>, mut window: Single<&mut Window>) { fn scale_window(events: MessageReader<WindowResized>, mut window: Single<&mut Window>) {
debug_assert!(!events.is_empty(), "Only scale window when resized"); debug_assert!(!events.is_empty(), "Only scale window when resized");
let r = &mut window.resolution; let r = &mut window.resolution;
@ -388,22 +388,22 @@ fn scale_window(events: EventReader<WindowResized>, mut window: Single<&mut Wind
); );
} }
/// Load all monologues so they are in the asset store and trigger on-load events /// Load all monologues so they are in the asset store and event on-load events
fn load_monologues(server: ResMut<AssetServer>, mut loaded_assets: Local<Vec<Handle<Monologue>>>) { fn load_monologues(server: ResMut<AssetServer>, mut loaded_assets: Local<Vec<AssetId<Monologue>>>) {
*loaded_assets = include_str!("../../../assets/trees/MONOLOGUES") *loaded_assets = include_str!("../../../assets/trees/MONOLOGUES")
.split("\n") .split("\n")
.map(|path| server.load(path)) .map(|path| server.load(path).id())
.collect(); .collect();
} }
#[derive(Event)] #[derive(Message)]
struct PlantTree(Option<Handle<Monologue>>); struct PlantTree(Option<AssetId<Monologue>>);
/// Plan a tree in the world /// Plan a tree in the world
/// Handles random placement, 3d model, materials, and observers /// Handles random placement, 3d model, materials, and observers
fn handle_plant_tree( fn handle_plant_tree(
mut events: EventReader<PlantTree>, mut events: MessageReader<PlantTree>,
mut assignments: EventWriter<AssignMonologue>, mut assignments: MessageWriter<AssignMonologue>,
trees: Query<Entity, With<Tree>>, trees: Query<Entity, With<Tree>>,
server: Res<AssetServer>, server: Res<AssetServer>,
mut commands: Commands, mut commands: Commands,
@ -442,21 +442,22 @@ fn handle_plant_tree(
tree.insert((mesh, material, transform)); tree.insert((mesh, material, transform));
if let Some(handle) = assignment { if let Some(id) = assignment {
assignments.write(AssignMonologue(handle.clone())); assignments.write(AssignMonologue(*id));
} }
}); });
} }
#[derive(Event, Debug)] #[derive(Message, Debug)]
struct AssignMonologue(Handle<Monologue>); struct AssignMonologue(AssetId<Monologue>);
/// Assign the given monologue to a tree /// Assign the given monologue to a tree
fn assign_monologue_to_tree( fn assign_monologue_to_tree(
mut events: EventReader<AssignMonologue>, mut events: MessageReader<AssignMonologue>,
query: Query<Entity, (With<Tree>, Without<TreeMonologue>)>, query: Query<Entity, (With<Tree>, Without<TreeMonologue>)>,
mut notice: ResMut<Notice>, mut notice: ResMut<Notice>,
mut commands: Commands, mut commands: Commands,
server: Res<AssetServer>,
) { ) {
// Kinda a weird hack because query does not update // Kinda a weird hack because query does not update
// If we do this inline we assign new monologues to the same first tree // If we do this inline we assign new monologues to the same first tree
@ -467,10 +468,10 @@ fn assign_monologue_to_tree(
// Get a valid tree to assign an entity to // Get a valid tree to assign an entity to
if let Some(tree) = t.next() { if let Some(tree) = t.next() {
// Create the TreeMonologue component // Create the TreeMonologue component
let monologue = TreeMonologue(event.0.clone()); let monologue = TreeMonologue(event.0);
// Insert the component to the entity // Insert the component to the entity
commands.entity(tree).insert(monologue); commands.entity(tree).insert(monologue);
} else if let Some(path) = event.0.path() { } else if let Some(path) = server.get_path(event.0) {
error!("No trees avaliable for {path:?}"); error!("No trees avaliable for {path:?}");
notice.0 = format!("No trees avaliable for {path:?}"); notice.0 = format!("No trees avaliable for {path:?}");
} else { } else {
@ -481,12 +482,12 @@ fn assign_monologue_to_tree(
} }
/// On startup, plant a forest (add a few trees to the game) /// On startup, plant a forest (add a few trees to the game)
fn plant_forest(monos: Res<Assets<Monologue>>, mut e_trees: EventWriter<PlantTree>) { fn plant_forest(monos: Res<Assets<Monologue>>, mut e_trees: MessageWriter<PlantTree>) {
let mut i = 10; let mut i = 10;
for id in monos.ids() { for id in monos.ids() {
debug!("Planting tree with monologue {:?}", id); debug!("Planting tree with monologue {:?}", id);
if i > 5 { if i > 5 {
e_trees.write(PlantTree(Some(Handle::Weak(id)))); e_trees.write(PlantTree(Some(id)));
} else if i > 0 { } else if i > 0 {
e_trees.write(PlantTree(None)); e_trees.write(PlantTree(None));
} else { } else {

@ -25,7 +25,7 @@ impl Plugin for DebuggingPlugin {
(toggle_light_gizmo, toggle_aabb_gizmo).run_if(state_changed::<DebuggingState>), (toggle_light_gizmo, toggle_aabb_gizmo).run_if(state_changed::<DebuggingState>),
( (
(hover_mesh, hover_ui) (hover_mesh, hover_ui)
.run_if(on_event::<Pointer<Over>>.or(on_event::<Pointer<Out>>)), .run_if(on_message::<Pointer<Over>>.or(on_message::<Pointer<Out>>)),
tooltip_follow.run_if(any_component_changed::<Window>), tooltip_follow.run_if(any_component_changed::<Window>),
tooltip_update_transform.run_if(any_component_changed::<Transform>), tooltip_update_transform.run_if(any_component_changed::<Transform>),
tooltip_draw.run_if(any_component_changed::<ToolTip>), tooltip_draw.run_if(any_component_changed::<ToolTip>),
@ -79,7 +79,7 @@ fn init_debug_ui(mut commands: Commands) {
children![(TextColor(BLACK.into()), Text::new(VERSION),)], children![(TextColor(BLACK.into()), Text::new(VERSION),)],
GlobalZIndex(i32::MAX - 1), GlobalZIndex(i32::MAX - 1),
BorderRadius::all(Val::Px(5.0)), BorderRadius::all(Val::Px(5.0)),
BorderColor(BLACK.into()), BorderColor::all(BLACK),
BackgroundColor(Color::WHITE), BackgroundColor(Color::WHITE),
Node { Node {
max_width: Val::Percent(50.0), max_width: Val::Percent(50.0),
@ -100,7 +100,7 @@ fn init_debug_ui(mut commands: Commands) {
)], )],
GlobalZIndex(i32::MAX - 1), GlobalZIndex(i32::MAX - 1),
BorderRadius::all(Val::Px(5.0)), BorderRadius::all(Val::Px(5.0)),
BorderColor(BLACK.into()), BorderColor::all(BLACK),
BackgroundColor(Color::WHITE), BackgroundColor(Color::WHITE),
Node { Node {
max_width: Val::Percent(50.0), max_width: Val::Percent(50.0),
@ -129,7 +129,7 @@ fn init_debug_ui(mut commands: Commands) {
Name::new("Debug Indicator"), Name::new("Debug Indicator"),
GlobalZIndex(i32::MAX - 1), GlobalZIndex(i32::MAX - 1),
BorderRadius::all(Val::Px(5.0)), BorderRadius::all(Val::Px(5.0)),
BorderColor(BLACK.into()), BorderColor::all(BLACK),
BackgroundColor(Color::WHITE), BackgroundColor(Color::WHITE),
children![(Text("Debug: OFF".into()), TextColor(BLACK.into()),)], children![(Text("Debug: OFF".into()), TextColor(BLACK.into()),)],
Button, Button,
@ -146,7 +146,7 @@ fn init_debug_ui(mut commands: Commands) {
..default() ..default()
}, },
BorderRadius::all(Val::Px(5.0)), BorderRadius::all(Val::Px(5.0)),
BorderColor(BLACK.into()), BorderColor::all(BLACK),
BackgroundColor(Color::WHITE), BackgroundColor(Color::WHITE),
)) ))
.with_children(|parent| { .with_children(|parent| {
@ -197,7 +197,7 @@ fn init_debug_ui(mut commands: Commands) {
Pickable::IGNORE, Pickable::IGNORE,
GlobalZIndex(i32::MAX), GlobalZIndex(i32::MAX),
BorderRadius::all(Val::Px(5.0)), BorderRadius::all(Val::Px(5.0)),
BorderColor(BLACK.into()), BorderColor::all(BLACK),
BackgroundColor(Color::WHITE), BackgroundColor(Color::WHITE),
Node { Node {
position_type: PositionType::Absolute, position_type: PositionType::Absolute,
@ -219,7 +219,7 @@ fn init_debug_ui(mut commands: Commands) {
}); });
} }
/// Toggles the debug state from off -> on // off -> on when triggered /// Toggles the debug state from off -> on // off -> on when evented
fn toggle_debug_state( fn toggle_debug_state(
mut next: ResMut<NextState<DebuggingState>>, mut next: ResMut<NextState<DebuggingState>>,
curr: Res<State<DebuggingState>>, curr: Res<State<DebuggingState>>,
@ -306,24 +306,24 @@ fn lock_tooltip(q: Query<Option<&ToolTip>>, hover_map: Res<HoverMap>, mut comman
} }
/// when a tooltip is added to an entity, insert the text component /// when a tooltip is added to an entity, insert the text component
fn add_tooltip_text(trigger: Trigger<OnAdd, ToolTip>, mut commands: Commands) { fn add_tooltip_text(event: On<Add, ToolTip>, mut commands: Commands) {
info!("Adding text2d to {:?}", trigger.target()); info!("Adding text2d to {:?}", event.entity);
commands.entity(trigger.target()).with_children(|parent| { commands.entity(event.entity).with_children(|parent| {
parent.spawn(( parent.spawn((
TextColor(BLACK.into()), TextColor(BLACK.into()),
Text2d("Placeholder".into()), Text2d("Placeholder".into()),
Transform::from_xyz(0.5, 0.0, 1.0).with_scale(Vec3::splat(0.01)), Transform::from_xyz(0.5, 0.0, 1.0).with_scale(Vec3::splat(0.01)),
Anchor::CenterLeft, Anchor::CENTER_LEFT,
DebuggingState::On, DebuggingState::On,
)); ));
}); });
} }
fn populate_tooltip_info( fn populate_tooltip_info(
trigger: Trigger<OnAdd, ToolTip>, event: On<Add, ToolTip>,
mut tooltips: Query<(&mut ToolTip, Entity, &Transform, &Name)>, mut tooltips: Query<(&mut ToolTip, Entity, &Transform, &Name)>,
) { ) {
if let Ok((mut tt, e, t, n)) = tooltips.get_mut(trigger.target()) { if let Ok((mut tt, e, t, n)) = tooltips.get_mut(event.entity) {
tt.insert("ID", format!("{e}")); tt.insert("ID", format!("{e}"));
tt.insert("Name", n.into()); tt.insert("Name", n.into());
tt.insert( tt.insert(
@ -350,13 +350,13 @@ fn tooltip_update_transform(mut events: Query<(&mut ToolTip, &Transform), Change
/// when a tooltip is removed to an entity, remove the text components /// when a tooltip is removed to an entity, remove the text components
fn remove_tooltip_text( fn remove_tooltip_text(
trigger: Trigger<OnRemove, ToolTip>, event: On<Remove, ToolTip>,
mut commands: Commands, mut commands: Commands,
parents: Query<&Children>, parents: Query<&Children>,
text_2d: Query<Entity, With<Text2d>>, text_2d: Query<Entity, With<Text2d>>,
) { ) {
// Get lis of children for this entity // Get lis of children for this entity
let children = parents.get(trigger.target()).unwrap(); let children = parents.get(event.entity).unwrap();
// Find the tooltip text child // Find the tooltip text child
if let Some(child) = children.iter().find_map(|child| text_2d.get(child).ok()) { if let Some(child) = children.iter().find_map(|child| text_2d.get(child).ok()) {
// Remove it from the world // Remove it from the world
@ -381,14 +381,14 @@ fn tooltip_draw(
/// When you hover over a mesh, update the tooltip with some info /// When you hover over a mesh, update the tooltip with some info
fn hover_mesh( fn hover_mesh(
mut over_events: EventReader<Pointer<Over>>, mut over_events: MessageReader<Pointer<Over>>,
mut out_events: EventReader<Pointer<Out>>, mut out_events: MessageReader<Pointer<Out>>,
mut tooltip: ResMut<ToolTip>, mut tooltip: ResMut<ToolTip>,
meshes: Query<(&Transform, Option<&Name>), Or<(With<Mesh3d>, With<Mesh2d>)>>, meshes: Query<(&Transform, Option<&Name>), Or<(With<Mesh3d>, With<Mesh2d>)>>,
) { ) {
out_events out_events
.read() .read()
.filter_map(|Pointer { target, .. }| meshes.contains(*target).then_some(*target)) .filter_map(|Pointer { entity, .. }| meshes.contains(*entity).then_some(*entity))
.for_each(|_| { .for_each(|_| {
tooltip.remove("ID"); tooltip.remove("ID");
tooltip.remove("Pos"); tooltip.remove("Pos");
@ -396,7 +396,7 @@ fn hover_mesh(
}); });
over_events over_events
.read() .read()
.filter_map(|Pointer { target, .. }| meshes.contains(*target).then_some(*target)) .filter_map(|Pointer { entity, .. }| meshes.contains(*entity).then_some(*entity))
.for_each(|e| { .for_each(|e| {
if let Ok((t, n)) = meshes.get(e) { if let Ok((t, n)) = meshes.get(e) {
let pos = (t.translation.x, t.translation.y, t.translation.z); let pos = (t.translation.x, t.translation.y, t.translation.z);
@ -414,21 +414,21 @@ fn hover_mesh(
} }
fn hover_ui( fn hover_ui(
mut over_events: EventReader<Pointer<Over>>, mut over_events: MessageReader<Pointer<Over>>,
mut out_events: EventReader<Pointer<Out>>, mut out_events: MessageReader<Pointer<Out>>,
mut tooltip: ResMut<ToolTip>, mut tooltip: ResMut<ToolTip>,
nodes: Query<Option<&Name>, With<Node>>, nodes: Query<Option<&Name>, With<Node>>,
) { ) {
out_events out_events
.read() .read()
.filter_map(|Pointer { target, .. }| nodes.contains(*target).then_some(*target)) .filter_map(|Pointer { entity, .. }| nodes.contains(*entity).then_some(*entity))
.for_each(|_| { .for_each(|_| {
tooltip.remove("ID"); tooltip.remove("ID");
tooltip.remove("Name"); tooltip.remove("Name");
}); });
over_events over_events
.read() .read()
.filter_map(|Pointer { target, .. }| nodes.contains(*target).then_some(*target)) .filter_map(|Pointer { entity, .. }| nodes.contains(*entity).then_some(*entity))
.for_each(|e| { .for_each(|e| {
if let Ok(n) = nodes.get(e) { if let Ok(n) = nodes.get(e) {
let name = match n { let name = match n {
@ -538,7 +538,7 @@ fn track_window_info(window: Single<&Window>, mut info: ResMut<WindowInfo>) {
/// Toggle the debug state when a button is clicked /// Toggle the debug state when a button is clicked
fn toggle_debug( fn toggle_debug(
_trigger: Trigger<Pointer<Click>>, _event: On<Pointer<Click>>,
curr: Res<State<DebuggingState>>, curr: Res<State<DebuggingState>>,
mut next: ResMut<NextState<DebuggingState>>, mut next: ResMut<NextState<DebuggingState>>,
) { ) {
@ -548,8 +548,8 @@ fn toggle_debug(
}); });
} }
fn close_on_click(trigger: Trigger<Pointer<Click>>, mut query: Query<&mut Visibility>) { fn close_on_click(event: On<Pointer<Click>>, mut query: Query<&mut Visibility>) {
if let Ok(mut v) = query.get_mut(trigger.target()) { if let Ok(mut v) = query.get_mut(event.entity) {
*v = Visibility::Hidden; *v = Visibility::Hidden;
} }
} }

@ -19,7 +19,11 @@ pub use std::fmt::Display;
// Community libraries // Community libraries
pub use bevy::{ pub use bevy::{
asset::{AssetLoader, AssetMetaCheck, LoadContext, LoadState, LoadedFolder, io::Reader}, asset::{
AssetLoader, AssetMetaCheck, LoadContext, LoadState, LoadedFolder, RenderAssetUsages,
io::Reader, uuid_handle,
},
camera::{primitives::*, visibility::*, *},
color::palettes::css::*, color::palettes::css::*,
gizmos::{aabb::AabbGizmoPlugin, light::LightGizmoPlugin}, gizmos::{aabb::AabbGizmoPlugin, light::LightGizmoPlugin},
input::{ input::{
@ -29,13 +33,16 @@ pub use bevy::{
mouse::MouseMotion, mouse::MouseMotion,
mouse::{MouseScrollUnit, MouseWheel}, mouse::{MouseScrollUnit, MouseWheel},
}, },
math::FloatOrd,
pbr::wireframe::{WireframeConfig, WireframePlugin}, pbr::wireframe::{WireframeConfig, WireframePlugin},
platform::{collections::HashMap, hash::RandomState}, platform::{collections::HashMap, hash::RandomState},
prelude::*, prelude::*,
reflect::TypePath, reflect::TypePath,
sprite::AlphaMode2d, render::render_resource::{Extent3d, TextureDimension, TextureFormat, TextureUsages},
sprite_render::*,
window::{WindowResized, WindowResolution}, window::{WindowResized, WindowResolution},
}; };
pub use itertools::Itertools;
pub use serde::Deserialize; pub use serde::Deserialize;
pub use thiserror::Error; pub use thiserror::Error;

@ -1,6 +1,3 @@
use bevy::render::mesh::MeshAabb;
use bevy::render::primitives::Aabb;
use super::*; use super::*;
pub struct ParallaxPlugin; pub struct ParallaxPlugin;

@ -6,7 +6,7 @@ pub struct Physics2dPlugin;
impl Plugin for Physics2dPlugin { impl Plugin for Physics2dPlugin {
fn build(&self, app: &mut App) { fn build(&self, app: &mut App) {
app.add_plugins((PhysicsPlugins::default(), PhysicsDebugPlugin::default())) app.add_plugins((PhysicsPlugins::default(), PhysicsDebugPlugin))
.add_systems( .add_systems(
Update, Update,
toggle_physics_debug_render.run_if(state_changed::<DebuggingState>), toggle_physics_debug_render.run_if(state_changed::<DebuggingState>),

@ -6,7 +6,7 @@ pub struct Physics3dPlugin;
impl Plugin for Physics3dPlugin { impl Plugin for Physics3dPlugin {
fn build(&self, app: &mut App) { fn build(&self, app: &mut App) {
app.add_plugins((PhysicsDebugPlugin::default(), PhysicsPlugins::default())) app.add_plugins((PhysicsDebugPlugin, PhysicsPlugins::default()))
.add_systems( .add_systems(
Update, Update,
toggle_physics_debug_render.run_if(state_changed::<DebuggingState>), toggle_physics_debug_render.run_if(state_changed::<DebuggingState>),

@ -39,42 +39,38 @@ impl Default for Style {
} }
fn add_ui_button(added: Query<Entity, Added<Button>>, mut commands: Commands) { fn add_ui_button(added: Query<Entity, Added<Button>>, mut commands: Commands) {
fn over( fn over(event: On<Pointer<Over>>, mut query: Query<&mut BorderColor>, style: Res<Style>) {
trigger: Trigger<Pointer<Over>>, if let Ok(mut bc) = query.get_mut(event.entity) {
mut query: Query<&mut BorderColor>, debug!("pointer over {:?}", event.entity);
style: Res<Style>, bc.set_all(style.accent);
) {
if let Ok(mut bc) = query.get_mut(trigger.target()) {
debug!("pointer over {:?}", trigger.target());
bc.0 = style.accent;
} }
} }
fn out(trigger: Trigger<Pointer<Out>>, mut query: Query<&mut BorderColor>, style: Res<Style>) { fn out(event: On<Pointer<Out>>, mut query: Query<&mut BorderColor>, style: Res<Style>) {
if let Ok(mut bc) = query.get_mut(trigger.target()) { if let Ok(mut bc) = query.get_mut(event.entity) {
debug!("pointer out {:?}", trigger.target()); debug!("pointer out {:?}", event.entity);
bc.0 = style.secondary; bc.set_all(style.secondary);
} }
} }
fn pressed( fn pressed(
trigger: Trigger<Pointer<Pressed>>, event: On<Pointer<Press>>,
mut query: Query<&mut BackgroundColor>, mut query: Query<&mut BackgroundColor>,
style: Res<Style>, style: Res<Style>,
) { ) {
if let Ok(mut bg) = query.get_mut(trigger.target()) { if let Ok(mut bg) = query.get_mut(event.entity) {
debug!("pointer pressed {:?}", trigger.target()); debug!("pointer pressed {:?}", event.entity);
bg.0 = style.accent; bg.0 = style.accent;
} }
} }
fn released( fn released(
trigger: Trigger<Pointer<Released>>, event: On<Pointer<Release>>,
mut query: Query<&mut BackgroundColor>, mut query: Query<&mut BackgroundColor>,
style: Res<Style>, style: Res<Style>,
) { ) {
if let Ok(mut bg) = query.get_mut(trigger.target()) { if let Ok(mut bg) = query.get_mut(event.entity) {
debug!("pointer released {:?}", trigger.target()); debug!("pointer released {:?}", event.entity);
bg.0 = style.primary; bg.0 = style.primary;
} }
} }
@ -93,31 +89,31 @@ fn add_ui_button(added: Query<Entity, Added<Button>>, mut commands: Commands) {
fn add_ui_text(added: Query<Entity, Added<Text>>, mut commands: Commands) { fn add_ui_text(added: Query<Entity, Added<Text>>, mut commands: Commands) {
fn pressed( fn pressed(
trigger: Trigger<Pointer<Pressed>>, event: On<Pointer<Press>>,
mut query: Query<(&mut TextColor, &ChildOf)>, mut query: Query<(&mut TextColor, &ChildOf)>,
buttons: Query<Entity, With<Button>>, buttons: Query<Entity, With<Button>>,
style: Res<Style>, style: Res<Style>,
) { ) {
// ONLY DO THIS IF CHILD OF BUTTON // ONLY DO THIS IF CHILD OF BUTTON
if let Ok((mut tc, ChildOf(p))) = query.get_mut(trigger.target()) if let Ok((mut tc, ChildOf(p))) = query.get_mut(event.entity)
&& buttons.contains(*p) && buttons.contains(*p)
{ {
debug!("pointer pressed {:?}", trigger.target()); debug!("pointer pressed {:?}", event.entity);
tc.0 = style.primary; tc.0 = style.primary;
} }
} }
fn released( fn released(
trigger: Trigger<Pointer<Released>>, event: On<Pointer<Release>>,
mut query: Query<(&mut TextColor, &ChildOf)>, mut query: Query<(&mut TextColor, &ChildOf)>,
buttons: Query<Entity, With<Button>>, buttons: Query<Entity, With<Button>>,
style: Res<Style>, style: Res<Style>,
) { ) {
// ONLY DO THIS IF CHILD OF BUTTON // ONLY DO THIS IF CHILD OF BUTTON
if let Ok((mut tc, ChildOf(p))) = query.get_mut(trigger.target()) if let Ok((mut tc, ChildOf(p))) = query.get_mut(event.entity)
&& buttons.contains(*p) && buttons.contains(*p)
{ {
debug!("pointer released {:?}", trigger.target()); debug!("pointer released {:?}", event.entity);
tc.0 = style.secondary; tc.0 = style.secondary;
} }
} }
@ -152,7 +148,7 @@ fn add_ui_node(
// Add extra stuff for non-text nodes // Add extra stuff for non-text nodes
this.insert_if_new(BackgroundColor(style.primary)); this.insert_if_new(BackgroundColor(style.primary));
this.insert_if_new(BorderColor(style.secondary)); this.insert_if_new(BorderColor::all(style.secondary));
this.insert_if_new(BorderRadius::all(Val::Px(5.0))); this.insert_if_new(BorderRadius::all(Val::Px(5.0)));
} }
}) })
@ -193,20 +189,20 @@ pub fn sync_singleton_to_ui<C: Component + Default + Display>(
} }
/// Updates the scroll position of scrollable nodes in response to mouse input /// Updates the scroll position of scrollable nodes in response to mouse input
pub fn scroll(trigger: Trigger<Pointer<Scroll>>, mut scrollers: Query<&mut ScrollPosition>) { pub fn scroll(event: On<Pointer<Scroll>>, mut scrollers: Query<&mut ScrollPosition>) {
let Pointer { let Pointer {
event: Scroll { unit, x, y, .. }, event: Scroll { unit, x, y, .. },
.. ..
} = trigger.event(); } = event.event();
let (dx, dy) = match unit { let (dx, dy) = match unit {
MouseScrollUnit::Line => (x * 16.0, y * 16.0), MouseScrollUnit::Line => (x * 16.0, y * 16.0),
MouseScrollUnit::Pixel => (x * 1., y * 1.), MouseScrollUnit::Pixel => (x * 1., y * 1.),
}; };
if let Ok(mut pos) = scrollers.get_mut(trigger.target()) { if let Ok(mut pos) = scrollers.get_mut(event.entity) {
pos.offset_x -= dx; pos.x -= dx;
pos.offset_y -= dy; pos.y -= dy;
} }
} }

Loading…
Cancel
Save