Compare commits

...

9 Commits

10 changed files with 434 additions and 17 deletions

4
.gitignore vendored
View File

@@ -1,2 +1,4 @@
/target
.idea
/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

@@ -38,11 +38,13 @@ impl<'a> AnimatedTexture<'a> {
}
}
// Get the current frame number
fn current_frame(&self) -> u32 {
self.ticker / self.ticks_per_frame
}
fn next_frame(&mut self) {
// Move to the next frame. If we are at the end of the animation, reverse the direction
pub fn tick(&mut self) {
if self.reversed {
self.ticker -= 1;
@@ -58,9 +60,14 @@ impl<'a> AnimatedTexture<'a> {
}
}
fn get_frame_rect(&self) -> Rect {
// Calculate the frame rect (portion of the texture to render) for the given frame.
fn get_frame_rect(&self, frame: u32) -> Rect {
if frame >= self.frame_count {
panic!("Frame {} is out of bounds for this texture", frame);
}
Rect::new(
self.current_frame() as i32 * self.frame_width as i32,
frame as i32 * self.frame_width as i32,
0,
self.frame_width,
self.frame_height,
@@ -73,7 +80,35 @@ impl<'a> AnimatedTexture<'a> {
position: (i32, i32),
direction: Direction,
) {
let frame_rect = self.get_frame_rect();
self.render_static(canvas, position, direction, Some(self.current_frame()));
self.tick();
}
// Functions like render, but only ticks the animation until the given frame is reached.
pub fn render_until(
&mut self,
canvas: &mut Canvas<Window>,
position: (i32, i32),
direction: Direction,
frame: u32,
) {
let current = self.current_frame();
self.render_static(canvas, position, direction, Some(current));
if frame != current {
self.tick();
}
}
// Renders a specific frame of the animation. Defaults to the current frame.
pub fn render_static(
&mut self,
canvas: &mut Canvas<Window>,
position: (i32, i32),
direction: Direction,
frame: Option<u32>,
) {
let frame_rect = self.get_frame_rect(frame.unwrap_or(self.current_frame()));
let position_rect = Rect::new(
position.0 + self.offset.0,
position.1 + self.offset.1,
@@ -92,7 +127,5 @@ impl<'a> AnimatedTexture<'a> {
false,
)
.expect("Could not render texture on canvas");
self.next_frame();
}
}

View File

@@ -1,3 +1,5 @@
use sdl2::keyboard::Keycode;
#[derive(Debug, Copy, Clone, PartialEq)]
pub enum Direction {
Up,
@@ -24,4 +26,18 @@ impl Direction {
Direction::Up => (0, -1),
}
}
pub fn from_keycode(keycode: Keycode) -> Option<Direction> {
match keycode {
Keycode::D => Some(Direction::Right),
Keycode::Right => Some(Direction::Right),
Keycode::A => Some(Direction::Left),
Keycode::Left => Some(Direction::Left),
Keycode::W => Some(Direction::Up),
Keycode::Up => Some(Direction::Up),
Keycode::S => Some(Direction::Down),
Keycode::Down => Some(Direction::Down),
_ => None,
}
}
}

View File

@@ -101,6 +101,10 @@ impl Game<'_> {
}
}
}
// Draw the next cell
let next_cell = self.pacman.next_cell(None);
self.draw_cell((next_cell.0 as u32, next_cell.1 as u32), Color::YELLOW);
}
self.canvas.present();

View File

