From 7bab06b2b6583bc50cd27b321eb735be720eb781 Mon Sep 17 00:00:00 2001 From: Thomas Forgione Date: Tue, 2 Apr 2019 20:27:05 +0200 Subject: [PATCH] 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") } }