feat: rewrite ghost/item collision eventing into trigger-based observer

This commit is contained in:
Ryan Walters
2025-09-10 17:15:15 -05:00
parent abf341d753
commit ae19ca1795
9 changed files with 240 additions and 222 deletions

View File

@@ -1,5 +1,8 @@
use bevy_ecs::{entity::Entity, system::RunSystemOnce};
use pacman::systems::{item_system, EntityType, GhostState, Position, ScoreResource};
use bevy_ecs::entity::Entity;
use pacman::{
events::CollisionTrigger,
systems::{EntityType, GhostState, Position, ScoreResource},
};
use speculoos::prelude::*;
mod common;
@@ -26,18 +29,17 @@ fn test_is_collectible_item() {
#[test]
fn test_item_system_pellet_collection() {
let mut world = common::create_test_world();
let (mut world, mut schedule) = common::create_test_world();
let pacman = common::spawn_test_pacman(&mut world, 0);
let pellet = common::spawn_test_item(&mut world, 1, EntityType::Pellet);
// Send collision event
common::send_collision_event(&mut world, pacman, pellet);
common::trigger_collision(&mut world, CollisionTrigger::ItemCollision { pacman, item: pellet });
// Run the item system
world.run_system_once(item_system).expect("System should run successfully");
schedule.run(&mut world);
// Check that score was updated
let score = world.resource::<ScoreResource>();
let score = world.resource_mut::<ScoreResource>();
assert_that(&score.0).is_equal_to(10);
// Check that the pellet was despawned (query should return empty)
@@ -51,13 +53,19 @@ fn test_item_system_pellet_collection() {
#[test]
fn test_item_system_power_pellet_collection() {
let mut world = common::create_test_world();
let (mut world, mut schedule) = common::create_test_world();
let pacman = common::spawn_test_pacman(&mut world, 0);
let power_pellet = common::spawn_test_item(&mut world, 1, EntityType::PowerPellet);
common::send_collision_event(&mut world, pacman, power_pellet);
common::trigger_collision(
&mut world,
CollisionTrigger::ItemCollision {
pacman,
item: power_pellet,
},
);
world.run_system_once(item_system).expect("System should run successfully");
schedule.run(&mut world);
// Check that score was updated with power pellet value
let score = world.resource::<ScoreResource>();
@@ -74,23 +82,31 @@ fn test_item_system_power_pellet_collection() {
#[test]
fn test_item_system_multiple_collections() {
let mut world = common::create_test_world();
let (mut world, mut schedule) = common::create_test_world();
let pacman = common::spawn_test_pacman(&mut world, 0);
let pellet1 = common::spawn_test_item(&mut world, 1, EntityType::Pellet);
let pellet2 = common::spawn_test_item(&mut world, 2, EntityType::Pellet);
let power_pellet = common::spawn_test_item(&mut world, 3, EntityType::PowerPellet);
// Send multiple collision events
common::send_collision_event(&mut world, pacman, pellet1);
common::send_collision_event(&mut world, pacman, pellet2);
common::send_collision_event(&mut world, pacman, power_pellet);
common::trigger_collision(&mut world, CollisionTrigger::ItemCollision { pacman, item: pellet1 });
common::trigger_collision(&mut world, CollisionTrigger::ItemCollision { pacman, item: pellet2 });
common::trigger_collision(
&mut world,
CollisionTrigger::ItemCollision {
pacman,
item: power_pellet,
},
);
world.run_system_once(item_system).expect("System should run successfully");
schedule.run(&mut world);
// Check final score: 2 pellets (20) + 1 power pellet (50) = 70
let score = world.resource::<ScoreResource>();
assert_that(&score.0).is_equal_to(70);
schedule.run(&mut world);
// Check that all items were despawned
let pellet_count = world
.query::<&EntityType>()
@@ -108,7 +124,7 @@ fn test_item_system_multiple_collections() {
#[test]
fn test_item_system_ignores_non_item_collisions() {
let mut world = common::create_test_world();
let (mut world, mut schedule) = common::create_test_world();
let pacman = common::spawn_test_pacman(&mut world, 0);
// Create a ghost entity (not an item)
@@ -118,9 +134,9 @@ fn test_item_system_ignores_non_item_collisions() {
let initial_score = world.resource::<ScoreResource>().0;
// Send collision event between pacman and ghost
common::send_collision_event(&mut world, pacman, ghost);
common::trigger_collision(&mut world, CollisionTrigger::ItemCollision { pacman, item: ghost });
world.run_system_once(item_system).expect("System should run successfully");
schedule.run(&mut world);
// Score should remain unchanged
let score = world.resource::<ScoreResource>();
@@ -137,14 +153,14 @@ fn test_item_system_ignores_non_item_collisions() {
#[test]
fn test_item_system_no_collision_events() {
let mut world = common::create_test_world();
let (mut world, mut schedule) = common::create_test_world();
let _pacman = common::spawn_test_pacman(&mut world, 0);
let _pellet = common::spawn_test_item(&mut world, 1, EntityType::Pellet);
let initial_score = world.resource::<ScoreResource>().0;
// Run system without any collision events
world.run_system_once(item_system).expect("System should run successfully");
schedule.run(&mut world);
// Nothing should change
let score = world.resource::<ScoreResource>();
@@ -159,19 +175,22 @@ fn test_item_system_no_collision_events() {
#[test]
fn test_item_system_collision_with_missing_entity() {
let mut world = common::create_test_world();
let (mut world, mut schedule) = common::create_test_world();
let pacman = common::spawn_test_pacman(&mut world, 0);
// Create a fake entity ID that doesn't exist
let fake_entity = Entity::from_raw(999);
common::send_collision_event(&mut world, pacman, fake_entity);
common::trigger_collision(
&mut world,
CollisionTrigger::ItemCollision {
pacman,
item: fake_entity,
},
);
// System should handle gracefully and not crash
world
.run_system_once(item_system)
.expect("System should handle missing entities gracefully");
schedule.run(&mut world);
// Score should remain unchanged
let score = world.resource::<ScoreResource>();
assert_that(&score.0).is_equal_to(0);
@@ -179,7 +198,7 @@ fn test_item_system_collision_with_missing_entity() {
#[test]
fn test_item_system_preserves_existing_score() {
let mut world = common::create_test_world();
let (mut world, mut schedule) = common::create_test_world();
// Set initial score
world.insert_resource(ScoreResource(100));
@@ -187,9 +206,9 @@ fn test_item_system_preserves_existing_score() {
let pacman = common::spawn_test_pacman(&mut world, 0);
let pellet = common::spawn_test_item(&mut world, 1, EntityType::Pellet);
common::send_collision_event(&mut world, pacman, pellet);
common::trigger_collision(&mut world, CollisionTrigger::ItemCollision { pacman, item: pellet });
world.run_system_once(item_system).expect("System should run successfully");
schedule.run(&mut world);
// Score should be initial + pellet value
let score = world.resource::<ScoreResource>();
@@ -198,7 +217,7 @@ fn test_item_system_preserves_existing_score() {
#[test]
fn test_power_pellet_does_not_affect_ghosts_in_eyes_state() {
let mut world = common::create_test_world();
let (mut world, mut schedule) = common::create_test_world();
let pacman = common::spawn_test_pacman(&mut world, 0);
let power_pellet = common::spawn_test_item(&mut world, 1, EntityType::PowerPellet);
@@ -208,9 +227,15 @@ fn test_power_pellet_does_not_affect_ghosts_in_eyes_state() {
// Spawn a ghost in Normal state
let normal_ghost = common::spawn_test_ghost(&mut world, 3, GhostState::Normal);
common::send_collision_event(&mut world, pacman, power_pellet);
common::trigger_collision(
&mut world,
CollisionTrigger::ItemCollision {
pacman,
item: power_pellet,
},
);
world.run_system_once(item_system).expect("System should run successfully");
schedule.run(&mut world);
// Check that the power pellet was collected and score updated
let score = world.resource::<ScoreResource>();