From 6bfdfb247ee3bca92b63d4d8f2bc7222dd76170b Mon Sep 17 00:00:00 2001 From: Thomas Forgione Date: Sat, 6 Oct 2018 18:41:30 +0200 Subject: [PATCH] Reasonable collisions --- assets/levels/level1.txt | 9 +- src/engine/map/mod.rs | 256 ++++++++++++++++++--------------------- src/engine/scene/mod.rs | 22 ++-- 3 files changed, 131 insertions(+), 156 deletions(-) diff --git a/assets/levels/level1.txt b/assets/levels/level1.txt index c60991c..a09ce43 100644 --- a/assets/levels/level1.txt +++ b/assets/levels/level1.txt @@ -1,5 +1,4 @@ -14 24 -0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +13 24 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 @@ -10,6 +9,6 @@ 0 0 0 0 0 0 0 0 0 0 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 -1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 -1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 +1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 1 +1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 1 +1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 1 diff --git a/src/engine/map/mod.rs b/src/engine/map/mod.rs index 3d94dbb..8ac1ff8 100644 --- a/src/engine/map/mod.rs +++ b/src/engine/map/mod.rs @@ -9,14 +9,37 @@ use engine::renderer::Drawable; use engine::math::Matrix; /// This enum represents if the collision happens on the X axis or the Y axis. +#[derive(Copy, Clone)] pub enum CollisionAxis { /// The X axis. X, /// The Y axis. Y, + + /// Both axis simultaneously + Both, } +impl CollisionAxis { + /// Returns true if the collision occured on X axis. + pub fn is_x(&self) -> bool { + match *self { + CollisionAxis::Y => false, + _ => true, + } + } + + /// Returns true if the collision occured on Y axis. + pub fn is_y(&self) -> bool { + match *self { + CollisionAxis::X => false, + _ => true, + } + } +} + + /// This struct represents the different sides from which a collision can occur. #[derive(Debug, Copy, Clone, PartialEq, Eq)] pub struct CollisionTile { @@ -359,164 +382,123 @@ impl Map { self.tiles.cols() } - /// Checks whether the bounding box collides with an element of the map - pub fn collides_bbox(&self, old: FloatRect, new: FloatRect) -> Option<(CollisionAxis, Vector2)> { - // Top left corner - if let Some((axis, collision)) = self.collides_point( - Vector2::new(old.left, old.top), - Vector2::new(new.left, new.top), - ) { - return Some((axis, collision)); - } - - // Top right corner - if let Some((axis, collision)) = self.collides_point( - Vector2::new(old.left + old.width, old.top), - Vector2::new(new.left + new.width, new.top), - ) { - return Some((axis, collision - Vector2::new(new.width, 0.0))); - } - - // Bottom left corner - if let Some((axis, collision)) = self.collides_point( - Vector2::new(old.left, old.top + old.height), - Vector2::new(new.left, new.top + new.height), - ) { - return Some((axis, collision - Vector2::new(0.0, new.height))); - } - - // Bottom right corner - if let Some((axis, collision)) = self.collides_point( - Vector2::new(old.left + old.width, old.top + old.height), - Vector2::new(new.left + new.width, new.top + new.height), - ) { - return Some((axis, collision - Vector2::new(new.width, new.height))); - } - - None - } - - /// Checks whether the vector (old, new) collides with an element of the map. + /// Checks whether the bounding box collides with elements of the map. /// - /// Returns the height of the collision if any. - pub fn collides_point(&self, old: Vector2, new: Vector2) + /// Returns the new correct position. + pub fn collides_bbox(&self, old: FloatRect, new: FloatRect) -> Option<(CollisionAxis, Vector2)> { - let vert = self.collides_point_vertical(old, new); - let horiz = self.collides_point_horizontal(old, new); - match (vert, horiz) { - (Some(a), Some(b)) => { - if (old.x - a.1.x).abs() < (old.x - b.1.x).abs() { - Some(a) - } else { - Some(b) + 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 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 mut collision_x = false; + let mut collision_y = false; + let mut new = new; + + + for col in min_col .. max_col + 1 { + for row in min_row .. max_row + 1 { + + let tile_left = col as f32 * 16.0; + let tile_top = row as f32 * 16.0; + + let tile = FloatRect::new(tile_left, tile_top, 16.0, 16.0); + + if ! overlap(new, tile) { + continue; } - }, - (Some(a), None) => Some(a), - (None, Some(b)) => Some(b), - (None, None) => None, - } - } - /// Checks whether the vector (old, new) collides horizontally with an element of the map. - /// - /// Returns the height of the collision if any. - pub fn collides_point_horizontal(&self, old: Vector2, new: Vector2) - -> Option<(CollisionAxis, Vector2)> { + // Collisions between feet and ground + if self.tiles[(row, col)].0.from_top { - let width = new.y - old.y; + if old.top + old.height <= tile_top && + new.top + new.height >= tile_top { - if width == 0.0 { - return None; - } + collision_y = true; + new.top = tile_top - new.height; + } - let x = if width > 0.0 { - (old.x / 16.0).ceil() * 16.0 - } else { - (old.x / 16.0).floor() * 16.0 - }; + } - let mut col = (x / 16.0) as isize; + if ! overlap(new, tile) { + continue; + } - while (col as f32 * 16.0 - new.x) * width.signum() < 0.0 { + // Collisions between right and right wall + if self.tiles[(row, col)].0.from_left { - let current_width = col as f32 * 16.0 - old.x; - let y = old.y + (new.y - old.y) * current_width / width; + if old.left + old.width <= tile_left && + new.left + new.width >= tile_left { - // Find tile on x, y - if x > 0.0 && y > 0.0 { + collision_x = true; + new.left = tile_left - new.width; + } + } - let row = (y / 16.0) as usize; + if ! overlap(new, tile) { + continue; + } - let col = if width > 0.0 { - col - } else { - col - 1 - }; + // Collisions between left and left wall + if self.tiles[(row, col)].0.from_right { - if let Some((tile, _)) = self.tiles.get((row, col as usize)) { - if tile.from_top { - return Some((CollisionAxis::X, Vector2::new(x, y))); + if old.left >= tile_left + 16.0 && + new.left <= tile_left + 16.0 { + + collision_x = true; + new.left = tile_left + 16.0; + } + } + + if ! overlap(new, tile) { + continue; + } + + // Collisions between head and roof + if self.tiles[(row, col)].0.from_bottom { + + if old.top >= tile_top + 16.0 && + new.top <= tile_top + 16.0 { + + collision_y = true; + new.top = tile_top + 16.0; } } } - - col += width.signum() as isize; } - None + let new_pos = Vector2::new(new.left, new.top); + match (collision_x, collision_y) { + (true, true) => Some((CollisionAxis::Both, new_pos)), + (true, false) => Some((CollisionAxis::X, new_pos)), + (false, true) => Some((CollisionAxis::Y, new_pos)), + (false, false) => None, + } } - - /// Checks whether the vector (old, new) collides vertically with an element of the map. - /// - /// Returns the height of the collision if any. - pub fn collides_point_vertical(&self, old: Vector2, new: Vector2) - -> Option<(CollisionAxis, Vector2)> { - - let height = new.y - old.y; - - if height == 0.0 { - return None; - } - - let y = if height > 0.0 { - (old.y / 16.0).ceil() * 16.0 - } else { - (old.y / 16.0).floor() * 16.0 - }; - - let mut row = (y / 16.0) as isize; - - while (row as f32 * 16.0 - new.y) * height.signum() < 0.0 { - - let current_height = row as f32 * 16.0 - old.y; - let x = old.x + (new.x - old.x) * current_height / height; - - // Find tile on x, y - if x > 0.0 && y > 0.0 { - - let col = (x / 16.0) as usize; - - let row = if height > 0.0 { - row - } else { - row - 1 - }; - - if let Some((tile, _)) = self.tiles.get((row as usize, col)) { - if tile.from_top { - return Some((CollisionAxis::Y, Vector2::new(x, y))); - } - } - - } - - row += height.signum() as isize; - } - - None - } - } +/// Checks if two boxes overlap. +pub fn overlap(box1: FloatRect, box2: FloatRect) -> bool { + box2.left < box1.left + box1.width && + box2.left + box2.width > box1.left && + box2.top < box1.top + box1.height && + box2.top + box2.height > box1.top +} + +/// Clamp a number between two boundaries. +pub fn clamp(number: f32, min: f32, max: f32) -> f32 { + if number < min { + min + } else if number > max { + max + } else { + number + } +} diff --git a/src/engine/scene/mod.rs b/src/engine/scene/mod.rs index c8cfdf3..b47e01b 100644 --- a/src/engine/scene/mod.rs +++ b/src/engine/scene/mod.rs @@ -4,10 +4,7 @@ use sfml::window::Event; use sfml::graphics::View; use engine::character::Character; -use engine::map::{ - Map, - CollisionAxis -}; +use engine::map::Map; /// Contains everything needed to play. pub struct Scene { @@ -82,16 +79,13 @@ impl Scene { c.update(duration); if let Some((axis, position)) = self.map.collides_bbox(old, c.bbox()) { - match axis { - CollisionAxis::X => { - c.speed.x = 0.0; - c.position.x = position.x; - }, - CollisionAxis::Y => { - c.speed.y = 0.0; - c.position.y = position.y; - c.ground_collision(); - }, + c.position = position; + if axis.is_x() { + c.speed.x = 0.0; + } + if axis.is_y() { + c.speed.y = 0.0; + c.ground_collision(); } } else { c.fall_off();