summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorYusuf Elsharawy <yusufse-2004@hotmail.com>2025-11-10 20:05:46 +0000
committerYusuf Elsharawy <yusufse-2004@hotmail.com>2025-11-10 20:10:38 +0000
commitb1c7a57e72ab7359191249d76ec84d6cae524e2a (patch)
treec533911f87b1641d893de5ab68ed05c2558b1cba
parentgraphics: decouple sdl & wayland (diff)
downloadDungeonCrawl-b1c7a57e72ab7359191249d76ec84d6cae524e2a.tar.gz
DungeonCrawl-b1c7a57e72ab7359191249d76ec84d6cae524e2a.tar.bz2
DungeonCrawl-b1c7a57e72ab7359191249d76ec84d6cae524e2a.zip
Refactored some code, implemented player movement
-rw-r--r--dungeon/src/entity.rs64
-rw-r--r--dungeon/src/lib.rs47
-rw-r--r--dungeon/src/player_input.rs16
-rw-r--r--dungeon/src/pos.rs160
-rw-r--r--game/src/main.rs103
-rw-r--r--graphics/src/lib.rs7
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)