feat(game): add collision detection, damage, and scoring
Projectiles destroy enemies on contact (radius check 0.55). Enemies damage the player on contact with 1s invincibility window. Score +100 per kill. Game over when HP reaches 0. update() now returns FrameEvents so the caller can trigger audio/visual feedback. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -5,6 +5,12 @@ use crate::projectile::Projectile;
|
||||
use crate::enemy::Enemy;
|
||||
use crate::wave::WaveSystem;
|
||||
|
||||
pub struct FrameEvents {
|
||||
pub shot_fired: bool,
|
||||
pub enemies_killed: u32,
|
||||
pub player_hit: bool,
|
||||
}
|
||||
|
||||
pub struct GameState {
|
||||
pub player: Player,
|
||||
pub projectiles: Vec<Projectile>,
|
||||
@@ -26,18 +32,28 @@ impl GameState {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn try_shoot(&mut self, aim_dir: Vec3) {
|
||||
pub fn try_shoot(&mut self, aim_dir: Vec3) -> bool {
|
||||
if self.player.fire_cooldown <= 0.0 {
|
||||
let origin = self.player.position + Vec3::new(0.0, 0.5, 0.0);
|
||||
self.projectiles.push(Projectile::new(origin, aim_dir, 20.0));
|
||||
self.player.fire_cooldown = 0.2;
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update(&mut self, input: &InputState, dt: f32) {
|
||||
pub fn update(&mut self, input: &InputState, dt: f32) -> FrameEvents {
|
||||
let mut events = FrameEvents {
|
||||
shot_fired: false,
|
||||
enemies_killed: 0,
|
||||
player_hit: false,
|
||||
};
|
||||
|
||||
if self.game_over {
|
||||
return;
|
||||
return events;
|
||||
}
|
||||
|
||||
self.player.update(input, dt);
|
||||
self.player.fire_cooldown = (self.player.fire_cooldown - dt).max(0.0);
|
||||
|
||||
@@ -64,5 +80,56 @@ impl GameState {
|
||||
|
||||
// Remove dead enemies
|
||||
self.enemies.retain(|e| e.alive);
|
||||
|
||||
// Collision: projectile vs enemy
|
||||
let mut killed_enemies = Vec::new();
|
||||
let mut killed_projectiles = Vec::new();
|
||||
|
||||
for (pi, proj) in self.projectiles.iter().enumerate() {
|
||||
for (ei, enemy) in self.enemies.iter().enumerate() {
|
||||
let dist = (proj.position - enemy.position).length();
|
||||
if dist < 0.55 {
|
||||
// projectile radius 0.15 + enemy radius 0.4
|
||||
killed_enemies.push(ei);
|
||||
killed_projectiles.push(pi);
|
||||
self.score += 100;
|
||||
events.enemies_killed += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove killed (reverse order to preserve indices)
|
||||
killed_enemies.sort();
|
||||
killed_enemies.dedup();
|
||||
for &i in killed_enemies.iter().rev() {
|
||||
self.enemies.remove(i);
|
||||
}
|
||||
killed_projectiles.sort();
|
||||
killed_projectiles.dedup();
|
||||
for &i in killed_projectiles.iter().rev() {
|
||||
self.projectiles.remove(i);
|
||||
}
|
||||
|
||||
// Collision: enemy vs player
|
||||
if self.player.invincible_timer <= 0.0 {
|
||||
for enemy in self.enemies.iter() {
|
||||
let dist = (enemy.position - self.player.position).length();
|
||||
if dist < 0.9 {
|
||||
// player radius 0.5 + enemy radius 0.4
|
||||
self.player.hp -= 1;
|
||||
self.player.invincible_timer = 1.0;
|
||||
events.player_hit = true;
|
||||
if self.player.hp <= 0 {
|
||||
self.game_over = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update invincible timer
|
||||
self.player.invincible_timer = (self.player.invincible_timer - dt).max(0.0);
|
||||
|
||||
events
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user