|
|
|
|
@ -15,7 +15,7 @@ fn main() {
|
|
|
|
|
title: "flappy bird (with rewind)".into(),
|
|
|
|
|
name: "flappy".into(),
|
|
|
|
|
game_type: GameType::Two,
|
|
|
|
|
target_resolution: (360.0, 640.0).into(),
|
|
|
|
|
target_resolution: (360, 640).into(),
|
|
|
|
|
..default()
|
|
|
|
|
},
|
|
|
|
|
Physics2dPlugin,
|
|
|
|
|
@ -99,8 +99,8 @@ fn main() {
|
|
|
|
|
(update_tooltip, debug_trail).run_if(in_state(DebuggingState::On)),
|
|
|
|
|
// TODO: Add run_if to this system
|
|
|
|
|
update_batch_position.run_if(any_component_changed::<Batch>),
|
|
|
|
|
move_batches.run_if(on_message::<CollisionStarted>.or(on_message::<CollisionEnded>)),
|
|
|
|
|
manage_score.run_if(on_message::<CollisionStarted>.or(on_message::<CollisionEnded>)),
|
|
|
|
|
move_batches.run_if(on_message::<CollisionStart>.or(on_message::<CollisionEnd>)),
|
|
|
|
|
manage_score.run_if(on_message::<CollisionStart>.or(on_message::<CollisionEnd>)),
|
|
|
|
|
shimmer_button::<RewindButton>.run_if(in_state(PlayerState::Stasis)),
|
|
|
|
|
shimmer_button::<FlapButton>.run_if(in_state(PlayerState::Pause)),
|
|
|
|
|
),
|
|
|
|
|
@ -142,7 +142,7 @@ struct Tape {
|
|
|
|
|
capacity: usize,
|
|
|
|
|
linear_velocities: VecDeque<LinearVelocity>,
|
|
|
|
|
angular_velocities: VecDeque<AngularVelocity>,
|
|
|
|
|
external_impulses: VecDeque<ExternalImpulse>,
|
|
|
|
|
constant_forces: VecDeque<ConstantForce>,
|
|
|
|
|
positions: VecDeque<Position>,
|
|
|
|
|
rotations: VecDeque<Rotation>,
|
|
|
|
|
}
|
|
|
|
|
@ -153,7 +153,7 @@ impl Tape {
|
|
|
|
|
capacity,
|
|
|
|
|
linear_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),
|
|
|
|
|
rotations: VecDeque::with_capacity(capacity),
|
|
|
|
|
}
|
|
|
|
|
@ -163,7 +163,7 @@ impl Tape {
|
|
|
|
|
&mut self,
|
|
|
|
|
lv: LinearVelocity,
|
|
|
|
|
av: AngularVelocity,
|
|
|
|
|
ei: ExternalImpulse,
|
|
|
|
|
ei: ConstantForce,
|
|
|
|
|
p: Position,
|
|
|
|
|
r: Rotation,
|
|
|
|
|
) {
|
|
|
|
|
@ -171,14 +171,14 @@ impl Tape {
|
|
|
|
|
if self.linear_velocities.len() == self.capacity {
|
|
|
|
|
self.linear_velocities.pop_front().unwrap();
|
|
|
|
|
self.angular_velocities.pop_front().unwrap();
|
|
|
|
|
self.external_impulses.pop_front().unwrap();
|
|
|
|
|
self.constant_forces.pop_front().unwrap();
|
|
|
|
|
self.positions.pop_front().unwrap();
|
|
|
|
|
self.rotations.pop_front().unwrap();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
self.linear_velocities.push_back(lv);
|
|
|
|
|
self.angular_velocities.push_back(av);
|
|
|
|
|
self.external_impulses.push_back(ei);
|
|
|
|
|
self.constant_forces.push_back(ei);
|
|
|
|
|
self.positions.push_back(p);
|
|
|
|
|
self.rotations.push_back(r);
|
|
|
|
|
}
|
|
|
|
|
@ -188,7 +188,7 @@ impl Tape {
|
|
|
|
|
) -> Option<(
|
|
|
|
|
LinearVelocity,
|
|
|
|
|
AngularVelocity,
|
|
|
|
|
ExternalImpulse,
|
|
|
|
|
ConstantForce,
|
|
|
|
|
Position,
|
|
|
|
|
Rotation,
|
|
|
|
|
)> {
|
|
|
|
|
@ -197,10 +197,10 @@ impl Tape {
|
|
|
|
|
} else {
|
|
|
|
|
let lv = self.linear_velocities.pop_back().unwrap();
|
|
|
|
|
let av = self.angular_velocities.pop_back().unwrap();
|
|
|
|
|
let ei = self.external_impulses.pop_back().unwrap();
|
|
|
|
|
let cf = self.constant_forces.pop_back().unwrap();
|
|
|
|
|
let p = self.positions.pop_back().unwrap();
|
|
|
|
|
let r = self.rotations.pop_back().unwrap();
|
|
|
|
|
Some((lv, av, ei, p, r))
|
|
|
|
|
Some((lv, av, cf, p, r))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
@ -214,7 +214,7 @@ fn init_bird(mut commands: Commands, bird_assets: Res<BirdAssets>) {
|
|
|
|
|
RigidBody::Static,
|
|
|
|
|
Collider::rectangle(1.0, 1.0),
|
|
|
|
|
Mass(10.0),
|
|
|
|
|
ExternalImpulse::default().with_persistence(false),
|
|
|
|
|
ConstantForce::default(),
|
|
|
|
|
MaxLinearSpeed(500.0),
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
@ -349,7 +349,7 @@ fn populate_ceiling(
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn move_pipe(
|
|
|
|
|
event: On<OnInsert, Batch>,
|
|
|
|
|
event: On<Insert, Batch>,
|
|
|
|
|
mut pipes: Query<(&Batch, &Pipe, &mut Transform)>,
|
|
|
|
|
rand: Res<Rand>,
|
|
|
|
|
) {
|
|
|
|
|
@ -524,42 +524,42 @@ fn init_ui(mut commands: Commands, server: Res<AssetServer>) {
|
|
|
|
|
parent.spawn((
|
|
|
|
|
Text::new("Game Over...?"),
|
|
|
|
|
TextColor(BLACK.into()),
|
|
|
|
|
TextLayout::new_with_justify(JustifyText::Center),
|
|
|
|
|
TextLayout::new_with_justify(Justify::Center),
|
|
|
|
|
));
|
|
|
|
|
parent.spawn((
|
|
|
|
|
SyncResource::<Score>::default(),
|
|
|
|
|
Text::default(),
|
|
|
|
|
TextColor(BLACK.into()),
|
|
|
|
|
TextLayout::new_with_justify(JustifyText::Center),
|
|
|
|
|
TextLayout::new_with_justify(Justify::Center),
|
|
|
|
|
));
|
|
|
|
|
parent.spawn((
|
|
|
|
|
SyncResource::<LongestRun>::default(),
|
|
|
|
|
Text::default(),
|
|
|
|
|
TextColor(BLACK.into()),
|
|
|
|
|
TextLayout::new_with_justify(JustifyText::Center),
|
|
|
|
|
TextLayout::new_with_justify(Justify::Center),
|
|
|
|
|
));
|
|
|
|
|
parent.spawn((
|
|
|
|
|
SyncResource::<Deaths>::default(),
|
|
|
|
|
Text::default(),
|
|
|
|
|
TextColor(BLACK.into()),
|
|
|
|
|
TextLayout::new_with_justify(JustifyText::Center),
|
|
|
|
|
TextLayout::new_with_justify(Justify::Center),
|
|
|
|
|
));
|
|
|
|
|
parent.spawn((
|
|
|
|
|
SyncResource::<Flaps>::default(),
|
|
|
|
|
Text::default(),
|
|
|
|
|
TextColor(BLACK.into()),
|
|
|
|
|
TextLayout::new_with_justify(JustifyText::Center),
|
|
|
|
|
TextLayout::new_with_justify(Justify::Center),
|
|
|
|
|
));
|
|
|
|
|
parent.spawn((
|
|
|
|
|
SyncResource::<RewindFrames>::default(),
|
|
|
|
|
Text::default(),
|
|
|
|
|
TextColor(BLACK.into()),
|
|
|
|
|
TextLayout::new_with_justify(JustifyText::Center),
|
|
|
|
|
TextLayout::new_with_justify(Justify::Center),
|
|
|
|
|
));
|
|
|
|
|
parent.spawn((
|
|
|
|
|
Text::new("Press R to Rewind"),
|
|
|
|
|
TextColor(BLACK.into()),
|
|
|
|
|
TextLayout::new_with_justify(JustifyText::Center),
|
|
|
|
|
TextLayout::new_with_justify(Justify::Center),
|
|
|
|
|
));
|
|
|
|
|
parent
|
|
|
|
|
.spawn((Node {
|
|
|
|
|
@ -589,7 +589,7 @@ fn init_ui(mut commands: Commands, server: Res<AssetServer>) {
|
|
|
|
|
{
|
|
|
|
|
fn quit_game(
|
|
|
|
|
_event: On<Pointer<Click>>,
|
|
|
|
|
mut exit: EventWriter<AppExit>,
|
|
|
|
|
mut exit: MessageWriter<AppExit>,
|
|
|
|
|
) {
|
|
|
|
|
warn!("Quitting game");
|
|
|
|
|
exit.write(AppExit::Success);
|
|
|
|
|
@ -629,7 +629,7 @@ fn init_ui(mut commands: Commands, server: Res<AssetServer>) {
|
|
|
|
|
children![(
|
|
|
|
|
Text::new(credits_str),
|
|
|
|
|
TextColor(BLACK.into()),
|
|
|
|
|
TextLayout::new_with_justify(JustifyText::Center)
|
|
|
|
|
TextLayout::new_with_justify(Justify::Center)
|
|
|
|
|
)],
|
|
|
|
|
))
|
|
|
|
|
.with_children(|parent| {
|
|
|
|
|
@ -734,7 +734,7 @@ fn init_ui(mut commands: Commands, server: Res<AssetServer>) {
|
|
|
|
|
Text::new("Rewind!\n(Hold R)"),
|
|
|
|
|
TextColor(BLACK.into()),
|
|
|
|
|
TextFont::from_font_size(20.0),
|
|
|
|
|
TextLayout::new_with_justify(JustifyText::Center)
|
|
|
|
|
TextLayout::new_with_justify(Justify::Center)
|
|
|
|
|
),
|
|
|
|
|
],
|
|
|
|
|
))
|
|
|
|
|
@ -766,7 +766,7 @@ fn init_ui(mut commands: Commands, server: Res<AssetServer>) {
|
|
|
|
|
Text::new("Flap!\n(Spacebar)"),
|
|
|
|
|
TextColor(BLACK.into()),
|
|
|
|
|
TextFont::from_font_size(20.0),
|
|
|
|
|
TextLayout::new_with_justify(JustifyText::Center)
|
|
|
|
|
TextLayout::new_with_justify(Justify::Center)
|
|
|
|
|
),
|
|
|
|
|
(
|
|
|
|
|
ImageNode {
|
|
|
|
|
@ -797,7 +797,7 @@ fn init_ui(mut commands: Commands, server: Res<AssetServer>) {
|
|
|
|
|
SyncResource::<Score>::default(),
|
|
|
|
|
Text::default(),
|
|
|
|
|
TextColor(BLACK.into()),
|
|
|
|
|
TextLayout::new_with_justify(JustifyText::Center),
|
|
|
|
|
TextLayout::new_with_justify(Justify::Center),
|
|
|
|
|
)],
|
|
|
|
|
));
|
|
|
|
|
}
|
|
|
|
|
@ -845,16 +845,16 @@ fn init_background(
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn start_rewind(_event: On<Pointer<Pressed>>, mut next: ResMut<NextState<PlayerState>>) {
|
|
|
|
|
fn start_rewind(_event: On<Pointer<Press>>, mut next: ResMut<NextState<PlayerState>>) {
|
|
|
|
|
next.set(PlayerState::Rewind);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn end_rewind(_event: On<Pointer<Released>>, mut next: ResMut<NextState<PlayerState>>) {
|
|
|
|
|
fn end_rewind(_event: On<Pointer<Release>>, mut next: ResMut<NextState<PlayerState>>) {
|
|
|
|
|
next.set(PlayerState::Alive);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn flap_button(
|
|
|
|
|
_event: On<Pointer<Pressed>>,
|
|
|
|
|
_event: On<Pointer<Press>>,
|
|
|
|
|
mut commands: Commands,
|
|
|
|
|
bird: Single<Entity, With<Bird>>,
|
|
|
|
|
curr: Res<State<PlayerState>>,
|
|
|
|
|
@ -865,7 +865,7 @@ fn flap_button(
|
|
|
|
|
}
|
|
|
|
|
let e = *bird;
|
|
|
|
|
debug!("Flapping {:?}", e);
|
|
|
|
|
commands.event_targets(Flap, e);
|
|
|
|
|
commands.trigger(Flap { entity: e });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Pause the game when the player presses "Escape"
|
|
|
|
|
@ -877,13 +877,15 @@ fn un_pause_game(mut next: ResMut<NextState<PlayerState>>) {
|
|
|
|
|
next.set(PlayerState::Alive);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Component, Clone, Message)]
|
|
|
|
|
struct Flap;
|
|
|
|
|
#[derive(Component, Clone, Message, EntityEvent)]
|
|
|
|
|
struct Flap {
|
|
|
|
|
entity: Entity
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Observer for flapping
|
|
|
|
|
fn flap(
|
|
|
|
|
event: On<Flap>,
|
|
|
|
|
mut bird: Query<&mut ExternalImpulse, With<Bird>>,
|
|
|
|
|
mut bird: Query<Forces, With<Bird>>,
|
|
|
|
|
mut flaps: ResMut<Flaps>,
|
|
|
|
|
) {
|
|
|
|
|
debug!("real flap for {:?}", event.entity);
|
|
|
|
|
@ -892,7 +894,7 @@ fn flap(
|
|
|
|
|
|
|
|
|
|
// Flap birds wings
|
|
|
|
|
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 +918,7 @@ fn flap_kb(
|
|
|
|
|
|
|
|
|
|
birds.iter().for_each(|e| {
|
|
|
|
|
debug!("Flapping {:?}", e);
|
|
|
|
|
commands.event_targets(Flap, e);
|
|
|
|
|
commands.trigger(Flap { entity: e });
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@ -941,7 +943,7 @@ fn record(
|
|
|
|
|
(
|
|
|
|
|
&LinearVelocity,
|
|
|
|
|
&AngularVelocity,
|
|
|
|
|
&ExternalImpulse,
|
|
|
|
|
&ConstantForce,
|
|
|
|
|
&Position,
|
|
|
|
|
&Rotation,
|
|
|
|
|
&mut Tape,
|
|
|
|
|
@ -956,7 +958,7 @@ fn record(
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
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 +969,7 @@ fn rewind(
|
|
|
|
|
(
|
|
|
|
|
&mut LinearVelocity,
|
|
|
|
|
&mut AngularVelocity,
|
|
|
|
|
&mut ExternalImpulse,
|
|
|
|
|
// TODO: Need to re-add impulse here
|
|
|
|
|
&mut Position,
|
|
|
|
|
&mut Rotation,
|
|
|
|
|
&mut Tape,
|
|
|
|
|
@ -984,11 +986,11 @@ fn rewind(
|
|
|
|
|
|
|
|
|
|
birds
|
|
|
|
|
.iter_mut()
|
|
|
|
|
.for_each(|(mut lv, mut av, mut ei, mut p, mut r, mut tape)| {
|
|
|
|
|
if let Some((new_lv, new_av, new_ei, new_p, new_r)) = tape.pop() {
|
|
|
|
|
.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() {
|
|
|
|
|
lv.0 = new_lv.0;
|
|
|
|
|
av.0 = new_av.0;
|
|
|
|
|
ei.set_impulse(new_ei.impulse());
|
|
|
|
|
// TODO: Need to re-add impulse here
|
|
|
|
|
p.0 = new_p.0;
|
|
|
|
|
*r = new_r;
|
|
|
|
|
frames.0 += 1;
|
|
|
|
|
@ -1016,15 +1018,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");
|
|
|
|
|
**bird = RigidBody::Dynamic;
|
|
|
|
|
commands.entity(*bird).remove::<RigidBodyDisabled>();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn pause_bird(
|
|
|
|
|
state: Res<State<PlayerState>>,
|
|
|
|
|
mut bird: Single<&mut RigidBody, With<Bird>>,
|
|
|
|
|
bird: Single<Entity, With<Bird>>,
|
|
|
|
|
mut deaths: ResMut<Deaths>,
|
|
|
|
|
mut commands: Commands,
|
|
|
|
|
) {
|
|
|
|
|
// Increment death count
|
|
|
|
|
if state.get() == &PlayerState::Stasis {
|
|
|
|
|
@ -1032,7 +1035,7 @@ fn pause_bird(
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
debug!("Setting bird to Static");
|
|
|
|
|
**bird = RigidBody::Static;
|
|
|
|
|
commands.entity(*bird).insert(RigidBodyDisabled);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn camera_follow_bird(
|
|
|
|
|
@ -1138,32 +1141,32 @@ impl Display for Deaths {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn manage_score(
|
|
|
|
|
mut start: MessageReader<CollisionStarted>,
|
|
|
|
|
mut end: MessageReader<CollisionEnded>,
|
|
|
|
|
mut start: MessageReader<CollisionStart>,
|
|
|
|
|
mut end: MessageReader<CollisionEnd>,
|
|
|
|
|
state: Res<State<PlayerState>>,
|
|
|
|
|
hitboxes: Query<&Batch, With<Hitbox>>,
|
|
|
|
|
mut score: ResMut<Score>,
|
|
|
|
|
) {
|
|
|
|
|
match state.get() {
|
|
|
|
|
PlayerState::Rewind => {
|
|
|
|
|
start.read().for_each(|CollisionStarted(a, b)| {
|
|
|
|
|
start.read().for_each(|CollisionStart { collider1, collider2, .. }| {
|
|
|
|
|
// 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}");
|
|
|
|
|
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}");
|
|
|
|
|
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
|
|
|
|
|
if let Ok(Batch(this)) = hitboxes.get(*b) {
|
|
|
|
|
if let Ok(Batch(this)) = hitboxes.get(*collider2) {
|
|
|
|
|
debug!("[Alive] Setting score to {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}");
|
|
|
|
|
score.0 = *this;
|
|
|
|
|
}
|
|
|
|
|
@ -1182,15 +1185,15 @@ fn manage_score(
|
|
|
|
|
/// 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
|
|
|
|
|
fn move_batches(
|
|
|
|
|
mut start: MessageReader<CollisionStarted>,
|
|
|
|
|
mut end: MessageReader<CollisionEnded>,
|
|
|
|
|
mut start: MessageReader<CollisionStart>,
|
|
|
|
|
mut end: MessageReader<CollisionEnd>,
|
|
|
|
|
hitboxes: Query<Entity, With<Hitbox>>,
|
|
|
|
|
batches: Query<(Entity, &Batch)>,
|
|
|
|
|
state: Res<State<PlayerState>>,
|
|
|
|
|
mut commands: Commands,
|
|
|
|
|
) {
|
|
|
|
|
let s = start.read().map(|CollisionStarted(a, b)| (a, b));
|
|
|
|
|
let e = end.read().map(|CollisionEnded(a, b)| (a, b));
|
|
|
|
|
let s = start.read().map(|CollisionStart { collider1, collider2, .. }| (collider1, collider2));
|
|
|
|
|
let e = end.read().map(|CollisionEnd { collider1, collider2, .. }| (collider1, collider2));
|
|
|
|
|
let c = s.chain(e);
|
|
|
|
|
c.for_each(|(a, b)| {
|
|
|
|
|
debug!("[batches] Collision {a} -> {b}");
|
|
|
|
|
|