Compare commits
6 Commits
70f2313766
...
33828d6a85
| Author | SHA1 | Date | |
|---|---|---|---|
| 33828d6a85 | |||
| 5e2018d3e4 | |||
| 7c877264a2 | |||
| 16842c13f7 | |||
| e199db16eb | |||
| f17910daef |
31
src/lib.rs
31
src/lib.rs
@@ -1,3 +1,7 @@
|
|||||||
|
//! Asteroids, implemented as a Bevy plugin.
|
||||||
|
//!
|
||||||
|
//! Compile-time configurables can be found in the [`config`] module.
|
||||||
|
|
||||||
pub mod config;
|
pub mod config;
|
||||||
mod events;
|
mod events;
|
||||||
mod machinery;
|
mod machinery;
|
||||||
@@ -24,6 +28,7 @@ use bevy_rapier2d::{
|
|||||||
use machinery::Lifetime;
|
use machinery::Lifetime;
|
||||||
use resources::{GameAssets, Lives, Score, WorldSize};
|
use resources::{GameAssets, Lives, Score, WorldSize};
|
||||||
|
|
||||||
|
/// The main game plugin.
|
||||||
pub struct AsteroidPlugin;
|
pub struct AsteroidPlugin;
|
||||||
|
|
||||||
impl Plugin for AsteroidPlugin {
|
impl Plugin for AsteroidPlugin {
|
||||||
@@ -35,10 +40,7 @@ impl Plugin for AsteroidPlugin {
|
|||||||
RapierDebugRenderPlugin::default(),
|
RapierDebugRenderPlugin::default(),
|
||||||
))
|
))
|
||||||
.insert_resource(ClearColor(BACKGROUND_COLOR))
|
.insert_resource(ClearColor(BACKGROUND_COLOR))
|
||||||
.insert_resource(WorldSize {
|
.insert_resource(WorldSize::default())
|
||||||
width: WINDOW_SIZE.x,
|
|
||||||
height: WINDOW_SIZE.y,
|
|
||||||
})
|
|
||||||
.insert_resource(Lives(3))
|
.insert_resource(Lives(3))
|
||||||
.register_type::<Lives>()
|
.register_type::<Lives>()
|
||||||
.insert_resource(Score(0))
|
.insert_resource(Score(0))
|
||||||
@@ -90,6 +92,7 @@ fn debug_collision_event_printer(mut collision_events: EventReader<CollisionEven
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The game's main state tracking mechanism, registered with Bevy as a [`State`](`bevy::prelude::State`).
|
||||||
#[derive(Clone, Debug, Eq, Hash, PartialEq, States)]
|
#[derive(Clone, Debug, Eq, Hash, PartialEq, States)]
|
||||||
pub enum GameState {
|
pub enum GameState {
|
||||||
TitleScreen, // Program is started. Present title screen and await user start
|
TitleScreen, // Program is started. Present title screen and await user start
|
||||||
@@ -102,9 +105,9 @@ fn spawn_camera(mut commands: Commands) {
|
|||||||
commands.spawn(Camera2d);
|
commands.spawn(Camera2d);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/// Player's thruster control system.
|
||||||
Checks if "W" is pressed and increases velocity accordingly.
|
///
|
||||||
*/
|
/// Checks if "W" is pressed and increases velocity accordingly.
|
||||||
fn input_ship_thruster(
|
fn input_ship_thruster(
|
||||||
keyboard_input: Res<ButtonInput<KeyCode>>,
|
keyboard_input: Res<ButtonInput<KeyCode>>,
|
||||||
mut query: Query<(&mut physics::Velocity, &Transform, &mut Children), With<Ship>>,
|
mut query: Query<(&mut physics::Velocity, &Transform, &mut Children), With<Ship>>,
|
||||||
@@ -135,10 +138,10 @@ fn input_ship_thruster(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/// Player's rotation control system.
|
||||||
Checks if "A" or "D" is pressed and updates the player's Rotation component accordingly
|
///
|
||||||
Does *not* rotate the graphical widget! (that's done by the `apply_rotation_to_mesh` system)
|
/// Checks if "A" or "D" is pressed and updates the player's [`AngularVelocity`]
|
||||||
*/
|
/// component accordingly.
|
||||||
fn input_ship_rotation(
|
fn input_ship_rotation(
|
||||||
keyboard_input: Res<ButtonInput<KeyCode>>,
|
keyboard_input: Res<ButtonInput<KeyCode>>,
|
||||||
mut query: Query<&mut AngularVelocity, With<Ship>>,
|
mut query: Query<&mut AngularVelocity, With<Ship>>,
|
||||||
@@ -157,6 +160,12 @@ fn input_ship_rotation(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Player's gun trigger.
|
||||||
|
///
|
||||||
|
/// Checks if the spacebar has just been pressed, spawning a bullet if so.
|
||||||
|
///
|
||||||
|
/// TODO: Hook up a timer to control weapon fire-rate. Something will have to
|
||||||
|
/// tick those timers. Maybe this system?
|
||||||
fn input_ship_shoot(
|
fn input_ship_shoot(
|
||||||
keyboard_input: Res<ButtonInput<KeyCode>>,
|
keyboard_input: Res<ButtonInput<KeyCode>>,
|
||||||
ship: Single<(&Transform, &physics::Velocity), With<Ship>>,
|
ship: Single<(&Transform, &physics::Velocity), With<Ship>>,
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
//! These are Systems that power the main game mechanics (and some misc items to support them)
|
//! Systems, Components, and any other items for powering the game logic.
|
||||||
|
//!
|
||||||
|
//! Where the objects (ship, asteroid, etc) carry their own behavioral systems,
|
||||||
|
//! the *game* keeps its main logic here. Its for ambient behaviors, like
|
||||||
|
//! asteroid spawning, or eventually the flying saucer spawns.
|
||||||
use rand::{Rng, SeedableRng};
|
use rand::{Rng, SeedableRng};
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
@@ -7,6 +10,12 @@ use bevy::prelude::*;
|
|||||||
|
|
||||||
use crate::{WorldSize, events::SpawnAsteroid, objects::AsteroidSize};
|
use crate::{WorldSize, events::SpawnAsteroid, objects::AsteroidSize};
|
||||||
|
|
||||||
|
/// Asteroid spawning parameters and state.
|
||||||
|
///
|
||||||
|
/// This struct keeps track of the rng and timer for spawning asteroids. In the
|
||||||
|
/// future it may contain additional fields to allow for more control.
|
||||||
|
///
|
||||||
|
/// It's values are operated by the [`tick_asteroid_manager`] system.
|
||||||
#[derive(Resource)]
|
#[derive(Resource)]
|
||||||
pub struct AsteroidSpawner {
|
pub struct AsteroidSpawner {
|
||||||
rng: std::sync::Mutex<rand::rngs::StdRng>,
|
rng: std::sync::Mutex<rand::rngs::StdRng>,
|
||||||
@@ -26,8 +35,8 @@ impl AsteroidSpawner {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Update the asteroid spawn timer and spawn any asteroids
|
/// Update the asteroid spawn timer in the [`AsteroidSpawner`] resource, and
|
||||||
/// 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: EventWriter<SpawnAsteroid>,
|
||||||
mut spawner: ResMut<AsteroidSpawner>,
|
mut spawner: ResMut<AsteroidSpawner>,
|
||||||
@@ -46,7 +55,7 @@ pub fn tick_asteroid_manager(
|
|||||||
let spawn_angle = rng.random_range(0.0..(std::f32::consts::PI * 2.0));
|
let spawn_angle = rng.random_range(0.0..(std::f32::consts::PI * 2.0));
|
||||||
// Rho will be the radius of a circle bordering the viewport, multiplied by 1.2
|
// Rho will be the radius of a circle bordering the viewport, multiplied by 1.2
|
||||||
// TODO: Use view diagonal to get a minimally sized circle around the play area
|
// TODO: Use view diagonal to get a minimally sized circle around the play area
|
||||||
let spawn_distance = play_area.width.max(play_area.height) / 2.0;
|
let spawn_distance = play_area.x.max(play_area.y) / 2.0;
|
||||||
|
|
||||||
// Convert polar to Cartesian, use as position
|
// Convert polar to Cartesian, use as position
|
||||||
let pos = Vec2::new(
|
let pos = Vec2::new(
|
||||||
@@ -77,9 +86,11 @@ pub fn tick_asteroid_manager(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sets a lifetime on an entity to automatically delete it after expiration.
|
||||||
#[derive(Component)]
|
#[derive(Component)]
|
||||||
pub struct Lifetime(pub Timer);
|
pub struct Lifetime(pub Timer);
|
||||||
|
|
||||||
|
/// System to tick the [`Lifetime`] timers and despawn expired entities.
|
||||||
pub fn tick_lifetimes(
|
pub fn tick_lifetimes(
|
||||||
mut commands: Commands,
|
mut commands: Commands,
|
||||||
time: Res<Time>,
|
time: Res<Time>,
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ use crate::{
|
|||||||
physics::{Velocity, Wrapping},
|
physics::{Velocity, Wrapping},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// The asteroid, defined entirely by [it's size](`AsteroidSize`).
|
||||||
#[derive(Component, Deref, DerefMut)]
|
#[derive(Component, Deref, DerefMut)]
|
||||||
pub struct Asteroid(pub AsteroidSize);
|
pub struct Asteroid(pub AsteroidSize);
|
||||||
|
|
||||||
@@ -39,6 +40,8 @@ pub enum AsteroidSize {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl AsteroidSize {
|
impl AsteroidSize {
|
||||||
|
/// Convenience util to get the "next smallest" size. Useful for splitting
|
||||||
|
/// after collision.
|
||||||
pub fn next(&self) -> Option<Self> {
|
pub fn next(&self) -> Option<Self> {
|
||||||
match self {
|
match self {
|
||||||
AsteroidSize::Small => None,
|
AsteroidSize::Small => None,
|
||||||
@@ -48,9 +51,11 @@ impl AsteroidSize {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Marker component for the player's ship.
|
||||||
#[derive(Component)]
|
#[derive(Component)]
|
||||||
pub struct Ship;
|
pub struct Ship;
|
||||||
|
|
||||||
|
/// Marker component for bullets.
|
||||||
#[derive(Component)]
|
#[derive(Component)]
|
||||||
pub struct Bullet;
|
pub struct Bullet;
|
||||||
|
|
||||||
@@ -126,6 +131,11 @@ pub fn split_asteroids(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Spawns the player at the world origin. Used during the state change to
|
||||||
|
/// [`GameState::Playing`] to spawn the player.
|
||||||
|
///
|
||||||
|
/// This only spawns the player. For player **re**-spawn activity, see the
|
||||||
|
/// [`ship_impact_listener()`] system.
|
||||||
pub fn spawn_player(mut commands: Commands, game_assets: Res<GameAssets>) {
|
pub fn spawn_player(mut commands: Commands, game_assets: Res<GameAssets>) {
|
||||||
commands
|
commands
|
||||||
.spawn((
|
.spawn((
|
||||||
@@ -160,6 +170,9 @@ pub fn bullet_impact_listener(mut commands: Commands, mut events: EventReader<Bu
|
|||||||
|
|
||||||
/// Watch for [`ShipDestroy`] events and update game state accordingly.
|
/// Watch for [`ShipDestroy`] events and update game state accordingly.
|
||||||
///
|
///
|
||||||
|
/// One life is taken from the counter, asteroids are cleared, and the player
|
||||||
|
/// is placed back at the origin. If lives reach 0, this system will change
|
||||||
|
/// states to [`GameState::GameOver`].
|
||||||
/// - Subtract a life
|
/// - Subtract a life
|
||||||
/// - Check life count. If 0, go to game-over state
|
/// - Check life count. If 0, go to game-over state
|
||||||
/// - Clear all asteroids
|
/// - Clear all asteroids
|
||||||
|
|||||||
@@ -1,5 +1,19 @@
|
|||||||
//! Custom physics items
|
//! Custom physics items
|
||||||
|
//!
|
||||||
//! TODO: Refactor in terms of Rapier2D, *or* implement colliders and remove it.
|
//! TODO: Refactor in terms of Rapier2D, *or* implement colliders and remove it.
|
||||||
|
//!
|
||||||
|
//! Position, both translation and rotation, are tracked by the built-in
|
||||||
|
//! Bevy [`Transform`] component. This module adds a (Linear) [`Velocity`] and
|
||||||
|
//! [`AngularVelocity`] component to the mix.
|
||||||
|
//! These two components are operated by simple integrator systems to
|
||||||
|
//! accumulate translation and rotation from the velocities.
|
||||||
|
//!
|
||||||
|
//! The [`Wrapping`] component marks things that should wrap around the world
|
||||||
|
//! boundaries. It's a kind of physical property of this world, so I'm putting
|
||||||
|
//! it here.
|
||||||
|
//!
|
||||||
|
//! Collisions are also considered physics. After all, I got *a physics engine*
|
||||||
|
//! to detect them for me (so that I don't have to write clipping code).
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
WorldSize, events,
|
WorldSize, events,
|
||||||
@@ -42,9 +56,9 @@ pub(crate) fn wrap_entities(
|
|||||||
mut query: Query<&mut Transform, With<Wrapping>>,
|
mut query: Query<&mut Transform, With<Wrapping>>,
|
||||||
world_size: Res<WorldSize>,
|
world_size: Res<WorldSize>,
|
||||||
) {
|
) {
|
||||||
let right = world_size.width / 2.0;
|
let right = world_size.x / 2.0;
|
||||||
let left = -right;
|
let left = -right;
|
||||||
let top = world_size.height / 2.0;
|
let top = world_size.y / 2.0;
|
||||||
let bottom = -top;
|
let bottom = -top;
|
||||||
|
|
||||||
for mut pos in query.iter_mut() {
|
for mut pos in query.iter_mut() {
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ use bevy::{
|
|||||||
Vec2,
|
Vec2,
|
||||||
primitives::{Circle, Triangle2d},
|
primitives::{Circle, Triangle2d},
|
||||||
},
|
},
|
||||||
prelude::{Deref, Reflect, ReflectResource},
|
prelude::{Deref, DerefMut, Reflect, ReflectResource},
|
||||||
render::mesh::Mesh,
|
render::mesh::Mesh,
|
||||||
sprite::ColorMaterial,
|
sprite::ColorMaterial,
|
||||||
};
|
};
|
||||||
@@ -19,7 +19,7 @@ use bevy_inspector_egui::inspector_options::ReflectInspectorOptions;
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
ASTEROID_SMALL_COLOR, BULLET_COLOR, PLAYER_SHIP_COLOR, SHIP_THRUSTER_COLOR_ACTIVE,
|
ASTEROID_SMALL_COLOR, BULLET_COLOR, PLAYER_SHIP_COLOR, SHIP_THRUSTER_COLOR_ACTIVE,
|
||||||
SHIP_THRUSTER_COLOR_INACTIVE,
|
SHIP_THRUSTER_COLOR_INACTIVE, config::WINDOW_SIZE,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Resource, Debug, Deref, Clone, Copy)]
|
#[derive(Resource, Debug, Deref, Clone, Copy)]
|
||||||
@@ -41,10 +41,13 @@ impl From<Lives> for String {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Resource)]
|
#[derive(Deref, DerefMut, Resource)]
|
||||||
pub struct WorldSize {
|
pub struct WorldSize(Vec2);
|
||||||
pub width: f32,
|
|
||||||
pub height: f32,
|
impl Default for WorldSize {
|
||||||
|
fn default() -> Self {
|
||||||
|
WorldSize(Vec2::new(WINDOW_SIZE.x, WINDOW_SIZE.y))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Resource)]
|
#[derive(Resource)]
|
||||||
|
|||||||
Reference in New Issue
Block a user