Reasonable collisions

This commit is contained in:
Thomas Forgione 2018-10-06 18:41:30 +02:00
parent 94f10594df
commit 6bfdfb247e
3 changed files with 131 additions and 156 deletions

View File

@ -1,5 +1,4 @@
14 24 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
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 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 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 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 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 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 0 0 0 1

View File

@ -9,14 +9,37 @@ use engine::renderer::Drawable;
use engine::math::Matrix; use engine::math::Matrix;
/// This enum represents if the collision happens on the X axis or the Y axis. /// This enum represents if the collision happens on the X axis or the Y axis.
#[derive(Copy, Clone)]
pub enum CollisionAxis { pub enum CollisionAxis {
/// The X axis. /// The X axis.
X, X,
/// The Y axis. /// The Y axis.
Y, 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. /// This struct represents the different sides from which a collision can occur.
#[derive(Debug, Copy, Clone, PartialEq, Eq)] #[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct CollisionTile { pub struct CollisionTile {
@ -359,164 +382,123 @@ impl Map {
self.tiles.cols() self.tiles.cols()
} }
/// Checks whether the bounding box collides with an element of the map /// Checks whether the bounding box collides with elements of the map.
pub fn collides_bbox(&self, old: FloatRect, new: FloatRect) -> Option<(CollisionAxis, Vector2<f32>)> {
// 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.
/// ///
/// Returns the height of the collision if any. /// Returns the new correct position.
pub fn collides_point(&self, old: Vector2<f32>, new: Vector2<f32>) pub fn collides_bbox(&self, old: FloatRect, new: FloatRect)
-> Option<(CollisionAxis, Vector2<f32>)> { -> Option<(CollisionAxis, Vector2<f32>)> {
let vert = self.collides_point_vertical(old, new); let cols = self.tiles.cols() - 1;
let horiz = self.collides_point_horizontal(old, new); let rows = self.tiles.rows() - 1;
match (vert, horiz) {
(Some(a), Some(b)) => { let min_col = clamp(new.left / 16.0, 0.0, cols as f32) as usize;
if (old.x - a.1.x).abs() < (old.x - b.1.x).abs() { let min_row = clamp(new.top / 16.0, 0.0, rows as f32) as usize;
Some(a)
} else { let max_col = clamp((new.left + new.width) / 16.0, 0.0, cols as f32) as usize;
Some(b) let max_row = clamp((new.top + new.height) / 16.0, 0.0, rows as f32) as usize;
}
}, let mut collision_x = false;
(Some(a), None) => Some(a), let mut collision_y = false;
(None, Some(b)) => Some(b), let mut new = new;
(None, None) => None,
}
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;
} }
/// Checks whether the vector (old, new) collides horizontally with an element of the map. // Collisions between feet and ground
/// if self.tiles[(row, col)].0.from_top {
/// Returns the height of the collision if any.
pub fn collides_point_horizontal(&self, old: Vector2<f32>, new: Vector2<f32>)
-> Option<(CollisionAxis, Vector2<f32>)> {
let width = new.y - old.y; if old.top + old.height <= tile_top &&
new.top + new.height >= tile_top {
if width == 0.0 { collision_y = true;
return None; 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;
while (col as f32 * 16.0 - new.x) * width.signum() < 0.0 {
let current_width = col as f32 * 16.0 - old.x;
let y = old.y + (new.y - old.y) * current_width / width;
// Find tile on x, y
if x > 0.0 && y > 0.0 {
let row = (y / 16.0) as usize;
let col = if width > 0.0 {
col
} else {
col - 1
};
if let Some((tile, _)) = self.tiles.get((row, col as usize)) {
if tile.from_top {
return Some((CollisionAxis::X, Vector2::new(x, y)));
}
} }
} }
col += width.signum() as isize; if ! overlap(new, tile) {
continue;
} }
None // Collisions between right and right wall
if self.tiles[(row, col)].0.from_left {
if old.left + old.width <= tile_left &&
new.left + new.width >= tile_left {
collision_x = true;
new.left = tile_left - new.width;
}
} }
/// Checks whether the vector (old, new) collides vertically with an element of the map. if ! overlap(new, tile) {
/// continue;
/// Returns the height of the collision if any.
pub fn collides_point_vertical(&self, old: Vector2<f32>, new: Vector2<f32>)
-> Option<(CollisionAxis, Vector2<f32>)> {
let height = new.y - old.y;
if height == 0.0 {
return None;
} }
let y = if height > 0.0 { // Collisions between left and left wall
(old.y / 16.0).ceil() * 16.0 if self.tiles[(row, col)].0.from_right {
} else {
(old.y / 16.0).floor() * 16.0
};
let mut row = (y / 16.0) as isize; if old.left >= tile_left + 16.0 &&
new.left <= tile_left + 16.0 {
while (row as f32 * 16.0 - new.y) * height.signum() < 0.0 { collision_x = true;
new.left = tile_left + 16.0;
}
}
let current_height = row as f32 * 16.0 - old.y; if ! overlap(new, tile) {
let x = old.x + (new.x - old.x) * current_height / height; continue;
}
// Find tile on x, y // Collisions between head and roof
if x > 0.0 && y > 0.0 { if self.tiles[(row, col)].0.from_bottom {
let col = (x / 16.0) as usize; if old.top >= tile_top + 16.0 &&
new.top <= tile_top + 16.0 {
let row = if height > 0.0 { collision_y = true;
row new.top = tile_top + 16.0;
} 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 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 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
}
}

View File

@ -4,10 +4,7 @@ use sfml::window::Event;
use sfml::graphics::View; use sfml::graphics::View;
use engine::character::Character; use engine::character::Character;
use engine::map::{ use engine::map::Map;
Map,
CollisionAxis
};
/// Contains everything needed to play. /// Contains everything needed to play.
pub struct Scene { pub struct Scene {
@ -82,16 +79,13 @@ impl Scene {
c.update(duration); c.update(duration);
if let Some((axis, position)) = self.map.collides_bbox(old, c.bbox()) { if let Some((axis, position)) = self.map.collides_bbox(old, c.bbox()) {
match axis { c.position = position;
CollisionAxis::X => { if axis.is_x() {
c.speed.x = 0.0; c.speed.x = 0.0;
c.position.x = position.x; }
}, if axis.is_y() {
CollisionAxis::Y => {
c.speed.y = 0.0; c.speed.y = 0.0;
c.position.y = position.y;
c.ground_collision(); c.ground_collision();
},
} }
} else { } else {
c.fall_off(); c.fall_off();