7 Commits

Author SHA1 Message Date
b1fd2e5f73 Add a readme
Some checks failed
Basic checks / Basic build-and-test supertask (push) Failing after 1m54s
There hasn't been a README this whole time... I guess there hasn't been
anything I need to immediately communicate to someone looking at the
source repository.

There is now a README so I have somewhere to record the extra licensing
information (for the Kenney asset).
2025-12-17 11:01:23 -06:00
09ff4dc6ca First sound! Added ship explosion sound effect
It's a simple one-shot sound clip that gets dispatched as one more part
of the ship impact routine.

The GameAssets struct has been updated to have an array of handles for
audio assets (just the one, for now).

This sound file, and the next several, are all from Kenney
(www.kenney.nl).
2025-12-17 10:46:51 -06:00
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
13 changed files with 1309 additions and 636 deletions

1798
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

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

14
README.md Normal file
View File

@@ -0,0 +1,14 @@
# Asteroids
*Another* Asteroids game I'm making. This time in Rust with the Bevy game engine.
## License
| File(s) | License |
|-|-|
| * | AGPLv3 |
| assets/* | CC0 |
(the most-specific match is the applicable license)
The sound files are from KenneyNL's "Sci-Fi Sounds (1.0)" pack. Find their work at [www.kenney.nl].

Binary file not shown.

View File

@@ -3,7 +3,7 @@
use bevy::color::Color; 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_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 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. //! Compile-time configurables can be found in the [`config`] module.
pub mod config; pub mod config;
mod events;
mod machinery; mod machinery;
mod messages;
mod objects; mod objects;
mod physics; mod physics;
mod resources; mod resources;
@@ -77,10 +77,10 @@ impl Plugin for AsteroidPlugin {
) )
.run_if(in_state(GameState::Playing)), .run_if(in_state(GameState::Playing)),
) )
.add_event::<events::SpawnAsteroid>() .add_message::<messages::SpawnAsteroid>()
.add_event::<events::AsteroidDestroy>() .add_message::<messages::AsteroidDestroy>()
.add_event::<events::ShipDestroy>() .add_message::<messages::ShipDestroy>()
.add_event::<events::BulletDestroy>(); .add_message::<messages::BulletDestroy>();
app.insert_state(GameState::TitleScreen); app.insert_state(GameState::TitleScreen);
} }
} }
@@ -182,7 +182,7 @@ fn input_ship_shoot(
// If the weapon is ready and the player presses the trigger, // If the weapon is ready and the player presses the trigger,
// spawn a bullet & reset the timer. // 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(); weapon.reset();
// Derive bullet velocity, add to the ship's velocity // Derive bullet velocity, add to the ship's velocity
let bullet_vel = (ship_pos.rotation * Vec3::X).xy() * BULLET_SPEED; 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 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. /// Asteroid spawning parameters and state.
/// ///
@@ -38,7 +43,7 @@ impl AsteroidSpawner {
/// Update the asteroid spawn timer in the [`AsteroidSpawner`] resource, and /// Update the asteroid spawn timer in the [`AsteroidSpawner`] resource, and
/// spawns any asteroids that are due this frame. /// spawns any asteroids that are due this frame.
pub fn tick_asteroid_manager( pub fn tick_asteroid_manager(
mut events: EventWriter<SpawnAsteroid>, mut events: MessageWriter<SpawnAsteroid>,
mut spawner: ResMut<AsteroidSpawner>, mut spawner: ResMut<AsteroidSpawner>,
time: Res<Time>, time: Res<Time>,
play_area: Res<WorldSize>, play_area: Res<WorldSize>,
@@ -140,10 +145,10 @@ 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) /// Refreshing the HUD element is done by [crate::widgets::operate_ui] (a private function)
pub fn update_scoreboard( pub fn update_scoreboard(
mut destroy_events: EventReader<AsteroidDestroy>, mut destroy_events: MessageReader<AsteroidDestroy>,
mut scoreboard: ResMut<Score>, mut scoreboard: ResMut<Score>,
) { ) {
for _event in destroy_events.read() { for _event in destroy_events.read() {
scoreboard.0 += 100; scoreboard.0 += 100;
} }
} }

View File

@@ -8,7 +8,7 @@ fn main() {
.add_plugins(DefaultPlugins.set(WindowPlugin { .add_plugins(DefaultPlugins.set(WindowPlugin {
primary_window: Some(Window { primary_window: Some(Window {
canvas: Some("#game-canvas".to_owned()), 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()
}), }),
..default() ..default()

View File

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

View File

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

View File

@@ -16,7 +16,7 @@
//! to detect them for me (so that I don't have to write clipping code). //! to detect them for me (so that I don't have to write clipping code).
use crate::{ use crate::{
WorldSize, events, WorldSize, messages,
objects::{Asteroid, Bullet, Ship}, 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 & 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) | /// | 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( pub fn collision_listener(
mut collisions: EventReader<CollisionEvent>, mut collisions: MessageReader<CollisionEvent>,
mut ship_writer: EventWriter<events::ShipDestroy>, mut ship_writer: MessageWriter<messages::ShipDestroy>,
mut asteroid_writer: EventWriter<events::AsteroidDestroy>, mut asteroid_writer: MessageWriter<messages::AsteroidDestroy>,
mut bullet_writer: EventWriter<events::BulletDestroy>, mut bullet_writer: MessageWriter<messages::BulletDestroy>,
player: Single<Entity, With<Ship>>, player: Single<Entity, With<Ship>>,
bullets: Query<&Bullet>, bullets: Query<&Bullet>,
rocks: Query<&Asteroid>, rocks: Query<&Asteroid>,
@@ -112,12 +112,12 @@ pub fn collision_listener(
if rocks.contains(*two) { if rocks.contains(*two) {
// player-asteroid collision // player-asteroid collision
dbg!("Writing ShipDestroy event"); dbg!("Writing ShipDestroy event");
ship_writer.write(events::ShipDestroy); ship_writer.write(messages::ShipDestroy);
} // else, we don't care } // else, we don't care
} else if *two == *player { } else if *two == *player {
if rocks.contains(*one) { if rocks.contains(*one) {
dbg!("Writing ShipDestroy event"); 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 bullets.contains(*one) {
if rocks.contains(*two) { if rocks.contains(*two) {
dbg!("Writing AsteroidDestroy & BulletDestroy events"); dbg!("Writing AsteroidDestroy & BulletDestroy events");
asteroid_writer.write(events::AsteroidDestroy(*two)); asteroid_writer.write(messages::AsteroidDestroy(*two));
bullet_writer.write(events::BulletDestroy(*one)); bullet_writer.write(messages::BulletDestroy(*one));
} }
} else if rocks.contains(*one) { } else if rocks.contains(*one) {
if bullets.contains(*two) { if bullets.contains(*two) {
dbg!("Writing AsteroidDestroy & BulletDestroy events"); dbg!("Writing AsteroidDestroy & BulletDestroy events");
asteroid_writer.write(events::AsteroidDestroy(*one)); asteroid_writer.write(messages::AsteroidDestroy(*one));
bullet_writer.write(events::BulletDestroy(*two)); bullet_writer.write(messages::BulletDestroy(*two));
} }
} }
} }

View File

@@ -1,7 +1,8 @@
//! All the resources for the game //! All the resources for the game
use bevy::{ use bevy::{
asset::{Assets, Handle}, asset::{AssetServer, Assets, Handle},
audio::AudioSource,
ecs::{ ecs::{
resource::Resource, resource::Resource,
world::{FromWorld, World}, world::{FromWorld, World},
@@ -10,9 +11,9 @@ use bevy::{
Vec2, Vec2,
primitives::{Circle, Triangle2d}, primitives::{Circle, Triangle2d},
}, },
mesh::Mesh,
prelude::{Deref, DerefMut, Reflect, ReflectResource}, prelude::{Deref, DerefMut, Reflect, ReflectResource},
render::mesh::Mesh, sprite_render::ColorMaterial,
sprite::ColorMaterial,
}; };
use bevy_inspector_egui::InspectorOptions; use bevy_inspector_egui::InspectorOptions;
use bevy_inspector_egui::inspector_options::ReflectInspectorOptions; use bevy_inspector_egui::inspector_options::ReflectInspectorOptions;
@@ -42,12 +43,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)] #[derive(Deref, DerefMut, Resource)]
pub struct WorldSize(Vec2); pub struct WorldSize(Vec2);
impl Default for WorldSize { impl Default for WorldSize {
fn default() -> Self { 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))
} }
} }
@@ -55,6 +58,7 @@ impl Default for WorldSize {
pub struct GameAssets { pub struct GameAssets {
meshes: [Handle<Mesh>; 5], meshes: [Handle<Mesh>; 5],
materials: [Handle<ColorMaterial>; 7], materials: [Handle<ColorMaterial>; 7],
sounds: [Handle<AudioSource>; 1],
} }
impl GameAssets { impl GameAssets {
@@ -93,6 +97,10 @@ impl GameAssets {
pub fn bullet(&self) -> (Handle<Mesh>, Handle<ColorMaterial>) { pub fn bullet(&self) -> (Handle<Mesh>, Handle<ColorMaterial>) {
(self.meshes[4].clone(), self.materials[6].clone()) (self.meshes[4].clone(), self.materials[6].clone())
} }
pub fn wreck_sound(&self) -> Handle<AudioSource> {
self.sounds[0].clone()
}
} }
impl FromWorld for GameAssets { impl FromWorld for GameAssets {
@@ -120,6 +128,12 @@ impl FromWorld for GameAssets {
world_materials.add(ASTEROID_SMALL_COLOR), world_materials.add(ASTEROID_SMALL_COLOR),
world_materials.add(BULLET_COLOR), world_materials.add(BULLET_COLOR),
]; ];
GameAssets { meshes, materials } let loader = world.resource_mut::<AssetServer>();
let sounds = [loader.load("explosionCrunch_004.ogg")];
GameAssets {
meshes,
materials,
sounds,
}
} }
} }

View File

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