From 7bab06b2b6583bc50cd27b321eb735be720eb781 Mon Sep 17 00:00:00 2001 From: Thomas Forgione Date: Tue, 2 Apr 2019 20:27:05 +0200 Subject: [PATCH 1/4] Implement automatic tile creation --- build.rs | 2 +- src/app/editor.rs | 16 ++- src/engine/map/mod.rs | 275 ++++++++++++++++--------------------- src/engine/renderer/mod.rs | 3 +- src/engine/scene/mod.rs | 9 +- src/engine/texture/mod.rs | 77 ++++++++++- 6 files changed, 207 insertions(+), 175 deletions(-) diff --git a/build.rs b/build.rs index 879bf6c..aa1bcd5 100644 --- a/build.rs +++ b/build.rs @@ -140,7 +140,7 @@ fn main() { let image_197 = superpose(&image_4, &image_193); vec.push(&image_197); - let image_199 = rotate90(&image_127); + let image_199 = rotate270(&image_31); vec.push(&image_199); let image_209 = rotate90(&image_116); diff --git a/src/app/editor.rs b/src/app/editor.rs index 979a241..ef69983 100644 --- a/src/app/editor.rs +++ b/src/app/editor.rs @@ -6,7 +6,8 @@ use sfml::window::mouse::Button; use sfml::graphics::{RectangleShape, RenderTarget, Transformable, Color, Shape}; use rusty::engine::renderer::Renderer; -use rusty::engine::map::{GraphicTile, CollisionTile, Map}; +use rusty::engine::map::{CollisionTile, Map}; +use rusty::engine::texture::SPRITE_SIZE_I32; fn main() { let _ = App::new("Rusty Editor") @@ -17,7 +18,7 @@ fn main() { let mut running = true; - let mut map = Map::load("./assets/levels/level2.lvl").unwrap(); + let mut map = Map::new(50, 50);//;Map::load("./assets/levels/level2.lvl").unwrap(); loop { let top_panel_size = Vector2::new(renderer.window().size().x as f32, 50.0); @@ -39,8 +40,8 @@ fn main() { button: Button::Left, x, y, } => { if x as f32 >= left_panel_size.x && y as f32 >= top_panel_size.y { - let x = ((x - left_panel_size.x as i32) / 16) as usize; - let y = ((y - top_panel_size.y as i32) / 16) as usize; + let x = ((x - left_panel_size.x as i32) / SPRITE_SIZE_I32) as usize; + let y = ((y - top_panel_size.y as i32) / SPRITE_SIZE_I32) as usize; if let Some(tile) = map.tile(y, x) { if tile == CollisionTile::empty() { @@ -74,7 +75,7 @@ fn main() { for i in 0 .. map.rows() { for j in 0 .. map.cols() { let tile = map.at(i, j); - if tile.graphic != GraphicTile::Hidden { + if tile.graphic.is_visible() { renderer.translate_and_draw(&tile, (left_panel_size.x, top_panel_size.y)); } } @@ -82,7 +83,10 @@ fn main() { // Draw the border of the map let mut border = RectangleShape::new(); - border.set_size(((map.cols() * 16) as f32, (map.rows() * 16) as f32)); + border.set_size((( + map.cols() * SPRITE_SIZE_I32 as usize) as f32, + (map.rows() * SPRITE_SIZE_I32 as usize) as f32 + )); border.set_position((left_panel_size.x, top_panel_size.y)); border.set_fill_color(&Color::rgba(0, 0, 0, 0)); border.set_outline_color(&Color::rgb(0, 0, 0)); diff --git a/src/engine/map/mod.rs b/src/engine/map/mod.rs index cffd8c3..379ca36 100644 --- a/src/engine/map/mod.rs +++ b/src/engine/map/mod.rs @@ -12,7 +12,7 @@ use sfml::system::Vector2; use crate::{Error, Result}; use crate::engine::math::{clamp, Matrix}; use crate::engine::renderer::Drawable; -use crate::engine::texture::Texture; +use crate::engine::texture::{Texture, byte_to_index, SPRITE_SIZE_F32, SPRITE_SIZE_I32}; use crate::engine::character::Damage; /// This enum represents if the collision happens on the X axis or the Y axis. @@ -86,172 +86,104 @@ impl CollisionTile { /// This struct represents a renderable tile linking to its part in the tileset texture. #[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub enum GraphicTile { - /// There is nothing to draw. - Hidden, - - /// Top left corner of a solid tile. - TopLeft, - - /// Top of a solid tile. - Top, - - /// Top right corner of a solid tile. - TopRight, - - /// Left of a solid tile. - Left, - - /// Center of a solid tile. - Center, - - /// Right of a solid tile. - Right, - - /// Bottom left corner of a solid tile. - BottomLeft, - - /// Bottom of a solid tile. - Bottom, - - /// Bottom right corner of a solid tile. - BottomRight, -} +pub struct GraphicTile(Option); impl GraphicTile { - /// Returns a graphic tile from a collision tile. - /// - /// This might be ugly for the moment. - pub fn from_collision_tile(collision: CollisionTile) -> GraphicTile { - if collision == CollisionTile::empty() { - GraphicTile::Hidden - } else { - GraphicTile::Center - } - } - - /// Checks if a graphic tile has a top border. - pub fn is_top(self) -> bool { - match self { - GraphicTile::TopLeft | GraphicTile::Top | GraphicTile::TopRight => true, - _ => false, - } - } - - /// Checks if a graphic tile has a left border. - pub fn is_left(self) -> bool { - match self { - GraphicTile::TopLeft | GraphicTile::Left | GraphicTile::BottomLeft => true, - _ => false, - } - } - - /// Checks if a graphic tile has a right border. - pub fn is_right(self) -> bool { - match self { - GraphicTile::TopRight | GraphicTile::Right | GraphicTile::BottomRight => true, - _ => false, - } - } - - /// Checks if a graphic tile has a bottom border. - pub fn is_bottom(self) -> bool { - match self { - GraphicTile::BottomLeft | GraphicTile::Bottom | GraphicTile::BottomRight => true, - _ => false, - } - } - - /// Creates a vec containing all the non hidden graphic tiles. - pub fn all() -> Vec { - vec![ - GraphicTile::TopLeft, - GraphicTile::Top, - GraphicTile::TopRight, - GraphicTile::Left, - GraphicTile::Center, - GraphicTile::Right, - GraphicTile::BottomLeft, - GraphicTile::Bottom, - GraphicTile::BottomRight, - ] - } /// Creates the correct graphic tile depending on the neighbours. /// /// A none will be considered solid. pub fn from_neighbour_options( + top_left: Option, top: Option, - left: Option, + top_right: Option, right: Option, + bottom_right: Option, bottom: Option, + bottom_left: Option, + left: Option, ) -> GraphicTile { GraphicTile::from_neighbours( + top_left.unwrap_or_else(CollisionTile::full), top.unwrap_or_else(CollisionTile::full), - left.unwrap_or_else(CollisionTile::full), + top_right.unwrap_or_else(CollisionTile::full), right.unwrap_or_else(CollisionTile::full), + bottom_right.unwrap_or_else(CollisionTile::full), bottom.unwrap_or_else(CollisionTile::full), + bottom_left.unwrap_or_else(CollisionTile::full), + left.unwrap_or_else(CollisionTile::full), ) } /// Creates the correct graphic tile depending on the neighbours. pub fn from_neighbours( + top_left: CollisionTile, top: CollisionTile, - left: CollisionTile, + top_right: CollisionTile, right: CollisionTile, + bottom_right: CollisionTile, bottom: CollisionTile, + bottom_left: CollisionTile, + left: CollisionTile, ) -> GraphicTile { - let mut all = GraphicTile::all() - .into_iter() - .map(|x| (x, true)) - .collect::>(); - for (ref mut tile, ref mut possible) in &mut all { - if tile.is_top() == (top == CollisionTile::full()) { - *possible = false; - } + let mut byte = 0; - if tile.is_left() == (left == CollisionTile::full()) { - *possible = false; - } - - if tile.is_right() == (right == CollisionTile::full()) { - *possible = false; - } - - if tile.is_bottom() == (bottom == CollisionTile::full()) { - *possible = false; - } + if top_left != CollisionTile::full() || + left != CollisionTile::full() || + top != CollisionTile::full() { + byte += 1; } - for (tile, possible) in all { - if possible { - return tile; - } + if top != CollisionTile::full() { + byte += 2; } - GraphicTile::Center + if top_right != CollisionTile::full() || + top != CollisionTile::full() || + right != CollisionTile::full() { + byte += 4; + } + + if right != CollisionTile::full() { + byte += 8; + } + + if bottom_right != CollisionTile::full() || + bottom != CollisionTile::full() || + right != CollisionTile::full() { + byte += 16; + } + + if bottom != CollisionTile::full() { + byte += 32; + } + + if bottom_left != CollisionTile::full() || + bottom != CollisionTile::full() || + left != CollisionTile::full() { + byte += 64; + } + + if left != CollisionTile::full() { + byte += 128; + } + + GraphicTile(Some(byte_to_index(byte))) } /// Returns the offset to the corresponding graphic tile in the texture. pub fn offset(self) -> (i32, i32) { - let vertical = match self { - GraphicTile::TopLeft | GraphicTile::Top | GraphicTile::TopRight => 0, - GraphicTile::Left | GraphicTile::Center | GraphicTile::Right => 16, - GraphicTile::BottomLeft | GraphicTile::Bottom | GraphicTile::BottomRight => 32, - _ => 0, - }; + match self.0 { + None => (0, 0), + Some(v) => (32 * v, 0) + } + } - let horizontal = match self { - GraphicTile::TopLeft | GraphicTile::Left | GraphicTile::BottomLeft => 0, - GraphicTile::Top | GraphicTile::Center | GraphicTile::Bottom => 16, - GraphicTile::TopRight | GraphicTile::Right | GraphicTile::BottomRight => 32, - _ => 0, - }; - - // (horizontal, vertical) - (vertical, horizontal) + /// Returns true if the tile is visible. + pub fn is_visible(&self) -> bool { + self.0.is_some() } } @@ -274,7 +206,7 @@ impl Drawable for PositionedTile { fn texture_rect(&self) -> IntRect { let offset = self.graphic.offset(); - IntRect::new(offset.0, offset.1, 16, 16) + IntRect::new(offset.0, offset.1, SPRITE_SIZE_I32, SPRITE_SIZE_I32) } fn position(&self) -> Vector2 { @@ -336,7 +268,7 @@ impl Map { let cols = tiles.cols(); let mut matrix = - Matrix::from_size(rows, cols, (CollisionTile::empty(), GraphicTile::Hidden)); + Matrix::from_size(rows, cols, (CollisionTile::empty(), GraphicTile(None))); for i in 0..rows { for j in 0..cols { @@ -346,13 +278,17 @@ impl Map { let (i, j) = (i as isize, j as isize); GraphicTile::from_neighbour_options( - tiles.get(((i) as usize, (j - 1) as usize)).cloned(), - tiles.get(((i - 1) as usize, (j) as usize)).cloned(), - tiles.get(((i + 1) as usize, (j) as usize)).cloned(), - tiles.get(((i) as usize, (j + 1) as usize)).cloned(), + tiles.get(((i - 1) as usize, (j - 1) as usize)).cloned(), + tiles.get(((i ) as usize, (j - 1) as usize)).cloned(), + tiles.get(((i + 1) as usize, (j - 1) as usize)).cloned(), + tiles.get(((i + 1) as usize, (j ) as usize)).cloned(), + tiles.get(((i + 1) as usize, (j + 1) as usize)).cloned(), + tiles.get(((i ) as usize, (j + 1) as usize)).cloned(), + tiles.get(((i - 1) as usize, (j + 1) as usize)).cloned(), + tiles.get(((i - 1) as usize, (j ) as usize)).cloned(), ) } else { - GraphicTile::Hidden + GraphicTile(None) }; matrix[(i, j)] = (tiles[(i, j)], graphic); @@ -365,6 +301,24 @@ impl Map { } } + /// Returns the graphic tile corresponding to the collision tiles. + pub fn graphic_tile(&self, i: usize, j: usize) -> GraphicTile { + if self.tiles[(i, j)].0 == CollisionTile::full() { + GraphicTile::from_neighbour_options( + self.tiles.get(((i - 1) as usize, (j - 1) as usize)).map(|x| x.0), + self.tiles.get(((i - 1) as usize, (j ) as usize)).map(|x| x.0), + self.tiles.get(((i - 1) as usize, (j + 1) as usize)).map(|x| x.0), + self.tiles.get(((i ) as usize, (j + 1) as usize)).map(|x| x.0), + self.tiles.get(((i + 1) as usize, (j + 1) as usize)).map(|x| x.0), + self.tiles.get(((i + 1) as usize, (j ) as usize)).map(|x| x.0), + self.tiles.get(((i + 1) as usize, (j - 1) as usize)).map(|x| x.0), + self.tiles.get(((i ) as usize, (j - 1) as usize)).map(|x| x.0), + ) + } else { + GraphicTile(None) + } + } + /// Returns a tile of the map. pub fn tile(&self, i: usize, j: usize) -> Option { self.tiles.get((i, j)).map(|x| x.0) @@ -372,7 +326,14 @@ impl Map { /// Changes a tile of the map. pub fn set_tile(&mut self, i: usize, j: usize, tile: CollisionTile) { - self.tiles[(i, j)] = (tile, GraphicTile::from_collision_tile(tile)); + self.tiles[(i, j)] = (tile, GraphicTile(None)); + for i in (i - 1) ..= (i + 1) { + for j in (j - 1) ..= (j + 1) { + if self.tiles.get((i, j)).is_some() { + self.tiles[(i, j)].1 = self.graphic_tile(i, j); + } + } + } } /// Finds a possible entrance. @@ -390,7 +351,7 @@ impl Map { PositionedTile { collision: self.tiles[(row, col)].0, graphic: self.tiles[(row, col)].1, - position: (col as f32 * 16.0, row as f32 * 16.0), + position: (col as f32 * SPRITE_SIZE_F32, row as f32 * SPRITE_SIZE_F32), } } @@ -418,11 +379,11 @@ impl Map { let cols = self.tiles.cols() - 1; let rows = self.tiles.rows() - 1; - let min_col = clamp(new.left / 16.0, 0.0, cols as f32) as usize; - let min_row = clamp(new.top / 16.0, 0.0, rows as f32) as usize; + let min_col = clamp(new.left / SPRITE_SIZE_F32, 0.0, cols as f32) as usize; + let min_row = clamp(new.top / SPRITE_SIZE_F32, 0.0, rows as f32) as usize; - let max_col = clamp((new.left + new.width) / 16.0, 0.0, cols as f32) as usize; - let max_row = clamp((new.top + new.height) / 16.0, 0.0, rows as f32) as usize; + let max_col = clamp((new.left + new.width) / SPRITE_SIZE_F32, 0.0, cols as f32) as usize; + let max_row = clamp((new.top + new.height) / SPRITE_SIZE_F32, 0.0, rows as f32) as usize; let mut collision_x = false; let mut collision_y = false; @@ -430,10 +391,10 @@ impl Map { for col in min_col..=max_col { for row in min_row..=max_row { - let tile_left = col as f32 * 16.0; - let tile_top = row as f32 * 16.0; + let tile_left = col as f32 * SPRITE_SIZE_F32; + let tile_top = row as f32 * SPRITE_SIZE_F32; - let tile = FloatRect::new(tile_left, tile_top, 16.0, 16.0); + let tile = FloatRect::new(tile_left, tile_top, SPRITE_SIZE_F32, SPRITE_SIZE_F32); if !overlap(new, tile) { continue; @@ -467,11 +428,11 @@ impl Map { // Collisions between left and left wall if self.tiles[(row, col)].0.from_right - && old.left >= tile_left + 16.0 - && new.left <= tile_left + 16.0 + && old.left >= tile_left + SPRITE_SIZE_F32 + && new.left <= tile_left + SPRITE_SIZE_F32 { collision_x = true; - new.left = tile_left + 16.0; + new.left = tile_left + SPRITE_SIZE_F32; } if !overlap(new, tile) { @@ -480,11 +441,11 @@ impl Map { // Collisions between head and roof if self.tiles[(row, col)].0.from_bottom - && old.top >= tile_top + 16.0 - && new.top <= tile_top + 16.0 + && old.top >= tile_top + SPRITE_SIZE_F32 + && new.top <= tile_top + SPRITE_SIZE_F32 { collision_y = true; - new.top = tile_top + 16.0; + new.top = tile_top + SPRITE_SIZE_F32; } } } @@ -496,14 +457,14 @@ impl Map { } // Collision between the player and right border of the level - if new.left > cols as f32 * 16.0 { - new.left = cols as f32 * 16.0; + if new.left > cols as f32 * SPRITE_SIZE_F32 { + new.left = cols as f32 * SPRITE_SIZE_F32; collision_x = true; } // Collision between the player and the void - if new.top > self.tiles.rows() as f32 * 16.0 { - new.top = self.tiles.rows() as f32 * 16.0; + if new.top > self.tiles.rows() as f32 * SPRITE_SIZE_F32 { + new.top = self.tiles.rows() as f32 * SPRITE_SIZE_F32; collision_y = true; damage = Damage::Death; } diff --git a/src/engine/renderer/mod.rs b/src/engine/renderer/mod.rs index f36f0d9..71c1696 100644 --- a/src/engine/renderer/mod.rs +++ b/src/engine/renderer/mod.rs @@ -6,7 +6,6 @@ use sfml::window::{Event, Style}; use sfml::system::Vector2; -use crate::engine::map::GraphicTile; use crate::engine::scene::Scene; use crate::engine::texture::{Texture, TextureManager}; @@ -101,7 +100,7 @@ impl Renderer { for i in 0..rows { for j in 0..cols { let tile = map.at(i, j); - if tile.graphic != GraphicTile::Hidden { + if tile.graphic.is_visible() { self.draw(&tile); } } diff --git a/src/engine/scene/mod.rs b/src/engine/scene/mod.rs index 59ee924..1055faa 100644 --- a/src/engine/scene/mod.rs +++ b/src/engine/scene/mod.rs @@ -4,6 +4,7 @@ use sfml::graphics::View; use sfml::system::Vector2; use sfml::window::Event; +use crate::engine::texture::SPRITE_SIZE_F32; use crate::engine::character::Character; use crate::engine::map::Map; @@ -38,8 +39,8 @@ impl Scene { /// Adds a character to the scene. pub fn add(&mut self, character: Character) { let mut character = character; - character.position.x = self.map.entrance().1 as f32 * 16.0; - character.position.y = self.map.entrance().0 as f32 * 16.0; + character.position.x = self.map.entrance().1 as f32 * SPRITE_SIZE_F32; + character.position.y = self.map.entrance().0 as f32 * SPRITE_SIZE_F32; self.characters.push(character); } @@ -69,8 +70,8 @@ impl Scene { center.y = size.y / 2.0; } - let right_limit = self.map.cols() as f32 * 16.0; - let bottom_limit = self.map.rows() as f32 * 16.0; + let right_limit = self.map.cols() as f32 * SPRITE_SIZE_F32; + let bottom_limit = self.map.rows() as f32 * SPRITE_SIZE_F32; if center.x + size.x / 2.0 > right_limit { center.x = right_limit - size.x / 2.0; diff --git a/src/engine/texture/mod.rs b/src/engine/texture/mod.rs index 8b239ee..5486c8b 100644 --- a/src/engine/texture/mod.rs +++ b/src/engine/texture/mod.rs @@ -1,11 +1,74 @@ use sfml::graphics::{IntRect, Texture as SfTexture}; +/// The number of pixels of a sprite. +pub const SPRITE_SIZE_I32: i32 = 32; + +/// The number of pixels of a sprite. +pub const SPRITE_SIZE_F32: f32 = 32.0; + +/// Converts the byte with bits corresponding to the neighbours to the offset on the generated +/// tileset. +pub fn byte_to_index(byte: u8) -> i32 { + match byte { + 0 => 0, + 1 => 1, + 4 => 2, + 5 => 3, + 7 => 4, + 16 => 5, + 17 => 6, + 20 => 7, + 21 => 8, + 23 => 9, + 28 => 10, + 29 => 11, + 31 => 12, + 64 => 13, + 65 => 14, + 68 => 15, + 69 => 16, + 71 => 17, + 80 => 18, + 81 => 19, + 84 => 20, + 85 => 21, + 87 => 22, + 92 => 23, + 93 => 24, + 95 => 25, + 112 => 26, + 113 => 27, + 116 => 28, + 117 => 29, + 119 => 30, + 124 => 31, + 125 => 32, + 127 => 33, + 193 => 34, + 197 => 35, + 199 => 36, + 209 => 37, + 213 => 38, + 215 => 39, + 221 => 40, + 223 => 41, + 241 => 42, + 245 => 43, + 247 => 44, + 253 => 45, + 255 => 46, + _ => panic!("Incorrect byte {}", byte), + } +} + macro_rules! make_textures { ( $( $enum_name: ident, $texture_name: ident, $function_name: ident, - $texture_path: tt, ) + $texture_path: tt, + $width: expr, + $height: expr, ) *) => { /// Describes all the textures that will be used in this game. @@ -39,7 +102,7 @@ macro_rules! make_textures { /// Creates the texture. fn $function_name() -> SfTexture { let bytes = include_bytes!($texture_path).to_vec(); - TextureManager::make_texture_from_bytes(bytes) + TextureManager::make_texture_from_bytes(bytes, $width, $height) } )* @@ -61,16 +124,20 @@ make_textures!( mario, make_mario_texture, "../../../assets/textures/mario.png", + 256, + 256, Overworld, overworld, make_overworld_texture, - "../../../assets/textures/overworld.png", + "../../../assets/textures-generated/grass.png", + 32 * 47, + 32, ); impl TextureManager { /// Creates a textures from an array of bytes. - fn make_texture_from_bytes(bytes: Vec) -> SfTexture { - SfTexture::from_memory(&bytes, &IntRect::new(0, 0, 256, 256)) + fn make_texture_from_bytes(bytes: Vec, width: i32, height: i32) -> SfTexture { + SfTexture::from_memory(&bytes, &IntRect::new(0, 0, width, height)) .expect("Failed to create texture") } } From a66e596db0e6febb974de9cff23d144120a1eddd Mon Sep 17 00:00:00 2001 From: Thomas Forgione Date: Tue, 2 Apr 2019 21:59:14 +0200 Subject: [PATCH 2/4] Cleaning --- src/app/editor.rs | 4 +- src/engine/map/mod.rs | 236 +++++++++++++++++++++-------------------- src/engine/math/mod.rs | 21 +++- 3 files changed, 145 insertions(+), 116 deletions(-) diff --git a/src/app/editor.rs b/src/app/editor.rs index ef69983..0a942ce 100644 --- a/src/app/editor.rs +++ b/src/app/editor.rs @@ -43,8 +43,8 @@ fn main() { let x = ((x - left_panel_size.x as i32) / SPRITE_SIZE_I32) as usize; let y = ((y - top_panel_size.y as i32) / SPRITE_SIZE_I32) as usize; - if let Some(tile) = map.tile(y, x) { - if tile == CollisionTile::empty() { + if let Some(tile) = map.collision_tile(y, x) { + if tile.is_empty() { map.set_tile(y, x, CollisionTile::full()); } else { map.set_tile(y, x, CollisionTile::empty()); diff --git a/src/engine/map/mod.rs b/src/engine/map/mod.rs index 379ca36..7ff5c48 100644 --- a/src/engine/map/mod.rs +++ b/src/engine/map/mod.rs @@ -82,6 +82,16 @@ impl CollisionTile { from_bottom: true, } } + + /// Tests whether a collision tile is full or not. + pub fn is_full(self) -> bool { + self.from_top && self.from_left && self.from_right && self.from_bottom + } + + /// Tests whether a collision tile is empty or not. + pub fn is_empty(self) -> bool { + !self.from_top && !self.from_left && !self.from_right && !self.from_bottom + } } /// This struct represents a renderable tile linking to its part in the tileset texture. @@ -94,79 +104,53 @@ impl GraphicTile { /// Creates the correct graphic tile depending on the neighbours. /// /// A none will be considered solid. - pub fn from_neighbour_options( - top_left: Option, - top: Option, - top_right: Option, - right: Option, - bottom_right: Option, - bottom: Option, - bottom_left: Option, - left: Option, - ) -> GraphicTile { - GraphicTile::from_neighbours( - top_left.unwrap_or_else(CollisionTile::full), - top.unwrap_or_else(CollisionTile::full), - top_right.unwrap_or_else(CollisionTile::full), - right.unwrap_or_else(CollisionTile::full), - bottom_right.unwrap_or_else(CollisionTile::full), - bottom.unwrap_or_else(CollisionTile::full), - bottom_left.unwrap_or_else(CollisionTile::full), - left.unwrap_or_else(CollisionTile::full), - ) + pub fn from_neighbour_options(tiles: &[Option; 8]) -> GraphicTile { + GraphicTile::from_neighbours(&[ + tiles[0].unwrap_or_else(CollisionTile::full), + tiles[1].unwrap_or_else(CollisionTile::full), + tiles[2].unwrap_or_else(CollisionTile::full), + tiles[3].unwrap_or_else(CollisionTile::full), + tiles[4].unwrap_or_else(CollisionTile::full), + tiles[5].unwrap_or_else(CollisionTile::full), + tiles[6].unwrap_or_else(CollisionTile::full), + tiles[7].unwrap_or_else(CollisionTile::full), + ]) } /// Creates the correct graphic tile depending on the neighbours. - pub fn from_neighbours( - top_left: CollisionTile, - top: CollisionTile, - top_right: CollisionTile, - right: CollisionTile, - bottom_right: CollisionTile, - bottom: CollisionTile, - bottom_left: CollisionTile, - left: CollisionTile, - ) -> GraphicTile { + pub fn from_neighbours(tiles: &[CollisionTile; 8]) -> GraphicTile { let mut byte = 0; - if top_left != CollisionTile::full() || - left != CollisionTile::full() || - top != CollisionTile::full() { + if !tiles[7].is_full() || !tiles[0].is_full() || !tiles[1].is_full() { byte += 1; } - if top != CollisionTile::full() { + if !tiles[1].is_full() { byte += 2; } - if top_right != CollisionTile::full() || - top != CollisionTile::full() || - right != CollisionTile::full() { + if !tiles[1] .is_full() || !tiles[2].is_full() || !tiles[3].is_full() { byte += 4; } - if right != CollisionTile::full() { + if !tiles[3] .is_full() { byte += 8; } - if bottom_right != CollisionTile::full() || - bottom != CollisionTile::full() || - right != CollisionTile::full() { + if !tiles[3].is_full() || !tiles[4].is_full() || !tiles[5].is_full() { byte += 16; } - if bottom != CollisionTile::full() { + if !tiles[5].is_full() { byte += 32; } - if bottom_left != CollisionTile::full() || - bottom != CollisionTile::full() || - left != CollisionTile::full() { + if !tiles[5].is_full() || !tiles[6].is_full() || !tiles[7].is_full() { byte += 64; } - if left != CollisionTile::full() { + if !tiles[7] .is_full() { byte += 128; } @@ -214,14 +198,34 @@ impl Drawable for PositionedTile { } } -/// The map represents the tiles contained in a level. +/// The content of a map that needs to be saved. #[derive(Serialize, Deserialize)] +struct SaveMap { + /// The entrance point of the character in the map. + entrance: (usize, usize), + + /// The collision tiles contained in the level. + collision_tiles: Matrix, +} + +impl SaveMap { + /// Creates a map from the save map. + fn to_map(self) -> Map { + Map::from_entrance_and_collision_tiles(self.entrance, self.collision_tiles) + } +} + +/// The map represents the tiles contained in a level. +#[derive(Clone)] pub struct Map { /// The entrace point of the character in the map. entrance: (usize, usize), - /// The tiles contained in the level. - tiles: Matrix<(CollisionTile, GraphicTile)>, + /// The collision tiles contained in the level. + collision_tiles: Matrix, + + /// The graphic tiles contained in the level. + graphic_tiles: Matrix, } impl Map { @@ -237,21 +241,29 @@ impl Map { Map::from_collision_tiles(tiles) } + /// Creates a SaveMap from a map + fn save_map(&self) -> SaveMap { + SaveMap { + entrance: self.entrance, + collision_tiles: self.collision_tiles.clone(), + } + } + /// Encodes the map into a binary format. pub fn encode(&self) -> Result> { - serialize(&self).map_err(Error::Encoding) + serialize(&self.save_map()).map_err(Error::Encoding) } /// Decodes a map from bytes. pub fn decode(content: &[u8]) -> Result { - deserialize(content).map_err(Error::Decoding) + deserialize(content).map(SaveMap::to_map).map_err(Error::Decoding) } /// Saves the map to a file. pub fn save>(&self, path: P) -> Result<()> { let file = File::create(path.as_ref()).map_err(Error::Save)?; let mut writer = BufWriter::new(file); - serialize_into(&mut writer, &self).map_err(Error::Encoding)?; + serialize_into(&mut writer, &self.save_map()).map_err(Error::Encoding)?; Ok(()) } @@ -259,85 +271,83 @@ impl Map { pub fn load>(path: P) -> Result { let file = File::open(path.as_ref()).map_err(Error::Load)?; let mut reader = BufReader::new(file); - Ok(deserialize_from(&mut reader).map_err(Error::Decoding)?) + Ok(deserialize_from(&mut reader).map(SaveMap::to_map).map_err(Error::Decoding)?) } - /// Creates a map from its tiles. - pub fn from_collision_tiles(tiles: Matrix) -> Map { - let rows = tiles.rows(); - let cols = tiles.cols(); + /// Creates a map from its entrance and collision tiles. + pub fn from_entrance_and_collision_tiles(e: (usize, usize), t: Matrix) -> Map { + let rows = t.rows(); + let cols = t.cols(); - let mut matrix = - Matrix::from_size(rows, cols, (CollisionTile::empty(), GraphicTile(None))); + let graphic_tiles = Matrix::from_size(rows, cols, GraphicTile(None)); - for i in 0..rows { - for j in 0..cols { - let graphic = if tiles[(i, j)] == CollisionTile::full() { - // TODO This is uggly - // If there is an overflow, we should give None instead - let (i, j) = (i as isize, j as isize); + let mut map = Map { + entrance: e, + collision_tiles: t, + graphic_tiles, + }; - GraphicTile::from_neighbour_options( - tiles.get(((i - 1) as usize, (j - 1) as usize)).cloned(), - tiles.get(((i ) as usize, (j - 1) as usize)).cloned(), - tiles.get(((i + 1) as usize, (j - 1) as usize)).cloned(), - tiles.get(((i + 1) as usize, (j ) as usize)).cloned(), - tiles.get(((i + 1) as usize, (j + 1) as usize)).cloned(), - tiles.get(((i ) as usize, (j + 1) as usize)).cloned(), - tiles.get(((i - 1) as usize, (j + 1) as usize)).cloned(), - tiles.get(((i - 1) as usize, (j ) as usize)).cloned(), - ) - } else { - GraphicTile(None) - }; - - matrix[(i, j)] = (tiles[(i, j)], graphic); + for i in 0 .. rows { + for j in 0 .. cols { + map.graphic_tiles[(i, j)] = map.graphic_tile(i, j); } } - Map { - entrance: Map::find_entrance(&matrix), - tiles: matrix, - } + map + } + + /// Creates a map from its tiles. + pub fn from_collision_tiles(collision_tiles: Matrix) -> Map { + let entrance = Map::find_entrance(&collision_tiles); + Map::from_entrance_and_collision_tiles(entrance, collision_tiles) + } + + /// Creates the neighbours of a tile. + pub fn neighbours(&self, i: isize, j: isize) -> [Option; 8] { + [ + self.collision_tiles.get_isize(i - 1, j - 1).cloned(), + self.collision_tiles.get_isize(i - 1, j ).cloned(), + self.collision_tiles.get_isize(i - 1, j + 1).cloned(), + self.collision_tiles.get_isize(i , j + 1).cloned(), + self.collision_tiles.get_isize(i + 1, j + 1).cloned(), + self.collision_tiles.get_isize(i + 1, j ).cloned(), + self.collision_tiles.get_isize(i + 1, j - 1).cloned(), + self.collision_tiles.get_isize(i , j - 1).cloned(), + ] } /// Returns the graphic tile corresponding to the collision tiles. pub fn graphic_tile(&self, i: usize, j: usize) -> GraphicTile { - if self.tiles[(i, j)].0 == CollisionTile::full() { - GraphicTile::from_neighbour_options( - self.tiles.get(((i - 1) as usize, (j - 1) as usize)).map(|x| x.0), - self.tiles.get(((i - 1) as usize, (j ) as usize)).map(|x| x.0), - self.tiles.get(((i - 1) as usize, (j + 1) as usize)).map(|x| x.0), - self.tiles.get(((i ) as usize, (j + 1) as usize)).map(|x| x.0), - self.tiles.get(((i + 1) as usize, (j + 1) as usize)).map(|x| x.0), - self.tiles.get(((i + 1) as usize, (j ) as usize)).map(|x| x.0), - self.tiles.get(((i + 1) as usize, (j - 1) as usize)).map(|x| x.0), - self.tiles.get(((i ) as usize, (j - 1) as usize)).map(|x| x.0), - ) + if self.collision_tiles[(i, j)].is_full() { + GraphicTile::from_neighbour_options(&self.neighbours(i as isize, j as isize)) } else { GraphicTile(None) } } /// Returns a tile of the map. - pub fn tile(&self, i: usize, j: usize) -> Option { - self.tiles.get((i, j)).map(|x| x.0) + pub fn collision_tile(&self, i: usize, j: usize) -> Option { + self.collision_tiles.get((i, j)).cloned() } /// Changes a tile of the map. pub fn set_tile(&mut self, i: usize, j: usize, tile: CollisionTile) { - self.tiles[(i, j)] = (tile, GraphicTile(None)); + // Change the collision tile + self.collision_tiles[(i, j)] = tile; + + // Refresh the current graphic tile and their neighbours for i in (i - 1) ..= (i + 1) { for j in (j - 1) ..= (j + 1) { - if self.tiles.get((i, j)).is_some() { - self.tiles[(i, j)].1 = self.graphic_tile(i, j); + let new_tile = self.graphic_tile(i, j); + if let Some(tile) = self.graphic_tiles.get_mut((i, j)) { + *tile = new_tile; } } } } /// Finds a possible entrance. - pub fn find_entrance(tiles: &Matrix<(CollisionTile, GraphicTile)>) -> (usize, usize) { + pub fn find_entrance(tiles: &Matrix) -> (usize, usize) { (tiles.rows() - 5, 1) } @@ -349,20 +359,20 @@ impl Map { /// Returns an iterator to the positioned tiles. pub fn at(&self, row: usize, col: usize) -> PositionedTile { PositionedTile { - collision: self.tiles[(row, col)].0, - graphic: self.tiles[(row, col)].1, + collision: self.collision_tiles[(row, col)], + graphic: self.graphic_tiles[(row, col)], position: (col as f32 * SPRITE_SIZE_F32, row as f32 * SPRITE_SIZE_F32), } } /// Returns the number of rows of the map. pub fn rows(&self) -> usize { - self.tiles.rows() + self.collision_tiles.rows() } /// Returns the number of columns of the map. pub fn cols(&self) -> usize { - self.tiles.cols() + self.collision_tiles.cols() } /// Checks whether the bounding box collides with elements of the map. @@ -376,8 +386,8 @@ impl Map { let mut damage = Damage::None; - let cols = self.tiles.cols() - 1; - let rows = self.tiles.rows() - 1; + let cols = self.collision_tiles.cols() - 1; + let rows = self.collision_tiles.rows() - 1; let min_col = clamp(new.left / SPRITE_SIZE_F32, 0.0, cols as f32) as usize; let min_row = clamp(new.top / SPRITE_SIZE_F32, 0.0, rows as f32) as usize; @@ -401,7 +411,7 @@ impl Map { } // Collisions between feet and ground - if self.tiles[(row, col)].0.from_top + if self.collision_tiles[(row, col)].from_top && old.top + old.height <= tile_top && new.top + new.height >= tile_top { @@ -414,7 +424,7 @@ impl Map { } // Collisions between right and right wall - if self.tiles[(row, col)].0.from_left + if self.collision_tiles[(row, col)].from_left && old.left + old.width <= tile_left && new.left + new.width >= tile_left { @@ -427,7 +437,7 @@ impl Map { } // Collisions between left and left wall - if self.tiles[(row, col)].0.from_right + if self.collision_tiles[(row, col)].from_right && old.left >= tile_left + SPRITE_SIZE_F32 && new.left <= tile_left + SPRITE_SIZE_F32 { @@ -440,7 +450,7 @@ impl Map { } // Collisions between head and roof - if self.tiles[(row, col)].0.from_bottom + if self.collision_tiles[(row, col)].from_bottom && old.top >= tile_top + SPRITE_SIZE_F32 && new.top <= tile_top + SPRITE_SIZE_F32 { @@ -463,8 +473,8 @@ impl Map { } // Collision between the player and the void - if new.top > self.tiles.rows() as f32 * SPRITE_SIZE_F32 { - new.top = self.tiles.rows() as f32 * SPRITE_SIZE_F32; + if new.top > self.collision_tiles.rows() as f32 * SPRITE_SIZE_F32 { + new.top = self.collision_tiles.rows() as f32 * SPRITE_SIZE_F32; collision_y = true; damage = Damage::Death; } diff --git a/src/engine/math/mod.rs b/src/engine/math/mod.rs index 475db8b..837b375 100644 --- a/src/engine/math/mod.rs +++ b/src/engine/math/mod.rs @@ -27,7 +27,7 @@ pub fn duration_as_f32(duration: &Duration) -> f32 { } /// A generic matrix type, useful for levels. -#[derive(Serialize, Deserialize)] +#[derive(Serialize, Deserialize, Clone)] pub struct Matrix { /// The number of rows of the matrix. rows: usize, @@ -77,6 +77,25 @@ impl Matrix { None } } + + + /// Returns the tile if any, none otherwise. + pub fn get_isize(&self, row: isize, col: isize) -> Option<&T> { + if row >= 0 && col >= 0 && (row as usize) < self.rows && (col as usize) < self.cols { + Some(&self[(row as usize, col as usize)]) + } else { + None + } + } + + /// Returns a mutable reference to the tile if any. + pub fn get_mut(&mut self, (row, col): (usize, usize)) -> Option<&mut T> { + if row < self.rows && col < self.cols { + Some(&mut self[(row, col)]) + } else { + None + } + } } impl Index<(usize, usize)> for Matrix { From 0bc6dcdf026027f06ae05773e5c55926372969f1 Mon Sep 17 00:00:00 2001 From: Thomas Forgione Date: Tue, 2 Apr 2019 22:03:41 +0200 Subject: [PATCH 3/4] I prefer this way --- src/engine/map/mod.rs | 24 ++++++++++++------------ src/engine/math/mod.rs | 14 ++------------ 2 files changed, 14 insertions(+), 24 deletions(-) diff --git a/src/engine/map/mod.rs b/src/engine/map/mod.rs index 7ff5c48..e12ec12 100644 --- a/src/engine/map/mod.rs +++ b/src/engine/map/mod.rs @@ -303,23 +303,23 @@ impl Map { } /// Creates the neighbours of a tile. - pub fn neighbours(&self, i: isize, j: isize) -> [Option; 8] { + pub fn neighbours(&self, i: usize, j: usize) -> [Option; 8] { [ - self.collision_tiles.get_isize(i - 1, j - 1).cloned(), - self.collision_tiles.get_isize(i - 1, j ).cloned(), - self.collision_tiles.get_isize(i - 1, j + 1).cloned(), - self.collision_tiles.get_isize(i , j + 1).cloned(), - self.collision_tiles.get_isize(i + 1, j + 1).cloned(), - self.collision_tiles.get_isize(i + 1, j ).cloned(), - self.collision_tiles.get_isize(i + 1, j - 1).cloned(), - self.collision_tiles.get_isize(i , j - 1).cloned(), + if i > 0 && j > 0 { self.collision_tiles.get(i - 1, j - 1).cloned() } else { None }, + if i > 0 { self.collision_tiles.get(i - 1, j ).cloned() } else { None }, + if i > 0 { self.collision_tiles.get(i - 1, j + 1).cloned() } else { None }, + self.collision_tiles.get(i , j + 1).cloned(), + self.collision_tiles.get(i + 1, j + 1).cloned(), + self.collision_tiles.get(i + 1, j ).cloned(), + if j > 0 { self.collision_tiles.get(i + 1, j - 1).cloned() } else { None }, + if j > 0 { self.collision_tiles.get(i , j - 1).cloned() } else { None }, ] } /// Returns the graphic tile corresponding to the collision tiles. pub fn graphic_tile(&self, i: usize, j: usize) -> GraphicTile { if self.collision_tiles[(i, j)].is_full() { - GraphicTile::from_neighbour_options(&self.neighbours(i as isize, j as isize)) + GraphicTile::from_neighbour_options(&self.neighbours(i, j)) } else { GraphicTile(None) } @@ -327,7 +327,7 @@ impl Map { /// Returns a tile of the map. pub fn collision_tile(&self, i: usize, j: usize) -> Option { - self.collision_tiles.get((i, j)).cloned() + self.collision_tiles.get(i, j).cloned() } /// Changes a tile of the map. @@ -339,7 +339,7 @@ impl Map { for i in (i - 1) ..= (i + 1) { for j in (j - 1) ..= (j + 1) { let new_tile = self.graphic_tile(i, j); - if let Some(tile) = self.graphic_tiles.get_mut((i, j)) { + if let Some(tile) = self.graphic_tiles.get_mut(i, j) { *tile = new_tile; } } diff --git a/src/engine/math/mod.rs b/src/engine/math/mod.rs index 837b375..087761f 100644 --- a/src/engine/math/mod.rs +++ b/src/engine/math/mod.rs @@ -70,7 +70,7 @@ impl Matrix { } /// Returns the tile if any, none otherwise. - pub fn get(&self, (row, col): (usize, usize)) -> Option<&T> { + pub fn get(&self, row: usize, col: usize) -> Option<&T> { if row < self.rows && col < self.cols { Some(&self[(row, col)]) } else { @@ -78,18 +78,8 @@ impl Matrix { } } - - /// Returns the tile if any, none otherwise. - pub fn get_isize(&self, row: isize, col: isize) -> Option<&T> { - if row >= 0 && col >= 0 && (row as usize) < self.rows && (col as usize) < self.cols { - Some(&self[(row as usize, col as usize)]) - } else { - None - } - } - /// Returns a mutable reference to the tile if any. - pub fn get_mut(&mut self, (row, col): (usize, usize)) -> Option<&mut T> { + pub fn get_mut(&mut self, row: usize, col: usize) -> Option<&mut T> { if row < self.rows && col < self.cols { Some(&mut self[(row, col)]) } else { From dec099ad32088ea17942a3b241aa0b9a4e09646f Mon Sep 17 00:00:00 2001 From: Thomas Forgione Date: Tue, 2 Apr 2019 22:06:54 +0200 Subject: [PATCH 4/4] Fixes a bug --- src/engine/map/mod.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/engine/map/mod.rs b/src/engine/map/mod.rs index e12ec12..bcc2302 100644 --- a/src/engine/map/mod.rs +++ b/src/engine/map/mod.rs @@ -336,8 +336,9 @@ impl Map { self.collision_tiles[(i, j)] = tile; // Refresh the current graphic tile and their neighbours - for i in (i - 1) ..= (i + 1) { - for j in (j - 1) ..= (j + 1) { + use std::cmp::max; + for i in max(i, 1) - 1 ..= (i + 1) { + for j in max(j, 1) - 1 ..= (j + 1) { let new_tile = self.graphic_tile(i, j); if let Some(tile) = self.graphic_tiles.get_mut(i, j) { *tile = new_tile; @@ -399,8 +400,8 @@ impl Map { let mut collision_y = false; let mut new = new; - for col in min_col..=max_col { - for row in min_row..=max_row { + for col in min_col ..= max_col { + for row in min_row ..= max_row { let tile_left = col as f32 * SPRITE_SIZE_F32; let tile_top = row as f32 * SPRITE_SIZE_F32;