5 Commits

Author SHA1 Message Date
a48dfc1d65 Finish the engine upgrade
Some checks failed
Basic checks / Basic build-and-test supertask (push) Failing after 2m1s
Events have been replaced with Messages, import paths have been updated
for new engine module layout, and minor API changes have been matched.
2025-12-17 10:15:33 -06:00
6e425e8eb9 WIP on engine upgrade 2025-12-17 09:44:48 -06:00
7bd88c702f Replace event derives with message derives 2025-12-17 09:44:48 -06:00
cfe35c61ed Rename crate::events to crate::messages
Bevy 0.17 introduces this idea of "Messages" which mostly replace the
previous usage of "Events." The old events still exist, but are meant
more for targetted actions -- something happening and immediately
triggering a function call on an observer.
2025-12-17 09:44:48 -06:00
13643f73fb Upgrade to Bevy 0.17... minus fixes for API change
Bevy 0.17 is out and I'm going to get started on an upgrade. Upgrading
dependencies will be it's own commit, as will many of the fixes. This
way I can cherry-pick anything, if need be.
2025-12-17 09:41:44 -06:00
11 changed files with 1274 additions and 634 deletions

1798
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,13 +1,13 @@
[package]
name = "asteroids"
version = "0.5.0"
version = "0.6.0-dev1"
edition = "2024"
license = "AGPL-3.0-only"
[dependencies]
bevy = "0.16"
bevy-inspector-egui = "0.32"
bevy_rapier2d = "0.31"
bevy = "0.17"
bevy-inspector-egui = "0.34"
bevy_rapier2d = "0.32"
rand = "0.9"
[features]

View File

@@ -3,7 +3,7 @@
use bevy::color::Color;
pub const WINDOW_SIZE: bevy::prelude::Vec2 = bevy::prelude::Vec2::new(800.0, 600.0);
pub const WINDOW_SIZE: (u32, u32) = (800, 600);
pub const UI_BUTTON_NORMAL: Color = Color::srgb(0.15, 0.15, 0.15); // Button color when it's just hanging out
pub const UI_BUTTON_HOVERED: Color = Color::srgb(0.25, 0.25, 0.25); // ... when it's hovered

View File

