mirror of
https://github.com/Xevion/Pac-Man.git
synced 2025-12-06 21:15:48 -06:00
Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 86ffc931e8 | |||
| d72f47d66c | |||
| 7a6182cb85 | |||
| a1d37a1a0b | |||
| 9066b2cdbc |
@@ -5,6 +5,7 @@ use std::borrow::Cow;
|
|||||||
use std::io;
|
use std::io;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
#[derive(Error, Debug)]
|
#[derive(Error, Debug)]
|
||||||
pub enum AssetError {
|
pub enum AssetError {
|
||||||
#[error("IO error: {0}")]
|
#[error("IO error: {0}")]
|
||||||
@@ -28,6 +29,7 @@ pub enum Asset {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Asset {
|
impl Asset {
|
||||||
|
#[allow(dead_code)]
|
||||||
pub fn path(&self) -> &str {
|
pub fn path(&self) -> &str {
|
||||||
use Asset::*;
|
use Asset::*;
|
||||||
match self {
|
match self {
|
||||||
|
|||||||
@@ -11,9 +11,16 @@ pub const BOARD_CELL_SIZE: UVec2 = UVec2::new(28, 31);
|
|||||||
pub const SCALE: f32 = 2.6;
|
pub const SCALE: f32 = 2.6;
|
||||||
|
|
||||||
/// The offset of the game board from the top-left corner of the window, in cells.
|
/// The offset of the game board from the top-left corner of the window, in cells.
|
||||||
pub const BOARD_OFFSET: UVec2 = UVec2::new(0, 0);
|
pub const BOARD_CELL_OFFSET: UVec2 = UVec2::new(0, 3);
|
||||||
|
/// The offset of the game board from the top-left corner of the window, in pixels.
|
||||||
|
pub const BOARD_PIXEL_OFFSET: UVec2 = UVec2::new(BOARD_CELL_OFFSET.x * CELL_SIZE, BOARD_CELL_OFFSET.y * CELL_SIZE);
|
||||||
/// The size of the game board, in pixels.
|
/// The size of the game board, in pixels.
|
||||||
pub const BOARD_PIXEL_SIZE: UVec2 = UVec2::new(BOARD_CELL_SIZE.x * CELL_SIZE, BOARD_CELL_SIZE.y * CELL_SIZE);
|
pub const BOARD_PIXEL_SIZE: UVec2 = UVec2::new(BOARD_CELL_SIZE.x * CELL_SIZE, BOARD_CELL_SIZE.y * CELL_SIZE);
|
||||||
|
/// The size of the canvas, in pixels.
|
||||||
|
pub const CANVAS_SIZE: UVec2 = UVec2::new(
|
||||||
|
(BOARD_CELL_SIZE.x + BOARD_CELL_OFFSET.x) * CELL_SIZE,
|
||||||
|
(BOARD_CELL_SIZE.y + BOARD_CELL_OFFSET.y) * CELL_SIZE,
|
||||||
|
);
|
||||||
|
|
||||||
/// An enum representing the different types of tiles on the map.
|
/// An enum representing the different types of tiles on the map.
|
||||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||||
|
|||||||
@@ -16,7 +16,12 @@ pub struct Blinky {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Blinky {
|
impl Blinky {
|
||||||
pub fn new(starting_position: UVec2, atlas: Rc<SpriteAtlas>, map: Rc<RefCell<Map>>, pacman: Rc<RefCell<Pacman>>) -> Blinky {
|
pub fn new(
|
||||||
|
starting_position: UVec2,
|
||||||
|
atlas: Rc<RefCell<SpriteAtlas>>,
|
||||||
|
map: Rc<RefCell<Map>>,
|
||||||
|
pacman: Rc<RefCell<Pacman>>,
|
||||||
|
) -> Blinky {
|
||||||
Blinky {
|
Blinky {
|
||||||
ghost: Ghost::new(GhostType::Blinky, starting_position, atlas, map, pacman),
|
ghost: Ghost::new(GhostType::Blinky, starting_position, atlas, map, pacman),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ impl Entity for Edible {
|
|||||||
impl Renderable for Edible {
|
impl Renderable for Edible {
|
||||||
fn render(&mut self, canvas: &mut WindowCanvas) -> Result<()> {
|
fn render(&mut self, canvas: &mut WindowCanvas) -> Result<()> {
|
||||||
let pos = self.base.pixel_position;
|
let pos = self.base.pixel_position;
|
||||||
let dest = match &self.sprite {
|
let dest = match &mut self.sprite {
|
||||||
EdibleSprite::Pellet(sprite) => {
|
EdibleSprite::Pellet(sprite) => {
|
||||||
let tile = sprite.current_tile();
|
let tile = sprite.current_tile();
|
||||||
let x = pos.x + ((crate::constants::CELL_SIZE as i32 - tile.size.x as i32) / 2);
|
let x = pos.x + ((crate::constants::CELL_SIZE as i32 - tile.size.x as i32) / 2);
|
||||||
@@ -76,7 +76,7 @@ impl Renderable for Edible {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
match &self.sprite {
|
match &mut self.sprite {
|
||||||
EdibleSprite::Pellet(sprite) => sprite.render(canvas, dest),
|
EdibleSprite::Pellet(sprite) => sprite.render(canvas, dest),
|
||||||
EdibleSprite::PowerPellet(sprite) => sprite.render(canvas, dest),
|
EdibleSprite::PowerPellet(sprite) => sprite.render(canvas, dest),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ impl Ghost {
|
|||||||
pub fn new(
|
pub fn new(
|
||||||
ghost_type: GhostType,
|
ghost_type: GhostType,
|
||||||
starting_position: UVec2,
|
starting_position: UVec2,
|
||||||
atlas: Rc<SpriteAtlas>,
|
atlas: Rc<RefCell<SpriteAtlas>>,
|
||||||
map: Rc<RefCell<Map>>,
|
map: Rc<RefCell<Map>>,
|
||||||
pacman: Rc<RefCell<Pacman>>,
|
pacman: Rc<RefCell<Pacman>>,
|
||||||
) -> Ghost {
|
) -> Ghost {
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ pub mod pacman;
|
|||||||
pub mod speed;
|
pub mod speed;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
constants::{MapTile, BOARD_CELL_SIZE, BOARD_OFFSET, CELL_SIZE},
|
constants::{MapTile, BOARD_CELL_OFFSET, BOARD_CELL_SIZE, CELL_SIZE},
|
||||||
entity::{direction::Direction, speed::SimpleTickModulator},
|
entity::{direction::Direction, speed::SimpleTickModulator},
|
||||||
map::Map,
|
map::Map,
|
||||||
};
|
};
|
||||||
@@ -145,8 +145,8 @@ impl Moving for MovableEntity {
|
|||||||
}
|
}
|
||||||
fn update_cell_position(&mut self) {
|
fn update_cell_position(&mut self) {
|
||||||
self.base.cell_position = UVec2::new(
|
self.base.cell_position = UVec2::new(
|
||||||
(self.base.pixel_position.x as u32 / CELL_SIZE) - BOARD_OFFSET.x,
|
(self.base.pixel_position.x as u32 / CELL_SIZE) - BOARD_CELL_OFFSET.x,
|
||||||
(self.base.pixel_position.y as u32 / CELL_SIZE) - BOARD_OFFSET.y,
|
(self.base.pixel_position.y as u32 / CELL_SIZE) - BOARD_CELL_OFFSET.y,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
fn next_cell(&self, direction: Option<Direction>) -> IVec2 {
|
fn next_cell(&self, direction: Option<Direction>) -> IVec2 {
|
||||||
@@ -162,13 +162,18 @@ impl Moving for MovableEntity {
|
|||||||
let at_left_tunnel = x == 0;
|
let at_left_tunnel = x == 0;
|
||||||
let at_right_tunnel = x == BOARD_CELL_SIZE.x - 1;
|
let at_right_tunnel = x == BOARD_CELL_SIZE.x - 1;
|
||||||
|
|
||||||
|
// Reset tunnel state if we're not at a tunnel position
|
||||||
if !at_left_tunnel && !at_right_tunnel {
|
if !at_left_tunnel && !at_right_tunnel {
|
||||||
|
self.in_tunnel = false;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If we're already in a tunnel, stay in tunnel state
|
||||||
if self.in_tunnel {
|
if self.in_tunnel {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Enter the tunnel and teleport to the other side
|
||||||
let new_x = if at_left_tunnel { BOARD_CELL_SIZE.x - 2 } else { 1 };
|
let new_x = if at_left_tunnel { BOARD_CELL_SIZE.x - 2 } else { 1 };
|
||||||
self.base.cell_position.x = new_x;
|
self.base.cell_position.x = new_x;
|
||||||
self.base.pixel_position = Map::cell_to_pixel(self.base.cell_position);
|
self.base.pixel_position = Map::cell_to_pixel(self.base.cell_position);
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ impl QueuedDirection for Pacman {
|
|||||||
|
|
||||||
impl Pacman {
|
impl Pacman {
|
||||||
/// Creates a new `Pacman` instance.
|
/// Creates a new `Pacman` instance.
|
||||||
pub fn new(starting_position: UVec2, atlas: Rc<SpriteAtlas>, map: Rc<RefCell<Map>>) -> Pacman {
|
pub fn new(starting_position: UVec2, atlas: Rc<RefCell<SpriteAtlas>>, map: Rc<RefCell<Map>>) -> Pacman {
|
||||||
let pixel_position = Map::cell_to_pixel(starting_position);
|
let pixel_position = Map::cell_to_pixel(starting_position);
|
||||||
let get = |name: &str| get_atlas_tile(&atlas, name);
|
let get = |name: &str| get_atlas_tile(&atlas, name);
|
||||||
|
|
||||||
|
|||||||
65
src/game.rs
65
src/game.rs
@@ -4,15 +4,14 @@ use std::ops::Not;
|
|||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use glam::{IVec2, UVec2};
|
use glam::UVec2;
|
||||||
use rand::rngs::SmallRng;
|
use rand::rngs::SmallRng;
|
||||||
use rand::seq::IteratorRandom;
|
use rand::seq::IteratorRandom;
|
||||||
use rand::SeedableRng;
|
use rand::SeedableRng;
|
||||||
use sdl2::image::LoadTexture;
|
use sdl2::image::LoadTexture;
|
||||||
use sdl2::keyboard::Keycode;
|
use sdl2::keyboard::Keycode;
|
||||||
|
|
||||||
use sdl2::render::{Texture, TextureCreator};
|
use sdl2::render::{Texture, TextureCreator};
|
||||||
use sdl2::rwops::RWops;
|
|
||||||
use sdl2::ttf::Font;
|
|
||||||
use sdl2::video::WindowContext;
|
use sdl2::video::WindowContext;
|
||||||
use sdl2::{pixels::Color, render::Canvas, video::Window};
|
use sdl2::{pixels::Color, render::Canvas, video::Window};
|
||||||
|
|
||||||
@@ -50,8 +49,7 @@ pub struct Game {
|
|||||||
fps_10s: f64,
|
fps_10s: f64,
|
||||||
|
|
||||||
// Rendering resources
|
// Rendering resources
|
||||||
atlas: Rc<SpriteAtlas>,
|
atlas: Rc<RefCell<SpriteAtlas>>,
|
||||||
font: Font<'static, 'static>,
|
|
||||||
map_texture: AtlasTile,
|
map_texture: AtlasTile,
|
||||||
text_texture: TextTexture,
|
text_texture: TextTexture,
|
||||||
|
|
||||||
@@ -76,14 +74,16 @@ impl Game {
|
|||||||
};
|
};
|
||||||
let atlas_json = get_asset_bytes(Asset::AtlasJson).expect("Failed to load asset");
|
let atlas_json = get_asset_bytes(Asset::AtlasJson).expect("Failed to load asset");
|
||||||
let atlas_mapper: AtlasMapper = serde_json::from_slice(&atlas_json).expect("Could not parse atlas JSON");
|
let atlas_mapper: AtlasMapper = serde_json::from_slice(&atlas_json).expect("Could not parse atlas JSON");
|
||||||
let atlas = Rc::new(SpriteAtlas::new(atlas_texture, atlas_mapper));
|
let atlas = Rc::new(RefCell::new(SpriteAtlas::new(atlas_texture, atlas_mapper)));
|
||||||
let pacman = Rc::new(RefCell::new(Pacman::new(
|
let pacman = Rc::new(RefCell::new(Pacman::new(
|
||||||
UVec2::new(1, 1),
|
UVec2::new(1, 1),
|
||||||
Rc::clone(&atlas),
|
Rc::clone(&atlas),
|
||||||
Rc::clone(&map),
|
Rc::clone(&map),
|
||||||
)));
|
)));
|
||||||
let blinky = Blinky::new(UVec2::new(13, 11), Rc::clone(&atlas), Rc::clone(&map), Rc::clone(&pacman));
|
let blinky = Blinky::new(UVec2::new(13, 11), Rc::clone(&atlas), Rc::clone(&map), Rc::clone(&pacman));
|
||||||
let map_texture = get_atlas_tile(&atlas, "maze/full.png");
|
let mut map_texture = get_atlas_tile(&atlas, "maze/full.png");
|
||||||
|
map_texture.color = Some(Color::RGB(0x20, 0x20, 0xf9));
|
||||||
|
|
||||||
let edibles = reconstruct_edibles(
|
let edibles = reconstruct_edibles(
|
||||||
Rc::clone(&map),
|
Rc::clone(&map),
|
||||||
AnimatedTexture::new(vec![get_atlas_tile(&atlas, "maze/pellet.png")], 0),
|
AnimatedTexture::new(vec![get_atlas_tile(&atlas, "maze/pellet.png")], 0),
|
||||||
@@ -94,15 +94,6 @@ impl Game {
|
|||||||
),
|
),
|
||||||
AnimatedTexture::new(vec![get_atlas_tile(&atlas, "edible/cherry.png")], 0),
|
AnimatedTexture::new(vec![get_atlas_tile(&atlas, "edible/cherry.png")], 0),
|
||||||
);
|
);
|
||||||
let font = {
|
|
||||||
let font_bytes = get_asset_bytes(Asset::FontKonami).expect("Failed to load asset").into_owned();
|
|
||||||
let font_bytes_static: &'static [u8] = Box::leak(font_bytes.into_boxed_slice());
|
|
||||||
let font_rwops = RWops::from_bytes(font_bytes_static).expect("Failed to create RWops for font");
|
|
||||||
let ttf_context_static: &'static sdl2::ttf::Sdl2TtfContext = unsafe { std::mem::transmute(ttf_context) };
|
|
||||||
ttf_context_static
|
|
||||||
.load_font_from_rwops(font_rwops, 24)
|
|
||||||
.expect("Could not load font from asset API")
|
|
||||||
};
|
|
||||||
let text_texture = TextTexture::new(Rc::clone(&atlas), 1.0);
|
let text_texture = TextTexture::new(Rc::clone(&atlas), 1.0);
|
||||||
let audio = Audio::new();
|
let audio = Audio::new();
|
||||||
Game {
|
Game {
|
||||||
@@ -113,7 +104,6 @@ impl Game {
|
|||||||
score: 0,
|
score: 0,
|
||||||
debug_mode: DebugMode::None,
|
debug_mode: DebugMode::None,
|
||||||
atlas,
|
atlas,
|
||||||
font,
|
|
||||||
map_texture,
|
map_texture,
|
||||||
text_texture,
|
text_texture,
|
||||||
audio,
|
audio,
|
||||||
@@ -269,20 +259,18 @@ impl Game {
|
|||||||
|
|
||||||
/// Draws the entire game to the canvas using a backbuffer.
|
/// Draws the entire game to the canvas using a backbuffer.
|
||||||
pub fn draw(&mut self, window_canvas: &mut Canvas<Window>, backbuffer: &mut Texture) -> Result<()> {
|
pub fn draw(&mut self, window_canvas: &mut Canvas<Window>, backbuffer: &mut Texture) -> Result<()> {
|
||||||
let texture_creator = window_canvas.texture_creator();
|
|
||||||
window_canvas
|
window_canvas
|
||||||
.with_texture_canvas(backbuffer, |texture_canvas| {
|
.with_texture_canvas(backbuffer, |texture_canvas| {
|
||||||
let this = self as *mut Self;
|
let this = self as *mut Self;
|
||||||
let this = unsafe { &mut *this };
|
let this = unsafe { &mut *this };
|
||||||
texture_canvas.set_draw_color(Color::BLACK);
|
texture_canvas.set_draw_color(Color::BLACK);
|
||||||
texture_canvas.clear();
|
texture_canvas.clear();
|
||||||
this.map.borrow_mut().render(texture_canvas, &this.map_texture);
|
this.map.borrow_mut().render(texture_canvas, &mut this.map_texture);
|
||||||
for edible in this.edibles.iter_mut() {
|
for edible in this.edibles.iter_mut() {
|
||||||
let _ = edible.render(texture_canvas);
|
let _ = edible.render(texture_canvas);
|
||||||
}
|
}
|
||||||
let _ = this.pacman.borrow_mut().render(texture_canvas);
|
let _ = this.pacman.borrow_mut().render(texture_canvas);
|
||||||
let _ = this.blinky.render(texture_canvas);
|
let _ = this.blinky.render(texture_canvas);
|
||||||
this.render_ui_on(texture_canvas, &texture_creator);
|
|
||||||
match this.debug_mode {
|
match this.debug_mode {
|
||||||
DebugMode::Grid => {
|
DebugMode::Grid => {
|
||||||
DebugRenderer::draw_debug_grid(
|
DebugRenderer::draw_debug_grid(
|
||||||
@@ -304,39 +292,33 @@ impl Game {
|
|||||||
})
|
})
|
||||||
.map_err(|e| anyhow::anyhow!(format!("Failed to render to backbuffer: {e}")))
|
.map_err(|e| anyhow::anyhow!(format!("Failed to render to backbuffer: {e}")))
|
||||||
}
|
}
|
||||||
pub fn present_backbuffer(&self, canvas: &mut Canvas<Window>, backbuffer: &Texture) -> Result<()> {
|
pub fn present_backbuffer(&mut self, canvas: &mut Canvas<Window>, backbuffer: &Texture) -> Result<()> {
|
||||||
canvas.set_draw_color(Color::BLACK);
|
canvas.set_draw_color(Color::BLACK);
|
||||||
canvas.clear();
|
canvas.clear();
|
||||||
canvas.copy(backbuffer, None, None).map_err(anyhow::Error::msg)?;
|
canvas.copy(backbuffer, None, None).map_err(anyhow::Error::msg)?;
|
||||||
|
self.render_ui_on(canvas);
|
||||||
canvas.present();
|
canvas.present();
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_ui_on<C: sdl2::render::RenderTarget>(
|
fn render_ui_on<C: sdl2::render::RenderTarget>(&mut self, canvas: &mut sdl2::render::Canvas<C>) {
|
||||||
&mut self,
|
|
||||||
canvas: &mut sdl2::render::Canvas<C>,
|
|
||||||
texture_creator: &TextureCreator<WindowContext>,
|
|
||||||
) {
|
|
||||||
let lives = 3;
|
let lives = 3;
|
||||||
let score_text = format!("{:02}", self.score);
|
let score_text = format!("{:02}", self.score);
|
||||||
let x_offset = 12;
|
let x_offset = 4;
|
||||||
let y_offset = 2;
|
let y_offset = 2;
|
||||||
let lives_offset = 3;
|
let lives_offset = 3;
|
||||||
let score_offset = 7 - (score_text.len() as i32);
|
let score_offset = 7 - (score_text.len() as i32);
|
||||||
let gap_offset = 6;
|
self.text_texture.set_scale(1.0);
|
||||||
self.text_texture.set_scale(2.0);
|
let _ = self.text_texture.render(
|
||||||
self.render_text_on(
|
|
||||||
canvas,
|
canvas,
|
||||||
&*texture_creator,
|
|
||||||
&format!("{lives}UP HIGH SCORE "),
|
&format!("{lives}UP HIGH SCORE "),
|
||||||
IVec2::new(24 * lives_offset + x_offset, y_offset),
|
UVec2::new(8 * lives_offset as u32 + x_offset, y_offset),
|
||||||
Color::WHITE,
|
Color::WHITE,
|
||||||
);
|
);
|
||||||
self.render_text_on(
|
let _ = self.text_texture.render(
|
||||||
canvas,
|
canvas,
|
||||||
&*texture_creator,
|
|
||||||
&score_text,
|
&score_text,
|
||||||
IVec2::new(24 * score_offset + x_offset, 24 + y_offset + gap_offset),
|
UVec2::new(8 * score_offset as u32 + x_offset, 8 + y_offset),
|
||||||
Color::WHITE,
|
Color::WHITE,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -350,17 +332,4 @@ impl Game {
|
|||||||
// Color::RGB(255, 255, 0), // Yellow color for FPS display
|
// Color::RGB(255, 255, 0), // Yellow color for FPS display
|
||||||
// );
|
// );
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_text_on<C: sdl2::render::RenderTarget>(
|
|
||||||
&mut self,
|
|
||||||
canvas: &mut sdl2::render::Canvas<C>,
|
|
||||||
texture_creator: &TextureCreator<WindowContext>,
|
|
||||||
text: &str,
|
|
||||||
position: IVec2,
|
|
||||||
color: Color,
|
|
||||||
) {
|
|
||||||
self.text_texture
|
|
||||||
.render(canvas, text, glam::UVec2::new(position.x as u32, position.y as u32))
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
12
src/main.rs
12
src/main.rs
@@ -1,6 +1,6 @@
|
|||||||
#![windows_subsystem = "windows"]
|
#![windows_subsystem = "windows"]
|
||||||
|
|
||||||
use crate::constants::{BOARD_PIXEL_SIZE, SCALE};
|
use crate::constants::{CANVAS_SIZE, SCALE};
|
||||||
use crate::game::Game;
|
use crate::game::Game;
|
||||||
use sdl2::event::{Event, WindowEvent};
|
use sdl2::event::{Event, WindowEvent};
|
||||||
use sdl2::keyboard::Keycode;
|
use sdl2::keyboard::Keycode;
|
||||||
@@ -105,8 +105,8 @@ pub fn main() {
|
|||||||
let window = video_subsystem
|
let window = video_subsystem
|
||||||
.window(
|
.window(
|
||||||
"Pac-Man",
|
"Pac-Man",
|
||||||
(BOARD_PIXEL_SIZE.x as f32 * SCALE).round() as u32,
|
(CANVAS_SIZE.x as f32 * SCALE).round() as u32,
|
||||||
(BOARD_PIXEL_SIZE.y as f32 * SCALE).round() as u32,
|
(CANVAS_SIZE.y as f32 * SCALE).round() as u32,
|
||||||
)
|
)
|
||||||
.resizable()
|
.resizable()
|
||||||
.position_centered()
|
.position_centered()
|
||||||
@@ -116,7 +116,7 @@ pub fn main() {
|
|||||||
let mut canvas = window.into_canvas().build().expect("Could not build canvas");
|
let mut canvas = window.into_canvas().build().expect("Could not build canvas");
|
||||||
|
|
||||||
canvas
|
canvas
|
||||||
.set_logical_size(BOARD_PIXEL_SIZE.x, BOARD_PIXEL_SIZE.y)
|
.set_logical_size(CANVAS_SIZE.x, CANVAS_SIZE.y)
|
||||||
.expect("Could not set logical size");
|
.expect("Could not set logical size");
|
||||||
|
|
||||||
let texture_creator = canvas.texture_creator();
|
let texture_creator = canvas.texture_creator();
|
||||||
@@ -127,7 +127,7 @@ pub fn main() {
|
|||||||
|
|
||||||
// Create a backbuffer texture for drawing
|
// Create a backbuffer texture for drawing
|
||||||
let mut backbuffer = texture_creator_static
|
let mut backbuffer = texture_creator_static
|
||||||
.create_texture_target(None, BOARD_PIXEL_SIZE.x, BOARD_PIXEL_SIZE.y)
|
.create_texture_target(None, CANVAS_SIZE.x, CANVAS_SIZE.y)
|
||||||
.expect("Could not create backbuffer texture");
|
.expect("Could not create backbuffer texture");
|
||||||
|
|
||||||
let mut event_pump = sdl_context.event_pump().expect("Could not get SDL EventPump");
|
let mut event_pump = sdl_context.event_pump().expect("Could not get SDL EventPump");
|
||||||
@@ -154,7 +154,7 @@ pub fn main() {
|
|||||||
let mut last_frame_time = Instant::now();
|
let mut last_frame_time = Instant::now();
|
||||||
|
|
||||||
event!(tracing::Level::INFO, "Starting game loop ({:?})", loop_time);
|
event!(tracing::Level::INFO, "Starting game loop ({:?})", loop_time);
|
||||||
let mut main_loop = || {
|
let mut main_loop = move || {
|
||||||
let start = Instant::now();
|
let start = Instant::now();
|
||||||
let current_frame_time = Instant::now();
|
let current_frame_time = Instant::now();
|
||||||
let frame_duration = current_frame_time.duration_since(last_frame_time);
|
let frame_duration = current_frame_time.duration_since(last_frame_time);
|
||||||
|
|||||||
16
src/map.rs
16
src/map.rs
@@ -3,7 +3,7 @@ use rand::rngs::SmallRng;
|
|||||||
use rand::seq::IteratorRandom;
|
use rand::seq::IteratorRandom;
|
||||||
use rand::SeedableRng;
|
use rand::SeedableRng;
|
||||||
|
|
||||||
use crate::constants::{MapTile, BOARD_CELL_SIZE, BOARD_OFFSET, CELL_SIZE};
|
use crate::constants::{MapTile, BOARD_CELL_OFFSET, BOARD_CELL_SIZE, BOARD_PIXEL_OFFSET, BOARD_PIXEL_SIZE, CELL_SIZE};
|
||||||
use crate::texture::sprite::AtlasTile;
|
use crate::texture::sprite::AtlasTile;
|
||||||
use glam::{IVec2, UVec2};
|
use glam::{IVec2, UVec2};
|
||||||
use once_cell::sync::OnceCell;
|
use once_cell::sync::OnceCell;
|
||||||
@@ -104,7 +104,10 @@ impl Map {
|
|||||||
///
|
///
|
||||||
/// * `cell` - The cell coordinates, in grid coordinates.
|
/// * `cell` - The cell coordinates, in grid coordinates.
|
||||||
pub fn cell_to_pixel(cell: UVec2) -> IVec2 {
|
pub fn cell_to_pixel(cell: UVec2) -> IVec2 {
|
||||||
IVec2::new((cell.x * CELL_SIZE) as i32, ((cell.y + BOARD_OFFSET.y) * CELL_SIZE) as i32)
|
IVec2::new(
|
||||||
|
(cell.x * CELL_SIZE) as i32,
|
||||||
|
((cell.y + BOARD_CELL_OFFSET.y) * CELL_SIZE) as i32,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a reference to a cached vector of all valid playable positions in the maze.
|
/// Returns a reference to a cached vector of all valid playable positions in the maze.
|
||||||
@@ -161,8 +164,13 @@ impl Map {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Renders the map to the given canvas using the provided map texture.
|
/// Renders the map to the given canvas using the provided map texture.
|
||||||
pub fn render(&self, canvas: &mut Canvas<Window>, map_texture: &AtlasTile) {
|
pub fn render(&self, canvas: &mut Canvas<Window>, map_texture: &mut AtlasTile) {
|
||||||
let dest = Rect::new(0, 0, CELL_SIZE * BOARD_CELL_SIZE.x, CELL_SIZE * BOARD_CELL_SIZE.y);
|
let dest = Rect::new(
|
||||||
|
BOARD_PIXEL_OFFSET.x as i32,
|
||||||
|
BOARD_PIXEL_OFFSET.y as i32,
|
||||||
|
BOARD_PIXEL_SIZE.x,
|
||||||
|
BOARD_PIXEL_SIZE.y,
|
||||||
|
);
|
||||||
let _ = map_texture.render(canvas, dest);
|
let _ = map_texture.render(canvas, dest);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,15 +34,15 @@ impl AnimatedTexture {
|
|||||||
self.ticker += 1;
|
self.ticker += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn current_tile(&self) -> &AtlasTile {
|
pub fn current_tile(&mut self) -> &mut AtlasTile {
|
||||||
if self.ticks_per_frame == 0 {
|
if self.ticks_per_frame == 0 {
|
||||||
return &self.frames[0];
|
return &mut self.frames[0];
|
||||||
}
|
}
|
||||||
let frame_index = (self.ticker / self.ticks_per_frame) as usize % self.frames.len();
|
let frame_index = (self.ticker / self.ticks_per_frame) as usize % self.frames.len();
|
||||||
&self.frames[frame_index]
|
&mut self.frames[frame_index]
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn render(&self, canvas: &mut WindowCanvas, dest: sdl2::rect::Rect) -> Result<()> {
|
pub fn render(&mut self, canvas: &mut WindowCanvas, dest: sdl2::rect::Rect) -> Result<()> {
|
||||||
let tile = self.current_tile();
|
let tile = self.current_tile();
|
||||||
tile.render(canvas, dest)
|
tile.render(canvas, dest)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ impl BlinkingTexture {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Renders the blinking texture.
|
/// Renders the blinking texture.
|
||||||
pub fn render(&self, canvas: &mut WindowCanvas, dest: sdl2::rect::Rect) -> Result<()> {
|
pub fn render(&mut self, canvas: &mut WindowCanvas, dest: sdl2::rect::Rect) -> Result<()> {
|
||||||
if self.visible {
|
if self.visible {
|
||||||
self.animation.render(canvas, dest)
|
self.animation.render(canvas, dest)
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -37,28 +37,28 @@ impl DirectionalAnimatedTexture {
|
|||||||
|
|
||||||
pub fn render(&mut self, canvas: &mut WindowCanvas, dest: sdl2::rect::Rect, direction: Direction) -> Result<()> {
|
pub fn render(&mut self, canvas: &mut WindowCanvas, dest: sdl2::rect::Rect, direction: Direction) -> Result<()> {
|
||||||
let frames = match direction {
|
let frames = match direction {
|
||||||
Direction::Up => &self.up,
|
Direction::Up => &mut self.up,
|
||||||
Direction::Down => &self.down,
|
Direction::Down => &mut self.down,
|
||||||
Direction::Left => &self.left,
|
Direction::Left => &mut self.left,
|
||||||
Direction::Right => &self.right,
|
Direction::Right => &mut self.right,
|
||||||
};
|
};
|
||||||
|
|
||||||
let frame_index = (self.ticker / self.ticks_per_frame) as usize % frames.len();
|
let frame_index = (self.ticker / self.ticks_per_frame) as usize % frames.len();
|
||||||
let tile = &frames[frame_index];
|
let tile = &mut frames[frame_index];
|
||||||
|
|
||||||
tile.render(canvas, dest)
|
tile.render(canvas, dest)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn render_stopped(&mut self, canvas: &mut WindowCanvas, dest: sdl2::rect::Rect, direction: Direction) -> Result<()> {
|
pub fn render_stopped(&mut self, canvas: &mut WindowCanvas, dest: sdl2::rect::Rect, direction: Direction) -> Result<()> {
|
||||||
let frames = match direction {
|
let frames = match direction {
|
||||||
Direction::Up => &self.up,
|
Direction::Up => &mut self.up,
|
||||||
Direction::Down => &self.down,
|
Direction::Down => &mut self.down,
|
||||||
Direction::Left => &self.left,
|
Direction::Left => &mut self.left,
|
||||||
Direction::Right => &self.right,
|
Direction::Right => &mut self.right,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Show the last frame (full sprite) when stopped
|
// Show the last frame (full sprite) when stopped
|
||||||
let tile = &frames[1];
|
let tile = &mut frames[1];
|
||||||
|
|
||||||
tile.render(canvas, dest)
|
tile.render(canvas, dest)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
use std::cell::RefCell;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
use crate::texture::sprite::{AtlasTile, SpriteAtlas};
|
use crate::texture::sprite::{AtlasTile, SpriteAtlas};
|
||||||
@@ -8,6 +9,6 @@ pub mod directional;
|
|||||||
pub mod sprite;
|
pub mod sprite;
|
||||||
pub mod text;
|
pub mod text;
|
||||||
|
|
||||||
pub fn get_atlas_tile(atlas: &Rc<SpriteAtlas>, name: &str) -> AtlasTile {
|
pub fn get_atlas_tile(atlas: &Rc<RefCell<SpriteAtlas>>, name: &str) -> AtlasTile {
|
||||||
SpriteAtlas::get_tile(atlas, name).unwrap_or_else(|| panic!("Could not find tile {}", name))
|
SpriteAtlas::get_tile(atlas, name).unwrap_or_else(|| panic!("Could not find tile {}", name))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use glam::U16Vec2;
|
use glam::U16Vec2;
|
||||||
|
use sdl2::pixels::Color;
|
||||||
use sdl2::rect::Rect;
|
use sdl2::rect::Rect;
|
||||||
use sdl2::render::{Canvas, RenderTarget, Texture};
|
use sdl2::render::{Canvas, RenderTarget, Texture};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
use std::cell::RefCell;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
@@ -21,15 +23,30 @@ pub struct MapperFrame {
|
|||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct AtlasTile {
|
pub struct AtlasTile {
|
||||||
pub atlas: Rc<SpriteAtlas>,
|
pub atlas: Rc<RefCell<SpriteAtlas>>,
|
||||||
pub pos: U16Vec2,
|
pub pos: U16Vec2,
|
||||||
pub size: U16Vec2,
|
pub size: U16Vec2,
|
||||||
|
pub color: Option<Color>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AtlasTile {
|
impl AtlasTile {
|
||||||
pub fn render<C: RenderTarget>(&self, canvas: &mut Canvas<C>, dest: Rect) -> Result<()> {
|
pub fn render<C: RenderTarget>(&mut self, canvas: &mut Canvas<C>, dest: Rect) -> Result<()> {
|
||||||
|
let color = self
|
||||||
|
.color
|
||||||
|
.unwrap_or(self.atlas.borrow().default_color.unwrap_or(Color::WHITE));
|
||||||
|
self.render_with_color(canvas, dest, color)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn render_with_color<C: RenderTarget>(&mut self, canvas: &mut Canvas<C>, dest: Rect, color: Color) -> Result<()> {
|
||||||
let src = Rect::new(self.pos.x as i32, self.pos.y as i32, self.size.x as u32, self.size.y as u32);
|
let src = Rect::new(self.pos.x as i32, self.pos.y as i32, self.size.x as u32, self.size.y as u32);
|
||||||
canvas.copy(&self.atlas.texture, src, dest).map_err(anyhow::Error::msg)?;
|
|
||||||
|
let mut atlas = self.atlas.borrow_mut();
|
||||||
|
if atlas.last_modulation != Some(color) {
|
||||||
|
atlas.texture.set_color_mod(color.r, color.g, color.b);
|
||||||
|
atlas.last_modulation = Some(color);
|
||||||
|
}
|
||||||
|
|
||||||
|
canvas.copy(&atlas.texture, src, dest).map_err(anyhow::Error::msg)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -37,6 +54,8 @@ impl AtlasTile {
|
|||||||
pub struct SpriteAtlas {
|
pub struct SpriteAtlas {
|
||||||
texture: Texture<'static>,
|
texture: Texture<'static>,
|
||||||
tiles: HashMap<String, MapperFrame>,
|
tiles: HashMap<String, MapperFrame>,
|
||||||
|
default_color: Option<Color>,
|
||||||
|
last_modulation: Option<Color>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SpriteAtlas {
|
impl SpriteAtlas {
|
||||||
@@ -44,17 +63,25 @@ impl SpriteAtlas {
|
|||||||
Self {
|
Self {
|
||||||
texture,
|
texture,
|
||||||
tiles: mapper.frames,
|
tiles: mapper.frames,
|
||||||
|
default_color: None,
|
||||||
|
last_modulation: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_tile(atlas: &Rc<SpriteAtlas>, name: &str) -> Option<AtlasTile> {
|
pub fn get_tile(atlas: &Rc<RefCell<SpriteAtlas>>, name: &str) -> Option<AtlasTile> {
|
||||||
atlas.tiles.get(name).map(|frame| AtlasTile {
|
let atlas_ref = atlas.borrow();
|
||||||
atlas: atlas.clone(),
|
atlas_ref.tiles.get(name).map(|frame| AtlasTile {
|
||||||
|
atlas: Rc::clone(atlas),
|
||||||
pos: U16Vec2::new(frame.x, frame.y),
|
pos: U16Vec2::new(frame.x, frame.y),
|
||||||
size: U16Vec2::new(frame.width, frame.height),
|
size: U16Vec2::new(frame.width, frame.height),
|
||||||
|
color: None,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_color(&mut self, color: Color) {
|
||||||
|
self.default_color = Some(color);
|
||||||
|
}
|
||||||
|
|
||||||
pub fn texture(&self) -> &Texture<'static> {
|
pub fn texture(&self) -> &Texture<'static> {
|
||||||
&self.texture
|
&self.texture
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,8 +48,9 @@
|
|||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use glam::UVec2;
|
use glam::UVec2;
|
||||||
use sdl2::rect::Rect;
|
use sdl2::pixels::Color;
|
||||||
use sdl2::render::{Canvas, RenderTarget};
|
use sdl2::render::{Canvas, RenderTarget};
|
||||||
|
use std::cell::RefCell;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
@@ -57,14 +58,14 @@ use crate::texture::sprite::{AtlasTile, SpriteAtlas};
|
|||||||
|
|
||||||
/// A text texture that renders characters from the atlas.
|
/// A text texture that renders characters from the atlas.
|
||||||
pub struct TextTexture {
|
pub struct TextTexture {
|
||||||
atlas: Rc<SpriteAtlas>,
|
atlas: Rc<RefCell<SpriteAtlas>>,
|
||||||
char_map: HashMap<char, AtlasTile>,
|
char_map: HashMap<char, AtlasTile>,
|
||||||
scale: f32,
|
scale: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TextTexture {
|
impl TextTexture {
|
||||||
/// Creates a new text texture with the given atlas and scale.
|
/// Creates a new text texture with the given atlas and scale.
|
||||||
pub fn new(atlas: Rc<SpriteAtlas>, scale: f32) -> Self {
|
pub fn new(atlas: Rc<RefCell<SpriteAtlas>>, scale: f32) -> Self {
|
||||||
Self {
|
Self {
|
||||||
atlas,
|
atlas,
|
||||||
char_map: HashMap::new(),
|
char_map: HashMap::new(),
|
||||||
@@ -107,13 +108,13 @@ impl TextTexture {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Renders a string of text at the given position.
|
/// Renders a string of text at the given position.
|
||||||
pub fn render<C: RenderTarget>(&mut self, canvas: &mut Canvas<C>, text: &str, position: UVec2) -> Result<()> {
|
pub fn render<C: RenderTarget>(&mut self, canvas: &mut Canvas<C>, text: &str, position: UVec2, color: Color) -> Result<()> {
|
||||||
let mut x_offset = 0;
|
let mut x_offset = 0;
|
||||||
let char_width = (8.0 * self.scale) as u32;
|
let char_width = (8.0 * self.scale) as u32;
|
||||||
let char_height = (8.0 * self.scale) as u32;
|
let char_height = (8.0 * self.scale) as u32;
|
||||||
|
|
||||||
for c in text.chars() {
|
for c in text.chars() {
|
||||||
if let Some(tile) = self.get_char_tile(c) {
|
if let Some(mut tile) = self.get_char_tile(c) {
|
||||||
let dest = sdl2::rect::Rect::new((position.x + x_offset) as i32, position.y as i32, char_width, char_height);
|
let dest = sdl2::rect::Rect::new((position.x + x_offset) as i32, position.y as i32, char_width, char_height);
|
||||||
tile.render(canvas, dest)?;
|
tile.render(canvas, dest)?;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user