This commit is contained in:
Thomas Forgione 2019-04-02 21:59:14 +02:00
parent 7bab06b2b6
commit a66e596db0
No known key found for this signature in database
GPG Key ID: BFD17A2D71B3B5E7
3 changed files with 145 additions and 116 deletions

View File

@ -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());

View File

@ -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<CollisionTile>,
top: Option<CollisionTile>,
top_right: Option<CollisionTile>,
right: Option<CollisionTile>,
bottom_right: Option<CollisionTile>,
bottom: Option<CollisionTile>,
bottom_left: Option<CollisionTile>,
left: Option<CollisionTile>,
) -> 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<CollisionTile>; 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<CollisionTile>,
}
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<CollisionTile>,
/// The graphic tiles contained in the level.
graphic_tiles: Matrix<GraphicTile>,
}
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<Vec<u8>> {
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<Map> {
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<P: AsRef<Path>>(&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<P: AsRef<Path>>(path: P) -> Result<Map> {
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<CollisionTile>) -> 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<CollisionTile>) -> 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<CollisionTile>) -> 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<CollisionTile>; 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<CollisionTile> {
self.tiles.get((i, j)).map(|x| x.0)
pub fn collision_tile(&self, i: usize, j: usize) -> Option<CollisionTile> {
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<CollisionTile>) -> (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;
}

View File

@ -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<T> {
/// The number of rows of the matrix.
rows: usize,
@ -77,6 +77,25 @@ impl<T> Matrix<T> {
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<T> Index<(usize, usize)> for Matrix<T> {