Compare commits

...

4 Commits

10 changed files with 104 additions and 69 deletions

View File

@@ -99,7 +99,7 @@ impl<'a> AnimatedTexture<'a> {
self.tick();
}
}
// Renders a specific frame of the animation. Defaults to the current frame.
pub fn render_static(
&mut self,

View File

@@ -48,4 +48,4 @@ pub const RAW_BOARD: [&str; BOARD_HEIGHT as usize] = [
"#.##########.##.##########.#",
"#..........................#",
"############################",
];
];

View File

@@ -40,4 +40,4 @@ impl Direction {
_ => None,
}
}
}
}

View File

@@ -2,18 +2,20 @@
#[cfg(target_os = "emscripten")]
pub mod emscripten {
use std::cell::RefCell;
use std::ptr::null_mut;
use std::os::raw::{c_int, c_void, c_char, c_float};
use std::ffi::{CStr, CString};
use std::os::raw::{c_char, c_float, c_int, c_void};
use std::ptr::null_mut;
#[allow(non_camel_case_types)]
type em_callback_func = unsafe extern "C" fn();
extern "C" {
// void emscripten_set_main_loop(em_callback_func func, int fps, int simulate_infinite_loop)
pub fn emscripten_set_main_loop(func: em_callback_func,
fps: c_int,
simulate_infinite_loop: c_int);
pub fn emscripten_set_main_loop(
func: em_callback_func,
fps: c_int,
simulate_infinite_loop: c_int,
);
pub fn emscripten_cancel_main_loop();
pub fn emscripten_pause_main_loop();
@@ -23,17 +25,20 @@ pub mod emscripten {
thread_local!(static MAIN_LOOP_CALLBACK: RefCell<*mut c_void> = RefCell::new(null_mut()));
pub fn set_main_loop_callback<F>(callback: F)
where F: FnMut()
where
F: FnMut(),
{
MAIN_LOOP_CALLBACK
.with(|log| { *log.borrow_mut() = &callback as *const _ as *mut c_void; });
MAIN_LOOP_CALLBACK.with(|log| {
*log.borrow_mut() = &callback as *const _ as *mut c_void;
});
unsafe {
emscripten_set_main_loop(wrapper::<F>, -1, 1);
}
unsafe extern "C" fn wrapper<F>()
where F: FnMut()
where
F: FnMut(),
{
MAIN_LOOP_CALLBACK.with(|z| {
let closure = *z.borrow_mut() as *mut F;
@@ -41,4 +46,4 @@ pub mod emscripten {
});
}
}
}
}

View File

@@ -6,6 +6,6 @@ pub trait Entity {
// 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)
// Tick the entity (move it, perform collision checks, etc)
fn tick(&mut self);
}
}

View File

@@ -43,28 +43,14 @@ impl Game<'_> {
}
}
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;
}
_ => {}
// Change direction
let direction = Direction::from_keycode(keycode);
self.pacman.next_direction = direction;
// Toggle debug mode
if keycode == Keycode::Space {
self.debug = !self.debug;
}
}
@@ -88,7 +74,10 @@ impl Game<'_> {
if self.debug {
for x in 0..BOARD_WIDTH {
for y in 0..BOARD_HEIGHT {
let tile = self.map.get_tile((x as i32, y as i32)).unwrap_or(MapTile::Empty);
let tile = self
.map
.get_tile((x as i32, y as i32))
.unwrap_or(MapTile::Empty);
let mut color = None;
if (x, y) == self.pacman.cell_position() {

View File

@@ -1,10 +1,10 @@
use crate::constants::{WINDOW_HEIGHT, WINDOW_WIDTH};
use crate::game::Game;
use tracing::{event};
use sdl2::event::{Event};
use sdl2::event::Event;
use sdl2::keyboard::Keycode;
use std::time::{Duration, Instant};
use spin_sleep::sleep;
use std::time::{Duration, Instant};
use tracing::event;
use tracing_error::ErrorLayer;
use tracing_subscriber::layer::SubscriberExt;
@@ -16,9 +16,9 @@ mod constants;
mod direction;
mod entity;
mod game;
mod pacman;
mod modulation;
mod map;
mod modulation;
mod pacman;
#[cfg(target_os = "emscripten")]
mod emscripten;
@@ -68,11 +68,17 @@ pub fn main() {
// 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;
let mut paused = false;
event!(tracing::Level::INFO, "Starting game loop ({:.3}ms)", loop_time.as_secs_f32() * 1000.0);
event!(
tracing::Level::INFO,
"Starting game loop ({:.3}ms)",
loop_time.as_secs_f32() * 1000.0
);
let mut main_loop = || {
let start = Instant::now();
// TODO: Fix key repeat delay issues by using VecDeque for instant key repeat
for event in event_pump.poll_iter() {
match event {
// Handle quitting keys or window close
@@ -82,8 +88,19 @@ pub fn main() {
..
} => {
event!(tracing::Level::INFO, "Exit requested. Exiting...");
return false
},
return false;
}
Event::KeyDown {
keycode: Some(Keycode::P),
..
} => {
paused = !paused;
event!(
tracing::Level::INFO,
"{}",
if paused { "Paused" } else { "Unpaused" }
);
}
Event::KeyDown { keycode, .. } => {
game.keyboard_event(keycode.unwrap());
}
@@ -91,8 +108,11 @@ pub fn main() {
}
}
game.tick();
game.draw();
// TODO: Proper pausing implementation that does not interfere with statistic gathering
if !paused {
game.tick();
game.draw();
}
if start.elapsed() < loop_time {
let time = loop_time - start.elapsed();
@@ -108,9 +128,11 @@ pub fn main() {
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;
const PERIOD: u32 = 60 * 2;
let tick_mod = tick_no % PERIOD;
if tick_mod % PERIOD == 0 || tick_no == PERIOD {
let average_fps = PERIOD as f32 / last_averaging_time.elapsed().as_secs_f32();
let average_sleep = sleep_time / PERIOD;
let average_process = loop_time - average_sleep;
event!(
@@ -118,7 +140,7 @@ pub fn main() {
"Timing Averages [fps={}] [sleep={:?}] [process={:?}]",
average_fps,
average_sleep,
average_process
average_process
);
sleep_time = Duration::ZERO;

View File

@@ -2,7 +2,7 @@ use crate::constants::MapTile;
use crate::constants::{BOARD_HEIGHT, BOARD_WIDTH};
pub struct Map {
inner: [[MapTile; BOARD_HEIGHT as usize]; BOARD_WIDTH as usize]
inner: [[MapTile; BOARD_HEIGHT as usize]; BOARD_WIDTH as usize],
}
impl Map {
@@ -16,7 +16,7 @@ impl Map {
if x >= line.len() {
break;
}
let i = (y * (BOARD_WIDTH as usize) + x) as usize;
let character = line
.chars()
@@ -30,7 +30,7 @@ impl Map {
' ' => MapTile::Empty,
c @ '0' | c @ '1' | c @ '2' | c @ '3' | c @ '4' => {
MapTile::StartingPosition(c.to_digit(10).unwrap() as u8)
},
}
'=' => MapTile::Empty,
_ => panic!("Unknown character in board: {}", character),
};
@@ -39,9 +39,7 @@ impl Map {
}
}
Map {
inner: inner
}
Map { inner: inner }
}
pub fn get_tile(&self, cell: (i32, i32)) -> Option<MapTile> {
@@ -58,4 +56,4 @@ impl Map {
pub fn cell_to_pixel(cell: (u32, u32)) -> (i32, i32) {
((cell.0 as i32) * 24, ((cell.1 + 3) as i32) * 24)
}
}
}

View File

@@ -1,15 +1,15 @@
/// 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;

View File

@@ -9,7 +9,7 @@ use tracing::event;
use crate::{
animation::AnimatedTexture,
constants::MapTile,
constants::{CELL_SIZE, BOARD_OFFSET},
constants::{BOARD_OFFSET, CELL_SIZE},
direction::Direction,
entity::Entity,
map::Map,
@@ -48,8 +48,8 @@ impl Pacman<'_> {
self.sprite
.render_until(canvas, self.position, self.direction, 2);
} else {
self.sprite.render(canvas, self.position, self.direction);
}
self.sprite.render(canvas, self.position, self.direction);
}
}
pub fn next_cell(&self, direction: Option<Direction>) -> (i32, i32) {
@@ -57,6 +57,26 @@ impl Pacman<'_> {
let cell = self.cell_position();
(cell.0 as i32 + x, cell.1 as i32 + y)
}
fn handle_requested_direction(&mut self) {
if self.next_direction.is_none() {
return;
}
if self.next_direction.unwrap() == self.direction {
self.next_direction = None;
return;
}
let proposed_next_cell = self.next_cell(self.next_direction);
let proposed_next_tile = self
.map
.get_tile(proposed_next_cell)
.unwrap_or(MapTile::Empty);
if proposed_next_tile != MapTile::Wall {
self.direction = self.next_direction.unwrap();
self.next_direction = None;
}
}
}
impl Entity for Pacman<'_> {
@@ -72,7 +92,10 @@ impl Entity for Pacman<'_> {
fn cell_position(&self) -> (u32, u32) {
let (x, y) = self.position;
((x as u32 / CELL_SIZE) - BOARD_OFFSET.0, (y as u32 / CELL_SIZE) - BOARD_OFFSET.1)
(
(x as u32 / CELL_SIZE) - BOARD_OFFSET.0,
(y as u32 / CELL_SIZE) - BOARD_OFFSET.1,
)
}
fn internal_position(&self) -> (u32, u32) {
@@ -82,11 +105,9 @@ impl Entity for Pacman<'_> {
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;
}
self.handle_requested_direction();
let next = self.next_cell(None);
let next_tile = self.map.get_tile(next).unwrap_or(MapTile::Empty);