Reasonable collisions
This commit is contained in:
parent
94f10594df
commit
6bfdfb247e
|
@ -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
|
||||
|
|
|
@ -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<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.
|
||||
/// 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<f32>, new: Vector2<f32>)
|
||||
/// Returns the new correct position.
|
||||
pub fn collides_bbox(&self, old: FloatRect, new: FloatRect)
|
||||
-> Option<(CollisionAxis, Vector2<f32>)> {
|
||||
|
||||
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<f32>, new: Vector2<f32>)
|
||||
-> Option<(CollisionAxis, Vector2<f32>)> {
|
||||
// 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<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 {
|
||||
(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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
Loading…
Reference in New Issue