Implement basic gun controller
All checks were successful
Basic checks / Basic build-and-test supertask (push) Successful in 7m3s

The spacebar fires the gun, bullets are spawned on top of the ship with
a velocity that sends them forward (relative to the ship).

I still need a despawn mechanism, and a fire rate control. To despawn
things, I'm already planning a `Lifetime` component. For rate of fire,
an additional component will be created and attached to the player ship.
This commit is contained in:
2025-08-10 13:30:42 -05:00
parent dea8a0dc1a
commit 65f28e832f
5 changed files with 55 additions and 5 deletions

View File

@@ -10,9 +10,12 @@ pub(crate) const PLAYER_SHIP_COLOR: Color = Color::srgb(1.0, 1.0, 1.0);
pub(crate) const SHIP_THRUSTER_COLOR_ACTIVE: Color = Color::srgb(1.0, 0.2, 0.2); pub(crate) const SHIP_THRUSTER_COLOR_ACTIVE: Color = Color::srgb(1.0, 0.2, 0.2);
pub(crate) const SHIP_THRUSTER_COLOR_INACTIVE: Color = Color::srgb(0.5, 0.5, 0.5); pub(crate) const SHIP_THRUSTER_COLOR_INACTIVE: Color = Color::srgb(0.5, 0.5, 0.5);
pub(crate) const ASTEROID_SMALL_COLOR: Color = Color::srgb(1.0, 0., 0.); pub(crate) const ASTEROID_SMALL_COLOR: Color = Color::srgb(1.0, 0., 0.);
pub(crate) const BULLET_COLOR: Color = Color::srgb(0.0, 0.1, 0.9);
// TODO: asteroid medium & large // TODO: asteroid medium & large
pub(crate) const SHIP_THRUST: f32 = 1.0; pub(crate) const SHIP_THRUST: f32 = 1.0;
pub(crate) const SHIP_ROTATION: f32 = 4.0; // +/- rotation speed in... radians per frame pub(crate) const SHIP_ROTATION: f32 = 4.0; // +/- rotation speed in... radians per frame
pub(crate) const BULLET_SPEED: f32 = 150.0;
pub const RNG_SEED: [u8; 32] = *b"12345678909876543210123456789098"; pub const RNG_SEED: [u8; 32] = *b"12345678909876543210123456789098";

View File

@@ -12,3 +12,10 @@ pub(crate) struct AsteroidDestroy(pub Entity);
// TODO: BulletDestroy // TODO: BulletDestroy
// Which depends on the still-pending Bullet component creation. // Which depends on the still-pending Bullet component creation.
/// Signals that a particular bullet has been destroyed.
/// Used to despawn the bullet after it strikes an Asteroid.
///
/// TODO: Maybe use it for lifetime expiration (which is also a TODO item).
#[derive(Event)]
pub(crate) struct BulletDestroy(pub Entity);

View File

