Compare commits

..

23 Commits

Author SHA1 Message Date
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
443afb1223 feat: new PacMan entity, entity trait implementation 2025-06-17 11:50:04 -05:00
724878dc17 feat: atlas texture, animated sprite management 2025-06-17 11:49:47 -05:00
274404b9ea feat: entity trait, direction enum (util) 2025-06-17 11:49:43 -05:00
2214a5541f feat: game loop, texture ownership, tick & draw with grid rendering 2025-06-17 11:49:40 -05:00
64de5fb732 feat: texture manager, texture loading 2025-06-17 11:49:33 -05:00
4a4e6e40a9 feat: constants & board preprocessing with lazy_static 2025-06-17 11:49:17 -05:00
ccde1b6538 feat: current canvas, window & mainloop 2025-06-17 11:49:08 -05:00
d90785a61a docs: implementation document 2025-06-17 11:48:59 -05:00
227c603ffe chore: project name & add lazy_static 2025-06-17 11:48:53 -05:00
361ff95f7f feat: new assets 2025-06-17 11:48:43 -05:00
f3fe1b783b docs: expand feature target details, experimental ideas 2025-06-17 11:48:34 -05:00
570cee0f62 feat: window events & canvas rendering 2025-06-17 11:48:26 -05:00
5b6fd34a6f docs: update installation instructions, remove DLLs 2025-06-17 11:48:16 -05:00
973fa08ac9 ci: better build scripts 2025-06-17 11:48:09 -05:00
4a1f565f72 feat(wasm): add index.html 2025-06-17 11:48:01 -05:00
2ddf2611d9 ci: add wasm32 cargo flags 2025-06-17 11:47:52 -05:00
b3a3664578 feat: game loop 2025-06-17 11:47:43 -05:00
2b667bb6a2 ci: add deploy workflow + build script 2025-06-17 11:47:25 -05:00
9a88e71202 chore: .idea folder in .gitignore 2025-06-17 11:47:18 -05:00
084428e775 feat: setup basic texture rendering, setup SLD2 DLLs & required features 2025-06-17 11:47:08 -05:00
31 changed files with 788 additions and 2 deletions

4
.cargo/config.toml Normal file
View File

@@ -0,0 +1,4 @@
[target.wasm32-unknown-emscripten]
rustflags = [
"--use-preload-plugins --preload-file assets -s USE_SDL=2 -s USE_SDL_IMAGE=2 -s ASSERTIONS=1",
]

28
.github/workflows/deploy.yaml vendored Normal file
View File

@@ -0,0 +1,28 @@
name: Github Pages
on: [push]
permissions:
contents: write
jobs:
build-github-pages:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2 # repo checkout
- uses: mymindstorm/setup-emsdk@v11 # setup emscripten toolchain
# with:
# version: 3.1.35
- uses: actions-rs/toolchain@v1 # get rust toolchain for wasm
with:
toolchain: stable
target: wasm32-unknown-emscripten
override: true
- name: Rust Cache # cache the rust build artefacts
uses: Swatinem/rust-cache@v1
- name: Build # build
run: ./build.sh
- name: Deploy
uses: JamesIves/github-pages-deploy-action@v4
with:
folder: dist

2
.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
/target
.idea

64
Cargo.lock generated Normal file
View File

@@ -0,0 +1,64 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "lazy_static"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libc"
version = "0.2.147"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3"
[[package]]
name = "pacman"
version = "0.1.0"
dependencies = [
"lazy_static",
"sdl2",
]
[[package]]
name = "sdl2"
version = "0.35.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7959277b623f1fb9e04aea73686c3ca52f01b2145f8ea16f4ff30d8b7623b1a"
dependencies = [
"bitflags",
"lazy_static",
"libc",
"sdl2-sys",
]
[[package]]
name = "sdl2-sys"
version = "0.35.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3586be2cf6c0a8099a79a12b4084357aa9b3e0b0d7980e3b67aaf7a9d55f9f0"
dependencies = [
"cfg-if",
"libc",
"version-compare",
]
[[package]]
name = "version-compare"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "579a42fc0b8e0c63b76519a339be31bed574929511fa53c1a3acae26eb258f29"

