Compare commits

..

17 Commits

Author SHA1 Message Date
18eaeee19e fix: compile time removal of tracing below WARN on release builds 2025-06-17 11:54:13 -05:00
b3c1a30a74 feat: tracing, sleep timing calculations, use spin_sleeper for accurate sleeps on Windows 2025-06-17 11:52:08 -05:00
0d76c6528b docs: add DLL instructions to README, expand .gitignore 2025-06-17 11:51:57 -05:00
da98b54216 feat: wall collisions 2025-06-17 11:51:49 -05:00
6ce3a5ce79 feat: speed modulation to implement precise speed decrease despite integers 2025-06-17 11:51:40 -05:00
b987599f10 reformat: general, target conditional module 2025-06-17 11:51:35 -05:00
786fbb5002 feat: change starting position of PacMan, draw current PacMan on grid 2025-06-17 11:51:26 -05:00
422535c00d feat: direction propagation, change direction at precise times 2025-06-17 11:51:21 -05:00
0120abe806 feat: add optional offset to AnimatedTexture 2025-06-17 11:51:17 -05:00
e61930c08a reformat: default debug off, conditional debug grid rendering, remove unused redraw() 2025-06-17 11:51:02 -05:00
f7ff9f5290 chore: delete TextureManager 2025-06-17 11:50:55 -05:00
de29dc6711 chore: remove unused tick timing 2025-06-17 11:50:49 -05:00
c90f221c73 feat: speed property to PacMan 2025-06-17 11:50:44 -05:00
841943e121 fix: frame flashing in sprite tick 2025-06-17 11:50:37 -05:00
83d665123c feat: smooth back-forth sprite frame ticks, sprite rotation 2025-06-17 11:50:32 -05:00
ffc21c8622 reformat: strict tick timing with lag prints 2025-06-17 11:50:26 -05:00
b46a51bc76 reformat: drop TextureManager based sprite rendering, directly hold Textures 2025-06-17 11:50:18 -05:00
13 changed files with 632 additions and 109 deletions

2
.gitignore vendored
View File

@@ -1,2 +1,4 @@
/target
/dist
.idea
*.dll

276
Cargo.lock generated
View File

