summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRyan Symons <47405201+rsymons22@users.noreply.github.com>2025-11-10 21:33:34 -0500
committerRyan Symons <47405201+rsymons22@users.noreply.github.com>2025-11-10 21:33:34 -0500
commit439395cca406c2d7ee17868c889222832a4cd686 (patch)
treea9fb4ed7efe5df8d31574c5d874aca911ad13f4b
parentupdate checkpoint doc (diff)
downloadDungeonCrawl-439395cca406c2d7ee17868c889222832a4cd686.tar.gz
DungeonCrawl-439395cca406c2d7ee17868c889222832a4cd686.tar.bz2
DungeonCrawl-439395cca406c2d7ee17868c889222832a4cd686.zip
Enemy now follows a moving player, updated checkpoint doc a bit
-rw-r--r--docs/Checkpoint.md6
-rw-r--r--dungeon/src/entity.rs80
2 files changed, 58 insertions, 28 deletions
diff --git a/docs/Checkpoint.md b/docs/Checkpoint.md
index ec891e0..9e77155 100644
--- a/docs/Checkpoint.md
+++ b/docs/Checkpoint.md
@@ -20,12 +20,11 @@ Give a summary of the progress made and lessons learned thus far.
So far, our group has been able to achieve most of our MVP functionalities, including:
- Dunegon floor procedural dungeon generation
-- Player movement and enemy spawning
+- Player movement and complex enemy movement (roaming and pathing to player).
- Collision Logic
We've even managed to achieve some stretch goals and additional features:
- Usage of pixel art sprite sheets
- Item/Inventory system
-However, we still have yet to implement a full enemy/player combat loop (attacking and defending).
## Lessons Learned
With the work completed so far, we've gained a greater understanding of creating Rust
@@ -108,6 +107,9 @@ Implemented Features:
- Finalize game textures
- Many textures are placeholders, or straight up "error" textures
+- Implement attacking for both enemies and player
+
+- Create new enemy types for a more interesting game experience, currently only a basic "Zombie" exists
TODO
## Additional Details
diff --git a/dungeon/src/entity.rs b/dungeon/src/entity.rs
index 762355e..82042b5 100644
--- a/dungeon/src/entity.rs
+++ b/dungeon/src/entity.rs
@@ -13,6 +13,19 @@ pub const PLAYER_INVENTORY_SIZE: u16 = 5;
/// `PLAYER_INVENTORY_SIZE_USIZE` is the maximum size of the inventory
pub const PLAYER_INVENTORY_SIZE_USIZE: usize = PLAYER_INVENTORY_SIZE as usize;
+/// 'MIN_ROAM_DIST' and 'MAX_ROAM_DIST' are the enemy roam ranges
+pub const MIN_ROAM_DIST: u16 = 1;
+pub const MAX_ROAM_DIST: u16 = 4;
+
+/// 'ZOMBIE_HEALTH' is the starting health of a zombie enemy
+pub const ZOMBIE_HEALTH: u32 = 5;
+
+/// 'ENEMY_VISION_RADIUS' is the range used to determine if the player is seen by an enemy
+pub const ENEMY_VISION_RADIUS: u16 = 10;
+
+/// 'IDLE_WAIT' is how long in seconds an enemy waits in the idle state
+pub const IDLE_WAIT: f32 = 1.;
+
/// The `Item` type represents any item an entity may be using
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum Item {
@@ -32,7 +45,7 @@ pub enum EntityKind {
}
impl EntityKind {
- // Tiles/s
+ /// Returns the move speed value for this type of entity in tiles/s
pub fn move_speed(&self) -> f32 {
match &self {
Self::Player => 5.,
@@ -42,36 +55,41 @@ impl EntityKind {
}
}
-pub const MIN_ROAM_DIST: u16 = 1;
-pub const MAX_ROAM_DIST: u16 = 4;
-
-pub const ZOMBIE_HEALTH: u32 = 5;
-pub const ENEMY_VISION_RADIUS: u16 = 10;
-
-pub const IDLE_WAIT: f32 = 1.;
-
+/// The `EnemyMoveState` enum describes the state of an enemies movement, either Idle
+/// Roam, or Attack. Idle carries a `f32` value to remember time waited in idle. Both Roam
+/// and Attack carry a `Vec<Pos>` to represent the sequence of moves to their desired locations.
+/// Attack also holds the desired location itself as a `Pos`.
#[derive(Clone, Debug, PartialEq)]
pub enum EnemyMoveState {
Idle(f32),
Roam(Vec<Pos>),
- Attack(Vec<Pos>),
+ Attack(Vec<Pos>, Pos),
}
impl EnemyMoveState {
+ /// Creates an `EnemyMoveState::Idle` enum, initialized to a time accumlation of 0.
pub const fn new_idle() -> Self {
Self::Idle(0.)
}
+ /// Creates an `EnemyMoveState::Attack` enum containing the list of steps for movement and the goal position.
+ ///
+ /// Will return `Some(EnemyMoveState::Attack)` if the player is within the vision radius and there is a valid path
+ /// to the player. Returns `None` if the player is outside the radius or there is no valid path to the player.
pub fn new_attack(starting_pos: Pos, player_pos: Pos, floor: &Floor) -> Option<Self> {
if player_pos.manhattan(starting_pos) < ENEMY_VISION_RADIUS {
let data = astar::astar(starting_pos, player_pos, floor)?;
let mut path = data.0;
path.reverse();
- return Some(Self::Attack(path));
+ return Some(Self::Attack(path, player_pos));
}
None
}
+ /// Creates an `EnemyMoveState::Roam` enum containing the list of steps for movement.
+ ///
+ /// Will randomly pick a direction and number of tiles within roaming range to move.
+ /// If an invalid tile is selected 50 times in a row, `EnemyMoveState::Idle` is returned instead.
pub fn new_roam(starting_pos: Pos, floor: &mut Floor) -> Self {
let mut loop_index = 0;
loop {
@@ -179,13 +197,10 @@ impl Entity {
Self::new(pos, dir, kind, health)
}
- pub fn move_by_dir(&mut self, dir: Direction, delta_time: f32) {
- if let Some(fp) = self.fpos.step_by(dir, delta_time * self.kind.move_speed()) {
- // TODO: collision
- self.fpos = fp;
- }
- }
-
+ /// Handle movement for this entity.
+ ///
+ /// TODO: Merge this implementation (and Self::zombie_movement and Self::movement_helper) with the player movement
+ /// in lib.rs.
pub fn handle_movement(&mut self, player_pos: Pos, floor: &mut Floor, delta_time: f32) {
match &self.kind {
EntityKind::Zombie(move_state) => {
@@ -196,6 +211,7 @@ impl Entity {
}
}
+ /// State machine for zombie_movement.
pub fn zombie_movement(
&mut self,
move_state: EnemyMoveState,
@@ -233,24 +249,36 @@ impl Entity {
self.kind = EntityKind::Zombie(EnemyMoveState::Roam(moves));
}
- EnemyMoveState::Attack(mut moves) => {
+ EnemyMoveState::Attack(mut moves, old_player_pos) => {
+ if old_player_pos != player_pos {
+ self.kind = EntityKind::Zombie(
+ match EnemyMoveState::new_attack(self.pos, player_pos, floor) {
+ Some(move_state) => move_state,
+ None => EnemyMoveState::new_idle(),
+ },
+ );
+ return;
+ }
+
// Stop at one move left so the enemy is flush with the player tile
if moves.len() == 1 {
return;
}
- Self::movement_helper(
- self,
- *moves.last().unwrap_or(&Pos::default()),
- &mut moves,
- delta_time,
- );
+ let next_move = *moves.last().unwrap_or(&Pos::default());
+
+ if next_move == self.pos {
+ moves.pop();
+ } else {
+ Self::movement_helper(self, next_move, &mut moves, delta_time);
+ }
- self.kind = EntityKind::Zombie(EnemyMoveState::Attack(moves));
+ self.kind = EntityKind::Zombie(EnemyMoveState::Attack(moves, player_pos));
}
}
}
+ /// Simple movement_helper for enemy/player movement.
fn movement_helper(
&mut self,
mut next_move: Pos,