I am trying to implement a simple AABB system wherein a bounding box takes the player's bounding box from their last position, and from their next position. The next bounding box is supposed to be modified to prevent any illegal movements (i.e, walking through walls).
Currently, my code works, to an extent. For example, when I move in one direction only (keyboard controls), I stop at the exact correct point:
Note that the white square is the actual bounding box, and the red border is the exclusive right and bottom coordinates (so it's correct that the red lines go inside the terrain).
The real problem appears when attempting to move by two axes at once. e.g. whenever I move left while hugging the top wall (holding W
), the player gets stuck at regular 16*16 intervals (on the edge of every tile). Once I have this sorted out, I'll be creating some sort of greedy algorithm to group bounding boxes, but currently there is just one BoundingBox
per filled tile in the map: int i, j; boxes.add(new BoundingBox(new Vec2(i = x * 16, j = y * 16), new Vec2(i + 16, j + 16)));
It may be easier to show what's happening in a GIF:
The collide
function is called for each bounding box, with the player's last position and the modified position from the last box, starting from the top left and incrementing X first. Here's the code for bounding boxes:
public class BoundingBox {
public Vec2 min, max;
public BoundingBox(Vec2 min, Vec2 max) {
this.min = min;
this.max = max;
}
@Override
public BoundingBox clone() {
return new BoundingBox(new Vec2(this.min.x, this.min.y), new Vec2(this.max.x, this.max.y));
}
public BoundingBox offset(Vec2 vec) {
this.min.x += vec.x;
this.max.x += vec.x;
this.min.y += vec.y;
this.max.y += vec.y;
return this;
}
private boolean lineCollision(int leftA, int rightA, int leftB, int rightB) {
return (leftA >= leftB && leftA < rightB) ||
(rightA >= leftB && rightA < rightB);
}
public boolean collide(BoundingBox last, BoundingBox next) {
boolean collided = false;
if(lineCollision(last.min.y, last.max.y, this.min.y, this.max.y) || lineCollision(next.min.y, next.max.y, this.min.y, this.max.y)) {
if(last.max.x <= this.min.x && next.max.x > this.min.x) { // Left -> Right
next.offset(new Vec2(this.min.x - next.max.x, 0));
collided = true;
}
if(last.min.x >= this.max.x && next.min.x < this.max.x) { // Right -> Left
next.offset(new Vec2(this.max.x - next.min.x, 0));
collided = true;
}
}
if(lineCollision(last.min.x, last.max.x, this.min.x, this.max.x) || lineCollision(next.min.x, next.max.x, this.min.x, this.max.x)) {
if(last.max.y <= this.min.y && next.max.y > this.min.y) { // Top -> Bottom
next.offset(new Vec2(0, this.min.y - next.max.y));
collided = true;
}
if(last.min.y >= this.max.y && next.min.y < this.max.y) { // Bottom -> Top
next.offset(new Vec2(0, this.max.y - next.min.y));
collided = true;
}
}
return collided;
}
}
Perhaps this issue has something to do with the order of operations? It seems unlikely, but it seems flipping the order of the two if
statements inside collide
changes the behaviour - the walls on which I get stuck is changed.
As far as I can tell, the first time there's a collision the resultant vector should be outside the wall, so it no longer collides - right? There must be some kind of logic error here, but I can't spot it myself.
As per request, extra code is available in this pastebin: http://pastebin.com/ueBAKJ9C