diff options
| author | Yusuf Elsharawy <yusufse-2004@hotmail.com> | 2025-11-10 20:05:46 +0000 |
|---|---|---|
| committer | Yusuf Elsharawy <yusufse-2004@hotmail.com> | 2025-11-10 20:10:38 +0000 |
| commit | b1c7a57e72ab7359191249d76ec84d6cae524e2a (patch) | |
| tree | c533911f87b1641d893de5ab68ed05c2558b1cba | |
| parent | graphics: decouple sdl & wayland (diff) | |
| download | DungeonCrawl-b1c7a57e72ab7359191249d76ec84d6cae524e2a.tar.gz DungeonCrawl-b1c7a57e72ab7359191249d76ec84d6cae524e2a.tar.bz2 DungeonCrawl-b1c7a57e72ab7359191249d76ec84d6cae524e2a.zip | |
Refactored some code, implemented player movement
| -rw-r--r-- | dungeon/src/entity.rs | 64 | ||||
| -rw-r--r-- | dungeon/src/lib.rs | 47 | ||||
| -rw-r--r-- | dungeon/src/player_input.rs | 16 | ||||
| -rw-r--r-- | dungeon/src/pos.rs | 160 | ||||
| -rw-r--r-- | game/src/main.rs | 103 | ||||
| -rw-r--r-- | graphics/src/lib.rs | 7 |
6 files changed, 329 insertions, 68 deletions
diff --git a/dungeon/src/entity.rs b/dungeon/src/entity.rs index c84fc42..762355e 100644 --- a/dungeon/src/entity.rs +++ b/dungeon/src/entity.rs @@ -35,7 +35,7 @@ impl EntityKind { // Tiles/s pub fn move_speed(&self) -> f32 { match &self { - Self::Player => 2., + Self::Player => 5., Self::Zombie(_) => 4., _ => 0., } @@ -252,50 +252,38 @@ impl Entity { } fn movement_helper( - entity: &mut Self, - next_move: Pos, + &mut self, + mut next_move: Pos, moves: &mut Vec<Pos>, delta_time: f32, ) { - if entity.pos.y() > next_move.y() { - entity.move_by_dir(Direction::North, delta_time); - if entity.fpos.y() <= next_move.y() as f32 { - if let Some(p) = moves.pop() { - entity.pos = p; - entity.fpos = FPos::from_pos(p); - } - } - } else if entity.pos.y() < next_move.y() { - entity.move_by_dir(Direction::South, delta_time); - if entity.fpos.y() >= next_move.y() as f32 { - if let Some(p) = moves.pop() { - entity.pos = p; - entity.fpos = FPos::from_pos(p); - } - } - } else if entity.pos.x() < next_move.x() { - entity.move_by_dir(Direction::East, delta_time); - if entity.fpos.x() >= next_move.x() as f32 { - if let Some(p) = moves.pop() { - entity.pos = p; - entity.fpos = FPos::from_pos(p); - } - } - } else { - entity.move_by_dir(Direction::West, delta_time); - if entity.fpos.x() <= next_move.x() as f32 { - if let Some(p) = moves.pop() { - entity.pos = p; - entity.fpos = FPos::from_pos(p); - } + let mut move_distance = self.kind.move_speed() * delta_time; + // having this be a loop is a *little* unnecessary, + // but is technically more correct if the entity is fast enough + // to move more than one tile in a single frame + loop { + move_distance -= self + .fpos + .move_towards_manhattan(FPos::from(next_move), move_distance); + if move_distance == 0.0 { + // can't move any further + break; } - }; + // otherwise, we reached `next_move`, set position & pop from vec + self.pos = next_move; + let _ = moves.pop(); + + // there is likely more distance to travel + let Some(last) = moves.last() else { break }; + next_move = *last; + } } } /// The `Player` type represents the main player entity #[derive(Clone, Debug, PartialEq)] pub struct Player { + pub moving_to: Option<Pos>, pub entity: Entity, pub inventory: Vec<Item>, } @@ -313,7 +301,11 @@ impl Player { pub fn new(pos: Pos) -> Self { let entity = Entity::player(pos); let inventory = vec![]; - Self { entity, inventory } + Self { + entity, + inventory, + moving_to: None, + } } } impl Default for Player { diff --git a/dungeon/src/lib.rs b/dungeon/src/lib.rs index cfd2fbe..399807c 100644 --- a/dungeon/src/lib.rs +++ b/dungeon/src/lib.rs @@ -5,11 +5,13 @@ pub mod astar; pub mod bsp; pub mod entity; pub mod map; +pub mod player_input; pub mod pos; pub use bsp::*; pub use entity::*; pub use map::*; +pub use player_input::*; pub use pos::*; /// The `Dungeon` type represents the game state of the @@ -55,6 +57,51 @@ impl Dungeon { pub fn camera(&self) -> FPos { self.player.entity.fpos } + + pub fn update(&mut self, player_input: PlayerInput, delta_time: f32) { + self.act_player(player_input, delta_time); + self.act_non_players(delta_time); + } + + fn act_player(&mut self, player_input: PlayerInput, delta_time: f32) { + let mut move_distance = self.player.entity.kind.move_speed() * delta_time; + // having this be a loop is a *little* unnecessary, + // but is technically more correct if the entity is fast enough + // to move more than one tile in a single frame + // (plus, it was easier to write if i thought of this like a state machine) + loop { + match (self.player.moving_to, player_input.direction) { + (Some(pos), _) => { + move_distance -= self + .player + .entity + .fpos + .move_towards_manhattan(pos.into(), move_distance); + if move_distance == 0.0 { + // can't move any further + break; + } + // otherwise, reached `pos` + self.player.entity.pos = pos; + self.player.moving_to = None; + // continue moving + } + (None, Some(dir)) => { + // set direction & find out next position + self.player.entity.dir = dir; + self.player.moving_to = self.player.entity.pos.step(dir); + } + (None, None) => break, + } + } + // TODO: reuse a similar structure across all entities + } + + fn act_non_players(&mut self, delta_time: f32) { + for enemy in self.enemies.iter_mut() { + enemy.handle_movement(self.player.entity.pos, &mut self.floor, delta_time); + } + } } impl Default for Dungeon { fn default() -> Self { diff --git a/dungeon/src/player_input.rs b/dungeon/src/player_input.rs new file mode 100644 index 0000000..643b014 --- /dev/null +++ b/dungeon/src/player_input.rs @@ -0,0 +1,16 @@ +use crate::pos::*; + +/// Carries information on the player's inputs. +/// Allows the game to retrieve player input commands, +/// without tightly binding to a specific GUI or keybinds. +/// This way, game logic can focus on "what the inputs do" +/// as opposed to "what do the key presses mean". +#[derive(Copy, Clone, Default, Debug)] +pub struct PlayerInput { + /// The direction that the player wants to move in. + /// The creator is responsible for resolving to just one direction + /// (eg if the player is holding multiple keys at once, + /// or a joystick) + pub direction: Option<Direction>, + // other player actions are to be added later +} diff --git a/dungeon/src/pos.rs b/dungeon/src/pos.rs index e3365ef..623d914 100644 --- a/dungeon/src/pos.rs +++ b/dungeon/src/pos.rs @@ -12,7 +12,7 @@ use rand::{ use std::{ fmt::Display, - ops::{AddAssign, SubAssign}, + ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Sub, SubAssign}, }; use crate::{MAP_SIZE_USIZE, map::MAP_SIZE}; @@ -499,6 +499,18 @@ impl FPos { self.1 } + /// Returns the component-wise absolute value of `FPos`. + #[must_use] + pub const fn abs(&self) -> Self { + Self(self.0.abs(), self.1.abs()) + } + + /// Returns the euclidean magnitude of `FPos`. + #[must_use] + pub fn magnitude(&self) -> f32 { + (self.0 * self.0 + self.1 * self.1).sqrt() + } + /// Steps `FPos` a given floating amount in the `Direction` `dir`. /// /// Returns `None` if the floating position wraps. @@ -534,6 +546,90 @@ impl FPos { Some(Self(x, y)) } + /// Moves from one position towards another by at most some euclidean distance. + /// Returns the distance moved. + /// + /// # Examples + /// Note: some assertions may be commented out, as they would fail due to floating-point error. + /// + /// ``` + /// use dungeon::FPos; + /// + /// let mut fpos1 = FPos::new(0.0,0.0); + /// let fpos2 = FPos::new(0.0,1.0); + /// let moved = fpos1.move_towards(fpos2, 0.6); + /// assert_eq!(moved, 0.6); + /// assert_eq!(fpos1, FPos::new(0.0, 0.6)); + /// let moved = fpos1.move_towards(fpos2, 0.6); + /// // assert_eq!(moved, 0.4); + /// assert_eq!(fpos1, FPos::new(0.0, 1.0)); + /// ``` + /// + pub fn move_towards(&mut self, goal: Self, by: f32) -> f32 { + let diff = goal - *self; + let dist = diff.magnitude(); + if by >= dist { + *self = goal; + dist + } else { + *self += diff * by / dist; + by + } + } + /// Equivalent to `move_towards` but returns a copy, discarding the distance moved. + /// + /// # Examples + /// + /// ``` + /// use dungeon::FPos; + /// + /// let fpos1 = FPos::new(0.0,0.0); + /// let fpos2 = FPos::new(0.0,1.0); + /// let fpos1 = fpos1.moved_towards(fpos2, 0.6); + /// assert_eq!(fpos1, FPos::new(0.0, 0.6)); + /// let fpos1 = fpos1.moved_towards(fpos2, 0.6); + /// assert_eq!(fpos1, FPos::new(0.0, 1.0)); + /// ``` + #[must_use] + pub fn moved_towards(mut self, goal: Self, by: f32) -> Self { + self.move_towards(goal, by); + self + } + + /// Moves from one position towards another by at most some manhattan distance. + /// Prefers vertical movement over horizontal movement. + /// Returns the distance moved. + /// + /// # Examples + /// Note: some assertions may be commented out, as they would fail due to floating-point error. + /// + /// ``` + /// use dungeon::FPos; + /// + /// let mut fpos1 = FPos::new(0.0,0.0); + /// let fpos2 = FPos::new(1.0,1.0); + /// let moved = fpos1.move_towards_manhattan(fpos2, 0.6); + /// assert_eq!(moved, 0.6); + /// assert_eq!(fpos1, FPos::new(0.0, 0.6)); + /// let moved = fpos1.move_towards_manhattan(fpos2, 0.6); + /// assert_eq!(moved, 0.6); + /// // assert_eq!(fpos1, FPos::new(0.2, 1.0)); + /// let moved = fpos1.move_towards_manhattan(fpos2, 0.6); + /// assert_eq!(moved, 0.6); + /// // assert_eq!(fpos1, FPos::new(0.8, 1.0)); + /// let moved = fpos1.move_towards_manhattan(fpos2, 0.6); + /// // assert_eq!(moved, 0.2); + /// assert_eq!(fpos1, FPos::new(1.0, 1.0)); + /// ``` + /// + pub fn move_towards_manhattan(&mut self, goal: Self, by: f32) -> f32 { + let vert_checkpoint = Self(self.0, goal.1); + let mut rem = by; + rem -= self.move_towards(vert_checkpoint, rem); + rem -= self.move_towards(goal, rem); + by - rem + } + /// Computes the absolute difference between to positions /// /// # Examples @@ -548,10 +644,8 @@ impl FPos { /// ``` /// #[must_use] - pub const fn abs_diff(self, other: Self) -> Self { - let x = (self.0 - other.0).abs(); - let y = (self.1 - other.1).abs(); - Self(x, y) + pub fn abs_diff(self, other: Self) -> Self { + (self - other).abs() } /// Returns the manhattan distance between `self` and `other` @@ -566,11 +660,65 @@ impl FPos { /// assert_eq!(fpos1.manhattan(fpos2), 1.5); /// ``` #[must_use] - pub const fn manhattan(self, other: Self) -> f32 { + pub fn manhattan(self, other: Self) -> f32 { let abs_diff = Self::abs_diff(self, other); abs_diff.0 + abs_diff.1 } } + +impl AddAssign<Self> for FPos { + fn add_assign(&mut self, rhs: Self) { + self.0 += rhs.0; + self.1 += rhs.1; + } +} +impl Add<Self> for FPos { + type Output = Self; + fn add(mut self, rhs: Self) -> Self { + self += rhs; + self + } +} +impl SubAssign<Self> for FPos { + fn sub_assign(&mut self, rhs: Self) { + self.0 -= rhs.0; + self.1 -= rhs.1; + } +} +impl Sub<Self> for FPos { + type Output = Self; + fn sub(mut self, rhs: Self) -> Self { + self -= rhs; + self + } +} +impl MulAssign<f32> for FPos { + fn mul_assign(&mut self, rhs: f32) { + self.0 *= rhs; + self.1 *= rhs; + } +} +impl Mul<f32> for FPos { + type Output = Self; + fn mul(mut self, rhs: f32) -> Self { + self *= rhs; + self + } +} +impl DivAssign<f32> for FPos { + fn div_assign(&mut self, rhs: f32) { + self.0 /= rhs; + self.1 /= rhs; + } +} +impl Div<f32> for FPos { + type Output = Self; + fn div(mut self, rhs: f32) -> Self { + self /= rhs; + self + } +} + impl Default for FPos { /// Returns a default postion at the origin (0,0) /// diff --git a/game/src/main.rs b/game/src/main.rs index d96273e..aabe38a 100644 --- a/game/src/main.rs +++ b/game/src/main.rs @@ -2,6 +2,82 @@ use clap::Parser; use dungeon::*; use graphics::*; +struct Game { + window: Window, + dungeon: Dungeon, + // to ensure the most recently-pressed direction key is used: + current_dir: Option<(Direction, KeyCode)>, +} + +impl Game { + fn new(window: Window) -> Self { + // Initial game state + let dungeon = Dungeon::new(); + Ok(Self { + window, + dungeon, + current_dir: None, + }) + } + + fn player_dir(&mut self) -> Option<Direction> { + const MOVE_KEYS: [(Direction, [KeyCode; 2]); 4] = [ + (Direction::North, [KeyCode::KEY_UP, KeyCode::KEY_W]), + (Direction::West, [KeyCode::KEY_LEFT, KeyCode::KEY_A]), + (Direction::South, [KeyCode::KEY_DOWN, KeyCode::KEY_S]), + (Direction::East, [KeyCode::KEY_RIGHT, KeyCode::KEY_D]), + ]; + + // if a key was just pressed, use it for the new direction to move in + for (dir, keys) in MOVE_KEYS { + for key in keys { + if self.window.is_key_pressed(key) { + self.current_dir = Some((dir, key)); + return Some(dir); + } + } + } + // otherwise, use existing direction, so long as the key is still down + match self.current_dir { + Some((dir, key)) if self.window.is_key_down(key) => return Some(dir), + _ => self.current_dir = None, + } + // otherwise, use any key that is already down + for (dir, keys) in MOVE_KEYS { + for key in keys { + if self.window.is_key_down(key) { + self.current_dir = Some((dir, key)); + return Some(dir); + } + } + } + // otherwise, no direction key is pressed, so return None + None + } + + fn run(&mut self) { + // Main game loop + while self.window.is_open() { + // Handle keyboard input + if self.window.is_key_pressed(KeyCode::KEY_F3) { + self.window.toggle_debug(); + } + + let inputs = PlayerInput { + direction: self.player_dir(), + }; + + // Update game state + self.dungeon.update(inputs, self.window.delta_time()); + + // Draw a single frame + self.window.draw_frame(&self.dungeon); + } + Ok(()) + } +} + + #[derive(Parser)] struct Args { /// Enable vsync @@ -16,33 +92,10 @@ fn main() -> Result<()> { // Parse arguments let args = Args::parse(); // Load the window - let mut window = WindowBuilder::new() + let window = WindowBuilder::new() .vsync(args.vsync) .verbose(args.verbose) .build()?; - // Initial game state - let mut dungeon = Dungeon::new(); - - // Main game loop - while window.is_open() { - // TODO update game state - - // Handle keyboard input - if window.is_key_pressed(KeyCode::KEY_F3) { - window.toggle_debug(); - } - - for enemy in dungeon.enemies.iter_mut() { - enemy.handle_movement( - dungeon.player.entity.pos, - &mut dungeon.floor, - window.delta_time(), - ); - } - - // Draw a single frame - window.draw_frame(&dungeon); - } - + Game::new(window).run(); Ok(()) } diff --git a/graphics/src/lib.rs b/graphics/src/lib.rs index 25b4287..b021cc6 100644 --- a/graphics/src/lib.rs +++ b/graphics/src/lib.rs @@ -161,11 +161,16 @@ impl Window { self.handle.borrow().get_frame_time() } - /// Returns if the provided `KeyCode` has been pressed once + /// Returns if the provided `KeyCode` has just been pressed pub fn is_key_pressed(&self, key: KeyCode) -> bool { self.handle.borrow().is_key_pressed(key) } + /// Returns if the provided `KeyCode` has just been released + pub fn is_key_released(&self, key: KeyCode) -> bool { + self.handle.borrow().is_key_released(key) + } + /// Returns if the provided `KeyCode` is NOT currently pressed pub fn is_key_up(&self, key: KeyCode) -> bool { self.handle.borrow().is_key_up(key) |