Compare commits

...

5 Commits

5 changed files with 83 additions and 26 deletions

View File

@@ -38,11 +38,13 @@ impl<'a> AnimatedTexture<'a> {
} }
} }
// Get the current frame number
fn current_frame(&self) -> u32 { fn current_frame(&self) -> u32 {
self.ticker / self.ticks_per_frame 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 { if self.reversed {
self.ticker -= 1; 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( Rect::new(
self.current_frame() as i32 * self.frame_width as i32, frame as i32 * self.frame_width as i32,
0, 0,
self.frame_width, self.frame_width,
self.frame_height, self.frame_height,
@@ -73,7 +80,18 @@ impl<'a> AnimatedTexture<'a> {
position: (i32, i32), position: (i32, i32),
direction: Direction, direction: Direction,
) { ) {
let frame_rect = self.get_frame_rect(); self.render_static(canvas, position, direction, Some(self.current_frame()));
self.tick();
}
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( let position_rect = Rect::new(
position.0 + self.offset.0, position.0 + self.offset.0,
position.1 + self.offset.1, position.1 + self.offset.1,
@@ -92,7 +110,5 @@ impl<'a> AnimatedTexture<'a> {
false, false,
) )
.expect("Could not render texture on canvas"); .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)] #[derive(Debug, Copy, Clone, PartialEq)]
pub enum Direction { pub enum Direction {
Up, Up,
@@ -24,4 +26,18 @@ impl Direction {
Direction::Up => (0, -1), 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(); self.canvas.present();

View File

@@ -5,6 +5,8 @@ use sdl2::event::{Event};
use sdl2::keyboard::Keycode; use sdl2::keyboard::Keycode;
use std::time::{Duration, Instant}; use std::time::{Duration, Instant};
use spin_sleep::sleep; use spin_sleep::sleep;
use tracing_error::ErrorLayer;
use tracing_subscriber::layer::SubscriberExt;
#[cfg(target_os = "emscripten")] #[cfg(target_os = "emscripten")]
pub mod emscripten; pub mod emscripten;
@@ -16,6 +18,7 @@ mod entity;
mod game; mod game;
mod pacman; mod pacman;
mod modulation; mod modulation;
mod map;
#[cfg(target_os = "emscripten")] #[cfg(target_os = "emscripten")]
mod emscripten; mod emscripten;
@@ -25,18 +28,12 @@ pub fn main() {
let video_subsystem = sdl_context.video().unwrap(); let video_subsystem = sdl_context.video().unwrap();
// Setup tracing // Setup tracing
#[cfg(debug_assertions)]
{
use tracing_error::ErrorLayer;
use tracing_subscriber::layer::SubscriberExt;
let subscriber = tracing_subscriber::fmt() let subscriber = tracing_subscriber::fmt()
.with_max_level(tracing::Level::DEBUG) .with_max_level(tracing::Level::DEBUG)
.finish() .finish()
.with(ErrorLayer::default()); .with(ErrorLayer::default());
tracing::subscriber::set_global_default(subscriber).expect("Could not set global default"); tracing::subscriber::set_global_default(subscriber).expect("Could not set global default");
}
let window = video_subsystem let window = video_subsystem
.window("Pac-Man", WINDOW_WIDTH, WINDOW_HEIGHT) .window("Pac-Man", WINDOW_WIDTH, WINDOW_HEIGHT)
@@ -83,7 +80,10 @@ pub fn main() {
| Event::KeyDown { | Event::KeyDown {
keycode: Some(Keycode::Escape) | Some(Keycode::Q), keycode: Some(Keycode::Escape) | Some(Keycode::Q),
.. ..
} => return false, } => {
event!(tracing::Level::INFO, "Exit requested. Exiting...");
return false
},
Event::KeyDown { keycode, .. } => { Event::KeyDown { keycode, .. } => {
game.keyboard_event(keycode.unwrap()); game.keyboard_event(keycode.unwrap());
} }
@@ -108,8 +108,8 @@ pub fn main() {
tick_no += 1; tick_no += 1;
if tick_no % (60 * 5) == 0 { if tick_no % (60 * 60) == 0 || tick_no == (60 * 2) {
let average_fps = tick_no as f32 / last_averaging_time.elapsed().as_secs_f32(); 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_sleep = sleep_time / tick_no;
let average_process = loop_time - average_sleep; let average_process = loop_time - average_sleep;
@@ -123,7 +123,6 @@ pub fn main() {
sleep_time = Duration::ZERO; sleep_time = Duration::ZERO;
last_averaging_time = Instant::now(); last_averaging_time = Instant::now();
tick_no = 0;
} }
true 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, tick_count: u32,
ticks_left: u32, ticks_left: u32,
} }
impl SpeedModulator { // TODO: Add tests
pub fn new(percent: f32) -> Self { // 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; let ticks_required: u32 = (1f32 / (1f32 - percent)).round() as u32;
SpeedModulator { SimpleTickModulator {
tick_count: ticks_required, tick_count: ticks_required,
ticks_left: ticks_required, ticks_left: ticks_required,
} }
} }
pub fn next(&mut self) -> bool { fn next(&mut self) -> bool {
self.ticks_left -= 1; self.ticks_left -= 1;
// Return whether or not we should skip this tick
if self.ticks_left == 0 { if self.ticks_left == 0 {
// We've reached the tick to skip, reset the counter
self.ticks_left = self.tick_count; self.ticks_left = self.tick_count;
false false
} else { } else {