Currently i am working on bouncing a ball of a wall, in classic 2D-geometry.
The wall can be hit on both vertical and horizontal sides, and the result of the reflection depends on which side the ball hits.
I have tried some different solutions, but they just make me more confused.
How can i determine whether the ball is hitting a vertical or horizontal side of the wall?
PseudoCode for the overview:
iterate through each wall
if (collision between ball and wall)
determine if vertical/horizontal hit
calculate new velocity for ball
I use this code for collision detection and it works like a charm:
source: Circle-Rectangle collision detection (intersection)
var isCollision = function (_projectile) {
if(direction != undefined){
var circleDistance = {};
circleDistance.x = Math.abs(_projectile.getCenter().x - centerX);
circleDistance.y = Math.abs(_projectile.getCenter().y - centerY);
if (circleDistance.x > (width/2 + _projectile.getRadius())) { return false; }
if (circleDistance.y > (height/2 + _projectile.getRadius())) { return false; }
if (circleDistance.x <= (width/2)) { return true; }
if (circleDistance.y <= (height/2)) { return true; }
var cornerDistance_sq = square(circleDistance.x - width/2) + square(circleDistance.y - height/2);
return (cornerDistance_sq <= (square(_projectile.getRadius())));
}
return false;
};
var square = function(_value){
return _value * _value;
};
Thank you!
update after question update
Asuming the ball has a direction/velocity vector dx,dy and has a radius of r (ball.x, ball.y are the ball positions of the ball center) do sth like this (have used similar in a billiard game, it is basic geometry and physics):
if (ball.x+ball.dx+r > wallV.x) ball.dx = -ball.dx // ball bounces off the
vertical wall
if ( ball.y+ball.dy+r > wallH.y ) ball.dy = -ball.dy // ball bounces off horizontal wall
do similar for opposite walls if needed, since velocity vector changes, the new ball position after bouncing (or anyway) will be ball.x += ball.dx; ball.y += ball.dy;
Bonus if you add some friction factor (meaning the magnitudes of dx dy eventually fade away to zero, the ball eventually stops after following a path)
To solve properly a collision, you can't just test on X => if collision solve and return THEN test on y => if collision solve on y.
You have to check which axe (x OR y) collides first, and there's also the case when the ball hits two walls at the same time.
So you can't just reason with space : you have to deal with time, and compute an ETA -Estimated Time Arrival- for all walls, based on the ball speed (assuming walls are still).
pseudo code :
minETA = a too big number;
colliderList = [];
for (each wall in walls) {
thisWallETA = computeETA(ball, wall);
if (thisWallETA > minETA) continue;
if (thisWallETA < minETA) {
minETA = thisWallETA;
colliderList.length = 0;
colliderList.push(wall);
continue;
}
if (thisWallETA == minETA) {
colliderList.push(wall);
continue;
}
}
Then when the colliderList has only one item => solve on corresponding axe. If it has two items => solve on both axes.
Then increase the current time by minETA, and try again until the currentTime + minETA you found is > to thisCycleTime + thisCycleDt.
If you're interested, i might clarify some things up.
Good luck !
Related
I'm working on a parallax sidescroller game in PixiJS for a school project, and I'm running into a bit of bother with getting things to work properly. Essentially, I have three backgrounds (using a class that extends from PIXI.extras.TilingSprite) that scroll at different speeds and a player (moving in a completely different way) when the user presses either of the arrow keys. I'm also using the Pixi Viewport extension (https://github.com/davidfig/viewport), to get a nice and clean view of the game following the player. It looks great and all (despite some issues I need to fix with shaders and what not), but now I'm trying to add static sprites into the game, and that unfortunately does not work as I thought it would. Here's a GIF of how it looks in-game:
Example GIF
Please excuse the awful quality, I had to jump through a few hoops to record that footage and make it into a GIF format.
Here's the relevant classes that I've made thus far :
class Monster extends PIXI.Sprite
{
constructor(texture, x, y)
{
super(texture);
this.anchor.x = 0.5;
this.x = x;
this.y = y;
}
}
class ParallaxLayer extends PIXI.extras.TilingSprite{
constructor(texture, deltaX = 0.5){
super(PIXI.loader.resources[texture].texture, viewport.worldWidth, viewport.worldHeight);
this.vx = 0;
this.viewportX = 0;
this.deltaX = deltaX;
this.filters = [];
this.setViewportX(-680);
this.x = -680;
}
setViewportX(newViewportX){
if (newViewportX > 0)
{
let distanceTravelled = newViewportX - this.viewportX;
this.viewportX = newViewportX;
this.tilePosition.x -= (distanceTravelled * this.deltaX);
}
}
getViewportX(){
return this.viewportX;
}
}
And here's the code that's running in my game loop to move the parallax backgrounds and the player:
// changes 'x' tile position (parallax scrolling effect
for (var i = 0; i < bgTilingSprites.length; i++)
{
if (bgTilingSprites[i].vx != 0)
{
let scale = bgTilingSprites[i].tileScale.x;
let speed = deltaTime * scale * bgTilingSprites[i].vx;
bgTilingSprites[i].setViewportX(bgTilingSprites[i].viewportX + ((i+1) * speed));
//bgTilingSprites[i].tilePosition.x -= (i + 1) * speed;
// it's "fix" a tilisprite position bug
var width = bgTilingSprites[i].texture.width * scale;
if (bgTilingSprites[i].tilePosition.x >= width)
{
//bgTilingSprites[i].tilePosition.x -= width;
bgTilingSprites[i].setViewportX(width);
}
else if (bgTilingSprites[i].tilePosition.x <= -width)
{
//bgTilingSprites[i].tilePosition.x += width;
bgTilingSprites[i].setViewportX(-width);
}
}
}
// If the player's x velocity is not 0
if (player.vx != 0)
{
// Calculate player speed
let playerSpeed = deltaTime * player.vx;
// Increment player's position by their speed
if (player.x > -675)
{
player.x += playerSpeed;
// Animate the player
if (player.playing != true)
{
player.texture = playerWalkFrames[0];
player.play();
}
}
}
else
{
if (player.playing != false){
player.gotoAndStop(0);
player.texture = playerTexture;
player.footstepToPlay = 0;
player.footstepDelay = 0;
}
}
If there is any other information that would be beneficial to helping me solve this conundrum, I would be happy to provide. I'm still very new to Stack Overflow (and I'm nowhere near an expert on JavaScript), so I would greatly appreciate advice that would help me get the best help possible for this.
How do you want it to look?
Right now I see the background layers are moving past the monster, so it looks like the monster is actually moving to the right, correct?
Well, all the parallax backgrounds are actually moving to the left, so the whole "world" is moving to the left.
Now, the monster is not moving at all (just the viewport extension camera is moving, that’s why the monster moves on the screen)
But since everything is moving to the left, makes sense that a static sprite will seem to be moving to the right.
You move the backgrounds relative to the players movement.
perhaps you should do the same to the monster, make it move like the backgrounds at a speed somewhere between the 1st and 2nd layer.
If a circle hits a rectangle and needs to bounce, I need to calculate its new direction.
This is what I have
function tick() {
var dx = Math.cos(ball.direction * (Math.PI / 180))*ball_md.speed;
var dy = Math.sin(ball.direction * (Math.PI / 180))*ball_md.speed;
ball.x += dx;
ball.y -= dy;
drawGame(); // refresh board
//console.log(ball);
paddles.some(function(paddle) {
var circle={x:ball.x+ball_md.radius, y:ball.y+ball_md.radius, r:ball_md.radius};
var rect={x:paddle.x, y:paddle.y, w:game_md.paddle.width, h:game_md.paddle.height};
var hit = RectCircleColliding(circle, rect);
if (hit) {
if (Math.floor(ball.y) + ball_md.radius*2 <= paddle.y || Math.ceil(ball.y) >= paddle.y + game_md.paddle.height) { // hit on top or below paddle
ball.direction = 360 - ball.direction;
} else { // hit left or right side
ball.direction = 180 - ball.direction;
}
return true;
}
});
if (ball.y < 0 || ball.y+ball_md.radius*2 >= game_md.height) { // hit top or bottom wall
ball.direction = 360 - ball.direction;
}
if (ball.x < 0 || ball.x+ball_md.radius*2 >= game_md.width) { // hit left or right wall
ball.direction = 180 - ball.direction;
}
}
but it doesn't seem to always work. Does anyone know why?
Cases when it fails. In this case, it zigzags really fast on the paddle surface.
DEMO: https://jsfiddle.net/3ok2farw/2/
You need to treat the corner collision separately. When it hits the corner the velocity of the ball doesn't change perpendicular to either the horizontal or vertical axis, but along the line connecting the center of the ball and the corner. I'm adding a drawing to make it a bit clearer. If the collision is perfectly elastic (no energy loss), then the v_normal component gets replaced by its negative, -v_normal. If the collision is perfectly plastic (maximum energy loss), the exit velocity is just v_tangential. Hope this helps!
I'm making a 2D game in JavaScript. For it, I need to be able to "perfectly" check collision between my players(the game has two players, open the picture please) and the walls! I mean, I have a function that actually works, but when I make them jump against the walls they pass through the walls and keep moving until they reach another area or even leave the canvas!
Also, if they are falling down and I make them collide with a wall, they just stop there wich is also pretty bad!
I really need help with that!! It's a university project and I have to finnish it really soon!
My game looks like this
The collision detection function I have is here:
function blockRectangle (objA, objB) {
var distX = (objA.x + objA.width / 2) - (objB.x + objB.width / 2);
var distY = (objA.y + objA.height / 2) - (objB.y + objB.height / 2);
var sumWidth = (objA.width + objB.width) / 2;
var sumHeight = (objA.height + objB.height) / 2;
if (Math.abs(distX) < sumWidth && Math.abs(distY) < sumHeight) {
var overlapX = sumWidth - Math.abs(distX);
var overlapY = sumHeight - Math.abs(distY);
if (overlapX > overlapY) {
objA.y = distY > 0 ? objA.y + overlapY : objA.y - overlapY;
}
else {
objA.x = distX > 0 ? objA.x + overlapX : objA.x - overlapX;
}
}
}
I did the walls with a maze and I'm using a for cycle to check the collisions with all of the walls I have saved in an array!
As you can see here:
for (var i in walls) {
var wall = walls[i];
if ((player.x < (wall.x + wall.width)) && ((player.x + player.width) > wall.x) && (player.y < (wall.y + wall.height)) && ((player.height + player.y) > wall.y)) {
player.falling = false;
}
blockRectangle(player, wall);
}
Please help me!!! Thank you all!
In your case I doubt a pixel perfect collision is required.
You can maintain a boolean matrix to store the position of solid objects. Solid objects like walls or players. Then in every frame you can check if your player is trying to move to a position where there is a solid object, if it is then stop it. You don't have to create grid of width x height in pixels, but rather choose a largest block (single element in the grid) in which each solid object reasonably occupies most of the block.
For example you can choose block size to be player_width / 2 x player_height /2.
See following image with grid
Another simple way could be to just check the background pixel color. Since your game is simple, background and object colors are different. So you just have to check if the player is trying to move somewhere where pixel color is not of background, thus there is a solid object and player should stop. You don't have to test for a lot of pixels, just 1 pixel in the direction the player is trying to move. (1 for horizontal and 1 for vertical). This however can not be used if you don't have a clear background color. Background color here is kind of the boolean grid for us in the previous suggestion.
function distance(x1, y1, x2, y2) {
var x = x1 - x2;
var y = y1 - y2;
return(Math.sqrt((x*x) + (y*y)))
};
function collisionCirc(circ1, circ2) {
var d = distance(circ1.x, circ1.y, circ2.x, circ2.y);
var r = circ1.radius + circ2.radius;
return(r > d);
};
function collisionCircPoint(circ1, circ2) {
var cx = ((circ1.x * circ2.radius) + (circ2.x * circ1.radius)) / (circ1.radius + circ2.radius);
var cy = ((circ1.y * circ2.radius) + (circ2.y * circ1.radius)) / (circ1.radius + circ2.radius);
var p = [cx, cy];
return p;
};
function angleDegrees(x1, y1, x2, y2) {
return (Math.atan2(y2 - y1, x2 - x1) * 180 / Math.PI) + 180;
};
function updateCollisions() {
var a;
var p;
Player.hitArea = new PIXI.Circle(Player.sprite.x, Player.sprite.y, 20);
MapObjects.chest.hitArea = new PIXI.Circle(MapObjects.chest.x, MapObjects.chest.y, 20);
if (collisionCirc(Player.hitArea, MapObjects.chest.hitArea)) {
a = angleDegrees(Player.sprite.x, Player.sprite.y, MapObjects.chest.x, MapObjects.chest.y);
p = collisionCircPoint(Player.hitArea, MapObjects.chest.hitArea);
Player.sprite.x = p[0];
Player.sprite.y = p[1];
};
};
I have 2 sprites on the map and each has a circle hitArea defined. I am trying to make a smooth circular collision that the player cannot pass through. I thought I could just set the Player.sprite's coordinates to the point of collision but it just warps him to the MapObjects.chest's coordinates, even though the point of collision is correct and is 20 pixels from the MapObject.chest's center. What am I doing wrong or what more information is needed to create a collision much like the JavaScript physics libraries where I can circle around a circle object?
The collision point is between the player and the obstacle. If you move the player towards the collision point, you are actually moving the player closer. For example, if there's exactly 40 px (r1+r2) between the player and the obstacle, the collision point is between them, at only 20 px from the obstacle!
When you have multiple objects, getting it right when the collision has already happened is difficult. If there is only one obstacle nearby, you can simply move the player directly away from the obstacle. However, this way the player might actually end up inside another obstacle.
Another solution is to go back to the start and try smaller movements, until there is no collision. This way you would eventually get it right, but this might also be slow.
The mathematically correct solution is to calculate the maximum distance to move before the collision happens. This is done by solving the following vector equation:
# p = player position before moving
# o = obstacle position
# u = player direction (unit vector)
# d = distance to move
distance(o, p + d * u) = o.radius + p.radius
That's mathematics, you may solve it by yourself or using a tool like Wolfram Alpha.
Solving this equation will give you zero, one or two possible values for the distance. Negative values you can dismiss, as they mean that the player is already past the obstacle. If you get only one value, it means that the player would merely brush the obstacle, which you can also dismiss. Two values mean that the collision happens between these distances; the smaller value is where the collision starts, and the larger value is where the player would already be through the obstacle. Also, if one value is positive and the other is negative, it means that the player is already inside the obstacle, which should never happen.
You should run this check for all nearby obstacles and then move the player according to the smallest non-negative result (that is, zero or positive), or less, if the player can't move that fast.
Finally, to circle around a round object, you can move the player a little bit in a perpendicular direction (either left or right, depending on which side of the obstacle the player will be passing) after a collision, if this doesn't cause any new collisions.
There are many other possible implementations.
Player.hitArea = new PIXI.Circle(Player.sprite.x, Player.sprite.y, 20);
MapObjects.chest.hitArea = new PIXI.Circle(MapObjects.chest.x, MapObjects.chest.y, 20);
if (collisionCirc(Player.hitArea, MapObjects.chest.hitArea)) {
p = collisionCircPoint(Player.hitArea, MapObjects.chest.hitArea);
a = angleDegrees(Player.sprite.x, Player.sprite.y, MapObjects.chest.x, MapObjects.chest.y);
if (Player.sprite.x - MapObjects.chest.x > 0) {
Player.sprite.x += 1;
} else if (Player.sprite.x + MapObjects.chest.x > 0) {
Player.sprite.x -= 1;
};
if (Player.sprite.y - MapObjects.chest.y > 0) {
Player.sprite.y += 1;
} else if (Player.sprite.y + MapObjects.chest.y > 0) {
Player.sprite.y -= 1;
};
};
};
I added that and it actually works well enough minus the player speed being slightly too fast when running into the MapObjects.chest's hitArea at certain angles. Work on that later.
I'm trying to replicate a game called haxball, which is a really simple and basic 2d football game. However I am having trouble on the collision detection and I didn't want to use a engine like Box2d because it's a bit of overkill for what I want and I'm making the game just to practice, since I'm a beginner.
I can check if the collision happens, but I can't resolve it properly. I loop through all objects and check if they are colliding with the ball and then, if they are, I put the ball at the "border" of the object so that it stops being "inside" the other.
The problem comes here, because if the ball collides with a circle and a edge at the same time it will stay inside the edge or inside the circle.
This is the code of collision resolving for the circle and detection and resolving of the edge:
this.resolveCollisionCircle = function(circle) {
var nv = circle.pos.sub(this.pos);
nv = nv.setMag(circle.radius + this.radius).add(circle.pos);
this.pos = nv;
// I'll try to add later the bounce effect
}
this.edgeCollision = function() {
if(this.pos.x-this.radius < 0) {
this.pos.x = this.radius;
this.velocity = this.velocity.mult(new Vector(-1, 1));
}
else if(this.pos.x+this.radius > Width) {
this.velocity = this.velocity.mult(new Vector(-1, 1));
}
if(this.pos.y-this.radius < 0) {
this.pos.y = this.radius;
this.velocity = this.velocity.mult(new Vector(1, -1));
}
else if(this.pos.y+this.radius > Height) {
console.log('baixo')
this.velocity = this.velocity.mult(new Vector(1, -1));
}
}
The ball moves accordingly to a velocity vector, in this case it starts as (-4,0)
Demo of the bug: http://antoniomc.0fees.us/
Also! If you could point me to a good canvas tutorial that could teach me this things, I would appreciate it! I only seem to find for another languages, which helped me anyway, but it would be nice to see a canvas collision detection and resolution tutorial once in a while...
In .resolveCollisionCircle(), store the old position, change the position, and revert back to the old position and stop the ball completely if the new position is outside of the canvas.
this.resolveCollisionCircle = function(circle) {
//previous position
var prevPos = this.pos;
//set new position
var nv = circle.pos.sub(this.pos);
nv = nv.setMag(circle.radius + this.radius).add(circle.pos);
this.pos = nv;
//change back if out of canvas
if ((this.pos.x-this.radius < 0) || (this.pos.x+this.radius > Width) || (this.pos.y-this.radius < 0) || (this.pos.y+this.radius > Height)) {
this.pos = prevPos;
this.velocity = this.acceleration = new Vector(0, 0);
}
}