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::enemy::Enemy;
|
||||||
use crate::wave::WaveSystem;
|
use crate::wave::WaveSystem;
|
||||||
|
|
||||||
|
pub struct FrameEvents {
|
||||||
|
pub shot_fired: bool,
|
||||||
|
pub enemies_killed: u32,
|
||||||
|
pub player_hit: bool,
|
||||||
|
}
|
||||||
|
|
||||||
pub struct GameState {
|
pub struct GameState {
|
||||||
pub player: Player,
|
pub player: Player,
|
||||||
pub projectiles: Vec<Projectile>,
|
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 {
|
if self.player.fire_cooldown <= 0.0 {
|
||||||
let origin = self.player.position + Vec3::new(0.0, 0.5, 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.projectiles.push(Projectile::new(origin, aim_dir, 20.0));
|
||||||
self.player.fire_cooldown = 0.2;
|
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 {
|
if self.game_over {
|
||||||
return;
|
return events;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.player.update(input, dt);
|
self.player.update(input, dt);
|
||||||
self.player.fire_cooldown = (self.player.fire_cooldown - dt).max(0.0);
|
self.player.fire_cooldown = (self.player.fire_cooldown - dt).max(0.0);
|
||||||
|
|
||||||
@@ -64,5 +80,56 @@ impl GameState {
|
|||||||
|
|
||||||
// Remove dead enemies
|
// Remove dead enemies
|
||||||
self.enemies.retain(|e| e.alive);
|
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