@@ -8,8 +8,9 @@ mod title_screen;
use crate::asteroids::{Asteroid, AsteroidSpawner}; use crate::asteroids::{Asteroid, AsteroidSpawner};
use crate::config::{ use crate::config::{
ASTEROID_SMALL_COLOR, BACKGROUND_COLOR, PLAYER_SHIP_COLOR, SHIP_ROTATION, SHIP_THRUST, ASTEROID_SMALL_COLOR, BACKGROUND_COLOR, BULLET_COLOR, BULLET_SPEED, PLAYER_SHIP_COLOR,
SHIP_THRUSTER_COLOR_ACTIVE, SHIP_THRUSTER_COLOR_INACTIVE, WINDOW_SIZE, SHIP_ROTATION, SHIP_THRUST, SHIP_THRUSTER_COLOR_ACTIVE, SHIP_THRUSTER_COLOR_INACTIVE,
WINDOW_SIZE,
}; };
use crate::physics::AngularVelocity; use crate::physics::AngularVelocity;
use crate::ship::Ship; use crate::ship::Ship;
@@ -50,6 +51,7 @@ impl Plugin for AsteroidPlugin {
( (
input_ship_thruster, input_ship_thruster,
input_ship_rotation, input_ship_rotation,
input_ship_shoot,
physics::wrap_entities, physics::wrap_entities,
asteroids::tick_asteroid_manager, asteroids::tick_asteroid_manager,
asteroids::spawn_asteroid.after(asteroids::tick_asteroid_manager), asteroids::spawn_asteroid.after(asteroids::tick_asteroid_manager),
@@ -163,8 +165,8 @@ struct WorldSize {
#[derive(Resource)] #[derive(Resource)]
struct GameAssets { struct GameAssets {
meshes: [Handle<Mesh>; 4], meshes: [Handle<Mesh>; 5],
materials: [Handle<ColorMaterial>; 6], materials: [Handle<ColorMaterial>; 7],
} }
impl GameAssets { impl GameAssets {
@@ -199,6 +201,10 @@ impl GameAssets {
fn asteroid_large(&self) -> (Handle<Mesh>, Handle<ColorMaterial>) { fn asteroid_large(&self) -> (Handle<Mesh>, Handle<ColorMaterial>) {
(self.meshes[3].clone(), self.materials[3].clone()) (self.meshes[3].clone(), self.materials[3].clone())
} }
fn bullet(&self) -> (Handle<Mesh>, Handle<ColorMaterial>) {
(self.meshes[4].clone(), self.materials[6].clone())
}
} }
impl FromWorld for GameAssets { impl FromWorld for GameAssets {
@@ -213,6 +219,7 @@ impl FromWorld for GameAssets {
world_meshes.add(Circle::new(10.0)), world_meshes.add(Circle::new(10.0)),
world_meshes.add(Circle::new(20.0)), world_meshes.add(Circle::new(20.0)),
world_meshes.add(Circle::new(40.0)), world_meshes.add(Circle::new(40.0)),
world_meshes.add(Circle::new(0.2)),
]; ];
let mut world_materials = world.resource_mut::<Assets<ColorMaterial>>(); let mut world_materials = world.resource_mut::<Assets<ColorMaterial>>();
let materials = [ let materials = [
@@ -223,6 +230,7 @@ impl FromWorld for GameAssets {
// TODO: asteroid medium and large colors // TODO: asteroid medium and large colors
world_materials.add(ASTEROID_SMALL_COLOR), world_materials.add(ASTEROID_SMALL_COLOR),
world_materials.add(ASTEROID_SMALL_COLOR), world_materials.add(ASTEROID_SMALL_COLOR),
world_materials.add(BULLET_COLOR),
]; ];
GameAssets { meshes, materials } GameAssets { meshes, materials }
} }
@@ -287,6 +295,35 @@ fn input_ship_rotation(
} }
} }
fn input_ship_shoot(
keyboard_input: Res<ButtonInput<KeyCode>>,
ship: Single<(&Transform, &physics::Velocity), With<Ship>>,
game_assets: Res<GameAssets>,
mut commands: Commands,
) {
let (ship_pos, ship_vel) = *ship;
// Derive bullet velocity, add to the ship's velocity
let bullet_vel = (ship_pos.rotation * Vec3::X).xy() * BULLET_SPEED;
let bullet_vel = bullet_vel + ship_vel.0;
// TODO: create a timer for the gun fire rate.
// For now, spawn one for each press of the spacebar.
if keyboard_input.just_pressed(KeyCode::Space) {
commands.spawn((
ship::Bullet,
Collider::ball(0.2),
Sensor,
ActiveEvents::COLLISION_EVENTS,
ActiveCollisionTypes::STATIC_STATIC,
physics::Velocity(bullet_vel),
Mesh2d(game_assets.bullet().0),
MeshMaterial2d(game_assets.bullet().1),
ship_pos.clone(), // clone ship transform
));
}
}
fn spawn_ui(mut commands: Commands, score: Res<Score>, lives: Res<Lives>) { fn spawn_ui(mut commands: Commands, score: Res<Score>, lives: Res<Lives>) {
commands.spawn(( commands.spawn((
Text::new(format!("Score: {score:?} | Lives: {lives:?}")), Text::new(format!("Score: {score:?} | Lives: {lives:?}")),

View File

@@ -5,7 +5,7 @@ use crate::WorldSize;
use bevy::prelude::*; use bevy::prelude::*;
#[derive(Component)] #[derive(Clone, Component)]
pub(crate) struct Velocity(pub(crate) bevy::math::Vec2); pub(crate) struct Velocity(pub(crate) bevy::math::Vec2);
#[derive(Component)] #[derive(Component)]

View File

@@ -9,6 +9,9 @@ use bevy_rapier2d::prelude::*;
#[derive(Component)] #[derive(Component)]
pub struct Ship; pub struct Ship;
#[derive(Component)]
pub struct Bullet;
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((