mirror of
https://github.com/Xevion/Pac-Man.git
synced 2025-12-06 09:15:46 -06:00
Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c71b6d69ab | |||
| a7e87c18a3 | |||
| 95298fbc00 | |||
| fe18eafbaf | |||
| 60eaa428ac |
@@ -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();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -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();
|
||||||
|
|||||||
19
src/main.rs
19
src/main.rs
@@ -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
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
Reference in New Issue
Block a user