@@ -2,6 +2,15 @@
# It is not intended for manual editing.
version = 3
[[package]]
name = "aho-corasick"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43f6cb1bf222025340178f382c426f13757b2960e89779dfcb319c32542a5a41"
dependencies = [
"memchr",
]
[[package]]
name = "bitflags"
version = "1.3.2"
@@ -26,14 +35,129 @@ version = "0.2.147"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3"
[[package]]
name = "log"
version = "0.4.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
[[package]]
name = "matchers"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558"
dependencies = [
"regex-automata 0.1.10",
]
[[package]]
name = "memchr"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
[[package]]
name = "nu-ansi-term"
version = "0.46.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84"
dependencies = [
"overload",
"winapi",
]
[[package]]
name = "once_cell"
version = "1.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d"
[[package]]
name = "overload"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
[[package]]
name = "pacman"
version = "0.1.0"
dependencies = [
"lazy_static",
"sdl2",
"spin_sleep",
"tracing",
"tracing-error",
"tracing-subscriber",
]
[[package]]
name = "pin-project-lite"
version = "0.2.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58"
[[package]]
name = "proc-macro2"
version = "1.0.66"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae"
dependencies = [
"proc-macro2",
]
[[package]]
name = "regex"
version = "1.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b2eae68fc220f7cf2532e4494aded17545fce192d59cd996e0fe7887f4ceb575"
dependencies = [
"aho-corasick",
"memchr",
"regex-automata 0.3.3",
"regex-syntax 0.7.4",
]
[[package]]
name = "regex-automata"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132"
dependencies = [
"regex-syntax 0.6.29",
]
[[package]]
name = "regex-automata"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39354c10dd07468c2e73926b23bb9c2caca74c5501e38a35da70406f1d923310"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax 0.7.4",
]
[[package]]
name = "regex-syntax"
version = "0.6.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1"
[[package]]
name = "regex-syntax"
version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2"
[[package]]
name = "sdl2"
version = "0.35.2"
@@ -57,8 +181,160 @@ dependencies = [
"version-compare",
]
[[package]]
name = "sharded-slab"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31"
dependencies = [
"lazy_static",
]
[[package]]
name = "smallvec"
version = "1.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62bb4feee49fdd9f707ef802e22365a35de4b7b299de4763d44bfea899442ff9"
[[package]]
name = "spin_sleep"
version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cafa7900db085f4354dbc7025e25d7a839a14360ea13b5fc4fd717f2d3b23134"
dependencies = [
"once_cell",
"winapi",
]
[[package]]
name = "syn"
version = "2.0.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "718fa2415bcb8d8bd775917a1bf12a7931b6dfa890753378538118181e0cb398"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "thread_local"
version = "1.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152"
dependencies = [
"cfg-if",
"once_cell",
]
[[package]]
name = "tracing"
version = "0.1.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8"
dependencies = [
"cfg-if",
"pin-project-lite",
"tracing-attributes",
"tracing-core",
]
[[package]]
name = "tracing-attributes"
version = "0.1.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "tracing-core"
version = "0.1.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0955b8137a1df6f1a2e9a37d8a6656291ff0297c1a97c24e0d8425fe2312f79a"
dependencies = [
"once_cell",
"valuable",
]
[[package]]
name = "tracing-error"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d686ec1c0f384b1277f097b2f279a2ecc11afe8c133c1aabf036a27cb4cd206e"
dependencies = [
"tracing",
"tracing-subscriber",
]
[[package]]
name = "tracing-log"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922"
dependencies = [
"lazy_static",
"log",
"tracing-core",
]
[[package]]
name = "tracing-subscriber"
version = "0.3.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30a651bc37f915e81f087d86e62a18eec5f79550c7faff886f7090b4ea757c77"
dependencies = [
"matchers",
"nu-ansi-term",
"once_cell",
"regex",
"sharded-slab",
"smallvec",
"thread_local",
"tracing",
"tracing-core",
"tracing-log",
]
[[package]]
name = "unicode-ident"
version = "1.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c"
[[package]]
name = "valuable"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d"
[[package]]
name = "version-compare"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "579a42fc0b8e0c63b76519a339be31bed574929511fa53c1a3acae26eb258f29"
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"

View File

@@ -8,3 +8,7 @@ edition = "2021"
[dependencies]
lazy_static = "1.4.0"
sdl2 = { version = "0.35", features = ["image", "ttf", "mixer"] }
spin_sleep = "1.1.1"
tracing = { version = "0.1.37", features = ["max_level_debug", "release_max_level_warn"]}
tracing-error = "0.2.0"
tracing-subscriber = {version = "0.3.17", features = ["env-filter"]}

View File

@@ -49,6 +49,16 @@ The latest releases can be found here:
Download each for your architecture, and locate the appropriately named DLL within. Move said DLL to root of this project.
In total, you should have the following DLLs in the root of the project:
- SDL2.dll
- SDL2_mixer.dll
- SDL2_ttf.dll
- SDL2_image.dll
- libpngX-X.dll
- Not sure on what specific version is to be used, or if naming matters. `libpng16-16.dll` is what I had used.
- zlib1.dll
## Building
To build the project, run the following command:

View File

