Files
another-asteroids/src/physics.rs
Robert Garrett 33828d6a85
All checks were successful
Basic checks / Basic build-and-test supertask (push) Successful in 7m7s
Rewrite WorldSize as a newtype around Vec2
Closes #16

Bevy provides Deref and DerefMut derives, so that's nice. I'm not sure
it makes any sense to keep the field private since those derefs expose
the Vec2 anyway. I added an `impl Default` anyway, though.
2025-08-12 23:39:19 -05:00

141 lines
5.1 KiB
Rust

//! Custom physics items
//!
//! 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::{
WorldSize, events,
objects::{Asteroid, Bullet, Ship},
};
use bevy::prelude::*;
use bevy_rapier2d::pipeline::CollisionEvent;
#[derive(Clone, Component)]
pub(crate) struct Velocity(pub(crate) bevy::math::Vec2);
#[derive(Component)]
pub(crate) struct AngularVelocity(pub(crate) f32);
/// Marker for any entity that should wrap on screen edges
#[derive(Component)]
pub(crate) struct Wrapping;
/// Integrate linear velocity and update the entity's transform.
pub(crate) fn integrate_velocity(mut query: Query<(&mut Transform, &Velocity)>, time: Res<Time>) {
for (mut transform, velocity) in &mut query {
let delta = velocity.0 * time.delta_secs();
transform.translation += delta.extend(0.0);
}
}
/// Integrate angular velocity and update the entity's transform.
pub(crate) fn integrate_angular_velocity(
mut objects: Query<(&mut Transform, &AngularVelocity)>,
time: Res<Time>,
) {
for (mut transform, ang_vel) in &mut objects {
let delta = ang_vel.0 * time.delta_secs();
transform.rotate_z(delta);
}
}
pub(crate) fn wrap_entities(
mut query: Query<&mut Transform, With<Wrapping>>,
world_size: Res<WorldSize>,
) {
let right = world_size.x / 2.0;
let left = -right;
let top = world_size.y / 2.0;
let bottom = -top;
for mut pos in query.iter_mut() {
if pos.translation.x > right {
pos.translation.x = left;
} else if pos.translation.x < left {
pos.translation.x = right;
}
if pos.translation.y > top {
pos.translation.y = bottom;
} else if pos.translation.y < bottom {
pos.translation.y = top;
}
}
}
/// The collision event routing system.
///
/// When a `CollisionEvent` occurrs, this system checks which things collided
/// and emits secondary events accordignly.
///
/// | Objects | Response |
/// |-|-|
/// | Ship & Asteroid | emits event [`ShipDestroy`](`crate::events::ShipDestroy`) |
/// | Asteroid & Bullet | emits event [`AsteroidDestroy`](`crate::events::AsteroidDestroy`) |
/// | Asteroid & Asteroid | Nothing. Asteroids won't collide with each other |
/// | 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>,
player: Single<Entity, With<Ship>>,
bullets: Query<&Bullet>,
rocks: Query<&Asteroid>,
) {
for event in collisions.read() {
if let CollisionEvent::Started(one, two, _flags) = event {
// Valid collisions are:
//
// - Ship & Asteroid
// - Bullet & Asteroid
//
// Asteroids don't collide with each other, bullets don't collide
// with each other, and bullets don't collide with the player ship.
// Option 1: Ship & Asteroid
if *one == *player {
if rocks.contains(*two) {
// player-asteroid collision
dbg!("Writing ShipDestroy event");
ship_writer.write(events::ShipDestroy);
} // else, we don't care
} else if *two == *player {
if rocks.contains(*one) {
dbg!("Writing ShipDestroy event");
ship_writer.write(events::ShipDestroy);
}
}
// Option 2: Bullet & Asteroid
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));
}
} 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));
}
}
}
}
}