@@ -3,8 +3,8 @@
//! Compile-time configurables can be found in the [`config`] module.
pub mod config;
mod events;
mod machinery;
mod messages;
mod objects;
mod physics;
mod resources;
@@ -77,10 +77,10 @@ impl Plugin for AsteroidPlugin {
)
.run_if(in_state(GameState::Playing)),
)
.add_event::<events::SpawnAsteroid>()
.add_event::<events::AsteroidDestroy>()
.add_event::<events::ShipDestroy>()
.add_event::<events::BulletDestroy>();
.add_message::<messages::SpawnAsteroid>()
.add_message::<messages::AsteroidDestroy>()
.add_message::<messages::ShipDestroy>()
.add_message::<messages::BulletDestroy>();
app.insert_state(GameState::TitleScreen);
}
}
@@ -182,7 +182,7 @@ fn input_ship_shoot(
// If the weapon is ready and the player presses the trigger,
// spawn a bullet & reset the timer.
if weapon.finished() && keyboard_input.pressed(KeyCode::Space) {
if weapon.is_finished() && keyboard_input.pressed(KeyCode::Space) {
weapon.reset();
// Derive bullet velocity, add to the ship's velocity
let bullet_vel = (ship_pos.rotation * Vec3::X).xy() * BULLET_SPEED;

View File

@@ -8,7 +8,12 @@ use std::time::Duration;
use bevy::prelude::*;
use crate::{WorldSize, events::{AsteroidDestroy, SpawnAsteroid}, objects::AsteroidSize, resources::Score};
use crate::{
WorldSize,
messages::{AsteroidDestroy, SpawnAsteroid},
objects::AsteroidSize,
resources::Score,
};
/// Asteroid spawning parameters and state.
///
@@ -38,7 +43,7 @@ impl AsteroidSpawner {
/// Update the asteroid spawn timer in the [`AsteroidSpawner`] resource, and
/// spawns any asteroids that are due this frame.
pub fn tick_asteroid_manager(
mut events: EventWriter<SpawnAsteroid>,
mut events: MessageWriter<SpawnAsteroid>,
mut spawner: ResMut<AsteroidSpawner>,
time: Res<Time>,
play_area: Res<WorldSize>,
@@ -140,7 +145,7 @@ pub fn operate_sparklers(sparklers: Query<(&mut Visibility, &mut Sparkler)>, tim
///
/// Refreshing the HUD element is done by [crate::widgets::operate_ui] (a private function)
pub fn update_scoreboard(
mut destroy_events: EventReader<AsteroidDestroy>,
mut destroy_events: MessageReader<AsteroidDestroy>,
mut scoreboard: ResMut<Score>,
) {
for _event in destroy_events.read() {

View File

@@ -8,7 +8,7 @@ fn main() {
.add_plugins(DefaultPlugins.set(WindowPlugin {
primary_window: Some(Window {
canvas: Some("#game-canvas".to_owned()),
resolution: WindowResolution::new(WINDOW_SIZE.x, WINDOW_SIZE.y),
resolution: WindowResolution::new(WINDOW_SIZE.0, WINDOW_SIZE.1),
..default()
}),
..default()

View File

@@ -6,15 +6,15 @@ use crate::objects::AsteroidSize;
///
/// Produced by the [`fn collision_listener(...)`](`crate::physics::collision_listener`)
/// system when the player collides with an asteroid. They are consumed by [`fn ship_impact_listener(...)`](`crate::objects::ship_impact_listener`).
#[derive(Event)]
#[derive(Message)]
pub(crate) struct ShipDestroy;
/// Signals that a particular asteroid has been destroyed.
///
/// Produced by the [`fn collision_listener(...)`](`crate::physics::collision_listener`)
/// system when bullets contact asteroids. It is consumed by [`fn split_asteroid(...)`](`crate::objects::spawn_asteroid`)
/// system, which re-emits [spawn events](`SpawnAsteroid`).
#[derive(Event)]
/// system, which re-emits [spawn messages](`SpawnAsteroid`).
#[derive(Message)]
pub(crate) struct AsteroidDestroy(pub Entity);
/// Signals that an asteroid needs to be spawned and provides the parameters
@@ -22,7 +22,7 @@ pub(crate) struct AsteroidDestroy(pub Entity);
///
/// Produced by the [`tick_asteroid_manager(...)`](`crate::machinery::tick_asteroid_manager`)
/// system and consumed by the [`spawn_asteroid(...)`](`crate::objects::spawn_asteroid`) system.
#[derive(Event)]
#[derive(Message)]
pub struct SpawnAsteroid {
pub pos: Vec2,
pub vel: Vec2,
@@ -32,5 +32,5 @@ pub struct SpawnAsteroid {
/// Signals that a particular bullet has been destroyed (after it strikes an Asteroid).
///
/// TODO: Maybe use it for lifetime expiration (which is also a TODO item).
#[derive(Event)]
#[derive(Message)]
pub(crate) struct BulletDestroy(pub Entity);

View File

@@ -3,17 +3,18 @@
//! Asteroids, the player's ship, and such.
use bevy::{
camera::visibility::Visibility,
ecs::{
component::Component,
entity::Entity,
event::{EventReader, EventWriter},
message::{MessageReader, MessageWriter},
query::With,
system::{Commands, Query, Res, ResMut, Single},
},
math::{Vec2, Vec3, Vec3Swizzles},
mesh::Mesh2d,
prelude::{Deref, DerefMut},
render::{mesh::Mesh2d, view::Visibility},
sprite::MeshMaterial2d,
sprite_render::MeshMaterial2d,
state::state::NextState,
time::{Timer, TimerMode},
transform::components::Transform,
@@ -23,8 +24,8 @@ use bevy_rapier2d::prelude::{ActiveCollisionTypes, ActiveEvents, Collider, Senso
use crate::{
AngularVelocity, GameAssets, GameState, Lives,
config::{ASTEROID_LIFETIME, DEBRIS_LIFETIME, SHIP_FIRE_RATE},
events::{AsteroidDestroy, BulletDestroy, ShipDestroy, SpawnAsteroid},
machinery::{Lifetime, Sparkler},
messages::{AsteroidDestroy, BulletDestroy, ShipDestroy, SpawnAsteroid},
physics::{Velocity, Wrapping},
};
@@ -69,7 +70,7 @@ pub struct Debris;
/// Responds to [`SpawnAsteroid`] events, spawning as specified
pub fn spawn_asteroid(
mut events: EventReader<SpawnAsteroid>,
mut events: MessageReader<SpawnAsteroid>,
mut commands: Commands,
game_assets: Res<GameAssets>,
) {
@@ -109,8 +110,8 @@ pub fn spawn_asteroid(
/// The velocity of the child asteroids is scattered somewhat, as if they were
/// explosively pushed apart.
pub fn split_asteroids(
mut destroy_events: EventReader<AsteroidDestroy>,
mut respawn_events: EventWriter<SpawnAsteroid>,
mut destroy_events: MessageReader<AsteroidDestroy>,
mut respawn_events: MessageWriter<SpawnAsteroid>,
mut commands: Commands,
query: Query<(&Transform, &Asteroid, &Velocity)>,
) {
@@ -171,7 +172,7 @@ pub fn spawn_player(mut commands: Commands, game_assets: Res<GameAssets>) {
/// Watch for [`BulletDestroy`] events and despawn
/// the associated bullet.
pub fn bullet_impact_listener(mut commands: Commands, mut events: EventReader<BulletDestroy>) {
pub fn bullet_impact_listener(mut commands: Commands, mut events: MessageReader<BulletDestroy>) {
for event in events.read() {
commands.entity(event.0).despawn();
}
@@ -187,7 +188,7 @@ pub fn bullet_impact_listener(mut commands: Commands, mut events: EventReader<Bu
/// - Clear all asteroids
/// - Respawn player
pub fn ship_impact_listener(
mut events: EventReader<ShipDestroy>,
mut events: MessageReader<ShipDestroy>,
mut commands: Commands,
mut lives: ResMut<Lives>,
rocks: Query<Entity, With<Asteroid>>,

View File

@@ -16,7 +16,7 @@
//! to detect them for me (so that I don't have to write clipping code).
use crate::{
WorldSize, events,
WorldSize, messages,
objects::{Asteroid, Bullet, Ship},
};
@@ -89,10 +89,10 @@ pub(crate) fn wrap_entities(
/// | Bullet & Bullet | Nothing. Bullets won't collide with each other (and probably can't under normal gameplay conditions) |
/// | Bullet & Ship | Nothing. The player shouldn't be able to shoot themselves (and the Flying Saucer hasn't been impl.'d, so it's bullets don't count) |
pub fn collision_listener(
mut collisions: EventReader<CollisionEvent>,
mut ship_writer: EventWriter<events::ShipDestroy>,
mut asteroid_writer: EventWriter<events::AsteroidDestroy>,
mut bullet_writer: EventWriter<events::BulletDestroy>,
mut collisions: MessageReader<CollisionEvent>,
mut ship_writer: MessageWriter<messages::ShipDestroy>,
mut asteroid_writer: MessageWriter<messages::AsteroidDestroy>,
mut bullet_writer: MessageWriter<messages::BulletDestroy>,
player: Single<Entity, With<Ship>>,
bullets: Query<&Bullet>,
rocks: Query<&Asteroid>,
@@ -112,12 +112,12 @@ pub fn collision_listener(
if rocks.contains(*two) {
// player-asteroid collision
dbg!("Writing ShipDestroy event");
ship_writer.write(events::ShipDestroy);
ship_writer.write(messages::ShipDestroy);
} // else, we don't care
} else if *two == *player {
if rocks.contains(*one) {
dbg!("Writing ShipDestroy event");
ship_writer.write(events::ShipDestroy);
ship_writer.write(messages::ShipDestroy);
}
}
@@ -125,14 +125,14 @@ pub fn collision_listener(
if bullets.contains(*one) {
if rocks.contains(*two) {
dbg!("Writing AsteroidDestroy & BulletDestroy events");
asteroid_writer.write(events::AsteroidDestroy(*two));
bullet_writer.write(events::BulletDestroy(*one));
asteroid_writer.write(messages::AsteroidDestroy(*two));
bullet_writer.write(messages::BulletDestroy(*one));
}
} else if rocks.contains(*one) {
if bullets.contains(*two) {
dbg!("Writing AsteroidDestroy & BulletDestroy events");
asteroid_writer.write(events::AsteroidDestroy(*one));
bullet_writer.write(events::BulletDestroy(*two));
asteroid_writer.write(messages::AsteroidDestroy(*one));
bullet_writer.write(messages::BulletDestroy(*two));
}
}
}

View File

@@ -10,9 +10,9 @@ use bevy::{
Vec2,
primitives::{Circle, Triangle2d},
},
mesh::Mesh,
prelude::{Deref, DerefMut, Reflect, ReflectResource},
render::mesh::Mesh,
sprite::ColorMaterial,
sprite_render::ColorMaterial,
};
use bevy_inspector_egui::InspectorOptions;
use bevy_inspector_egui::inspector_options::ReflectInspectorOptions;
@@ -42,12 +42,14 @@ impl From<Lives> for String {
}
}
// TODO: consider switching this to use a u32 pair like the Window settings
// thing now does.
#[derive(Deref, DerefMut, Resource)]
pub struct WorldSize(Vec2);
impl Default for WorldSize {
fn default() -> Self {
WorldSize(Vec2::new(WINDOW_SIZE.x, WINDOW_SIZE.y))
WorldSize(Vec2::new(WINDOW_SIZE.0 as f32, WINDOW_SIZE.1 as f32))
}
}

View File

@@ -124,7 +124,7 @@ fn button_bundle(text: &str) -> impl Bundle {
margin: UiRect::all(Val::Px(5.0)),
..default()
},
BorderColor(Color::BLACK),
BorderColor::all(Color::BLACK),
BorderRadius::MAX,
BackgroundColor(UI_BUTTON_NORMAL),
children![(
@@ -152,7 +152,7 @@ fn spawn_menu(mut commands: Commands) {
cmds.spawn((
Text::new("Robert's Bad Asteroids Game"),
TextFont::from_font_size(50.0),
TextLayout::new_with_justify(JustifyText::Center),
TextLayout::new_with_justify(Justify::Center),
TextShadow::default(),
));
cmds.spawn((
@@ -247,7 +247,7 @@ fn animate_get_ready_widget(
bar_segment.width = Val::Percent(100.0 * (1.0 - timer.fraction()));
// If the timer has expired, change state to playing.
if timer.finished() {
if timer.is_finished() {
game_state.set(GameState::Playing);
}
}
@@ -274,14 +274,14 @@ fn operate_buttons(
(Changed<Interaction>, With<Button>),
>,
mut game_state: ResMut<NextState<GameState>>,
mut app_exit_events: EventWriter<AppExit>,
mut app_exit_events: MessageWriter<AppExit>,
) {
// TODO: Better colors. These are taken from the example and they're ugly.
for (interaction, mut color, mut border_color, menu_action) in &mut interactions {
match *interaction {
Interaction::Pressed => {
*color = UI_BUTTON_PRESSED.into();
border_color.0 = DARK_GRAY.into();
border_color.set_all(DARK_GRAY);
match menu_action {
ButtonMenuAction::ToMainMenu => {
game_state.set(GameState::TitleScreen);
@@ -296,11 +296,11 @@ fn operate_buttons(
}
Interaction::Hovered => {
*color = UI_BUTTON_HOVERED.into();
border_color.0 = WHITE.into();
border_color.set_all(WHITE);
}
Interaction::None => {
*color = UI_BUTTON_NORMAL.into();
border_color.0 = BLACK.into();
border_color.set_all(BLACK);
}
}
}