@@ -4,9 +4,14 @@ use sdl2::{
video::Window,
};
use crate::direction::Direction;
pub struct AnimatedTexture<'a> {
raw_texture: &'a Texture<'a>,
current_frame: u32,
raw_texture: Texture<'a>,
ticker: u32,
reversed: bool,
offset: (i32, i32),
ticks_per_frame: u32,
frame_count: u32,
frame_width: u32,
frame_height: u32,
@@ -14,39 +19,79 @@ pub struct AnimatedTexture<'a> {
impl<'a> AnimatedTexture<'a> {
pub fn new(
texture: &'a Texture<'a>,
texture: Texture<'a>,
ticks_per_frame: u32,
frame_count: u32,
frame_width: u32,
frame_height: u32,
offset: Option<(i32, i32)>,
) -> Self {
AnimatedTexture {
raw_texture: texture,
current_frame: 0,
ticker: 0,
reversed: false,
ticks_per_frame,
frame_count,
frame_width,
frame_height,
offset: offset.unwrap_or((0, 0)),
}
}
fn current_frame(&self) -> u32 {
self.ticker / self.ticks_per_frame
}
fn next_frame(&mut self) {
self.current_frame = (self.current_frame + 1) % self.frame_count;
if self.reversed {
self.ticker -= 1;
if self.ticker == 0 {
self.reversed = !self.reversed;
}
} else {
self.ticker += 1;
if self.ticker + 1 == self.ticks_per_frame * self.frame_count {
self.reversed = !self.reversed;
}
}
}
fn get_frame_rect(&self) -> Rect {
Rect::new(
(self.current_frame * self.frame_width) as i32,
self.current_frame() as i32 * self.frame_width as i32,
0,
self.frame_width,
self.frame_height,
)
}
pub fn render(&mut self, canvas: &mut Canvas<Window>, position: (i32, i32)) {
pub fn render(
&mut self,
canvas: &mut Canvas<Window>,
position: (i32, i32),
direction: Direction,
) {
let frame_rect = self.get_frame_rect();
let position_rect = Rect::new(position.0, position.1, self.frame_width, self.frame_height);
let position_rect = Rect::new(
position.0 + self.offset.0,
position.1 + self.offset.1,
self.frame_width,
self.frame_height,
);
canvas
.copy(&self.raw_texture, frame_rect, position_rect)
.expect("Could not render sprite on canvas");
.copy_ex(
&self.raw_texture,
Some(frame_rect),
Some(position_rect),
direction.angle(),
None,
false,
false,
)
.expect("Could not render texture on canvas");
self.next_frame();
}

View File

@@ -1,6 +1,27 @@
#[derive(Debug, Copy, Clone, PartialEq)]
pub enum Direction {
Up,
Down,
Left,
Right,
}
impl Direction {
pub fn angle(&self) -> f64 {
match self {
Direction::Right => 0f64,
Direction::Down => 90f64,
Direction::Left => 180f64,
Direction::Up => 270f64,
}
}
pub fn offset(&self) -> (i32, i32) {
match self {
Direction::Right => (1, 0),
Direction::Down => (0, 1),
Direction::Left => (-1, 0),
Direction::Up => (0, -1),
}
}
}

View File

@@ -5,4 +5,7 @@ pub trait Entity {
fn position(&self) -> (i32, i32);
// Returns the cell position of the entity (XY position within the grid)
fn cell_position(&self) -> (u32, u32);
fn internal_position(&self) -> (u32, u32);
// Tick the entity (move it, perform collision checks, etc)
fn tick(&mut self);
}

View File

@@ -1,12 +1,17 @@
use sdl2::image::LoadTexture;
use sdl2::keyboard::Keycode;
use sdl2::render::{Texture, TextureCreator};
use sdl2::video::WindowContext;
use sdl2::{pixels::Color, render::Canvas, video::Window};
use crate::constants::{MapTile, BOARD, BOARD_HEIGHT, BOARD_WIDTH};
use crate::direction::Direction;
use crate::entity::Entity;
use crate::pacman::Pacman;
use crate::textures::TextureManager;
pub struct Game<'a> {
pub textures: TextureManager<'a>,
canvas: &'a mut Canvas<Window>,
map_texture: Texture<'a>,
pacman: Pacman<'a>,
debug: bool,
}
@@ -14,19 +19,51 @@ pub struct Game<'a> {
impl Game<'_> {
pub fn new<'a>(
canvas: &'a mut Canvas<Window>,
texture_manager: TextureManager<'a>,
texture_creator: &'a TextureCreator<WindowContext>,
) -> Game<'a> {
let pacman = Pacman::new(None, &texture_manager.pacman);
let pacman_atlas = texture_creator
.load_texture("assets/32/pacman.png")
.expect("Could not load pacman texture");
let pacman = Pacman::new(Some(Game::cell_to_pixel((1, 4))), pacman_atlas);
Game {
canvas,
textures: texture_manager,
pacman: pacman,
debug: true,
debug: false,
map_texture: texture_creator
.load_texture("assets/map.png")
.expect("Could not load pacman texture"),
}
}
pub fn tick(&mut self) {}
pub fn cell_to_pixel(cell: (u32, u32)) -> (i32, i32) {
((cell.0 as i32 * 24), ((cell.1) as i32 * 24))
}
pub fn keyboard_event(&mut self, keycode: Keycode) {
match keycode {
Keycode::D => {
self.pacman.next_direction = Some(Direction::Right);
}
Keycode::A => {
self.pacman.next_direction = Some(Direction::Left);
}
Keycode::W => {
self.pacman.next_direction = Some(Direction::Up);
}
Keycode::S => {
self.pacman.next_direction = Some(Direction::Down);
}
Keycode::Space => {
self.debug = !self.debug;
}
_ => {}
}
}
pub fn tick(&mut self) {
self.pacman.tick();
}
pub fn draw(&mut self) {
// Clear the screen (black)
@@ -34,30 +71,50 @@ impl Game<'_> {
self.canvas.clear();
self.canvas
.copy(&self.textures.map, None, None)
.copy(&self.map_texture, None, None)
.expect("Could not render texture on canvas");
// Render the pacman
self.pacman.render(self.canvas);
// Draw a grid
if self.debug {
for x in 0..BOARD_WIDTH {
for y in 0..BOARD_HEIGHT {
let tile = BOARD[x as usize][y as usize];
let color = match tile {
let mut color = None;
if (x, y) == self.pacman.cell_position() {
self.draw_cell((x, y), Color::CYAN);
} else {
color = match tile {
MapTile::Empty => None,
MapTile::Wall => Some(Color::BLUE),
MapTile::Pellet => Some(Color::RED),
MapTile::PowerPellet => Some(Color::MAGENTA),
MapTile::StartingPosition(_) => Some(Color::GREEN),
};
}
if let Some(color) = color {
self.canvas.set_draw_color(color);
self.canvas
.draw_rect(sdl2::rect::Rect::new(x as i32 * 24, y as i32 * 24, 24, 24))
.expect("Could not draw rectangle");
self.draw_cell((x, y), color);
}
}
}
}
self.canvas.present();
}
fn draw_cell(&mut self, cell: (u32, u32), color: Color) {
self.canvas.set_draw_color(color);
self.canvas
.draw_rect(sdl2::rect::Rect::new(
cell.0 as i32 * 24,
cell.1 as i32 * 24,
24,
24,
))
.expect("Could not draw rectangle");
}
}

