So, I am currently reinventing the wheel (and learning a lot) by trying my hand at making a simple physics engine for my game engine. I have been searching the internet, trying (and failing) to fix my current problem. There are a lot of resources out there on the subject, but none of those that I have found seem to apply to my case.
THE PROBLEM IN SHORT: The collision resolution does not work as intended on some of the corners when two rectangles are colliding. How it fails varies based on the dimensions of the rectangles. What I am looking for is a "shortest overlap" kind of resolution for the collision or another fairly simple solution (I am open for suggestions!). (Scroll down for a better explaination and illustrations).
WARNING: The following code is probably not very efficient...
First of all, here is my physics loop. It simply loops through all of the game entities and checks if they collide with any other game entities. It is not efficient (n^2 and all of that), but it works for now.
updatePhysics: function(step) {
// Loop through entities and update positions based on velocities
for (var entityID in Vroom.entityList) {
var entity = Vroom.entityList[entityID];
if (entity.physicsEnabled) {
switch (entity.entityType) {
case VroomEntity.KINEMATIC:
entity.pos.x += entity.vel.x * step;
entity.pos.y += entity.vel.y * step;
break;
case VroomEntity.DYNAMIC:
// Dynamic stuff
break;
}
}
}
// Loop through entities and detect collisions. Resolve collisions as they are detected.
for (var entityID in Vroom.entityList) {
var entity = Vroom.entityList[entityID];
if (entity.physicsEnabled && entity.entityType !== VroomEntity.STATIC) {
for (var targetID in Vroom.entityList) {
if (targetID !== entityID) {
var target = Vroom.entityList[targetID];
if (target.physicsEnabled) {
// Check if current entity and target is colliding
if (Vroom.collideEntity(entity, target)) {
switch (entity.collisionType) {
case VroomEntity.DISPLACE:
Vroom.resolveTestTest(entity, target);
break;
}
}
}
}
}
}
}
},
Here is the code for the actual collision detection. This also seems to work alright.
collideEntity: function(entity, target) {
if (entity.getBottom() < target.getTop() || entity.getTop() > target.getBottom() || entity.getRight() < target.getLeft() || entity.getLeft() > target.getRight()) {
return false;
}
return true;
},
Here is where the problems start to pop up. I want the entity to simply be "pushed" out of the target entity and have the velocity set to 0. This works fine as long as both the entity and the target are perfect squares. If let's say the entity (the player figure in the gif) is a rectangle, then the collision will "slipp" when colliding the longest sides (the X axis) with the target (the square). If I swap the player dimensions so that it is short and wide, then the same problem appears for the Y axis instead.
resolveTestTest: function(entity, target) {
var normalizedX = (target.getMidX() - entity.getMidX());
var normalizedY = (target.getMidY() - entity.getMidY());
var absoluteNormalizedX = Math.abs(normalizedX);
var absoluteNormalizedY = Math.abs(normalizedY);
console.log(absoluteNormalizedX, absoluteNormalizedY);
// The collision is comming from the left or right
if (absoluteNormalizedX > absoluteNormalizedY) {
if (normalizedX < 0) {
entity.pos.x = target.getRight();
} else {
entity.pos.x = target.getLeft() - entity.dim.width;
}
// Set velocity to 0
entity.vel.x = 0;
// The collision is comming from the top or bottom
} else {
if (normalizedY < 0) {
entity.pos.y = target.getBottom();
} else {
entity.pos.y = target.getTop() - entity.dim.height;
}
// Set velocity to 0
entity.vel.y = 0;
}
},
Collision on the Y axis works with these shapes
Collision on the X axis slips with these shapes
What can I do to fix this slipping problem? I have been bashing my head against this for the last 5 days, so I would be immensely grateful if some one could help push me in the right direction!
Thank you :)
-- EDIT: --
The slipping also happens if only moving in one direction along the left or right side.
-- EDIT 2 WORKING CODE: --
See my answer below for an example of the working code!
The important logical error you have made is this line:
if (absoluteNormalizedX > absoluteNormalizedY) {
This only works if both entities are square.
Consider a near-extremal case for your X-slipping example: if they almost touch at the corner:
Although the diagram is a little exaggerated, you can see that absoluteNormalizedX < absoluteNormalizedY in this case - your implementation would move on to resolve a vertical collision instead of the expected horizontal one.
Another error is that you always set the corresponding velocity component to zero regardless of which side the collision is on: you must only zero the component if is it in the opposite direction to the collision normal, or you won't be able to move away from the surface.
A good way to overcome this is to also record the collided face(s) when you do collision detection:
collideEntity: function(entity, target) {
// adjust this parameter to your liking
var eps = 1e-3;
// no collision
var coll_X = entity.getRight() > target.getLeft() && entity.getLeft() < target.getRight();
var coll_Y = entity.getBottom() > target.getTop() && entity.getTop() < target.getBottom();
if (!(coll_X && coll_Y)) return 0;
// calculate bias flag in each direction
var bias_X = entity.targetX() < target.getMidX();
var bias_Y = entity.targetY() < target.getMidY();
// calculate penetration depths in each direction
var pen_X = bias_X ? (entity.getRight() - target.getLeft())
: (target.getRight() - entity.getLeft());
var pen_Y = bias_Y ? (entity.getBottom() - target.getUp())
: (target.getBottom() - entity.getUp());
var diff = pen_X - pen_Y;
// X penetration greater
if (diff > eps)
return (1 << (bias_Y ? 0 : 1));
// Y pentration greater
else if (diff < -eps)
return (1 << (bias_X ? 2 : 3));
// both penetrations are approximately equal -> treat as corner collision
else
return (1 << (bias_Y ? 0 : 1)) | (1 << (bias_X ? 2 : 3));
},
updatePhysics: function(step) {
// ...
// pass collision flag to resolver function
var result = Vroom.collideEntity(entity, target);
if (result > 0) {
switch (entity.collisionType) {
case VroomEntity.DISPLACE:
Vroom.resolveTestTest(entity, target, result);
break;
}
}
// ...
}
Using a bit flag instead of a boolean array for efficiency. The resolver function can then be re-written as:
resolveTestTest: function(entity, target, flags) {
if (!!(flags & (1 << 0))) { // collision with upper surface
entity.pos.y = target.getTop() - entity.dim.height;
if (entity.vel.y > 0) // travelling downwards
entity.vel.y = 0;
}
else
if (!!(flags & (1 << 1))) { // collision with lower surface
entity.pos.y = target.getBottom();
if (entity.vel.y < 0) // travelling upwards
entity.vel.y = 0;
}
if (!!(flags & (1 << 2))) { // collision with left surface
entity.pos.x = target.getLeft() - entity.dim.width;
if (entity.vel.x > 0) // travelling rightwards
entity.vel.x = 0;
}
else
if (!!(flags & (1 << 3))) { // collision with right surface
entity.pos.x = target.getRight();
if (entity.vel.x < 0) // travelling leftwards
entity.vel.x = 0;
}
},
Note that unlike your original code, the above also allows corners to collide - i.e. for velocities and positions to be resolved along both axes.
MY WORKING CODE
So with some help and guidance from the amazing #meowgoesthedog I finally got on the right track and found what I was looking for. The problem (as #meowgoesthedog pointed out) was that my code was really only going to work with squares. The solution was to check the intersection of the colliding bodies and solve based on the shortest intersection. Note: this will probably not be a suitable solution if you need accurate physics with small and fast moving objects. The code for finding the intersection depth is based on this: https://github.com/kg/PlatformerStarterKit/blob/0e2fafb8dbc845279fe4116c37b6f2cdd3e636d6/RectangleExtensions.cs which is related to this project: https://msdn.microsoft.com/en-us/library/dd254916(v=xnagamestudio.31).aspx.
Here is my working code:
My physics loop has not been changed much, except for some better names for some functions.
updatePhysics: function(step) {
// Loop through entities and update positions based on velocities
for (var entityID in Vroom.entityList) {
var entity = Vroom.entityList[entityID];
if (entity.physicsEnabled) {
switch (entity.entityType) {
case VroomEntity.KINEMATIC:
entity.pos.x += entity.vel.x * step;
entity.pos.y += entity.vel.y * step;
break;
case VroomEntity.DYNAMIC:
// Dynamic stuff
break;
}
}
}
// Loop through entities and detect collisions. Resolve collisions as they are detected.
for (var entityID in Vroom.entityList) {
var entity = Vroom.entityList[entityID];
if (entity.physicsEnabled && entity.entityType !== VroomEntity.STATIC) {
for (var targetID in Vroom.entityList) {
if (targetID !== entityID) {
var target = Vroom.entityList[targetID];
if (target.physicsEnabled) {
// Check if current entity and target is colliding
if (Vroom.collideEntity(entity, target)) {
switch (entity.collisionType) {
case VroomEntity.DISPLACE:
Vroom.resolveDisplace(entity, target);
break;
}
}
}
}
}
}
}
},
The collision detection remains the same as well.
collideEntity: function(entity, target) {
if (entity.getBottom() < target.getTop() || entity.getTop() > target.getBottom() || entity.getRight() < target.getLeft() || entity.getLeft() > target.getRight()) {
return false;
}
return true;
},
Here is the code that basically fixes the problem. The comments in the code should explain what it does pretty well.
getIntersectionDepth: function(entity, target) {
// Calculate current and minimum-non-intersecting distances between centers.
var distanceX = entity.getMidX() - target.getMidX();
var distanceY = entity.getMidY() - target.getMidY();
var minDistanceX = entity.halfDim.width + target.halfDim.width;
var minDistanceY = entity.halfDim.height + target.halfDim.height;
// If we are not intersecting at all, return 0.
if (Math.abs(distanceX) >= minDistanceX || Math.abs(distanceY) >= minDistanceY) {
return {
x: 0,
y: 0,
};
}
// Calculate and return intersection depths.
var depthX = distanceX > 0 ? minDistanceX - distanceX : -minDistanceX - distanceX;
var depthY = distanceY > 0 ? minDistanceY - distanceY : -minDistanceY - distanceY;
return {
x: depthX,
y: depthY,
};
},
Here is the updated resolving function. It now takes intersection depth in to account when determining axis of collision and then uses the sign of the intersection depth for the colliding axis when determining the direction to resolve.
resolveDisplace: function(entity, target) {
var intersection = Vroom.getIntersectionDepth(entity, target);
if (intersection.x !== 0 && intersection.y !== 0) {
if (Math.abs(intersection.x) < Math.abs(intersection.y)) {
// Collision on the X axis
if (Math.sign(intersection.x) < 0) {
// Collision on entity right
entity.pos.x = target.getLeft() - entity.dim.width;
} else {
// Collision on entity left
entity.pos.x = target.getRight();
}
entity.vel.x = 0;
} else if (Math.abs(intersection.x) > Math.abs(intersection.y)) {
// Collision on the Y axis
if (Math.sign(intersection.y) < 0) {
// Collision on entity bottom
entity.pos.y = target.getTop() - entity.dim.height;
} else {
// Collision on entity top
entity.pos.y = target.getBottom();
}
entity.vel.y = 0;
}
}
},
Thank you all for your help!
The problem may be that you're correcting both X and Y collision based on the same position:
Player is at a certain position. Let's check collision.
Player's bottom right corner overlaps top left corner of object.
X position is corrected: Player is moved to the left.
Player's bottom right corner overlaps top left corner of object.
Y position is corrected: Player is moved up.
End result: The player is moved up and to the left.
You probably need to "get" the player's position again, between checks.
Related
I realize this may be a common question however after looking at the other answers i'm not sure my implementation benefits from the answers.
My game has the player shooting from its X Y position towards the mouse X Y position, with the enemies falling linearly down the Y axis.
However it seems only the first shot or a random shot in on the screen will sometimes hit and remove an enemy, with some bullets passing straight through with a direct hit and not invoking the removal of the enemy.
The game can be seen here:
https://liammorgan.github.io/wave_defence/
And the snippet for hit detection is here, which works around 20% of the time, or on the first bullet shot.
Each shot has an X,Y,bulletSpeed, xVelocity, yVelocity
Each enemy has an X,Y,speed
shot.js -
this.hit = function() {
for(enemy in enemies) {
let e = enemies[enemy];
if(e != null) {
if(this.x+this.size > e.x && this.x-this.size < e.x &&
this.y+this.size > e.y && this.y-this.size < e.y) {
enemies[enemy] = null;
return true;
} else {
return false;
}
}
}
}
sketch.js -
let shots = [];
let enemies = [];
if(player.lives > 0) {
player.update();
player.draw();
for(shot in shots) {
let s = shots[shot];
if(s != null) {
if(s.hit() && s != null) {
shots[shot] = null;
continue;
}
shots[shot].update();
shots[shot].draw();
if(s.remove() && s != null) {
shots[shot] = null;
}
}
}
}
It seems to me that in your collision logic, you're not accounting for the size of the enemy itself. So for the collision to count, the shot has to PERFECTLY hit almost the exact center of the enemy.
A better way to do it would be to measure the distance from the center the bullet to the center of the enemy, and check that to their known sizes, since both the enemy and bullet are circles. This also means that you would have to include a radius or size field to the enemy objects.
I currently have a JavaScript platformer game. I have created a polygon-polygon collision detection with a response, using the SAT method portrayed on many other websites.
I have also created a function inside the Polygon constructor that will return either the sides exposed to the top of the polygon or the sides exposed to the bottom of the polygon, depending on which direction is chosen. The array that is returned contains many different Vectors, which will be explained more elaborately later.
The current code looks a bit like this:
Polygon.prototype.getSidesOn = function(dir) {
if (dir === "top" || dir === "bottom") {
var result = [], start = false, stop = false, elen =
this.calcPoints.length, e = 0, f = 0, point, next, prevX, prevY, directionX, directionY;
while (!stop) {
if (e >= 5*elen) {return;}
f = e%elen;
prev = f-1 < 0 ? this.calcPoints[elen-1] : this.calcPoints[f-1];
point = this.calcPoints[f];
prevX = directionX;
prevY = directionY;
directionX = point.x > prev.x ? "right" : point.x < prev.x ? "left" : directionX;
directionY = point.y < prev.y ? "up" : point.y > prev.y ? "down" : directionY;
if (prevX !== directionX && prevX && prevY) {
if (!start) {
start = dir === "top" ? directionY === "up" : directionY === "down";
} else {
break;
}
}
if (start) {
if (point.x !== prev.x) {
if (!result.length) {
result.push(new Vector(prev.x,prev.y),new
Vector(point.x,point.y));
} else {
result.push(new Vector(point.x,point.y));
}
} else {
break;
}
}
e++;
}
return result;
} else {
return;
}
}
I understand that it is a bit messy, but that is not a worry as I will optimise it later.
Basically, it works on ONLY convex polygons (I mean the word "convex" as in a triangle or a shape that does not go back into itself). Anyway, it works because of the fact that all convex polygons only have two points where the adjacent sides are facing the opposite X direction.
Anyway, the array that is outputted looks a bit like this:
//[Vector,Vector,Vector,Vector...]
that is not what it actually looks like, but basically, it outputs an array of Vectors for each point detected.
What I need to implement is a way to detect collision between two of these polylines. Unlike the polygon-polygon collision detection system, there does not need to be a response vector, and instead, the code should output either a "true" or "false" bool.
The only way that I can currently think of doing this is as follows:
function PolylinePolyline(line1,line2) {
var i, ilen = line1.length, j, jlen = line2.length;
for (i = 0; i < ilen; i++) {
for (j = 0; j < jlen; j++) {
var point1 = line1[i];
var point2 = i+1 < ilen ? line1[i+1] : line1[0];
var point3 = line2[j];
var point4 = j+1 < jlen ? line2[j+1] : line2[0];
var line01 = new line(point1,point2);
var line02 = new line(point3,point4);
if (LineLine(line01,line02) {
return true;
}
}
}
return false;
}
And although this works, it is very expensive to use if the lines are, let's say, 5 points long, and I have to detect collision between 4 of them. I would prefer an alternative that returns true if they are colliding and false if not. I will not explain why I need this, as it is a bit abstract but an example of a polyline would be this:
Note that the polylines are convex-shaped
You can speed up your collision search by defining (pseudo random) infinite length lines that go between two polygons such that the line splits up the polygons into 3 sets: above, below and colliding. Because they are dvidied by the line, you know that all above do not collide with all below. Repeat this a few times until you have a low number of undecided polygon pairs, then apply your old algorithm to the remaining pairs.
You can find (pseudo random) infinite length lines by calculating the normal at the middle of the line connecting the centers of two polygons in your set of polygons.
I have a 2D voxel map for a game, which is a 2D array where 1 means ground and 0 means sky.
Example: all 1's in the array (ground) are green boxes
The algorithm starts at the leftmost ground voxel that touches the sky (red box in picture).
It will explore 8 neighbours of the current position to check if one of them is a ground voxel and also touches a sky voxel. This means it should be added to the groundline.
Example of the algorithm working (it's able to go in 'caves' too)
On this map it figured it out and returned a line across the ground.
In some situations it suddenly stops though, like on this map:
After about 10 loops it stopped creating the line.
Here's the code, with some explanatory comments in there:
voxelToLine() {
let voxels = this.voxels.length,//this.voxels is the 2d array
lineGround = [],
checkedVoxels = [],
nowChecking,
toCheck = [],
otherPaths = [],
done = false;
for (let y = 1; y < voxels - 1; y++)//sets first coordinate for line
if (this.voxels[0][y] && (!this.voxels[0][y - 1] || !this.voxels[1][y] || !this.voxels[0][y + 1])) {
lineGround[0] = [0, y / voxels];
nowChecking = [1, y];//search starts from this point
}
let looped = 0;
while (!done) {//continues search untill right side is located, or it got stuk (max 10*voxelmap width loops)
toCheck = nowChecking.neighbours(8, (n) => n[0] > 0 && n[0] < voxels - 1);//gets 8 neighbour points around current point, neighbours between 1 and (voxelwidth -1) get returned
let foundNew = false;
for (let i = 0; i < toCheck.length; i++) {//check every neighbour
let x = toCheck[i][0],
y = toCheck[i][1],
index = y * voxels + x;
if (!checkedVoxels.includes(index)) {
if (this.voxels[x][y] && (!this.voxels[x][y - 1] || !this.voxels[x + 1][y] || !this.voxels[x - 1][y] || !this.voxels[x][y + 1])) {
//if the neighbour is a floor voxel, and touches a skyvoxel this neighbour is added to the line
checkedVoxels.push(index);
if (foundNew) {//if a valid neighbour is already found, this means there are 2 possible paths from the current point
otherPaths.push([x, y]);
} else {
lineGround.push([x / voxels, y / voxels]);
nowChecking = [x, y];
//valid point gets added to the line and currently explored point get updated
foundNew = true;
}
if (x >= voxels) done = true;
}
} else if (i == toCheck.length - 1 && !foundNew) {
if (otherPaths.length > 0) {
nowChecking = otherPaths.pop();
//if none of the neighbours are correct an alternative path gets explored
foundNew = true;
}
}
}
if (!foundNew || looped++ > voxels * 10) {
//if it never found a valid neighbour, or it's looped too often break from the whileloop
console.log('loops: ', looped);
break;
}
}
if (lineGround[0][0] !== 0) lineGround.splice(0, 0, [0, lineGround[0][1]]);
if (lineGround[lineGround.length - 1][0] !== 1) lineGround.push([1, lineGround[lineGround.length - 1][1]]);
//x=0 and x=1 have to exist, so if they don't exist yet, add them
return lineGround;
}
You can also test it here: game. If you click you remove (set to 0) a few voxels within a radius of where you clicked. Also the line gets recalculated.
I'm stuck on this, because I have no idea why the line stops in some situations.
All code is here. The relevant file is js/Level.js
There are more problems than the one you raised. I played a bit on your site and there are many patterns where things go wrong.
I tried to follow the logic of your code, but got lost in details. So I rewrote most of the code. The main idea is that you should keep record of which direction (slope) you are travelling along the ground in order to know in which order you should look among the neighbours for one that is part of the ground.
Let's say the neighbours are numbered as follows, from 0 to 7:
+---+---+---+
| 7 | 0 | 1 |
+---+---+---+
| 6 | * | 2 |
+---+---+---+
| 5 | 4 | 3 |
+---+---+---+
The cell marked with * is the last cell you found to be on ground level. Now let's say the previous one found was at 6, then the search among the neighbours should start at 7, then 0, 1, 2, ... 5. The first one that is found to be solid, should be the next cell added to ground level.
Another example: if the previous one found was at 4 (we're going upward), then the neighbours should be searched starting at 5, then 6, 7, 0, 1, 2 and 3.
The first neighbour that is found to be solid (ground) is the one you want to add to your ground line. This way you will follow every curve, into "caves", upward or downward, left or right.
Of course, things can still go weird if you start on an island. But I did not attempt to solve that particular case.
I've implemented the above idea in the following version of your method:
voxelToLine() {
let voxels = this.voxels.length, x, y, i;
// neighbors' relative coordinates listed in clockwise order
const neighbor = [ [0,-1], [1,-1], [1,0], [1,1], [0,1], [-1,1], [-1,0], [-1,-1] ];
for (y = 0; y < voxels; y++) //sets first coordinate for line.
if (this.voxels[0][y]) break; // found ground, don't look further down
let lineGround = [[0, y / voxels]];
let [curX, curY] = [0, y]; //search starts here
let direction = 0; // upward
let looped = 0;
do {// Continues search until right side is located,
// or it got stuk (max 10*voxelmap width loops)
for (i = 0; i < 8; i++) {//check every neighbour, starting at `direction`
[x, y] = [curX + neighbor[direction][0], curY + neighbor[direction][1]];
// if we found ground, then pick that cell as the next one on the line
if (x>=0 && x<voxels && y>=0 && y<voxels && this.voxels[x][y]) break;
direction = (direction + 1) % 8; // turn clockwise to get next neighbour
}
//if it never found a valid neighbour
if (i === 8) break;
lineGround.push([x / voxels, y / voxels]);
// prepare for next round
[curX, curY] = [x, y];
direction = (direction + 5) % 8;
} while (looped++ <= voxels*10 && curX < voxels - 1);
//x=0 and x=1 have to exist, so if they don't exist yet, add them
if (lineGround[0][0] !== 0) lineGround.splice(0, 0, [0, lineGround[0][1]]);
if (lineGround[lineGround.length - 1][0] !== 1)
lineGround.push([1, lineGround[lineGround.length - 1][1]]);
return lineGround;
}
Looks like it's skipping over the voxel right below the last legitimate ground voxel because it's already been "checked" (added to the checkedVoxels array).
Interestingly, this would prevent your ground path to ever turn 90 degrees (you'll notice your example picture doesn't have such a voxel pattern).
I'm building a turn based HTML game based on a 2D square grid. Each grid square could take a variable number of movement points to cross (IE: 1 MP for roads, 1.5 MP for grasslands, 2 MP for forests, etc). When the user clicks on a unit I want to determine all possible movable spaces with said unit's allotted movement points so that I can highlight them and make them clickable.
Is there a free library available to do this? I've seen a few pathing algorithms but nothing about determining movable area. How do other game developers handle this problem? I'm open to both vanilla JS and JQuery solutions.
Well, I decided to try and attack this myself. I've never been great at these sorts of algorithms so I'm sure there's a more efficient way to handle it than what I've done. However, for my purposes it runs quickly enough and is very simple and easy to understand.
In case it's helpful to anyone else looking to do the same, I've included the code below. This is an updated version of my original answer, which I modified to also store the path taken so that you can show the units moving through the correct spaces. This answer uses JQuery in the lower examples, but only in a few places; you can easily enough replace them with vanilla JS. And the first block of code, containing the actual path/area finding functionality, is pure JS.
<script>
var possibleMovementAreaArray = new Array(); // This array will hold our allowable movement tiles. Your other functions can access this after running possibleMovementArea().
function possibleMovementArea(unitIndex) {
// I'm storing each unit in my game in an array. So I pass in the index of the unit I want to determine the movement area for.
var x = unitList[unitIndex][10]; // x coordinate on the playgrid
var y = unitList[unitIndex][11]; // y coordinate on the playgrid
var mp = unitList[unitIndex][15]; // number of movement points
possibleMovementAreaArray.length = 0; // Clear our array so previous runs don't interfere.
findPossibleMovement(x, y, mp);
}
function findPossibleMovement(x, y, mp, prevStepX, prevStepY) {
// This is a recursive function; something I'm not normally too good at.
for (var d=1; d<=4; d++) {
// We run through each of the four cardinal directions. Bump this to 8 and add 4 more cases to include corners.
if (d == 1) {
// Check Up
var newX = x;
var newY = y - 1;
} else if (d == 2) {
// Check Down
var newX = x;
var newY = y + 1;
} else if (d == 3) {
// Check Left
var newX = x - 1;
var newY = y;
} else if (d == 4) {
// Check Right
var newX = x + 1;
var newY = y;
}
// Check to see if this square is occupied by another unit. Two units cannot occupy the same space.
spaceOccupied = false;
for (var j=1; j<=numUnits; j++) {
if (unitList[j][10] == newX && unitList[j][11] == newY)
spaceOccupied = true;
}
if (!spaceOccupied) {
// Modify this for loop as needed for your usage. I have a 2D array called mainMap that holds the ID of a type of terrain for each tile.
// I then have an array called terList that holds all the details for each type of terrain, such as movement points needed to get past.
// This for loop is just looking up the ID of the terrain for use later. Sort of like a "SELECT * FROM terrainInfo WHERE ID=terrainOfCurrentTile".
for (var j=1; j<=numTerrains; j++) {
if (newX > 0 && newX <= mapWidth && newY > 0 && newY <= mapHeight && terList[j][1] == mainMap[newX][newY])
break; // After finding the index of terList break out of the loop so j represents the correct index.
}
if (j <= numTerrains) { // Run if an actual terrain is found. No terrain is found if the search runs off the sides of the map.
var newMp = mp - terList[j][7]; // Decrement the movement points for this particular path.
if (newMp >= 0) { // Only continue if there were enough movement points to move to this square.
// Check to see if this square is already logged. For both efficiency and simplicity we only want each square logged once.
var newIndex = possibleMovementAreaArray.length
var alreadyLogged = false
if (possibleMovementAreaArray.length > 0) {
for (var j=0; j<possibleMovementAreaArray.length; j++) {
if (possibleMovementAreaArray[j][1] == newX && possibleMovementAreaArray[j][2] == newY) {
alreadyLogged = true;
var alreadyLoggedIndex = j;
}
}
}
if (!alreadyLogged) {
// This adds a row to the array and records the x and y coordinates of this tile as movable
possibleMovementAreaArray[newIndex] = new Array(6);
possibleMovementAreaArray[newIndex][1] = newX;
possibleMovementAreaArray[newIndex][2] = newY;
possibleMovementAreaArray[newIndex][3] = prevStepX; // This tracks the x coords of the steps taken so far to get here.
possibleMovementAreaArray[newIndex][4] = prevStepY; // This tracks the y coords of the steps taken so far to get here.
possibleMovementAreaArray[newIndex][5] = newMp; // Records remaining MP after the previous steps have been taken.
}
if (alreadyLogged && newMp > possibleMovementAreaArray[alreadyLoggedIndex][5]) {
// If this tile was already logged, but there was less MP remaining on that attempt, then this one is more efficient. Update the old path with this one.
possibleMovementAreaArray[alreadyLoggedIndex][3] = prevStepX;
possibleMovementAreaArray[alreadyLoggedIndex][4] = prevStepY;
possibleMovementAreaArray[alreadyLoggedIndex][5] = newMp;
}
if (newMp > 0) {
// Now update the list of previous steps to include this tile. This list will be passed along to the next call of this function, thus building a path.
if (prevStepX == '') {
var newPrevStepX = [newX];
var newPrevStepY = [newY];
} else {
// This code is required to make a full copy of the array holding the existing list of steps. If you use a simple equals then you just create a reference and
// subsequent calls are all updating the same array which creates a chaotic mess. This way we store a separate array for each possible path.
var newPrevStepX = prevStepX.slice();
newPrevStepX.push(newX);
var newPrevStepY = prevStepY.slice();
newPrevStepY.push(newY);
}
// If there are still movement points remaining, check and see where we could move next.
findPossibleMovement(newX, newY, newMp, newPrevStepX, newPrevStepY);
}
}
}
}
}
}
</script>
After running the above, you can then loop through the array to find all usable tiles. Here is how I did it:
<script>
// Shows the movement area based on the currently selected unit.
function showMovement() {
var newHTML = "";
curAction = "move";
possibleMovementArea(curUnit); // See above code
for (x=0; x<possibleMovementAreaArray.length; x++) {
// Loop over the array and do something with each tile. In this case I'm creating an overlay that I'll fade in and out.
var tileLeft = (possibleMovementAreaArray[x][1] - 1) * mapTileSize; // Figure out where to absolutely position this tile.
var tileTop = (possibleMovementAreaArray[x][2] - 1) * mapTileSize; // Figure out where to absolutely position this tile.
newHTML = newHTML + "<img id='path_" + possibleMovementAreaArray[x][1] + "_" + possibleMovementAreaArray[x][2] + "' onClick='mapClk(" + possibleMovementAreaArray[x][1] + ", " + possibleMovementAreaArray[x][2] + ", 0);' src='imgs/path.png' class='mapTile' style='left:" + tileLeft + "px; top:" + tileTop + "px;'>";
}
$("#movementDiv").html(newHTML); // Add all those images into a preexisting div.
$("#movementDiv").css("opacity", "0.5"); // Fade the div to 50%
$("#movementDiv").show(); // Make the div visible.
startFading(); // Run a routine to fade the div in and out.
}
</script>
Since we determined the path, we can easily show movement as well by looping through the stored information:
<script>
for (j=0; j<possibleMovementAreaArray[areaIndex][3].length; j++) {
// This loop moves the unit img to each tile on its way to its destination. The final destination tile is not included.
var animSpeed = 150; // Time in ms that it takes to move each square.
var animEase = "linear"; // We want movement to remain a constant speed through each square in this case.
var targetLeft = (possibleMovementAreaArray[areaIndex][3][j]-1) * mapTileSize; // This looks at each step in the path array and multiplies it by tile size to determine the new horizonal position.
var targetTop = (possibleMovementAreaArray[areaIndex][4][j]-1) * mapTileSize; // This looks at each step in the path array and multiplies it by tile size to determine the new vertical position.
$("#char_"+curUnit).animate({"left":targetLeft, "top":targetTop}, animSpeed, animEase); // Do the animation. Subsequent animations get queued.
}
// Now we need to move to that last tile.
newLeft = (x-1) * mapTileSize;
newTop = (y-1) * mapTileSize;
$("#char_"+curUnit).animate({"left":newLeft, "top":newTop}, 400, "easeOutCubic"); // Slow unit at the end of journey for aesthetic purposes.
$("#char_"+curUnit).addClass("unitMoved", 250); // Turns the image grayscale so it can easily be seen that it has already moved.
</script>
Hopefully this is helpful to others.
Hi everyone I started to write a little game with ball and bricks and have some problem with collision detection. Here is my code http://jsbin.com/ibufux/9 . I know that detection works though array, but I can't figure how I can apply it to my code.
Here is what i have tried:
bricksCollision: function() {
for (var i = 0; i < $bricks.length; i++) {
if ($ball.t == $bricks[i].offset().top) {
$bricks[i].splice(i, 1);
}
}
Every bricks in game are generated by for loop and then goes to $bricks array. Every brick after generating receive top and left position and have position absolute. I have tried to check if $ball.t (it's properties of my ball object which detects ball top position) reach the bricks and than remove bricks.
Thanks for any help. I'm only start to learn JS that's why my code is
knotty.
First of all, let'stalk about some code errors
$ball.t should be probably $ball.top
you do not need to have $ as a prefix, for your code, it's simply a variable and you are calling $ball instead of ball witch results in assumption errors!
for those assumption errors here is what you are doing wrong:
$ball is a dom element, not a jQuery element
the same as $bricks
$ball is an array
with those concluded from some console.log() let's try to fix the code:
the $ball should be called, as there's only one, by it's array element, as $ball[0] and because you have variables pointing to DOM elements and not jQuery elements, you need to wrap it in Jquery as:
if ( $($ball[0]).top === $($bricks[i]).offset().top ) { ...
a good idea not to get confused is only use $ in jQuery elements, prefixing it in a variable, does not make them a jQuery Element.
And everytime you see that you have an error such as "element x has no method y" always assume that you're calling a method from a DOM element, and not a jQuery element.
Now, that #balexandre has nicely explained some points about your code, lets examine how we can compute the collision.
Imagine 2 Ranges overlapping each other (Range a partly overlaps Range b)
[100 .|.. 300]
[200 ..|. 400]
The part overlapping is from | to | -> 200 to 300, so the size of the overlap is 100
If you look at the numbers, you notice, that the overlap could be seen like,
Take the smaller number of the right side -> 300
Take the greate number of the left side -> 200
Subtract them from each other -> 300 - 200 = 100.
Lets take a look at 2 other situations. (Range b completely in Range a)
[50 ... 150]
[75...125]
So the values we have are: Math.min (150,125) //125 for the end value and Math.max (50,75) // 75 for the start value, resulting in a value of 125 - 75 = 50 for the overlap
Let's take a look the last example (Range a not in Range b)
[50 ... 150]
[200 ... 300]
Using the above formula, yields the result Math.min (150 , 300 ) - Math.max (50,200) // -50 which absolutes' value is the gap between the 2 Ranges, 50
Now we can add a last condition, as you want to compute the collision, only values > 0 are of interest for us. Given this we can put it into one condition.
Math.min ((Brick["Right"],Ball["Right"]) - Math.max (Brick["Left"], Ball["Left"]) > 0)
Which will yield true if the elements' overlap and false if they don't.
Applying this to your code, we could compute the collision the following way
bricksCollision: function () {
for (var i = 0; i < $bricks.length; i++) {
var $brick = $($bricks[i]);
var offset = $brick.offset();
var brickBounds = [offset.left - field.l]; //brick left pos
brickBounds[1] = brickBounds[0] + 40 //bricks right pos -> left pos + .bricks.width;
var ballBounds = [ball.l]; //balls left pos
ballBounds[1] = ballBounds[0] + 20 //balls right pos -> left pos + #ball.width;
if (ball.t <= (offset.top + 20) && (Math.min(brickBounds[1], ballBounds[1]) - Math.max(brickBounds[0], ballBounds[0])) > 0) {
$bricks[i].style.opacity = 0; //Make the brick opaque so it is not visible anymore
$bricks.splice(i, 1) //remove the brick from the array -> splice on the array, not the element
return true;
}
}
}
With this we could return true to the move function, when the Ball collides with a Brick.
But hey, we want it to Bounce off in the right direction, so we will face another problem.
So rather then returning a Boolean value whether the Brick collides or not, we could return a new direction in which the Ball will should move.
To be able to easily change only the x or the y part of the direction, we should use something like a vector.
To do so, we could use 2 Bits of an Integer, where the bit b0 stays for the x direction and the bit b1 for the y direction. Such that.
Dec Bin Direction
0 -> 00 -> Down Left
^ -> Left
^ -> Down
1 -> 01 -> Down Right
^ -> Right
^ -> Down
2 -> 10 -> Up Left
^ -> Left
^ -> Up
3 -> 11 -> Up Right
^ -> Right
^ -> Up
But to be able to change only a part of the direction, we need to pass the old direction to the collision function, and use bitwise & and | respectively to turn them off or on
Also we have to compute from which side the ball collides.
Fortunatly we have overlap calculation from before, which already uses all values we need, to compute the direction of collision.
If it comes frome the
right
Brick ["Right"] - Ball["Left"] has to be the same value as the overlap.
left
Ball ["Right"] - Brick["Left"] has to be the same value as the overlap.
If none of them are true, it has to either come from the
bottom
if Ball["Top"] is more than ( Brick["Top"] plus half of the Brick["height"] )
or else from the top.
To reduce the range where the condition, for the collision from the side, evaluates to true we can add another condition that the overlap has to be less than e.g ... && overlap < 2
So if it collides with the edge it doesn't always bounce of to the side.
So enough the talking, in code this could look like something like this.
bricksCollision: function (direction) {
var newDirection = direction
var ballBounds = [ball.l]; //balls left pos
ballBounds[1] = ballBounds[0] + 20 //balls right pos -> left pos + #ball.width;
for (var i = 0; i < $bricks.length; i++) {
var $brick = $($bricks[i]);
var offset = $brick.offset();
var brickBounds = [offset.left - field.l]; //brick left pos
brickBounds[1] = brickBounds[0] + 40 //bricks right pos -> left pos + .bricks.width;
var overlap = Math.min(brickBounds[1], ballBounds[1]) - Math.max(brickBounds[0], ballBounds[0]);
if (ball.t <= ((offset.top - field.t) + 20) && overlap > 0) {
$bricks[i].style.opacity = 0; //Make the brick opaque so it is not visible anymore
$bricks.splice(i, 1) //remove the brick from the array -> splice on the array, not the element
if (ballBounds[1] - brickBounds[0] == overlap && overlap < 2) { //ball comes from the left side
newDirection &= ~(1); //Turn the right bit off -> set x direction to left
} else if (brickBounds[1] - ballBounds[0] == overlap && overlap < 2) { //ball comes from the right side
newDirection |= 1; // Turn the right bit on -> set x direction to right;
} else {
if (ball.t > (offset.top + (20 / 2))) //Ball comes from downwards
newDirection &= ~(2) // Turn the left bit off -> set y direction to down;
else //Ball comes from upwards
newDirection |= 2; // Turn the left bit on -> set y direction to up;
}
//console.log("Coming from: %s Going to: %s", field.directionsLkp[direction], field.directionsLkp[newDirection], direction)
return newDirection;
}
}
return direction;
}
To get that to work, we should also change the moveXX functions, to use the new direction, returned.
But if we are going to get the new direction from the collision function anyway, we could move the complete collision detection to the function, to simplify our move functions.
But before that, we should have a look at the move functions and, add a lookup object to field which holds the numbers for the direction, to maintain readability.
var field = {
directions: {
uR : 3, // 11
dR : 1, // 01
dL : 0, // 00
uL : 2 // 10
},
directionsLkp: [
"dL","dR","uL","uR"
],
...
}
Now the move functions could then look like this,
ballCondact: function () {
var moves = [moveDl,moveDr,moveUl,moveUr]
var timeout = 5;
function moveUr() {
var timer = setInterval(function () {
$ball.css({
top: (ball.t--) + "px",
left: (ball.l++) + "px"
})
var newDirection = game.bricksCollision(field.directions.uR) //get the new direction from the collision function
if (newDirection !== field.directions.uR) {
clearInterval(timer);
moves[newDirection](); //move in the new direction
}
}, timeout);
}
...
}
Like this, the move function simply changes the direction if the collision function returns a direction which differs from the current one.
Now we can start moving the wall collisions to the collision function, to do this we could add another check at the beginning.
bricksCollision: function (direction) {
...
if (ball.t <= field.t)
newDirection &= ~(2); //Ball is at top, move down
else if (ball.l <= 0) //Ball is at the left, move right
newDirection |= 1;
else if (ball.t >= field.b - ball.height) //Ball is at the bottom, move up
newDirection |= 2;
else if (ball.l > field.width - ball.width) //Ball is at the right, move left
newDirection &= ~(1);
if (direction !== newDirection)
return newDirection
...
}
Note, i left out the collision check for the platform, as the idea should be clear =)
Here is a Fiddle