10
Cargo.toml Normal file
View File

@@ -0,0 +1,10 @@
[package]
name = "pacman"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
lazy_static = "1.4.0"
sdl2 = { version = "0.35", features = ["image", "ttf", "mixer"] }

35
IMPLEMENTATION.md Normal file
View File

@@ -0,0 +1,35 @@
# Implementation
A document detailing the implementation the project from rendering, to game logic, to build systems.
## Rendering
1. Map
- May require procedural text generation later on (cacheable?)
2. Pacman
3. Ghosts
- Requires colors
4. Items
5. Interface
- Requires fonts
## Grid System
1. How does the grid system work?
The grid is 28 x 36 (although, the map texture is 28 x 37), and each cell is 24x24 (pixels).
Many of the walls in the map texture only occupy a portion of the cell, so some items are able to render across multiple cells.
24x24 assets include pellets, the energizer, and the map itself ()
2. What constraints must be enforced on Ghosts and PacMan?
3. How do movement transitions work?
All entities store a precise position, and a direction. This position is only used for animation, rendering, and collision purposes. Otherwise, a separate 'cell position' (which is 24 times less precise, owing to the fact that it is based on the entity's position within the grid).
When an entity is transitioning between cells, movement directions are acknowledged, but won't take effect until the next cell has been entered completely.
4. Between transitions, how does collision detection work?
It appears the original implementation used cell-level detection.
I worry this may be prone to division errors. Make sure to use rounding (50% >=).

View File

@@ -2,11 +2,65 @@
If the title doesn't clue you in, I'm remaking Pac-Man with SDL and Rust.
The project is *extremely* early in development, but check back in a week, and maybe I'll have something cool to look
The project is _extremely_ early in development, but check back in a week, and maybe I'll have something cool to look
at.
## Feature Targets
- Near-perfect replication of logic, scoring, graphics, sound, and behaviors.
- Written in Rust, buildable on Windows, Linux, Mac and WebAssembly.
- Online demo, playable in a browser.
- Online demo, playable in a browser.
- Automatic build system, with releases for Windows, Linux, and Mac & Web-Assembly.
- Debug tooling
- Game state visualization
- Game speed controls + pausing
- Log tracing
- Performance details
## Experimental Ideas
- Perfected Ghost Algorithms
- More than 4 ghosts
- Custom Level Generation
- Multi-map tunnelling
## Installation
Besides SDL2, the following extensions are required: Image, Mixer, and TTF.
### Ubuntu
On Ubuntu, you can install the required packages with the following command:
```
sudo apt install libsdl2-dev libsdl2-image-dev libsdl2-mixer-dev libsdl2-ttf-dev
```
### Windows
On Windows, installation requires either building from source (not covered), or downloading the pre-built binaries.
The latest releases can be found here:
- [SDL2](https://github.com/libsdl-org/SDL/releases/latest/)
- [SDL2_image](https://github.com/libsdl-org/SDL_image/releases/latest/)
- [SDL2_mixer](https://github.com/libsdl-org/SDL_mixer/releases/latest/)
- [SDL2_ttf](https://github.com/libsdl-org/SDL_ttf/releases/latest/)
Download each for your architecture, and locate the appropriately named DLL within. Move said DLL to root of this project.
## Building
To build the project, run the following command:
```
cargo build
```
During development, you can easily run the project with:
```
cargo run
cargo run -q # Quiet mode, no logging
cargo run --release # Release mode, optimized
```

BIN
assets/24/energizer.png Normal file
View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 174 B

BIN
assets/24/pellet.png Normal file
View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 158 B

BIN
assets/32/fruit.png Normal file
View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

BIN
assets/32/game_over.png Normal file
View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

BIN
assets/32/ghost_body.png Normal file
View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 528 B

BIN
assets/32/ghost_eyes.png Normal file
View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 394 B

BIN
assets/32/life.png Normal file
View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 228 B

BIN
assets/32/pacman.png Normal file
View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 370 B

BIN
assets/door.png Normal file
View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 B

BIN
assets/map.png Normal file
View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

7
build.ps1 Normal file
View File

@@ -0,0 +1,7 @@
& cargo build --target=wasm32-unknown-emscripten --release
mkdir -p dist -Force
cp ./target/wasm32-unknown-emscripten/release/Pac_Man.wasm ./dist
cp ./target/wasm32-unknown-emscripten/release/Pac-Man.js ./dist
cp index.html dist

10
build.sh Executable file
View File

@@ -0,0 +1,10 @@
#!/bin/sh
set -eux
cargo build --target=wasm32-unknown-emscripten --release
mkdir -p dist
cp target/wasm32-unknown-emscripten/release/Pac_Man.wasm dist
cp target/wasm32-unknown-emscripten/release/Pac-Man.js dist
cp index.html dist

21
index.html Normal file
View File

@@ -0,0 +1,21 @@
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
</head>
<body>
<canvas id="canvas"></canvas>
<script type="text/javascript">
let Module = {
canvas: (function () {
// this is how we provide a canvas to our sdl2
return document.getElementById("canvas");
})(),
preRun: [function () {
ENV.RUST_LOG = "info,wgpu=warn"
}]
};
</script>
<script src="Pac-Man.js"></script>
</body>
</html>

3
serve.ps1 Normal file
View File

@@ -0,0 +1,3 @@
& ./build.ps1
cd ./dist
python -m http.server

84
src/animation.rs Normal file
View File

@@ -0,0 +1,84 @@
use sdl2::{
rect::Rect,
render::{Canvas, Texture},
video::Window,
};
use crate::direction::Direction;
pub struct AnimatedTexture<'a> {
raw_texture: Texture<'a>,
ticker: u32,
reversed: bool,
ticks_per_frame: u32,
frame_count: u32,
frame_width: u32,
frame_height: u32,
}
impl<'a> AnimatedTexture<'a> {
pub fn new(
texture:Texture<'a>,
ticks_per_frame: u32,
frame_count: u32,
frame_width: u32,
frame_height: u32,
) -> Self {
AnimatedTexture {
raw_texture: texture,
ticker: 0,
reversed: false,
ticks_per_frame,
frame_count,
frame_width,
frame_height,
}
}
fn current_frame(&self) -> u32 {
self.ticker / self.ticks_per_frame
}
fn next_frame(&mut self) {
if self.reversed {
self.ticker -= 1;
if self.ticker == 0 {
self.reversed = !self.reversed;
}
} else {
self.ticker += 1;
if self.ticker > self.ticks_per_frame * self.frame_count {
self.reversed = !self.reversed;
}
}
}
fn get_frame_rect(&self) -> Rect {
Rect::new(
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), direction: Direction) {
let frame_rect = self.get_frame_rect();
let position_rect = Rect::new(position.0, position.1, self.frame_width, self.frame_height);
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();
}
}

0
src/board.rs Normal file
View File

95
src/constants.rs Normal file
View File

@@ -0,0 +1,95 @@
use lazy_static::lazy_static;
pub const BOARD_WIDTH: u32 = 28;
pub const BOARD_HEIGHT: u32 = 37; // Adjusted to fit map texture?
pub const CELL_SIZE: u32 = 24;
pub const WINDOW_WIDTH: u32 = CELL_SIZE * BOARD_WIDTH;
pub const WINDOW_HEIGHT: u32 = CELL_SIZE * BOARD_HEIGHT;
#[derive(Debug, Copy, Clone, PartialEq)]
pub enum MapTile {
Empty,
Wall,
Pellet,
PowerPellet,
StartingPosition(u8),
}
pub const RAW_BOARD: [&str; BOARD_HEIGHT as usize] = [
" ",
" ",
" ",
"############################",
"#............##............#",
"#.####.#####.##.#####.####.#",
"#o####.#####.##.#####.####o#",
"#.####.#####.##.#####.####.#",
"#..........................#",
"#.####.##.########.##.####.#",
"#.####.##.########.##.####.#",
"#......##....##....##......#",
"######.##### ## #####.######",
" #.##### ## #####.# ",
" #.## 1 ##.# ",
" #.## ###==### ##.# ",
"######.## # # ##.######",
" . #2 3 4 # . ",
"######.## # # ##.######",
" #.## ######## ##.# ",
" #.## ##.# ",
" #.## ######## ##.# ",
"######.## ######## ##.######",
"#............##............#",
"#.####.#####.##.#####.####.#",
"#.####.#####.##.#####.####.#",
"#o..##.......0 .......##..o#",
"###.##.##.########.##.##.###",
"###.##.##.########.##.##.###",
"#......##....##....##......#",
"#.##########.##.##########.#",
"#.##########.##.##########.#",
"#..........................#",
"############################",
" ",
" ",
" ",
];
lazy_static! {
pub static ref BOARD: [[MapTile; BOARD_HEIGHT as usize]; BOARD_HEIGHT as usize] = {
let mut board = [[MapTile::Empty; BOARD_HEIGHT as usize]; BOARD_HEIGHT as usize];
for y in 0..BOARD_HEIGHT as usize {
let line = RAW_BOARD[y];
for x in 0..BOARD_WIDTH as usize {
if x >= line.len() {
break;
}
let i = (y * (BOARD_WIDTH as usize) + x) as usize;
let character = line
.chars()
.nth(x as usize)
.unwrap_or_else(|| panic!("Could not get character at {} = ({}, {})", i, x, y));
let tile = match character {
'#' => MapTile::Wall,
'.' => MapTile::Pellet,
'o' => MapTile::PowerPellet,
' ' => 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),
};
board[x as usize][y as usize] = tile;
}
}
board
};
}

18
src/direction.rs Normal file
View File

@@ -0,0 +1,18 @@
#[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,
}
}
}

