I'm working on a JS game. At some point a character's bullet needs to travel and hit a target from any angle. Ive tried something like this in the game loop:
if (bx < targetX-speed) bx += speed;
if (bx > targetX+speed) bx -= speed;
if (by < targetY-speed) by += speed;
if (by > targetY+speed) by -= speed;
Obviously it can only travel at 0 and 45 degree angles and that just looks horrible.
I thought of bringing geometry into play by calculating the angle before the bullet fires as such:
angle = Math.atan((by-targetY)/(bx-targetX));
Knowing the angle I can probably calculate either bx or by increasing one parameter i.e.:
by += speed;
bx = by*Math.tan(angle);
The only problem is that I cant do both at once. And I wouldn't be able to use the same for all angles.
Does anyone have a better solution?
Thanks in advance <3
Walt
You've got the solution (though personally I'd use sin instead of tan because tan is discontinuous). The only thing is you're confusing the coordinate system.
The solution is:
angle = Math.atan((shooterY-targetY)/(shooterX-targetX));
Calculate that only once when the bullet is fired then store that angle in a variable. Then you can do:
by += speed;
bx = by*Math.tan(angle);
Additional answer
My personally preferred solution is:
var dy = shooterY-targetY;
var dx = shooterX-targetX;
var distance = Math.sqrt(dy*dy+dx*dx);
var angle = Math.asin(dy/distance);
Then calculate dy and dx for the bullet:
var speed = SOME_SPEED;
var b_dy = Math.sin(angle) * speed;
var b_dx = Math.cos(angle) * speed;
Then move the bulled each frame:
by += b_dy;
bx += b_dx;
Related
So I am creating a simulation of a bouncing ball, and the user can place lines the ball can collide with on the canvas by dragging from one point to another. There are essentially four lines that can be created:
So the object that stores a line is defined as such:
export interface pathSection {
xfrom: number;
yfrom: number;
xto: number;
yto: number;
length: number;
}
The first and third lines in the image for example dont give the same value from
Math.atan2(yto - yfrom, xto - from);
So given the (relative) complexity of the surfaces, I need to find the angle between a moving object and that surface at the point of collision:
The ball strikes the surface at an angle a, which is what I want!
However I am having trouble finding the angle between the two vectors. This is what I understood would work:
var dx = this.path[index_for_path_section].xfrom - this.path[index_for_path_section].xto;
var dy = this.path[index_for_path_section].yfrom - this.path[index_for_path_section].yto;
var posX = this.particle.pos.x;
var posY = this.particle.pos.y;
var posNextX = posX + this.particle.v.x;
var posNextY = posY + this.particle.v.y;
var angleOfRamp = Math.atan2(dy, dx);
var angleOfvelocity = Math.atan2(posNextY - posY, posNextX - posX);
var angleBetween = angleOfRamp - angleOfvelocity;
This is then used to calculate the speed of the object after the collision:
var spd = Math.sqrt(this.particle.v.x * this.particle.v.x + this.particle.v.y * this.particle.v.y);
var restitution = this.elasticity / 100;
this.particle.v.x = restitution * spd * Math.cos(angleBetween);
this.particle.v.y = restitution * spd * Math.sin(angleBetween);
However the angle calculated is around -4.5 Pi, about -90 degrees for the object directly down and the surface at what looks to be around 45-60 degrees…
The red arrow shows the path of the object moving through the surface - the white dots show where a collision has been detected between the surface and the object.
Any help on how to get the correct and usable angle between the two velocity and the line would be appreciated!
Note I have tried utilizing this solution, but have struggled to adapt it to my own work.
So it took me some time, and I am not 100% sure still of why it works because I think im finding the JavaScript angles system a bit tricky, but:
var dx = this.path[collided].xfrom - this.path[collided].xto;
var dy = this.path[collided].yfrom - this.path[collided].yto;
var spd = Math.sqrt(this.particle.v.x * this.particle.v.x + this.particle.v.y * this.particle.v.y);
var angleOfRamp = Math.atan2(dy, dx);
var angleOfvelocity = Math.atan2(this.particle.v.y, this.particle.v.x);
var angleBetween = angleOfRamp * 2 - angleOfvelocity; // not sure why :)
if (angleBetween < 0) { angleBetween += 2*Math.PI; } // not sure why :)
const restitution = this.elasticity / 100;
this.particle.v.x = restitution * spd * Math.cos(angleBetween);
this.particle.v.y = restitution * spd * Math.sin(angleBetween);
Thanks to all who looked :)
I've been working on this problem for a bit, and it doesn't seem too hard, but I'm getting tired and it seems more and more complicated the more I try (but it's probably really easy).
My goal is to have a ball bounce off another ball. Seems easy enough.
Ball 2 is controlled by the user's mouse (so far it's sort of like single player pong, but it's a circle instead of a rectangle) so its velocity doesn't matter.
Ball 1 has a few attributes, including dx (the x distance it moves every frame) and dy (dx, but for the y coordinate)
The problem with what I have so far is that you don't know what values will be positive and what will be negative (so the speed can severely increase or decrease instantly), you might be able to fix this using many else if's, but I'm too confused to think right now.
Here is the important part of this function. Also, I've tried to set it up so that dx + dy is always the same, even when the numbers change, so that it looks more natural.
if (collision(ball, paddle)) {
diffX = paddle.x-ball.x;
diffY = paddle.y-ball.y;
totalVel = ball.dx+ball.dy;
dir = {
x : diffX/(diffX+diffY)*-totalVel,
y : diffY/(diffX+diffY)*-totalVel
};
ball.dx = dir.x;
ball.dy = dir.y;
}
Here is a JSFiddle with the full code
https://jsfiddle.net/a2prr0uw/1/
So firstly let's start by defining what a "bounce" is - the speed is the same, but the direction (on both axis) will be inverted. If we treat dx and dy like a vector, then we can first get the incoming speed of the ball like this:
var ballSpeed = Math.sqrt((ball.dx * ball.dx) + (ball.dy * ball.dy));
The above value will always be positive, regardless of what dx and dy are doing.
Next, we'll need the incoming direction of the ball - that bit is the same as what you've currently got:
diffX = paddle.x-ball.x;
diffY = paddle.y-ball.y;
However if we treat this as a vector too, it essentially has a totally unknown length. So, let's normalise it so it's a direction vector with a length of 1:
var distanceBetweenPaddleAndBall = Math.sqrt((diffX * diffX) + (diffY * diffY));
diffX /= distanceBetweenPaddleAndBall;
diffY /= distanceBetweenPaddleAndBall;
diffX and diffY is now a normalised direction vector - the direction the ball is currently going in - and ballSpeed is the speed we'd like it to go.
So now we'll apply our bounce - flip the direction and retain the speed. That becomes this:
dir = {
x : -diffX * ballSpeed,
y : -diffY * ballSpeed
};
Put it all together and we end up with this:
if (collision(ball, paddle)) {
diffX = paddle.x-ball.x;
diffY = paddle.y-ball.y;
// How fast is the ball coming in?
var ballSpeed = Math.sqrt((ball.dx * ball.dx) + (ball.dy * ball.dy));
// How far is the ball from the paddle?
var distanceBetweenPaddleAndBall = Math.sqrt((diffX * diffX) + (diffY * diffY));
// Normalise diffX and diffY so we have a direction vector:
diffX /= distanceBetweenPaddleAndBall;
diffY /= distanceBetweenPaddleAndBall;
// Apply the bounce and the original ball speed:
dir = {
x : -diffX * ballSpeed,
y : -diffY * ballSpeed
};
ball.dx = dir.x;
ball.dy = dir.y;
}
And here it is as a fork of your fiddle too.
not an answer but some considerations on your bouncing logic:
you have to calculate the balls direction (dy/dx)
the collision has also a direction (angle beween both centers = b.x-p.x / b.y-p.y)
the angle after bouncing has to be calculated based on these two angles: using ther 2nd for mirroring
to calculate the new dx & dy after collision you will need the original velocity Math.abs(Math.sqrt(Math.pow(dx)+Math.pow(dy))) of the ball
based on this velocity and the new direction you can calc the new dx & dy
So I want a snappy movement for my player. Right now my code looks like
move() {
var vel = this.body.GetLinearVelocity()
if(!this.pressingDown && !this.pressingUp){
vel.y = 0;
}
if(!this.pressingRight && !this.pressingRight){
vel.x = 0;
}
if(this.pressingDown){
vel.y = this.speed;
}
if(this.pressingUp){
vel.y = -this.speed;
}
if(this.pressingRight){
vel.x = this.speed;
}
if(this.pressingLeft){
vel.x = -this.speed
}
this.body.SetLinearVelocity(vel)
and this works but when I'm moving diagnolly the player is moving faster than the max speed. How do I fix this?
Determine the directional unit vector and then multiply it by this.speed. That way the magnitude of the velocity is always this.speed. Otherwise, as you discovered, your speed may be sqrt(this.speed * this.speed * 2) instead of just this.speed.
A way to determine this directional unit vector would be to recognize the angle you want to move at based on the keys pressed and then getting the sine and cosine values for that angle. So when this.pressingRight, the angle is 0. When this.pressingUp, the angle is 90 degrees (or Pi/2 radians). Or when this.pressingUp && this.pressingRight, the angle is 45 degrees (Pi/4 radians). Just complete the if-statement for all serviceable combinations. Perhaps put that in its own function called something like getAngleInRadiansForKeyPresses.
The implementation (in pseudo-javascript-code) might then look something like:
move() {
var angle = getAngleInRadiansForKeyPresses();
var vel = new b2Vec2(Math.cos(angle) * this.speed, Math.sin(angle) * this.speed);
this.body.SetLinearVelocity(vel);
}
I am having a go at building a game in html canvas. It's a Air Hockey game and I have got pretty far though it. There are three circles in the game, the disc which is hit and the two controllers(used to hit the disc/circle).
I've got the disc rebounding off the walls and have a function to detect when the disc has collided with a controller. The bit I am struggling with is when the two circle's collide, the controller should stay still and the disc should move away in the correct direction. I've read a bunch of article's but still can't get it right.
Here's a Codepen link my progress so far. You can see that the puck rebounds off the controller but not in the correct direction. You'll also see if the puck comes from behind the controller it goes through it.
http://codepen.io/allanpope/pen/a01ddb29cbdecef58197c2e829993284?editors=001
I think what I am after is elastic collision but not sure on how to work it out. I've found this article but have been unable to get it working.
http://gamedevelopment.tutsplus.com/tutorials/when-worlds-collide-simulating-circle-circle-collisions--gamedev-769
Heres is my collision detection function. Self refer's to the disc and the controller[i] is the controller the disc hits.
this.discCollision = function() {
for (var i = 0; i < controllers.length; i++) {
// Minus the x pos of one disc from the x pos of the other disc
var distanceX = self.x - controllers[i].x,
// Minus the y pos of one disc from the y pos of the other disc
distanceY = self.y - controllers[i].y,
// Multiply each of the distances by itself
// Squareroot that number, which gives you the distance between the two disc's
distance = Math.sqrt(distanceX * distanceX + distanceY * distanceY),
// Added the two disc radius together
addedRadius = self.radius + controllers[i].radius;
// Check to see if the distance between the two circles is smaller than the added radius
// If it is then we know the circles are overlapping
if (distance <= addedRadius) {
var newVelocityX = (self.velocityX * (self.mass - controllers[i].mass) + (2 * controllers[i].mass * controllers[i].velocityX)) / (self.mass + controllers[i].mass);
var newVelocityY = (self.velocityY * (self.mass - controllers[i].mass) + (2 * controllers[i].mass * controllers[i].velocityX)) / (self.mass + controllers[i].mass);
self.velocityX = newVelocityX;
self.velocityY = newVelocityY;
self.x = self.x + newVelocityX;
self.y = self.y + newVelocityY;
}
}
}
Updated
Deconstructed a circle collision demo & tried to implement their collision formula. This is it below, works for hitting the puck/disc forward & down but wont hit the back backwards or up for some reason.
this.discCollision = function() {
for (var i = 0; i < controllers.length; i++) {
// Minus the x pos of one disc from the x pos of the other disc
var distanceX = self.x - controllers[i].x,
// Minus the y pos of one disc from the y pos of the other disc
distanceY = self.y - controllers[i].y,
// Multiply each of the distances by itself
// Squareroot that number, which gives you the distance between the two disc's
distance = Math.sqrt(distanceX * distanceX + distanceY * distanceY),
// Added the two disc radius together
addedRadius = self.radius + controllers[i].radius;
// Check to see if the distance between the two circles is smaller than the added radius
// If it is then we know the circles are overlapping
if (distance < addedRadius) {
var normalX = distanceX / distance,
normalY = distanceY / distance,
midpointX = (controllers[i].x + self.x) / 2,
midpointY = (controllers[i].y + self.y) / 2,
delta = ((controllers[i].velocityX - self.velocityX) * normalX) + ((controllers[i].velocityY - self.velocityY) * normalY),
deltaX = delta*normalX,
deltaY = delta*normalY;
// Rebound puck
self.x = midpointX + normalX * self.radius;
self.y = midpointY + normalY * self.radius;
self.velocityX += deltaX;
self.velocityY += deltaY;
// Accelerate once hit
self.accelerationX = 3;
self.accelerationY = 3;
}
}
}
I'm not great at these types of math problems, but it looks like you need to rotate your vectors around sine and cosine angles. I will point you at a working example and the source code that drives it. I did not derive this example.
I solved just the circle collision detection part of this problem recently, but one solution I came across includes code for establishing new vector directions. Ira Greenburg hosts his original source at processing.org. Ira further cites Keith Peter's Solution in Foundation Actionscript Animation: Making Things Move!
I copied Ira's code into Processing's Javascript mode then pushed it to Github Pages so you can see it before you try it.
The main issue with my code was the user controller was attached to the mouse. When a collision would happen, the function would run constantly because the circles where still touching due to the mouse position. I changed my code so the controller is controlled by the users keyboard.
I also asked for help on reddit & got some help with my collision code. Some good resources where linked.
(http://www.reddit.com/r/javascript/comments/3cjivi/having_a_go_at_building_an_air_hockey_game_stuck/)
Yes theres a few threads on this, but not many using angles and I'm really trying to figure it out this way,
I'm now stuck on setting the new velocity angles for the circles. I have been looking at:
http://www.hoomanr.com/Demos/Elastic2/
as a reference to it, but I'm stuck now.
Can anybody shed some light?
cx/cy/cx2/cy2 = center x/y for balls 1 and 2.
vx/vy/vx2/vy2 = velocities for x/y for balls 1 and 2
function checkCollision() {
var dx = cx2 - cx; //distance between x
var dy = cy2 - cy; // distance between y
var distance = Math.sqrt(dx * dx + dy * dy);
var ang = Math.atan2(cy - cy2, cx - cx2);
// was displaying these in a div to check
var d1 = Math.atan2(vx, vy); //ball 1 direction
var d2 = Math.atan2(vx2, vy2); //ball 2 direction
// this is where I am stuck, and i've worked out this is completely wrong now
// how do i set up the new velocities for
var newvx = vx * Math.cos(d1 - ang);
var newvy = vy * Math.sin(d1 - ang);
var newvx2 = vx2 * Math.cos(d2 - ang);
var newvy2 = vy2 * Math.sin(d2 - ang);
if (distance <= (radius1 + radius2)) {
//Set new velocity angles here at collision..
}
Heres a codepen link:
http://codepen.io/anon/pen/MwbMxX
A few directions :
• As mentioned in the comments, use only radians (no more *180/PI).
• atan2 takes y as first param, x as second param.
var d1 = Math.atan2(vy, vx); //ball 1 direction in angles
var d2 = Math.atan2(vy2, vx2); //ball 2 direction in angles
• to rotate a vector, compute first its norm, then only project it with the new angle :
var v1 = Math.sqrt(vx*vx+vy*vy);
var v2 = Math.sqrt(vx2*vx2+vy2*vy2);
var newvx = v1 * Math.cos(d1 - ang);
var newvy = v1 * Math.sin(d1 - ang);
var newvx2 = v2 * Math.cos(d2 - ang);
var newvy2 = v2 * Math.sin(d2 - ang);
• You are detecting the collision when it already happened, so both circles overlap, but you do NOT solve the collision, meaning the circles might still overlap on next iteration, leading to a new collision and a new direction taken, ... not solved, etc..
-->> You need to ensure both circles are not colliding any more after you solved the collision.
• Last issue, but not a small one, is how you compute the angle. No more time for you sorry, but it would be helpful both for you and us to build one (several) scheme showing how you compute the angles.
Updated (but not working) codepen here :
http://codepen.io/anon/pen/eNgmaY
Good luck.
Edit :
Your code at codepen.io/anon/pen/oXZvoe simplify to this :
var angle = Math.atan2(dy, dx),
spread = minDistance - distance,
ax = spread * Math.cos(angle),
ay = spread * Math.sin(angle);
vx -= ax;
vy -= ay;
vx2 += ax;
vy2 += ay;
You are substracting the gap between both circles from the speed. Since later you add the speed to the position, that will do the spatial separation (=> no more collision).
I think to understand what vx-=ax means, we have to remember newton : v = a*t, where a is the acceleration, so basically doing vx=-ax means applying a force having the direction between both centers as direction, and the amount by which both circle collided (spread) as intensity. That amount is obviously quite random, hence the numerical instability that you see : sometimes a small effect, sometimes a big one.
look here for a constant punch version :
http://codepen.io/anon/pen/WvpjeK
var angle = Math.atan2(dy, dx),
spread = minDistance - distance,
ax = spread * Math.cos(angle),
ay = spread * Math.sin(angle);
// solve collision (separation)
cx -= ax;
cy -= ay;
// give a punch to the speed
var punch = 2;
vx -= punch*Math.cos(angle);
vy -= punch*Math.sin(angle);
vx2 += punch*Math.cos(angle);
vy2 += punch*Math.sin(angle);