summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFreya Murphy <freya@freyacat.org>2025-11-06 21:01:34 -0500
committerFreya Murphy <freya@freyacat.org>2025-11-06 21:09:00 -0500
commit73c4521b723129dff2d7c4ccac4d48dd16eae2b3 (patch)
tree7f188f740bed947a34b380d8234c7f1e8a2eb170
parentLight bsp refactoring (diff)
downloadDungeonCrawl-73c4521b723129dff2d7c4ccac4d48dd16eae2b3.tar.gz
DungeonCrawl-73c4521b723129dff2d7c4ccac4d48dd16eae2b3.tar.bz2
DungeonCrawl-73c4521b723129dff2d7c4ccac4d48dd16eae2b3.zip
graphics: refactor renderer
-rw-r--r--game/src/main.rs2
-rw-r--r--graphics/src/assets.rs124
-rw-r--r--graphics/src/audio.rs32
-rw-r--r--graphics/src/lib.rs29
-rw-r--r--graphics/src/render.rs604
5 files changed, 353 insertions, 438 deletions
diff --git a/game/src/main.rs b/game/src/main.rs
index 038b2ac..f26ece4 100644
--- a/game/src/main.rs
+++ b/game/src/main.rs
@@ -19,7 +19,7 @@ fn main() -> Result<()> {
}
// Draw a single frame
- window.renderer().draw_frame(&dungeon);
+ window.draw_frame(&dungeon);
}
Ok(())
diff --git a/graphics/src/assets.rs b/graphics/src/assets.rs
deleted file mode 100644
index bb989a3..0000000
--- a/graphics/src/assets.rs
+++ /dev/null
@@ -1,124 +0,0 @@
-//! The `assets` crate stores all audio and image assets that need to be
-//! loaded during runtime
-
-use dungeon::Item;
-use raylib::{RaylibHandle, RaylibThread, audio::RaylibAudio, texture::Texture2D};
-
-#[expect(dead_code)]
-type Sound = raylib::audio::Sound<'static>;
-
-/// The `AudioData` container initalizes the audio subsystem
-/// for raylib, leaks it (to gurentee audio is statically loaded),
-/// then loads all needed audio samples
-#[derive(Debug)]
-pub struct AudioData {}
-impl AudioData {
- pub(crate) fn load() -> crate::Result<Self> {
- // Phantom handle to the raylib audio subsystem
- // Raylib doesnt use a handle, but the rust bindings
- // have one to ensure memory safety.
- //
- // We must leak this handle after allocating it,
- // if we dont then all audio will be unloaded :(
- //
- // NOTE: would this cause issues if `AudioData::load` was
- // called multiple times?
- let _handle = Box::leak(Box::new(RaylibAudio::init_audio_device()?));
-
- // TODO: load audio samples
-
- //let example = handle.new_sound("example.ogg")?;
-
- Ok(Self {})
- }
-}
-
-/// The baseline size of all ingame sprites and tile textures
-pub(crate) const BASE_TILE_SIZE: i32 = 32;
-
-/// The height of the wall (offset between tile layers)
-pub(crate) const WALL_HEIGHT: i32 = 13;
-
-/// Texture indexes into the atlas
-#[derive(Clone, Copy, Debug)]
-pub(crate) enum AtlasTexture {
- Wall,
- FloorFull,
- FloorEmpty,
- WallBase,
- WallEdgeNorth,
- WallEdgeEast,
- WallEdgeSouth,
- WallEdgeWest,
- Player,
- InvTopLayer,
- InvBottomLayer,
- Error,
-}
-impl AtlasTexture {
- /// Returns the x,y position of the sprite on the atlas.bmp texture
- #[must_use]
- pub(crate) const fn xy(&self) -> (i32, i32) {
- match self {
- Self::Wall => (0, 0),
- Self::FloorFull => (1, 0),
- Self::FloorEmpty => (2, 0),
- Self::WallBase => (3, 0),
- Self::WallEdgeNorth => (0, 1),
- Self::WallEdgeEast => (1, 1),
- Self::WallEdgeSouth => (2, 1),
- Self::WallEdgeWest => (3, 1),
- Self::Player => (0, 2),
- Self::InvTopLayer => (1, 2),
- Self::InvBottomLayer => (2, 2),
- Self::Error => (3, 3),
- }
- }
-
- /// Returns the x position of the sprite on the atlas.bmp texture
- #[must_use]
- pub(crate) const fn x(&self) -> i32 {
- self.xy().0
- }
-
- /// Returns the y position of the sprite on the atlas.bmp texture
- #[must_use]
- pub(crate) const fn y(&self) -> i32 {
- self.xy().1
- }
-}
-
-/// The `ImageData` container loads all game sprites, and other images into memory.
-#[derive(Debug)]
-pub(crate) struct ImageData {
- pub(crate) atlas: Texture2D,
- pub(crate) heart_full: Texture2D,
- pub(crate) heart_half: Texture2D,
- pub(crate) heart_empty: Texture2D,
- pub(crate) error: Texture2D,
-}
-impl ImageData {
- pub(crate) fn load(
- handle: &mut RaylibHandle,
- thread: &RaylibThread,
- ) -> crate::Result<Self> {
- let atlas = handle.load_texture(thread, "assets/atlas.bmp")?;
- let heart_full = handle.load_texture(thread, "assets/heart_full.bmp")?;
- let heart_half = handle.load_texture(thread, "assets/heart_half.bmp")?;
- let heart_empty = handle.load_texture(thread, "assets/heart_empty.bmp")?;
- let error = handle.load_texture(thread, "assets/error.bmp")?;
-
- Ok(Self {
- atlas,
- heart_full,
- heart_half,
- heart_empty,
- error,
- })
- }
-
- pub(crate) fn get_item_texture(&self, _item: &Item) -> &Texture2D {
- // TODO: make item textures
- &self.error
- }
-}
diff --git a/graphics/src/audio.rs b/graphics/src/audio.rs
new file mode 100644
index 0000000..8cf155f
--- /dev/null
+++ b/graphics/src/audio.rs
@@ -0,0 +1,32 @@
+//! The `audio` crate stores all audio assets that need to be loaded during runtime
+
+use raylib::audio::RaylibAudio;
+
+#[expect(dead_code)]
+type Sound = raylib::audio::Sound<'static>;
+
+/// The `Audio` container initalizes the audio subsystem
+/// for raylib, leaks it (to gurentee audio is statically loaded),
+/// then loads all needed audio samples
+#[derive(Debug)]
+pub struct Audio {}
+impl Audio {
+ pub(crate) fn load() -> crate::Result<Self> {
+ // Phantom handle to the raylib audio subsystem
+ // Raylib doesnt use a handle, but the rust bindings
+ // have one to ensure memory safety.
+ //
+ // We must leak this handle after allocating it,
+ // if we dont then all audio will be unloaded :(
+ //
+ // NOTE: would this cause issues if `Audio::load` was
+ // called multiple times?
+ let _handle = Box::leak(Box::new(RaylibAudio::init_audio_device()?));
+
+ // TODO: load audio samples
+
+ //let example = handle.new_sound("example.ogg")?;
+
+ Ok(Self {})
+ }
+}
diff --git a/graphics/src/lib.rs b/graphics/src/lib.rs
index c625025..9917a94 100644
--- a/graphics/src/lib.rs
+++ b/graphics/src/lib.rs
@@ -3,12 +3,13 @@
use std::cell::RefCell;
+use dungeon::Dungeon;
use raylib::prelude::*;
-use crate::assets::{AudioData, ImageData};
-use crate::render::{FrameRendererImpl, Renderer};
+use crate::audio::Audio;
+use crate::render::Renderer;
-mod assets;
+mod audio;
mod render;
/// The `KeyCode` type represents different keys being pressed on the users keyboard
@@ -29,7 +30,7 @@ pub struct Window {
handle: RefCell<RaylibHandle>,
thread: RaylibThread,
// audio data/subsystem
- audio: AudioData,
+ audio: Audio,
}
impl Window {
/// Instantiates a new window provided with the default
@@ -50,12 +51,11 @@ impl Window {
.vsync()
.build();
- // load assets
- let audio = AudioData::load()?;
- let image = ImageData::load(&mut handle, &thread)?;
+ // load audio
+ let audio = Audio::load()?;
// load renderer
- let renderer = Renderer::new(&mut handle, &thread, image)?;
+ let renderer = Renderer::new(&mut handle, &thread)?;
Ok(Self {
handle: RefCell::new(handle),
@@ -71,17 +71,20 @@ impl Window {
!self.handle.borrow().window_should_close()
}
- /// Returns the per frame renderer for the game
+ /// Draws the next ingame frame
///
/// # Examples
/// ```no_run
/// use graphics::Window;
+ /// use dungeon::Dungeon;
///
+ /// let dungeon = Dungeon::new();
/// let mut window = Window::new(800, 600, "Dungeon Crawl").unwrap();
- /// let mut renderer = window.renderer();
+ /// window.draw_frame(&dungeon);
/// ```
- pub fn renderer(&mut self) -> FrameRendererImpl<'_> {
- self.renderer.invoke(self.handle.get_mut(), &self.thread)
+ pub fn draw_frame(&mut self, dungeon: &Dungeon) {
+ self.renderer
+ .draw_frame(self.handle.get_mut(), &self.thread, dungeon);
}
/// Returns the per frame delta time
@@ -110,7 +113,7 @@ impl Window {
}
/// Get audio data for the window
- pub fn audio(&self) -> &AudioData {
+ pub fn audio(&self) -> &Audio {
&self.audio
}
}
diff --git a/graphics/src/render.rs b/graphics/src/render.rs
index 4166a7a..2467bb1 100644
--- a/graphics/src/render.rs
+++ b/graphics/src/render.rs
@@ -1,30 +1,78 @@
-//! The `render` module contains the structures for displaying
-//! the game, with each frame represented by a `Renderer` and
-//! frame specific information in `FrameInfo`.
-
-use std::{cell::RefCell, ops::Div, rc::Rc};
+use std::ops::Div;
use dungeon::{
- Direction, Dungeon, Entity, EntityKind, FPos, Floor, MAP_SIZE, PLAYER_FULL_HEALTH,
- Player, Pos, Tile,
+ Direction, Dungeon, Entity, EntityKind, FPos, Floor, Item, MAP_SIZE,
+ PLAYER_FULL_HEALTH, Player, Pos, Tile,
};
use raylib::{
- RaylibThread,
+ RaylibHandle, RaylibThread,
camera::Camera2D,
color::Color,
math::{Rectangle, Vector2},
- prelude::{
- RaylibDraw, RaylibDrawHandle, RaylibHandle, RaylibMode2D, RaylibMode2DExt,
- RaylibTextureModeExt,
- },
- texture::{RaylibTexture2D, RenderTexture2D},
+ prelude::{RaylibDraw, RaylibMode2DExt, RaylibTextureModeExt},
+ texture::{RaylibTexture2D, RenderTexture2D, Texture2D},
};
-use crate::assets::{AtlasTexture, BASE_TILE_SIZE, ImageData, WALL_HEIGHT};
+/// The baseline size of all ingame sprites and tile textures
+const BASE_TILE_SIZE: i32 = 32;
+
+/// The height of the wall (offset between tile layers)
+const WALL_HEIGHT: i32 = 13;
/// The (prefered) view distance of the game
const VIEW_DISTANCE: i32 = 5;
+#[derive(Debug)]
+struct Textures {
+ // Tilemap
+ atlas: Texture2D,
+ // UI
+ heart_full: Texture2D,
+ heart_half: Texture2D,
+ heart_empty: Texture2D,
+ // Misc
+ error: Texture2D,
+}
+impl Textures {
+ fn new(handle: &mut RaylibHandle, thread: &RaylibThread) -> crate::Result<Self> {
+ let atlas = handle.load_texture(thread, "assets/atlas.bmp")?;
+ let heart_full = handle.load_texture(thread, "assets/heart_full.bmp")?;
+ let heart_half = handle.load_texture(thread, "assets/heart_half.bmp")?;
+ let heart_empty = handle.load_texture(thread, "assets/heart_empty.bmp")?;
+ let error = handle.load_texture(thread, "assets/error.bmp")?;
+
+ Ok(Self {
+ atlas,
+ heart_full,
+ heart_half,
+ heart_empty,
+ error,
+ })
+ }
+
+ fn item_texture(&self, _item: &Item) -> &Texture2D {
+ // TODO: make item textures
+ &self.error
+ }
+}
+
+// Tile atlas.bmp textures
+const ATLAS_WALL_SIDE: (i32, i32) = (0, 0);
+const ATLAS_FLOOR_FULL: (i32, i32) = (1, 0);
+const ATLAS_FLOOR_EMPTY: (i32, i32) = (2, 0);
+const ATLAS_WALL_TOP: (i32, i32) = (3, 0);
+const ATLAS_WALL_EDGE: (i32, i32) = (0, 1);
+
+// Entity atlas.bmp textures
+const ATLAS_PLAYER: (i32, i32) = (0, 2);
+
+// UI atlas.bmp textures
+const ATLAS_INV_TOP_LAYER: (i32, i32) = (1, 2);
+const ATLAS_INV_BOTTOM_LAYER: (i32, i32) = (2, 2);
+
+// Misc atlas.bmp textures
+const ATLAS_ERROR: (i32, i32) = (3, 3);
+
/// Full source rec for any texture
const FULL_SOURCE_REC: Rectangle = Rectangle {
x: 0.0,
@@ -33,208 +81,131 @@ const FULL_SOURCE_REC: Rectangle = Rectangle {
height: BASE_TILE_SIZE as f32,
};
-/// The `FrameInfo` struct stores information used during a single frame
-#[derive(Clone, Copy, Debug)]
-struct FrameInfo {
- /// The last calculated fps
+#[derive(Debug)]
+pub struct Renderer {
+ /* Persistant Render Data */
+ /// All loaded image resources/textures
+ textures: Textures,
+ /// Top layer of the tilemap (walls)
+ tilemap_fg: RenderTexture2D,
+ /// Bottom layer of the tilemap (floor and half-height walls)
+ tilemap_bg: RenderTexture2D,
+ /// Last computed hash of the tilemap (floor)
+ tiles_hash: Option<u64>,
+ /* Per Frame Caculated Data */
+ /// Current tile size we are rendering
+ tile_size: i32,
+ /// Last known FPS
fps: u32,
- /// The render width of the framebuffer in pixels
+ /// Render width of the current frame
width: i32,
- /// The render height of the framebuffer in pixels
+ /// Render height of the current frame
height: i32,
- /// The tile size in pixels
- tile_size: i32,
-}
-impl FrameInfo {
- fn new(handle: &RaylibHandle) -> Self {
- let fps = handle.get_fps();
- let width = handle.get_render_width();
- let height = handle.get_render_height();
- let tile_size = {
- let size = width.min(height);
- let dist = VIEW_DISTANCE * 2 + 1;
- let pixels = size.div(dist).max(BASE_TILE_SIZE);
- 1 << (i32::BITS - pixels.leading_zeros())
- };
-
- Self {
- fps,
- width,
- height,
- tile_size,
- }
- }
-}
-
-/// The `State` struct stores persistant renderer data used across multiple frames
-#[derive(Debug)]
-struct State {
- /// Set of sprites to be drawn
- image: ImageData,
- /// Top layer of the tile map (used for back and top sides of walls)
- tiles_tex_fg: RefCell<RenderTexture2D>,
- /// Bottom layer of the tile map (used for most things)
- tiles_tex_bg: RefCell<RenderTexture2D>,
- /// Hash of tiles used to draw the tile textures
- tiles_hash: RefCell<u64>,
}
-impl State {
- fn new(
- handle: &mut RaylibHandle,
- thread: &RaylibThread,
- image: ImageData,
- ) -> crate::Result<Self> {
+impl Renderer {
+ pub fn new(handle: &mut RaylibHandle, thread: &RaylibThread) -> crate::Result<Self> {
+ // Load resources
+ let textures = Textures::new(handle, thread)?;
+ // Load render textures
let tex_size = MAP_SIZE as u32 * BASE_TILE_SIZE as u32;
- let tiles_tex_fg = handle.load_render_texture(thread, tex_size, tex_size)?;
- let tiles_tex_bg = handle.load_render_texture(thread, tex_size, tex_size)?;
+ let tilemap_fg = handle.load_render_texture(thread, tex_size, tex_size)?;
+ let tilemap_bg = handle.load_render_texture(thread, tex_size, tex_size)?;
Ok(Self {
- image,
- tiles_tex_fg: RefCell::new(tiles_tex_fg),
- tiles_tex_bg: RefCell::new(tiles_tex_bg),
- tiles_hash: RefCell::new(0),
+ textures,
+ tilemap_fg,
+ tilemap_bg,
+ tiles_hash: None,
+ tile_size: 0,
+ fps: 0,
+ width: 0,
+ height: 0,
})
}
-}
-/// The `Renderer` struct is the persistant renderer
-/// for the duration for the application.
-#[derive(Debug)]
-pub struct Renderer {
- /// Persistant render state
- state: Rc<State>,
-}
-impl Renderer {
- pub(crate) fn new(
+ pub fn draw_frame(
+ &mut self,
handle: &mut RaylibHandle,
- thread: &RaylibThread,
- image: ImageData,
- ) -> crate::Result<Self> {
- let state = Rc::new(State::new(handle, thread, image)?);
-
- Ok(Self { state })
+ t: &RaylibThread,
+ dungeon: &Dungeon,
+ ) {
+ self.update_per_frame_data(handle);
+ let mut r = handle.begin_drawing(t);
+ r.clear_background(Color::BLACK);
+ self.draw_dungeon(&mut r, t, dungeon);
+ self.draw_ui(&mut r, dungeon);
}
- /// Invokes the renderer for the current frame
- pub(crate) fn invoke<'a>(
- &'a mut self,
- handle: &'a mut RaylibHandle,
- thread: &'a RaylibThread,
- ) -> FrameRendererImpl<'a> {
- let info = FrameInfo::new(handle);
- let state = Rc::clone(&self.state);
- FrameRenderer {
- handle: handle.begin_drawing(thread),
- thread,
- info,
- state,
- }
- }
-}
-
-pub type FrameRendererImpl<'a> = FrameRenderer<'a, RaylibDrawHandle<'a>>;
-
-macro_rules! tex_renderer {
- ($fr:expr, $tex:expr) => {
- FrameRenderer {
- handle: $fr.handle.begin_texture_mode($fr.thread, $tex),
- thread: &$fr.thread,
- info: $fr.info,
- state: Rc::clone(&$fr.state),
- }
- };
-}
-
-pub struct FrameRenderer<'a, T>
-where
- T: RaylibDraw,
-{
- /// The current draw handle for raylib
- handle: T,
- /// The raylib thread
- thread: &'a RaylibThread,
- /// Non drawing information for this current frame
- info: FrameInfo,
- /// Persistant render state
- state: Rc<State>,
-}
-impl<'a, T> FrameRenderer<'a, T>
-where
- T: RaylibDraw + RaylibMode2DExt + RaylibTextureModeExt,
-{
- /// Draws an entire frame
- pub fn draw_frame(&mut self, dungeon: &Dungeon) {
- self.clear(Color::BLACK);
- self.draw_dungeon(dungeon);
- self.draw_ui(dungeon);
+ fn update_per_frame_data(&mut self, handle: &RaylibHandle) {
+ // Get last known fps
+ self.fps = handle.get_fps();
+ // Get size (in pixels) to draw each tile
+ self.width = handle.get_render_width();
+ self.height = handle.get_render_height();
+ self.tile_size = {
+ let size = self.width.min(self.height);
+ let dist = VIEW_DISTANCE * 2 + 1;
+ let pixels = size.div(dist).max(BASE_TILE_SIZE);
+ 1 << (i32::BITS - pixels.leading_zeros())
+ };
}
- /// Draws the dungeon, (tiles and entities)
- pub fn draw_dungeon(&mut self, dungeon: &Dungeon) {
- self.update_tilemaps(&dungeon.floor);
- let camera = dungeon.camera();
- let mut renderer = self.camera_renderer(camera);
- renderer.draw_tiles_bg();
- renderer.draw_entities(dungeon);
- renderer.draw_tiles_fg();
- }
-}
-impl<'a, T> FrameRenderer<'a, T>
-where
- T: RaylibDraw + RaylibMode2DExt,
-{
- /// Returns a raylib camera for the given position
- #[must_use]
- fn camera_renderer<'b>(
- &'b mut self,
- cpos: FPos,
- ) -> FrameRenderer<'b, RaylibMode2D<'b, T>> {
- let width = self.info.width;
- let height = self.info.height;
- let camera = Camera2D {
+ fn render_camera(&self, dungeon: &Dungeon) -> Camera2D {
+ let cpos = dungeon.camera();
+ let width = self.width;
+ let height = self.height;
+ Camera2D {
target: Vector2::from(cpos.xy())
- .scale_by(self.info.tile_size as f32)
+ .scale_by(self.tile_size as f32)
.max(Vector2::new(width as f32 / 2.0, height as f32 / 2.0))
.min(Vector2::new(
- (MAP_SIZE as i32 * self.info.tile_size) as f32 - (width as f32 / 2.0),
- (MAP_SIZE as i32 * self.info.tile_size) as f32 - (height as f32 / 2.0),
+ (MAP_SIZE as i32 * self.tile_size) as f32 - (width as f32 / 2.0),
+ (MAP_SIZE as i32 * self.tile_size) as f32 - (height as f32 / 2.0),
)),
offset: Vector2::new(width as f32 / 2.0, height as f32 / 2.0),
rotation: 0.0,
zoom: 1.0,
- };
- let handle = self.handle.begin_mode2D(camera);
- FrameRenderer {
- handle,
- thread: self.thread,
- info: self.info,
- state: Rc::clone(&self.state),
}
}
-}
-impl<'a, T> FrameRenderer<'a, T>
-where
- T: RaylibDraw + RaylibTextureModeExt,
-{
+
+ fn draw_dungeon<R>(&mut self, r: &mut R, t: &RaylibThread, dungeon: &Dungeon)
+ where
+ R: RaylibDraw + RaylibMode2DExt + RaylibTextureModeExt,
+ {
+ self.update_tilemaps(r, t, &dungeon.floor);
+ let camera = self.render_camera(dungeon);
+ let mut rc = r.begin_mode2D(camera);
+ self.draw_tiles_bg(&mut rc);
+ self.draw_entities(&mut rc, dungeon);
+ self.draw_tiles_fg(&mut rc);
+ }
+
/// Updates all tilemaps
- fn update_tilemaps(&mut self, floor: &Floor) {
- let hash = floor.hash();
- let old_hash = std::mem::replace(&mut *self.state.tiles_hash.borrow_mut(), hash);
- if old_hash == hash {
+ fn update_tilemaps<R>(&mut self, r: &mut R, t: &RaylibThread, floor: &Floor)
+ where
+ R: RaylibDraw + RaylibTextureModeExt,
+ {
+ let current_hash = floor.hash();
+ if let Some(old_hash) = self.tiles_hash
+ && old_hash == current_hash
+ {
// Textures are up to date
return;
- }
+ };
+ self.tiles_hash = Some(current_hash);
- self.update_fg_tilemap(floor);
- self.update_bg_tilemap(floor);
+ self.update_fg_tilemap(r, t, floor);
+ self.update_bg_tilemap(r, t, floor);
}
/// Draws the foregound tile map
- fn update_fg_tilemap(&mut self, floor: &Floor) {
+ fn update_fg_tilemap<R>(&mut self, r: &mut R, t: &RaylibThread, floor: &Floor)
+ where
+ R: RaylibDraw + RaylibTextureModeExt,
+ {
let size = BASE_TILE_SIZE as f32;
- let tex = &mut self.state.tiles_tex_fg.borrow_mut();
- let mut renderer = tex_renderer!(self, tex);
- renderer.clear(Color::BLANK);
+ let mut rt = r.begin_texture_mode(t, &mut self.tilemap_fg);
+ rt.clear_background(Color::BLANK);
for pos in Pos::values() {
let (fx, fy) = FPos::from(pos).xy();
@@ -246,86 +217,138 @@ where
};
// draw base wall top texture
- renderer.draw_atlas(AtlasTexture::WallBase, xs, ys, size, 0.0);
+ rt.draw_atlas(&self.textures.atlas, ATLAS_WALL_TOP, xs, ys, size, 0.0);
// draw top wall borders
let is_wall =
|dir| pos.step(dir).map_or(Tile::Wall, |p| floor.get(p)).is_wall();
if !is_wall(Direction::North) {
- renderer.draw_atlas(AtlasTexture::WallEdgeNorth, xs, ys, size, 0.0);
+ rt.draw_atlas(&self.textures.atlas, ATLAS_WALL_EDGE, xs, ys, size, 0.0);
}
if !is_wall(Direction::East) {
- renderer.draw_atlas(AtlasTexture::WallEdgeEast, xs, ys, size, 0.0);
+ rt.draw_atlas(&self.textures.atlas, ATLAS_WALL_EDGE, xs, ys, size, 90.0);
}
if !is_wall(Direction::South) {
- renderer.draw_atlas(AtlasTexture::WallEdgeSouth, xs, ys, size, 0.0);
+ rt.draw_atlas(&self.textures.atlas, ATLAS_WALL_EDGE, xs, ys, size, 180.0);
}
if !is_wall(Direction::West) {
- renderer.draw_atlas(AtlasTexture::WallEdgeWest, xs, ys, size, 0.0);
+ rt.draw_atlas(&self.textures.atlas, ATLAS_WALL_EDGE, xs, ys, size, 270.0);
}
}
}
/// Draws the foregound tile map
- fn update_bg_tilemap(&mut self, floor: &Floor) {
+ fn update_bg_tilemap<R>(&mut self, r: &mut R, t: &RaylibThread, floor: &Floor)
+ where
+ R: RaylibDraw + RaylibTextureModeExt,
+ {
let size = BASE_TILE_SIZE as f32;
- let tex = &mut self.state.tiles_tex_bg.borrow_mut();
- let mut renderer = tex_renderer!(self, tex);
- renderer.clear(Color::BLACK);
+ let mut rt = r.begin_texture_mode(t, &mut self.tilemap_bg);
+ rt.clear_background(Color::BLACK);
for pos in Pos::values() {
let (x, y) = pos.xy();
let tile = floor.get(pos);
- let tex = match tile {
- Tile::Wall => AtlasTexture::Wall,
- Tile::Air if (x + y) % 2 == 0 => AtlasTexture::FloorFull,
- Tile::Air if (x + y) % 2 == 1 => AtlasTexture::FloorEmpty,
- _ => AtlasTexture::Error,
+ let idx = match tile {
+ Tile::Wall => ATLAS_WALL_SIDE,
+ Tile::Air if (x + y) % 2 == 0 => ATLAS_FLOOR_FULL,
+ Tile::Air if (x + y) % 2 == 1 => ATLAS_FLOOR_EMPTY,
+ _ => ATLAS_ERROR,
};
- renderer.draw_atlas(tex, x as f32 * size, y as f32 * size, size, 0.0);
+ rt.draw_atlas(
+ &self.textures.atlas,
+ idx,
+ x as f32 * size,
+ y as f32 * size,
+ size,
+ 0.0,
+ );
}
}
-}
-impl<'a, T> FrameRenderer<'a, T>
-where
- T: RaylibDraw,
-{
- /// Clear the screen
- pub fn clear(&mut self, color: Color) {
- self.handle.clear_background(color);
+
+ /// Draw dungeon tiles (foreground layer)
+ fn draw_tiles_fg<R>(&mut self, r: &mut R)
+ where
+ R: RaylibDraw,
+ {
+ let size = self.tile_size as f32;
+ let offset = WALL_HEIGHT as f32;
+ r.draw_tilemap(&self.tilemap_fg, size, offset);
+ }
+
+ /// Draw dungeon tiles (background layer)
+ fn draw_tiles_bg<R>(&mut self, r: &mut R)
+ where
+ R: RaylibDraw,
+ {
+ let size = self.tile_size as f32;
+ let offset = 0.0;
+ r.draw_tilemap(&self.tilemap_bg, size, offset);
+ }
+
+ /// Draws the entities on the map
+ fn draw_entities<R>(&mut self, r: &mut R, dungeon: &Dungeon)
+ where
+ R: RaylibDraw,
+ {
+ self.draw_entity(r, &dungeon.player.entity);
+ for enemy in &dungeon.enemies {
+ self.draw_entity(r, &enemy.entity);
+ }
+ }
+
+ /// Draws an entity
+ fn draw_entity<R>(&self, r: &mut R, entity: &Entity)
+ where
+ R: RaylibDraw,
+ {
+ let size = self.tile_size as f32;
+ let x = entity.fpos.x();
+ let y = entity.fpos.y();
+ let tex = match entity.kind {
+ EntityKind::Player => ATLAS_PLAYER,
+ _ => ATLAS_ERROR,
+ };
+ r.draw_atlas(&self.textures.atlas, tex, x * size, y * size, size, 0.0);
}
/// Draws player HP, inventory, and floor number
- pub fn draw_ui(&mut self, dungeon: &Dungeon) {
+ fn draw_ui<R>(&self, r: &mut R, dungeon: &Dungeon)
+ where
+ R: RaylibDraw,
+ {
// Draw core ui components
- self.draw_health(&dungeon.player);
- self.draw_inventory(&dungeon.player);
+ self.draw_health(r, &dungeon.player);
+ self.draw_inventory(r, &dungeon.player);
// Draw debug info
#[cfg(feature = "debug")]
- self.draw_debug_ui(dungeon);
+ self.draw_debug_ui(r);
}
- fn draw_health(&mut self, player: &Player) {
+ fn draw_health<R>(&self, r: &mut R, player: &Player)
+ where
+ R: RaylibDraw,
+ {
let health = player.entity.health.unwrap_or(0);
let hearts = PLAYER_FULL_HEALTH.div_ceil(2);
let mut full_hearts = health / 2;
let mut half_hearts = health % 2;
let mut empty_hearts = hearts.saturating_sub(full_hearts + half_hearts);
- let size = self.info.tile_size.div(2).max(BASE_TILE_SIZE);
+ let size = self.tile_size.div(2).max(BASE_TILE_SIZE);
let y = BASE_TILE_SIZE;
let mut x = BASE_TILE_SIZE;
loop {
let tex = if full_hearts > 0 {
full_hearts -= 1;
- &self.state.image.heart_full
+ &self.textures.heart_full
} else if half_hearts > 0 {
half_hearts -= 1;
- &self.state.image.heart_half
+ &self.textures.heart_half
} else if empty_hearts > 0 {
empty_hearts -= 1;
- &self.state.image.heart_empty
+ &self.textures.heart_empty
} else {
break;
};
@@ -336,7 +359,7 @@ where
width: size as f32,
height: size as f32,
};
- self.handle.draw_texture_pro(
+ r.draw_texture_pro(
tex,
FULL_SOURCE_REC,
dest_rec,
@@ -348,15 +371,19 @@ where
}
}
- fn draw_inventory(&mut self, player: &Player) {
+ fn draw_inventory<R>(&self, r: &mut R, player: &Player)
+ where
+ R: RaylibDraw,
+ {
let len = i32::try_from(player.inventory.len()).unwrap_or(0);
// size of the inv blocks
- let size = self.info.tile_size.div(2).max(BASE_TILE_SIZE);
+ let size = self.tile_size.div(2).max(BASE_TILE_SIZE);
+ let half_size = size as f32 / 2.0;
// position of the inv blocks
- let y = self.info.height - size;
- let mut x = self.info.width / 2 - (size * len / 2);
+ let y = self.height - size;
+ let mut x = self.width / 2 - (size * len / 2);
// size of font for number index
let font_size = size / 3;
@@ -369,14 +396,16 @@ where
width: size as f32,
height: size as f32,
};
- self.draw_ui_atlas(
- AtlasTexture::InvBottomLayer,
- x as f32,
- y as f32,
+ r.draw_atlas(
+ &self.textures.atlas,
+ ATLAS_INV_BOTTOM_LAYER,
+ x as f32 + half_size,
+ y as f32 + half_size,
size as f32,
+ 0.0,
);
- let tex = self.state.image.get_item_texture(item);
- self.handle.draw_texture_pro(
+ let tex = self.textures.item_texture(item);
+ r.draw_texture_pro(
tex,
FULL_SOURCE_REC,
dest_rec,
@@ -384,8 +413,15 @@ where
0.0,
Color::WHITE,
);
- self.draw_ui_atlas(AtlasTexture::InvTopLayer, x as f32, y as f32, size as f32);
- self.handle.draw_text(
+ r.draw_atlas(
+ &self.textures.atlas,
+ ATLAS_INV_TOP_LAYER,
+ x as f32 + half_size,
+ y as f32 + half_size,
+ size as f32,
+ 0.0,
+ );
+ r.draw_text(
&format!("{}", idx + 1),
x + font_offset + font_offset / 5,
y + font_offset,
@@ -398,63 +434,54 @@ where
/// Draws debug information ontop of other UI elements
/// Called by default if `debug` featue is enabled
- pub fn draw_debug_ui(&mut self, _dungeon: &Dungeon) {
- self.draw_fps();
+ fn draw_debug_ui<R>(&self, r: &mut R)
+ where
+ R: RaylibDraw,
+ {
+ self.draw_fps(r);
}
/// Draw FPS counter (debug)
- fn draw_fps(&mut self) {
- let fps_str = format!("{}", self.info.fps);
- self.handle.draw_text(&fps_str, 10, 10, 30, Color::YELLOW);
- }
-
- /// Draws the entities on the map
- fn draw_entities(&mut self, dungeon: &Dungeon) {
- self.draw_entity(&dungeon.player.entity);
- for enemy in &dungeon.enemies {
- self.draw_entity(&enemy.entity);
- }
- }
-
- /// Draws an entity
- fn draw_entity(&mut self, entity: &Entity) {
- let size = self.info.tile_size as f32;
- let x = entity.fpos.x();
- let y = entity.fpos.y();
- let tex = match entity.kind {
- EntityKind::Player => AtlasTexture::Player,
- _ => AtlasTexture::Error,
- };
- self.draw_atlas(tex, x * size, y * size, size, 0.0);
- }
-
- /// Draw dungeon tiles (background layer)
- fn draw_tiles_bg(&mut self) {
- let tex = &*self.state.tiles_tex_bg.borrow();
- let size = self.info.tile_size as f32;
- let offset = 0.0;
- self.handle.draw_tilemap(tex, size, offset);
+ fn draw_fps<R>(&self, r: &mut R)
+ where
+ R: RaylibDraw,
+ {
+ let fps_str = format!("{}", self.fps);
+ r.draw_text(&fps_str, 10, 10, 30, Color::YELLOW);
}
+}
- /// Draw dungeon tiles (foreground layer)
- fn draw_tiles_fg(&mut self) {
- let tex = &*self.state.tiles_tex_fg.borrow();
- let size = self.info.tile_size as f32;
- let offset = WALL_HEIGHT as f32;
- self.handle.draw_tilemap(tex, size, offset);
+trait Vector2Ext {
+ fn min(self, other: Self) -> Self;
+ fn max(self, other: Self) -> Self;
+}
+impl Vector2Ext for Vector2 {
+ fn min(self, other: Self) -> Self {
+ Self::new(self.x.min(other.x), self.y.min(other.y))
}
- /// Draw an atlas texture index (with ui offset applied)
- fn draw_ui_atlas(&mut self, tex: AtlasTexture, x: f32, y: f32, size: f32) {
- let half_size = size / 2.0;
- self.draw_atlas(tex, x + half_size, y + half_size, size, 0.0);
+ fn max(self, other: Self) -> Self {
+ Self::new(self.x.max(other.x), self.y.max(other.y))
}
+}
+trait RaylibDrawExt
+where
+ Self: RaylibDraw,
+{
/// Draw an atlas texture index
- fn draw_atlas(&mut self, tex: AtlasTexture, x: f32, y: f32, size: f32, rotation: f32) {
+ fn draw_atlas(
+ &mut self,
+ tex: &Texture2D,
+ (ax, ay): (i32, i32),
+ x: f32,
+ y: f32,
+ size: f32,
+ rotation: f32,
+ ) {
let source_rec = Rectangle {
- x: (tex.x() * BASE_TILE_SIZE) as f32,
- y: (tex.y() * BASE_TILE_SIZE) as f32,
+ x: (ax * BASE_TILE_SIZE) as f32,
+ y: (ay * BASE_TILE_SIZE) as f32,
width: BASE_TILE_SIZE as f32,
height: BASE_TILE_SIZE as f32,
};
@@ -468,32 +495,9 @@ where
x: dest_rec.width / 2.0,
y: dest_rec.height / 2.0,
};
- self.handle.draw_texture_pro(
- &self.state.image.atlas,
- source_rec,
- dest_rec,
- origin,
- rotation,
- Color::WHITE,
- );
- }
-}
-
-trait Vector2Ext {
- fn min(self, other: Self) -> Self;
- fn max(self, other: Self) -> Self;
-}
-impl Vector2Ext for Vector2 {
- fn min(self, other: Self) -> Self {
- Self::new(self.x.min(other.x), self.y.min(other.y))
+ self.draw_texture_pro(tex, source_rec, dest_rec, origin, rotation, Color::WHITE);
}
- fn max(self, other: Self) -> Self {
- Self::new(self.x.max(other.x), self.y.max(other.y))
- }
-}
-
-trait RaylibDrawExt: RaylibDraw {
/// Draw dungeon tiles helper function
fn draw_tilemap(&mut self, tex: &RenderTexture2D, size: f32, offset: f32) {
let scale = size / BASE_TILE_SIZE as f32;
@@ -515,4 +519,4 @@ trait RaylibDrawExt: RaylibDraw {
self.draw_texture_pro(tex, source_rec, dest_rec, origin, 0.0, Color::WHITE);
}
}
-impl<T: RaylibDraw> RaylibDrawExt for T {}
+impl<T> RaylibDrawExt for T where T: RaylibDraw {}