44
src/emscripten.rs Normal file
View File

@@ -0,0 +1,44 @@
// taken from https://github.com/Gigoteur/PX8/blob/master/src/px8/emscripten.rs
#[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};
#[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_cancel_main_loop();
pub fn emscripten_pause_main_loop();
pub fn emscripten_get_now() -> c_float;
}
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()
{
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()
{
MAIN_LOOP_CALLBACK.with(|z| {
let closure = *z.borrow_mut() as *mut F;
(*closure)();
});
}
}
}

10
src/entity.rs Normal file
View File

@@ -0,0 +1,10 @@
pub trait Entity {
// Returns true if the entity is colliding with the other entity
fn is_colliding(&self, other: &dyn Entity) -> bool;
// Returns the absolute position of the entity
fn position(&self) -> (i32, i32);
// Returns the cell position of the entity (XY position within the grid)
fn cell_position(&self) -> (u32, u32);
// Tick the entity (move it, perform collision checks, etc)
fn tick(&mut self);
}

99
src/game.rs Normal file
View File

@@ -0,0 +1,99 @@
use sdl2::image::LoadTexture;
use sdl2::keyboard::Keycode;
use sdl2::render::{TextureCreator, Texture};
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};
pub struct Game<'a> {
canvas: &'a mut Canvas<Window>,
map_texture: Texture<'a>,
pacman: Pacman<'a>,
debug: bool,
}
impl Game<'_> {
pub fn new<'a>(
canvas: &'a mut Canvas<Window>,
texture_creator: &'a TextureCreator<WindowContext>,
) -> Game<'a> {
let pacman_atlas = texture_creator
.load_texture("assets/32/pacman.png")
.expect("Could not load pacman texture");
let pacman = Pacman::new(None, pacman_atlas);
Game {
canvas,
pacman: pacman,
debug: true,
map_texture: texture_creator
.load_texture("assets/map.png")
.expect("Could not load pacman texture"),
}
}
pub fn keyboard_event(&mut self, keycode: Keycode) {
match keycode {
Keycode::D => {
self.pacman.direction = Direction::Right;
}
Keycode::A => {
self.pacman.direction = Direction::Left;
}
Keycode::W => {
self.pacman.direction = Direction::Up;
}
Keycode::S => {
self.pacman.direction = 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)
self.canvas.set_draw_color(Color::RGB(0, 0, 0));
self.canvas.clear();
self.canvas
.copy(&self.map_texture, None, None)
.expect("Could not render texture on canvas");
// Render the pacman
self.pacman.render(self.canvas);
// Draw a grid
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 {
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.canvas.present();
}
}

111
src/main.rs Normal file
View File

@@ -0,0 +1,111 @@
use crate::constants::{WINDOW_HEIGHT, WINDOW_WIDTH};
use crate::game::Game;
use crate::textures::TextureManager;
use sdl2::event::{Event};
use sdl2::keyboard::Keycode;
use sdl2::pixels::Color;
use sdl2::render::{Canvas, Texture};
use std::time::{Duration, Instant};
#[cfg(target_os = "emscripten")]
pub mod emscripten;
mod constants;
mod direction;
mod game;
mod pacman;
mod textures;
mod entity;
mod animation;
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");
}
pub fn main() {
let sdl_context = sdl2::init().unwrap();
let video_subsystem = sdl_context.video().unwrap();
let window = video_subsystem
.window("Pac-Man", WINDOW_WIDTH, WINDOW_HEIGHT)
.position_centered()
.build()
.expect("Could not initialize window");
let mut canvas = window
.into_canvas()
.build()
.expect("Could not build canvas");
canvas
.set_logical_size(WINDOW_WIDTH, WINDOW_HEIGHT)
.expect("Could not set logical size");
let texture_creator = canvas.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");
game.draw();
game.tick();
let loop_time = Duration::from_millis(1000 / 60);
let mut main_loop = || {
let start = Instant::now();
for event in event_pump.poll_iter() {
match event {
// Handle quitting keys or window close
Event::Quit { .. }
| Event::KeyDown {
keycode: Some(Keycode::Escape) | Some(Keycode::Q),
..
} => return false,
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()
};
if start.elapsed() < loop_time {
::std::thread::sleep(loop_time - start.elapsed());
} else {
println!("Game loop behind schedule by: {:?}", start.elapsed() - loop_time);
}
true
};
#[cfg(target_os = "emscripten")]
use emscripten::emscripten;
#[cfg(target_os = "emscripten")]
emscripten::set_main_loop_callback(main_loop);
#[cfg(not(target_os = "emscripten"))]
loop {
if !main_loop() {
break;
}
}
}

58
src/pacman.rs Normal file
View File

@@ -0,0 +1,58 @@
use sdl2::{render::{Canvas, Texture}, video::Window};
use crate::{direction::Direction, entity::Entity, animation::AnimatedTexture};
pub struct Pacman<'a> {
// Absolute position on the board (precise)
pub position: (i32, i32),
pub direction: Direction,
sprite: AnimatedTexture<'a>,
}
impl Pacman<'_> {
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, 4, 3, 32,32),
}
}
pub fn render(&mut self, canvas: &mut Canvas<Window>) {
self.sprite.render(canvas, self.position, self.direction);
}
}
impl Entity for Pacman<'_> {
fn is_colliding(&self, other: &dyn Entity) -> bool {
let (x, y) = self.position();
let (other_x, other_y) = other.position();
x == other_x && y == other_y
}
fn position(&self) -> (i32, i32) {
self.position
}
fn cell_position(&self) -> (u32, u32) {
let (x, y) = self.position();
(x as u32 / 24, y as u32 / 24)
}
fn tick(&mut self) {
match self.direction {
Direction::Right => {
self.position.0 += 1;
}
Direction::Left => {
self.position.0 -= 1;
}
Direction::Up => {
self.position.1 -= 1;
}
Direction::Down => {
self.position.1 += 1;
}
}
}
}

29
src/textures.rs Normal file
View File

@@ -0,0 +1,29 @@
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,
}
}
}