# Cannot properly get 2D top-down tile-based collisions

I am using HTML, CSS, and plain JavaScript for a 2D top-down tile-based simulation. I am trying to do collisions in which, for example, if I move up and hit the ceiling collision tile, I cannot proceed to go up and can still move left, right, or down.

Each title is a 64x64 HTML IMG element, including the player. The player's movement uses CSS Transform style for X and Y movements when moving with arrow keys.

For my collision function, I have the following code:

function checkCollision(arrPassableMovements) {
let childrens = cntWorldBaseCollision.children;
let tenantDOM = imgWorldTenant.getBoundingClientRect();

// For number of tiles set in the collision world
for (let i = 0; i < childrens.length; i++) {

// Get the HTML element of that tile
let childrenElement = document.getElementById(childrens[i].id);

// Get the JavaScript DOM object of that tile
let childrenDOM = childrenElement.getBoundingClientRect();

// If that tile's last fixed character ends with C
if (childrens[i].id.substring(9) == 'C') {

if (Math.abs(tenantDOM.x - childrenDOM.x) * 2 < (tenantDOM.width + childrenDOM.width) &&
Math.abs(tenantDOM.y - childrenDOM.y) * 2 < (tenantDOM.height + childrenDOM.height)) {

// Checks for up. Else, down.
if (tenantDOM.bottom - childrenDOM.bottom > 0 &&
tenantDOM.top - childrenDOM.top > 0) {
arrPassableMovements[0] = false;

} else if (tenantDOM.bottom - childrenDOM.bottom < 0 &&
tenantDOM.top - childrenDOM.top < 0) {
arrPassableMovements[1] = false;
}

// Checks for left. Else, right.
if (tenantDOM.left - childrenDOM.left > 0 &&
tenantDOM.right - childrenDOM.right > 0) {
arrPassableMovements[2] = false;
} else if (tenantDOM.left - childrenDOM.left < 0 &&
tenantDOM.right - childrenDOM.right < 0) {
arrPassableMovements[3] = false;
}

}
}
}
}


The issue begins after it is determined that the player is intercepting a collision tile. Let us say that we move up to block a ceiling tile; then, it is impossible to move left or right unless we go back—the same story with left tile, unable to move up or down unless we go right.

I had tried the following:

• Checking if it is not possible to move up or down, allow left and right movement. This, of course, meant that left and right collisions were nullified.
if (!arrPassableMovements[0] || !arrPassableMovements[1]) {
arrPassableMovements[2] = true;
arrPassableMovements[3] = true;
}

• In addition to the above, I have also added that allows up and down movement if it is not possible to move left or right, allow up and down motion. Same story as one above.
if (!arrPassableMovements[2] || !arrPassableMovements[3]) {
arrPassableMovements[0] = true;
arrPassableMovements[1] = true;
}


If I have to speculate, it has to be how I check when to nullify exact movements. In other words, possibly not enough additional conditional statements to correctly conclude that we cannot move in this direction.

NOTES:

• Player is referred to as tenant
• children(s) as the world tile (both plain and collider-bound tiles.)
• Each world tile has a fixed identification. For example, imgWBC000C breaks down the following: three characters for what HTML element it is, followed by the abbreviation for World Based Collision and a number of that, and if it is plain (P) or collision (C) tile.
• arrPassableMovements is an array with four booleans that starts with true. Each element is as follows: up, down, left, and right.
• If you believe that the provided codes are not enough, then refer to the intragarden-dev repository. The ones used are in world.js: https://github.com/Tyu-Uyt/intragarden-dev/blob/menu-redesign/world.js

## 1 Answer

If I understand correctly, checkCollision should give you the direction in which the player can move. So you are calling it before the motion.

However, you are checking if the character is already colliding, here:

if (Math.abs(tenantDOM.x - childrenDOM.x) * 2 < (tenantDOM.width + childrenDOM.width) &&
Math.abs(tenantDOM.y - childrenDOM.y) * 2 < (tenantDOM.height + childrenDOM.height)) {
// …
}


Perhaps I'm misunderstanding, however, I believe you should be checking if the character will collide if it moves in each direction. Something like this:

let step = 3;

// …

if (Math.abs(tenantDOM.x - childrenDOM.x) * 2 < (tenantDOM.width + childrenDOM.width) &&
Math.abs(tenantDOM.y - childrenDOM.y - step) * 2 < (tenantDOM.height + childrenDOM.height)) {
arrPassableMovements[0] = false;
}

if (Math.abs(tenantDOM.x - childrenDOM.x) * 2 < (tenantDOM.width + childrenDOM.width) &&
Math.abs(tenantDOM.y - childrenDOM.y + step) * 2 < (tenantDOM.height + childrenDOM.height)) {
arrPassableMovements[1] = false;
}

if (Math.abs(tenantDOM.x - childrenDOM.x - step) * 2 < (tenantDOM.width + childrenDOM.width) &&
Math.abs(tenantDOM.y - childrenDOM.y) * 2 < (tenantDOM.height + childrenDOM.height)) {
arrPassableMovements[2] = false;
}

if (Math.abs(tenantDOM.x - childrenDOM.x + step) * 2 < (tenantDOM.width + childrenDOM.width) &&
Math.abs(tenantDOM.y - childrenDOM.y) * 2 < (tenantDOM.height + childrenDOM.height)) {
arrPassableMovements[3] = false;
}



I'm not sure if I flipped the directions.

You can - of course - optimize that further, since there are repeated checks and computations there.

There are other optimizations you could do, like keeping the tile elements in an actual array, so you can go directly to the ones nearby the character instead of iterating over them all, but that is beyond the question.

• Works as intended. Is it appropriate to add image offset when calculating the sum of two widths and heights? In other words, since all tiles are 64x64, including the player, it will always be equal to 128. And since the player looks smaller than that, I would subtract the sum by 16, right? Nov 24 '21 at 15:41
• One more thing. Based on this comment, can you confirm that I am understanding the conditional statements correctly? 'In general, check if the difference in X and Y coordinates is less than the surrounding collision tile. Subtract or add based on which direction it will affect the ability to move.' Nov 24 '21 at 16:08
• @ATOPL the step is intended to be how much the character would move. If the character moves in whole tiles, then yes use the size of the tile. This is because you check for possible collisions before the movement, we check the future position. The traditional way is to have the character move, and then check if it collided, and if it did, fix it. For small steps either is fine. Now, In general, it would be better to check against an elongated tile meant to represent sweeping motion (the area from where the tile is to where it will be). That would be equivalent to continuos collision detection. Nov 24 '21 at 20:54
• Based on your last sentence, is it not that continuous collision detection is mainly for simulating physics? Nov 25 '21 at 21:17
• @ATOPL It is for detecting collisions. Physics simulation has three steps: movement, constraint resolution, and response. And collisions are a type of constraint, the most common one. Now, with discrete collision detection you only check a fixed number of positions (e.g. at the start of the movement and at the end of the movement), which means that fast moving objects could tunnel through each other. You could checks multiple times along the way, or you could use continuous collision detection. If in your game the objects don't move fast enough to cause this problem, you don't need that. Nov 26 '21 at 2:31