@@ -1,8 +1,12 @@
use crate::constants::{WINDOW_HEIGHT, WINDOW_WIDTH};
use crate::game::Game;
use sdl2::event::Event;
use tracing::{event};
use sdl2::event::{Event};
use sdl2::keyboard::Keycode;
use std::time::{Duration, Instant};
use spin_sleep::sleep;
use tracing_error::ErrorLayer;
use tracing_subscriber::layer::SubscriberExt;
#[cfg(target_os = "emscripten")]
pub mod emscripten;
@@ -14,6 +18,7 @@ mod entity;
mod game;
mod pacman;
mod modulation;
mod map;
#[cfg(target_os = "emscripten")]
mod emscripten;
@@ -22,6 +27,14 @@ pub fn main() {
let sdl_context = sdl2::init().unwrap();
let video_subsystem = sdl_context.video().unwrap();
// Setup tracing
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()
@@ -45,11 +58,18 @@ pub fn main() {
.event_pump()
.expect("Could not get SDL EventPump");
// Initial draw and tick
game.draw();
game.tick();
let loop_time = Duration::from_millis(1000 / 60);
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();
@@ -60,7 +80,10 @@ pub fn main() {
| Event::KeyDown {
keycode: Some(Keycode::Escape) | Some(Keycode::Q),
..
} => return false,
} => {
event!(tracing::Level::INFO, "Exit requested. Exiting...");
return false
},
Event::KeyDown { keycode, .. } => {
game.keyboard_event(keycode.unwrap());
}
@@ -72,14 +95,36 @@ pub fn main() {
game.draw();
if start.elapsed() < loop_time {
::std::thread::sleep(loop_time - start.elapsed());
let time = loop_time - start.elapsed();
sleep(time);
sleep_time += time;
} else {
println!(
event!(
tracing::Level::WARN,
"Game loop behind schedule by: {:?}",
start.elapsed() - loop_time
);
}
tick_no += 1;
if tick_no % (60 * 60) == 0 || tick_no == (60 * 2) {
let average_fps = (tick_no % (60 * 60)) 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();
}
true
};

View File

@@ -1,22 +1,44 @@
pub struct SpeedModulator {
/// A tick modulator allows you to slow down operations by a percentage.
///
/// Unfortunately, switching to floating point numbers for entities can induce floating point errors, slow down calculations
/// and make the game less deterministic. This is why we use a speed modulator instead.
/// Additionally, with small integers, lowering the speed by a percentage is not possible. For example, if we have a speed of 2,
/// and we want to slow it down by 10%, we would need to slow it down by 0.2. However, since we are using integers, we can't.
/// The only amount you can slow it down by is 1, which is 50% of the speed.
///
/// The basic principle of the Speed Modulator is to instead 'skip' movement ticks every now and then.
/// At 60 ticks per second, skips could happen several times per second, or once every few seconds.
/// Whatever it be, as long as the tick rate is high enough, the human eye will not be able to tell the difference.
///
/// For example, if we want to slow down the speed by 10%, we would need to skip every 10th tick.
pub trait TickModulator {
fn new(percent: f32) -> Self;
fn next(&mut self) -> bool;
}
pub struct SimpleTickModulator {
tick_count: u32,
ticks_left: u32,
}
impl SpeedModulator {
pub fn new(percent: f32) -> Self {
// TODO: Add tests
// TODO: Look into average precision, binary code modulation strategy
impl TickModulator for SimpleTickModulator {
fn new(percent: f32) -> Self {
let ticks_required: u32 = (1f32 / (1f32 - percent)).round() as u32;
SpeedModulator {
SimpleTickModulator {
tick_count: ticks_required,
ticks_left: ticks_required,
}
}
pub fn next(&mut self) -> bool {
fn next(&mut self) -> bool {
self.ticks_left -= 1;
// Return whether or not we should skip this tick
if self.ticks_left == 0 {
// We've reached the tick to skip, reset the counter
self.ticks_left = self.tick_count;
false
} else {

View File

@@ -34,6 +34,11 @@ impl Pacman<'_> {
}
pub fn render(&mut self, canvas: &mut Canvas<Window>) {
// When stopped, render the last frame of the animation
if self.stopped {
self.sprite
.render_until(canvas, self.position, self.direction, 2);
} else {
self.sprite.render(canvas, self.position, self.direction);
}