diff --git a/res/tiles.png b/res/tiles.png index 114cafa..21d6e73 100644 Binary files a/res/tiles.png and b/res/tiles.png differ diff --git a/src/game.rs b/src/game.rs index 8e18d3a..b9d93cf 100644 --- a/src/game.rs +++ b/src/game.rs @@ -1,6 +1,11 @@ -use sfml::graphics::{RenderTarget, RenderWindow, Sprite}; +use sfml::graphics::{Rect, RenderTarget, RenderWindow, Sprite, Transformable}; -use crate::{math::WorldPos, res::Res, world::World}; +use crate::{ + graphics::{ScreenPos, NATIVE_RESOLUTION}, + math::{WorldPos, WorldPosScalar, TILE_SIZE}, + res::Res, + world::{TilePos, World}, +}; pub struct GameState { camera_offset: WorldPos, @@ -8,7 +13,33 @@ pub struct GameState { } impl GameState { pub(crate) fn draw_world(&mut self, rw: &mut RenderWindow, res: &Res) { - rw.draw(&Sprite::with_texture(&res.tile_atlas)); + let mut s = Sprite::with_texture(&res.tile_atlas); + for_each_tile(self.camera_offset, |tp, sp| { + let tile = self.world.tile_at_mut(tp); + s.set_texture_rect(Rect::new(tile.id as i32 * 32, 0, 32, 32)); + s.set_position(sp.to_sf_vec()); + rw.draw(&s); + }); + } +} + +fn for_each_tile(camera_offset: WorldPos, mut f: impl FnMut(TilePos, ScreenPos)) { + for y in (camera_offset.y..camera_offset.y + NATIVE_RESOLUTION.h as WorldPosScalar).step_by(32) + { + for x in + (camera_offset.x..camera_offset.x + NATIVE_RESOLUTION.w as WorldPosScalar).step_by(32) + { + f( + TilePos { + x: x / 32, + y: y / 32, + }, + ScreenPos { + x: (x - camera_offset.x) as i16, + y: (y - camera_offset.y) as i16, + }, + ) + } } } diff --git a/src/graphics.rs b/src/graphics.rs index 88598d1..480b24a 100644 --- a/src/graphics.rs +++ b/src/graphics.rs @@ -1,12 +1,13 @@ use sfml::{ graphics::RenderWindow, + system::Vector2f, window::{ContextSettings, Style, VideoMode}, }; use sfml_xt::graphics::RenderWindowExt; -struct ScreenRes { - w: u16, - h: u16, +pub struct ScreenRes { + pub w: u16, + pub h: u16, } impl ScreenRes { @@ -19,7 +20,19 @@ impl ScreenRes { } } -const NATIVE_RESOLUTION: ScreenRes = ScreenRes { w: 640, h: 360 }; +// We assume this game won't be played above 32767*32767 resolution +pub struct ScreenPos { + pub x: i16, + pub y: i16, +} + +impl ScreenPos { + pub fn to_sf_vec(&self) -> Vector2f { + Vector2f::new(self.x.into(), self.y.into()) + } +} + +pub const NATIVE_RESOLUTION: ScreenRes = ScreenRes { w: 960, h: 540 }; pub fn make_window() -> RenderWindow { let mut rw = RenderWindow::new( diff --git a/src/math.rs b/src/math.rs index ba7a38b..58a8849 100644 --- a/src/math.rs +++ b/src/math.rs @@ -1,6 +1,20 @@ +use crate::world::TilePos; + pub type WorldPosScalar = i32; +#[derive(Clone, Copy)] pub struct WorldPos { pub x: WorldPosScalar, pub y: WorldPosScalar, } + +impl WorldPos { + pub fn tile_pos(&self) -> TilePos { + TilePos { + x: self.x / TILE_SIZE as i32, + y: self.y / TILE_SIZE as i32, + } + } +} + +pub const TILE_SIZE: u8 = 32; diff --git a/src/world.rs b/src/world.rs index c0b28f2..0601c7c 100644 --- a/src/world.rs +++ b/src/world.rs @@ -3,13 +3,14 @@ use rand::{thread_rng, Rng}; type ChunkPosScalar = i16; -#[derive(Hash)] +#[derive(Hash, PartialEq, Eq)] struct ChunkPos { x: ChunkPosScalar, y: ChunkPosScalar, } pub struct World { + /// The currently loaded chunks chunks: FnvHashMap, } @@ -21,6 +22,48 @@ impl Default for World { } } +impl World { + /// Get mutable access to the tile at `pos`. + /// + /// Loads or generates the containing chunk if necessary. + pub fn tile_at_mut(&mut self, pos: TilePos) -> &mut Tile { + let (chk, local) = pos.to_chunk_and_local(); + let chk = self.chunks.entry(chk).or_insert_with(Chunk::new_rand); + chk.at_mut(local) + } +} + +pub struct TilePos { + pub x: TilePosScalar, + pub y: TilePosScalar, +} + +pub struct ChunkLocalTilePos { + pub x: ChunkLocalTilePosScalar, + pub y: ChunkLocalTilePosScalar, +} + +type ChunkLocalTilePosScalar = i16; + +impl TilePos { + fn to_chunk_and_local(&self) -> (ChunkPos, ChunkLocalTilePos) { + // 0,0 is chunk (0, 0) + // -1, -1 is chunk (-1, -1) + let chk = ChunkPos { + x: (self.x / CHUNK_EXTENT as i32) as i16, + y: (self.y / CHUNK_EXTENT as i32) as i16, + }; + let local = ChunkLocalTilePos { + x: (self.x % CHUNK_EXTENT as i32) as i16, + y: (self.y % CHUNK_EXTENT as i32) as i16, + }; + (chk, local) + } +} + +// Need to support at least 8 million tiles long +type TilePosScalar = i32; + const CHUNK_EXTENT: u16 = 256; const CHUNK_N_TILES: usize = CHUNK_EXTENT as usize * CHUNK_EXTENT as usize; @@ -35,15 +78,19 @@ impl Chunk { let mut rng = thread_rng(); let mut tiles = [Tile { id: 0 }; CHUNK_N_TILES]; for b in &mut tiles { - b.id = rng.gen(); + b.id = rng.gen_range(0..8); } Self { tiles } } + + fn at_mut(&mut self, local: ChunkLocalTilePos) -> &mut Tile { + &mut self.tiles[CHUNK_EXTENT as usize * local.y as usize + local.x as usize] + } } type TileId = u16; #[derive(Clone, Copy)] pub struct Tile { - id: TileId, + pub id: TileId, }