15
src/helper.rs Normal file
View File

@@ -0,0 +1,15 @@
pub fn is_adjacent(a: (u32, u32), b: (u32, u32), diagonal: bool) -> bool {
let (ax, ay) = a;
let (bx, by) = b;
if diagonal {
(ax == bx && (ay == by + 1 || ay == by - 1))
|| (ay == by && (ax == bx + 1 || ax == bx - 1))
|| (ax == bx + 1 && ay == by + 1)
|| (ax == bx + 1 && ay == by - 1)
|| (ax == bx - 1 && ay == by + 1)
|| (ax == bx - 1 && ay == by - 1)
} else {
(ax == bx && (ay == by + 1 || ay == by - 1))
|| (ay == by && (ax == bx + 1 || ax == bx - 1))
}
}

View File

@@ -1,35 +1,43 @@
use crate::constants::{WINDOW_HEIGHT, WINDOW_WIDTH};
use crate::game::Game;
use crate::textures::TextureManager;
use tracing::{event};
use sdl2::event::{Event};
use sdl2::keyboard::Keycode;
use sdl2::pixels::Color;
use sdl2::render::{Canvas, Texture};
use std::time::{Duration, Instant};
use spin_sleep::sleep;
#[cfg(target_os = "emscripten")]
pub mod emscripten;
mod animation;
mod constants;
mod direction;
mod entity;
mod game;
mod pacman;
mod textures;
mod entity;
mod animation;
mod modulation;
fn redraw(canvas: &mut Canvas<sdl2::video::Window>, tex: &Texture, i: u8) {
canvas.set_draw_color(Color::RGB(i, i, i));
canvas.clear();
canvas
.copy(tex, None, None)
.expect("Could not render texture on canvas");
}
#[cfg(target_os = "emscripten")]
mod emscripten;
pub fn main() {
let sdl_context = sdl2::init().unwrap();
let video_subsystem = sdl_context.video().unwrap();
// Setup tracing
#[cfg(debug_assertions)]
{
use tracing_error::ErrorLayer;
use tracing_subscriber::layer::SubscriberExt;
let subscriber = tracing_subscriber::fmt()
.with_max_level(tracing::Level::DEBUG)
.finish()
.with(ErrorLayer::default());
tracing::subscriber::set_global_default(subscriber).expect("Could not set global default");
}
let window = video_subsystem
.window("Pac-Man", WINDOW_WIDTH, WINDOW_HEIGHT)
.position_centered()
@@ -38,6 +46,7 @@ pub fn main() {
let mut canvas = window
.into_canvas()
.accelerated()
.build()
.expect("Could not build canvas");
@@ -46,16 +55,27 @@ pub fn main() {
.expect("Could not set logical size");
let texture_creator = canvas.texture_creator();
let mut game = Game::new(&mut canvas, TextureManager::new(&texture_creator));
let mut game = Game::new(&mut canvas, &texture_creator);
let mut event_pump = sdl_context
.event_pump()
.expect("Could not get SDL EventPump");
// Initial draw and tick
game.draw();
game.tick();
let loop_time = Duration::from_secs(1) / 60;
let mut tick_no = 0u32;
// The start of a period of time over which we average the frame time.
let mut last_averaging_time = Instant::now();
let mut sleep_time = Duration::ZERO;
event!(tracing::Level::INFO, "Starting game loop ({:.3}ms)", loop_time.as_secs_f32() * 1000.0);
let mut main_loop = || {
let start = Instant::now();
for event in event_pump.poll_iter() {
match event {
// Handle quitting keys or window close
@@ -64,34 +84,48 @@ pub fn main() {
keycode: Some(Keycode::Escape) | Some(Keycode::Q),
..
} => return false,
event @ Event::KeyDown { .. } => {
println!("{:?}", event);
Event::KeyDown { keycode, .. } => {
game.keyboard_event(keycode.unwrap());
}
_ => {}
}
}
let tick_time = {
let start = Instant::now();
game.tick();
start.elapsed()
};
let draw_time = {
let start = Instant::now();
game.draw();
start.elapsed()
};
// Alert if tick time exceeds 10ms
if tick_time > Duration::from_millis(3) {
println!("Tick took: {:?}", tick_time);
}
if draw_time > Duration::from_millis(3) {
println!("Draw took: {:?}", draw_time);
if start.elapsed() < loop_time {
let time = loop_time - start.elapsed();
sleep(time);
sleep_time += time;
} else {
event!(
tracing::Level::WARN,
"Game loop behind schedule by: {:?}",
start.elapsed() - loop_time
);
}
tick_no += 1;
if tick_no % (60 * 5) == 0 {
let average_fps = tick_no as f32 / last_averaging_time.elapsed().as_secs_f32();
let average_sleep = sleep_time / tick_no;
let average_process = loop_time - average_sleep;
event!(
tracing::Level::DEBUG,
"Timing Averages [fps={}] [sleep={:?}] [process={:?}]",
average_fps,
average_sleep,
average_process
);
sleep_time = Duration::ZERO;
last_averaging_time = Instant::now();
tick_no = 0;
}
::std::thread::sleep(Duration::from_millis(10));
true
};

26
src/modulation.rs Normal file
View File

@@ -0,0 +1,26 @@
pub struct SpeedModulator {
tick_count: u32,
ticks_left: u32,
}
impl SpeedModulator {
pub fn new(percent: f32) -> Self {
let ticks_required: u32 = (1f32 / (1f32 - percent)).round() as u32;
SpeedModulator {
tick_count: ticks_required,
ticks_left: ticks_required,
}
}
pub fn next(&mut self) -> bool {
self.ticks_left -= 1;
if self.ticks_left == 0 {
self.ticks_left = self.tick_count;
false
} else {
true
}
}
}

View File

@@ -1,25 +1,46 @@
use sdl2::{render::{Canvas, Texture}, video::Window};
use sdl2::{
render::{Canvas, Texture},
video::Window,
};
use crate::{direction::Direction, entity::Entity, animation::AnimatedTexture};
use crate::{
constants::{BOARD, MapTile},
animation::AnimatedTexture, constants::CELL_SIZE, direction::Direction, entity::Entity,
modulation::SpeedModulator,
};
pub struct Pacman<'a> {
// Absolute position on the board (precise)
pub position: (i32, i32),
pub direction: Direction,
pub next_direction: Option<Direction>,
pub stopped: bool,
speed: u32,
modulation: SpeedModulator,
sprite: AnimatedTexture<'a>,
}
impl Pacman<'_> {
pub fn new<'a>(starting_position: Option<(i32, i32)>, atlas: &'a Texture<'a>) -> Pacman<'a> {
pub fn new<'a>(starting_position: Option<(i32, i32)>, atlas: Texture<'a>) -> Pacman<'a> {
Pacman {
position: starting_position.unwrap_or((0i32, 0i32)),
direction: Direction::Right,
sprite: AnimatedTexture::new(atlas, 2, 24, 24),
next_direction: None,
speed: 2,
stopped: false,
modulation: SpeedModulator::new(0.9333),
sprite: AnimatedTexture::new(atlas, 4, 3, 32, 32, Some((-4, -4))),
}
}
pub fn render(&mut self, canvas: &mut Canvas<Window>) {
self.sprite.render(canvas, self.position);
self.sprite.render(canvas, self.position, self.direction);
}
fn next_cell(&self) -> (i32, i32) {
let (x, y) = self.direction.offset();
let cell = self.cell_position();
(cell.0 as i32 + x, cell.1 as i32 + y)
}
}
@@ -36,6 +57,44 @@ impl Entity for Pacman<'_> {
fn cell_position(&self) -> (u32, u32) {
let (x, y) = self.position();
(x as u32 / 24, y as u32 / 24)
(x as u32 / CELL_SIZE, y as u32 / CELL_SIZE)
}
fn internal_position(&self) -> (u32, u32) {
let (x, y) = self.position();
(x as u32 % CELL_SIZE, y as u32 % CELL_SIZE)
}
fn tick(&mut self) {
let can_change = self.internal_position() == (0, 0);
if can_change {
if let Some(direction) = self.next_direction {
self.direction = direction;
self.next_direction = None;
}
}
if !self.stopped && self.modulation.next() {
let speed = self.speed as i32;
match self.direction {
Direction::Right => {
self.position.0 += speed;
}
Direction::Left => {
self.position.0 -= speed;
}
Direction::Up => {
self.position.1 -= speed;
}
Direction::Down => {
self.position.1 += speed;
}
}
}
let next = self.next_cell();
if BOARD[next.1 as usize][next.0 as usize] == MapTile::Wall {
self.stopped = true;
}
}
}

View File

@@ -1,29 +0,0 @@
use sdl2::{
image::LoadTexture,
render::{Texture, TextureCreator},
video::WindowContext,
};
pub struct TextureManager<'a> {
pub map: Texture<'a>,
pub pacman: Texture<'a>,
}
impl<'a> TextureManager<'a> {
pub fn new(texture_creator: &'a TextureCreator<WindowContext>) -> Self {
let map_texture = texture_creator
.load_texture("assets/map.png")
.expect("Could not load pacman texture");
let pacman_atlas = texture_creator
.load_texture("assets/pacman.png")
.expect("Could not load pacman texture");
TextureManager {
map: map_texture,
pacman: pacman_atlas,
}
}
}