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.
Related
I have completed a tutorial on how to create a simple brick breaker game in Javascript and now would like to expand on it. I have run into an issue of how to differentiate between a side/side collision and a top/bottom collision.
Right now, the collision detection function I have only checks if there has been a collision. Then in a separate class, if the collision function returns true, it negates the y coordinate of the trajectory, but not the x. So when the ball collides with either the top or bottom of a brick, it will change y directions which works, but if it collides with the side of a brick, it keeps traveling in the same x direction.
Here's the collision function:
export function objectCollision(ball, gameObject){
//Position of Ball
let topOfBall = ball.position.y;
let bottomOfBall = ball.position.y + ball.height;
let leftSideOfBall = ball.position.x;
let rightSideOfBall = ball.position.x + ball.width;
//Position of Object
let topOfObject = gameObject.position.y;
let bottomofObject = gameObject.position.y + gameObject.height;
let leftSideOfObject = gameObject.position.x;
let rightSideOfObject = gameObject.position.x + gameObject.width;
//Split up the top/bottom and left/right hit detection (causes abnormal behavior)
//Need to negate the x trajectory
//Set ball.speed.x = -ball.speed.x if left/right hit detection
if(bottomOfBall >= topOfObject && topOfBall <= bottomofObject && leftSideOfBall >= leftSideOfObject && rightSideOfBall <= rightSideOfObject){
//ball.speed.x = -ball.speed.x;
return true;
}
else
return false;
}
Then in the brick class, we change the y coordinate of the trajectory.
//Brick class
import { objectCollision } from "/src/collisionDetection"
export default class Brick{
//......
update(deltaTime){
if(objectCollision(this.game.ball, this)){
this.game.ball.speed.y = -this.game.ball.speed.y;
this.isHit = true;
}
}
//.....
}
My issue is that if I check for y coordinate collisions separate from the x coordinate collisions, it will detect a collision when the ball's x coordinate is the same as a brick's, even if the ball is lower/higher than the brick. I've tried checking for y's first and then in a nested if() check the x's, but it yielded the same result.
Something along the lines of:
if(bottomOfBall >= topOfObject && topOfBall <= bottomofObject && leftSideOfBall){
if(leftSideOfBall >= leftSideOfObject && rightSideOfBall <= rightSideOfObject){
ball.speed.x = -ball.speed.x;
return true;
}
}
But it gave the same abnormal behavior or broke collision all together.
Hopefully it's something simple that I'm just not seeing and an extra pair of eyes would fix. I really don't want to completely redo the collision detection.
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.
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.
Im currently working on getting my player sprite to move around my screen, but when a key is pressed all the sprite seems to do is disappear! I have no errors coming up in firebug, so i am assuming that the sprite isn't being redrawn correctly or something along those lines.
Here is my code for my player:
function Player()
{
var sprite = new Sprite(),
player,
x,
y,
w = sprite.width,
h = sprite.height,
speed = 4;
this.init_Player = function(pos_X, pos_Y){
player = sprite.load("player");
x = pos_X;
y = pos_Y;
};
this.update = function(delta) {
var calculated_speed = (speed * delta) * (60/1000);
$(document).keydown(function(e)
{
var cancel_default = (e.which === 32 || (e.which > 36 && e.which < 41));
cancel_default && e.preventDefault();
if(e.keyCode == 37){
x -=calculated_speed;
}
else if(e.keyCode == 38){
y -=calculated_speed;
}
else if(e.keyCode == 39){
x +=calculated_speed;
}
else if(e.keyCode == 40){
y +=calculated_speed;
}
});
};
this.draw = function() {
ctx.drawImage(player,x, y, w ,h);
};
}
The player is created in my main game javascript file like so:
player.init_Player(location_X,location_Y);
And then in my main game loop i have the now, delta and last times being made as well as the call to player.update and player.render like so:
function update(){
now = Date.now();
delta = now - last_update;
ctx.clearRect(0,0,canvas.width,canvas.height);
gameGUI.update();
player.update(delta);
player.draw();
last_update = now;
setTimeout(update,1);
}
Like i said at the top, all my sprite does on a key press is disappear. The code you can see above is all the code i have for the player so somewhere in here is the bug!
How would i accomplish making my sprite move on screen with a time-based animation like the one i've set up?
Thanks
EDIT
Also to let you know, i have last_update equal to Date.now() the line before my update call gets made initially like so:
function game_init(state) {
game_settings(state);
last_update = Date.now();
update();
}
Edit 2
On continued inspection, it doesn't seem like the sprite is disappearing after all, just moving very far e.i off the game screen... so another guess is that my calculations are wrong somewhere?
All sorted guys, i have my character moving using this little tutorial! Real easy to read and understand for the beginners out there.
LINK
What do the pros think of this tutorial?
For my education I have to make a basic game in HTML5 canvas. The game is a shooter game. When you can move left -> right and space is shoot. When I shoot the bullets will move up. The enemy moves down. When the bullet hits the enemy the enemy has to dissapear and it will gain +1 score. But the enemy will dissapear after it comes up the screen.
Demo: http://jordikroon.nl/test.html
space = shoot + enemy shows up
This is my code:
for (i=0;i<enemyX.length;i++) {
if(enemyX[i] > canvas.height) {
enemyY.splice(i,1);
enemyX.splice(i,1);
} else {
enemyY[i] += 5;
moveEnemy(enemyX[i],enemyY[i]);
}
}
for (i=0;i<bulletX.length;i++) {
if(bulletY[i] < 0) {
bulletY.splice(i,1);
bulletX.splice(i,1);
} else {
bulletY[i] -= 5;
moveBullet(bulletX[i],bulletY[i]);
for (ib=0;ib<enemyX.length;ib++) {
if(bulletX[i] + 50 < enemyX[ib] ||
enemyX[ib] + 50 < bulletX[i] ||
bulletY[i] + 50 < enemyY[ib] ||
enemyY[ib] + 50 < bulletY[i])
{
++score;
enemyY.splice(i,1);
enemyX.splice(i,1);
}
}
}
}
Objects:
function moveBullet(posX,posY) {
//console.log(posY);
ctx.arc(posX, (posY-150), 10, 0 , 2 * Math.PI, false);
}
function moveEnemy(posX,posY) {
ctx.rect(posX, posY, 50, 50);
ctx.fillStyle = '#ffffff';
ctx.fill();
}
Further to Ben's correction, for future reference it's better to do a line-line collision detection, rather than your point-box collision detection.
The reason for this is, say your enemy is small and your bullet is moving fast. There is always a chance the bullet will appear BEFORE the enemy in one frame, and then behind the enemy in the next, therefore never registering as a hit.
Testing for an intersect with the bullet path and an imaginary line on the enemy will be far more accurate.
ORI think I see a problem. You have the following code on your collision detection
if(bulletX[i] + 50 < enemyX[ib] ||
enemyX[ib] + 50 < bulletX[i] ||
bulletY[i] + 50 < enemyY[ib] ||
enemyY[ib] + 50 < bulletY[i])
This is straight ORs(||), and the +50 is on the less than side. That means that it should actually trigger as true whenever the bullet is not in the hitbox. I suspect that you instead want to have the +50s on the greater than side, and have the ORs(||) be ANDs(&&) instead.