This is for pong.
I can get collision of the ball and front paddle to fire but not the ball and top of paddle, but this code seems correct for it. This code is inside the ball object. The ball is a square image with correct width and height attributes.
// Paddle collision detection
// Hits front of paddle
if ((this.x) == (playerPaddle.x + playerPaddle.width) && (this.y > playerPaddle.y) && this.y < (playerPaddle.y + playerPaddle.height)) {
console.log("front connect");
this.vx = -this.vx;
}
// Hits top of paddle
if ((this.x + this.width) >= playerPaddle.x && this.x <= (playerPaddle.x + playerPaddle.width) && (this.y + this.height) == playerPaddle.y) {
console.log("top connect");
this.vy = -this.vy;
}
It fires if i change && (this.y + this.height) == playerPaddle.y) to && (this.y + this.height) > playerPaddle.y) but obviously this is wrong, causing it to fire whenever the ball is way below the paddle. It seems like a bug in the browser as it looks correct to me. I'm using chrome which seems to always work well.
Since a 2d ball is circular, you'll need to use trig functions to properly determine a connect with the edge. I suggest you check the general area first with a "quick check" to prevent slowing down your app.
// Paddle Collision Detection
// Start by a global check to see if the ball is behind the paddle's face
if (this.x <= playerPaddle.x + playerPaddle.width) {
// Get the ball's radius
var rad = this.width/2;
// Give yourself a 3px 'padding' area into the front of the paddle
// For the detection fo collisions to prevent collision issues caused
// By a ball moving > 1px in a given frame. You may want to adjust this number
var padding = 3;
// Detect if ball hits front of paddle
// y collision should be from 1/2 the ball width above the paddle's edge
// to 1/2 the ball width below the paddle's edge
if (this.x + padding >= playerPaddle.x + playerPaddle.width
&& this.y - rad >= playerPaddle.y
&& this.y + rad <= playerPaddle.y + playerPaddle.height) {
console.log("front connect");
this.vx = -this.vx;
// Next, do a "quick check" to see if the ball is in the correct
// general area for an edge collision prior to doing expensive trig math
} else if (this.y - this.height >= playerPaddle.y
&& this.y <= playerPaddle.y
&& this.x - rad >= playerPaddle.x) {
// Get the position of the center of the ball
var x = this.x + rad;
var y = this.y + rad;
// Get the position of the corner of the paddle
var px = playerPaddle.x + playerPaddle.width;
var py = playerPaddle.y;
if (this.y + this.height > playerPaddle.y) {
// if the ball is below the top edge, use the bottom corner
// of the paddle - else use the top corner of the paddle
py += playerPaddle.height;
}
// Do some trig to determine if the ball surface touched the paddle edge
// Calc the distance (C = sqrt(A^2 + B^2))
var dist = Math.pow(Math.pow(x - px, 2) + Math.pow(y - py, 2), 0.5);
// Check if the distance is within the padding zone
if (dist <= rad && dist >= rad - padding) {
// Get the angle of contact as Arc-sin of dx/dy
var angle = Math.asin(x - px / y - py);
// Adjust the velocity accordingly
this.vy = (-this.vy * Math.cos(angle)) + (-this.vx * Math.sin(angle));
this.vx = (-this.vx * Math.cos(angle)) + (-this.vy * Math.sin(angle));
}
}
}
Related
I am having an issue with my implementation of gravity and collision on many objects on a canvas...I understand where I have gone wrong I just cannot for the life of me think of a solution to fix it.
I am checking if dropped objects reach a y position on the canvas and then reverse the velocity * friction to bounce the object...This works fine with one object or many objects. However, because of my conditional when the objects stack on top of each other the velocity never decreases as it is always being increased by the gravity, meaning the objects slowly pass through each other after they have finished bouncing around.
I have tried checking if "this" object is colliding however this will not work as the objects should be able to bounce until friction causes the velocity to decrease or increase to zero(or very close in this case).
Here is the function for gravity.
gravity() {
//If the objects y + the height and delta are greater than the canvases height reverse the velocity * friction also apply
//same friction to x to stop the objects from sliding
if (this.y + this.height + this.delta_y >= this.canvas.height) {
this.delta_y = -this.delta_y * this.FRICTION;
this.delta_x = this.delta_x * this.FRICTION;
//If the delta_y does not push the objects y over the canvas height increase the delta by the gravitational pull
} else {
this.delta_y += this.GRAVITY;
}
//Apply reversed velocity over x - works because no constant gravity being added
if (this.x + this.width >= this.canvas.width || this.x - this.width <= 0) {
this.delta_x = -this.delta_x * this.FRICTION;
}
//update the x and y coordinates with the new delta value
this.x += this.delta_x;
this.y += this.delta_y;
//set the new centre for collision.
this.center = {
"x": this.x + this.width / 2,
"y": this.y + this.height / 2
};
}
I also thought I maybe able to run a timer on the y variable for a few milliseconds and if it has not changed outside of a bound then I could stop movement. However, this is not ideal and im not sure if a timeout would be good as javascript is single threaded? I would be at home in python but I am struggling here.
I have enclosed the calling function here
gameLoop(timeStamp) {
// Calculate how much time has passed // was thinking I could use this to check y after some time
//but stopped and decided to post to stack
let secondsPassed = (timeStamp - this.oldTimeStamp) / 1000;
this.oldTimeStamp = timeStamp;
if (this.gameObjects.length > 0) {
//update all objects and pass all objects to update to check for collision
for (let i = 0; i < this.gameObjects.length; i++) {
this.gameObjects[i].update(this.gameObjects);
}
//clear the canvas ready for next draw
this.clearCanvas();
//draw the next iteration of the gameobjects
for (let i = 0; i < this.gameObjects.length; i++) {
this.gameObjects[i].draw();
}
}
window.requestAnimationFrame((timeStamp) => this.gameLoop(timeStamp));
}
Finally here is the update function for completeness
update(gameObjects) {
//impose gravity on this object
this.gravity();
//loop through objects and check for collision
for (let i = 0; i < gameObjects.length; i++) {
// if this object is the same as the iterated object skip it
if (this === gameObjects[i]){continue;}
//test variable to check collision on this object
this.colliding = this.collisionDetection(gameObjects[i]);
//if colliding resolve the collision
if (this.colliding) {
this.resolveCollision(this, gameObjects[i]);
}
}
}
Thank you, vanilla JS only please.
I think the problem is that, when the object is out of bounds, you reduce its speed, so it doesn't have enough speed to come back in 100%.
Maybe you can apply your "friction" in the next update, not the one with the collision.
For anyone that gets stuck on this I was able to get it to working by passing the game objects to the gravity method and then adding a check in the conditional to see if a collision has occured. Its not perfect and there are a few other things to sort but for now its progress.
code
gravity(objects) {
//initialize collision to false
let thisCollide = false;
//loop through all objects
for(let i = 0; i < objects.length; i++){
//If the iteration is on this object then skip
if(this === objects[i]) continue;
//if there is a collision && that collision is on the top of the current object
if(this.collisionDetection(objects[i]) && (this.y - objects[i].y <= 1)){
//set the collision to true and break
thisCollide = true;
}
}
//If the objects y + the height and delta are greater than the canvases height or the object has collide on the top
//reverse the velocity * friction also apply same friction to x to stop the objects from sliding
if (this.y + this.height + this.delta_y >= this.canvas.height || thisCollide) {
this.delta_y = -this.delta_y * this.FRICTION;
this.delta_x = this.delta_x * this.FRICTION;
//If the delta_y does not push the objects y over the canvas height increase the delta by the gravitational pull
} else {
this.delta_y += this.GRAVITY;
}
//Apply reversed velocity over x - works because no constant gravity being added
if (this.x + this.width >= this.canvas.width || this.x - this.width <= 0) {
this.delta_x = -this.delta_x * this.FRICTION;
}
//update the x and y coordinates with the new delta value
this.x += this.delta_x;
this.y += this.delta_y;
//set the new centre for collision.
this.center = {
"x": this.x + this.width / 2,
"y": this.y + this.height / 2
};
}
I would recommend this order:
Apply forces
Update Velocity
Update Position
In your case:
//apply acceleration
this.delta_y += this.GRAVITY;
//apply friction
this.delta_x -= this.delta_x * this.FRICTION;
this.delta_y -= this.delta_y * this.FRICTION;
//update position
this.x += this.delta_x;
this.y += this.delta_y;
//collision
if (this.x + this.width >= this.canvas.width || this.x - this.width <= 0) {
this.delta_x = -this.delta_x;
}
if (this.y + this.height >= this.canvas.height) {
this.delta_y = -this.delta_y;
}
I am currently working on a 2D javascript game and I am doing the movement mechanics right now. I want players to be able to move forward and backward, towards or away from the mouse, while also having the ability to strafe.
I got the mouse following down pretty well but I am stuck on how to implement the strafing. I need my player to move along a dynamic circular path that changes sizes depending on how far away the player is from the mouse.
ex: If the mouse was the red X, I want to move the player along the green circular path.
This path of course will be changing size based on how far away the player is from the mouse.
I am updating the players position by moving whenever the movement keys are pressed so I am really looking for an equation to move the player in the correct circular path that can be implemented in a "position updated every second" sort of way.
I know that for a circular path, the coordinates can be found by:
x = centerX * radius * Math.cos(theta);
y = centerY * radius * Math.sin(theta);
But I am having trouble implementing. Here is some of my framework, but I am afraid all the solutions I have tried haven't even gotten me close so I will not post the broken math I have already deleted
Player.prototype.update = function(delta){
this.playerCenter = [this.x+this.width/2, this.y+this.height/2];
let dX = (GAME.mouse.position.x - this.playerCenter[0]),
dY = (GAME.mouse.position.y - this.playerCenter[1]);
radius = Math.sqrt(dX * dX + dY * dY);
// Movement Forward
if(GAME.keyDown[87] && radius >= 50){
this.x += (dX / radius) * this.movementSpeed * delta;
this.y += (dY / radius) * this.movementSpeed * delta;
}
// Movement Backward
if(GAME.keyDown[83]){
this.x -= (dX / radius) * this.movementSpeed * delta;
this.y -= (dY / radius) * this.movementSpeed * delta;
}
// Strafe left
if(GAME.keyDown[65]){
}
// Strafe right
if(GAME.keyDown[68]){
}
}
You almost have the solution.
You need to go at 90 deg to the forward vector. To rotate a vector 90cw you swap the x,and y and negate the new x.
EG
dx = ?; // forward direction
dy = ?;
// rotate 90 clockwise
dx1 = -dy;
dy1 = dx;
Thus you can update your code as follows
var dX = (GAME.mouse.position.x - this.playerCenter[0]);
var dY = (GAME.mouse.position.y - this.playerCenter[1]);
var radius = Math.sqrt(dX * dX + dY * dY);
//normalise
if(radius > 0){
dX = (dX / radius) * this.movementSpeed * delta;
dY = (dY / radius) * this.movementSpeed * delta;;
}else{
dX = dY = 0; // too close need to set this or you will get NaN propagating through your position variables.
}
if(GAME.keyDown[87] && radius >= 50){ // Movement Forward
this.x += dX;
this.y += dY;
}
if(GAME.keyDown[83]){ // Movement Backward
this.x -= dX;
this.y -= dY;
}
if(GAME.keyDown[65]){ // Strafe left
this.x += -dY; // swap x and y negate new x to rotate vector 90
this.y += dX;
}
if(GAME.keyDown[68]){ // Strafe right
this.x -= -dY; // swap x and y negate new x to rotate vector 90
this.y -= dX;
}
So I'm creating a brick breaker game, and I need some help finding an angle.
Pretty much the game consists of blocks that, when hit, will cause you to lose 1 health. The point of the game is to hit the blocks with the balls to break them before they reach the bottom. If the ball hits a wall or a block, its trajectory is reversed.
I want the user to be able to click someone within the html canvas. Then the balls, which start in the center of the screen at the bottom of the canvas, will follow that angle. In other words, the user will click and the balls will move to that spot and then continue until it hits something.
I have some code here, But it probably won't help on how to achieve the angle thing.
function animate(callback) {
window.requestAnimationFrame(function() {
window.setTimeout(callback, 1000/60);
});
}
// canvas
var canvas = document.getElementById('canvas');
var context = canvas.getContext('2d');
// variables
var ballList = [];
var maxBalls = 1;
var checkAmount = 0;
var interval;
// onload/refresh/update/render
window.onload = function() {
refresh();
}
function refresh() {
update();
render();
animate(refresh);
}
function update() {
document.addEventListener("click", spawn);
for(var i = 0; i < ballList.length; i++) {
ballList[i].move();
}
}
function render() {
context.fillStyle = '#000';
context.fillRect(0, 0, canvas.width, canvas.height);
for(var i = 0; i < ballList.length; i++) {
ballList[i].show();
}
}
// ball
function Ball() {
this.x = canvas.width / 2;
this.y = canvas.height - 50;
this.width = 10;
this.height = 10;
this.xVel = 5;
this.yVel = -10;
this.show = function() {
context.fillStyle = '#fff';
context.fillRect(this.x, this.y, this.width, this.height);
}
this.move = function() {
this.x += this.xVel;
this.y += this.yVel;
if(this.x >= canvas.width || this.x <= 0) {
this.xVel *= -1;
}
if(this.y >= canvas.height || this.y <= 0) {
this.yVel *= -1;
}
}
}
function spawn(event) {
var xVel = (event.clientX - canvas.width / 2) / 90;
if(ballList.length < maxBalls) {
if(checkAmount < maxBalls) {
interval = setInterval(function() {
ballList.push(new Ball((event.clientX)));
checkAmount++;
if(checkAmount > maxBalls) {
clearInterval(interval);
checkAmount = 0;
}
}, 10);
}
}
}
Thanks in advance.
Unit Vectors
To move an object from one point towards another you use a vector. A vector is just two numbers that represent a direction and a speed. It can be polar in that one number is an angle and the other is a distance, or cartesian that represent the vector as the amount of change in x and y.
Cartesian unit vector
For this you can use either but I prefer the cartesian vector and a particular type called a unit vector. The unit vector is 1 unit long. In computer graphics the unit is normally the pixel.
So we have a point to start at
var startX = ?
var startY = ?
And a point the we want to head towards
var targetX = ?
var targetY = ?
We want the unit vector from start to target,
var vectorX = targetX - startX;
var vectorY = targetY - startY;
The vector's length is the distance between the two points. This is not so handy so we will turn it into a unit vector by dividing both the x and y by the length
var length = Math.sqrt(vectorX * vectorX + vectorY * vectorY);
var unitVectX = vectorX / length;
var unitVectY = vectorY / length;
Now we have a one pixel long unit vector.
The Ball will start at start
var ballX = startX
var ballY = startY
And will move at a speed of 200 pixels per second (assuming 60fps)
var ballSpeed = 200 / 60;
Now to move the ball just add the unit vector times the speed and you are done. Well till the next frame that is.
ballX += unitVectX * ballSpeed;
ballY += unitVectY * ballSpeed;
Using the cartesian makes it very easy to bounce off of walls that are aligned to the x or y axis.
if(ballX + ballRadius > canvas.width){
ballX = canvas.width - ballRadius;
unitVectX = - unitVectX;
}
Polar vector
You can also use polar coordinates. As we use a unit vector the polar unit vector just needs the direction. You use the trig function atan2
// get the direction in radians
var polarDirection = Math.atan2(targetY - startY, targetX - startX);
The direction is in radians, many poeple don't like radians and convert to degrees, but there is no need to know which way it is going just as long as it goes in the correct direction. To remember radians is easy. 360 degrees is 2 radian 180 is 1 randian 90 is 0.5. The actual units used are PI (not many people know many of the digits of pi but you don't need to). So 270 degree is 1.5 radians or as a number 1.5 * Math.PI.
The angles start at the 3 o'clock point (pointing to the right of screen) as 0 radians or 0 deg then clockwise 90deg is at 6 o'clock 0.5 radian, and 180deg 1 radian at 6 o'clock and so on.
To move the ball with the polarDirection you need to use some more trig.
// do this once a frame
ballX += Math.cos(polarDirection) * ballSpeed;
ballY += Math.sin(polarDirection) * ballSpeed;
// note that the cos and sin actually generate the cartesian unit vector
/**
* #param {number} x1 - x coordinate of the first point
* #param {number} y1 - y coordinate of the first point
* #param {number} x2 - x coordinate of the second point
* #param {number} y2 - y coordinate of the second point
* #return {number} - the angle (between 0 and 360)
*/
function getDirection(x1, y1, x2, y2) {
// might be negative:
var angle = Math.atan2(y2 - y1, x2 - x1) * 180 / Math.PI;
// correct, positive angle:
return (angle + 360) % 360;
}
I wrote this function for a similar purpose. Don't forget that you might have to negate x.
Rotation of rectangles within an html5 canvas is being stored in radians. In order to find whether subsequent mouse clicks are within any given rectangle, I am translating the mouse x and y to the origin of rotation for the rectangle, applying the reverse of the rotation to the mouse coordinates, and then translating the mouse coordinates back.
This simply isn't working - mouse coordinates are not being transformed as expected (that is, not within the bounds of the original rectangle when clicking within the visible bounds of the rotated rectangle), and testing against the rectangle's bounds is failing. Detection of mouse clicks works only within the centre-most area of the rectangle. Please see the code snippet below and tell me if you can see what's wrong here.
// Our origin of rotation is the center of the rectangle
// Our rectangle has its upper-left corner defined by x,y, its width
// defined in w, height in h, and rotation(in radians) in r.
var originX = this.x + this.w/2, originY = this.y + this.h/2, r = -this.r;
// Perform origin translation
mouseX -= originX, mouseY -= originY;
// Rotate mouse coordinates by opposite of rectangle rotation
mouseX = mouseX * Math.cos(r) - mouseY * Math.sin(r);
mouseY = mouseY * Math.cos(r) + mouseX * Math.sin(r);
// Reverse translation
mouseX += originX, mouseY += originY;
// Bounds Check
if ((this.x <= mouseX) && (this.x + this.w >= mouseX) && (this.y <= mouseY) && (this.y + this.h >= mouseY)){
return true;
}
After some further work, came to the following solution, which I thought I'd transcribe here for anyone who might need it in the future:
// translate mouse point values to origin
var dx = mouseX - originX, dy = mouseY - originY;
// distance between the point and the center of the rectangle
var h1 = Math.sqrt(dx*dx + dy*dy);
var currA = Math.atan2(dy,dx);
// Angle of point rotated around origin of rectangle in opposition
var newA = currA - this.r;
// New position of mouse point when rotated
var x2 = Math.cos(newA) * h1;
var y2 = Math.sin(newA) * h1;
// Check relative to center of rectangle
if (x2 > -0.5 * this.w && x2 < 0.5 * this.w && y2 > -0.5 * this.h && y2 < 0.5 * this.h){
return true;
}
I am writing a canvas-based game which involves many sprites on the canvas at one time. In some cases the sprites are not visible and to save render cycles I do not render them to the canvas if the player will not see them. This works great for sprites that are not rotated but as soon as they become rotated (especially rectangles) I can no longer accurately determine if they are still within the visible canvas.
Here is what I am doing so far as part of my main render loop:
if (image !== null) {
ctx.save();
ctx.translate(this.x, this.y);
ctx.rotate(this.rotation * Math.PI/180);
ctx.drawImage(image, 0,0, this.width, this.height);
ctx.restore();
}
Before I render the sprite using the above code I determine if it is visible using this code:
// Only draw sprite sthat are visible to the player.
if (sprite.x + boundingBox >= 0 && sprite.y + boundingBox >= 0 && sprite.x <= this.width && sprite.y <= this.height) {
sprite.draw(this.gameConsole.ctx);
}
What happens is that once I rotate a nonuniform sprite for example a rectangle the width and height are no longer correct because they assume they are in an nonrotated state. How would you approach this problem?
the rotation says that where point P is a corner-point (one of the 4) of the nonuniform sprite resulting in R after Rotation
a = this.rotation * (PI/180)
with the help of a rotation matrix
Rx = Px * cos(a) + Py * -sin(a)
Ry = Px * sin(a) + Py * cos(a)
so you could test if R is inside the canvas.
if you use ctx.setTransform instead of rotate you can do it all at once, that is test first, render if needed ;)
You could calculate the diagonal and use that to determine whether the sprite is visible
var diagonal = Math.sqrt(boundingBox * boundingBox * 2);
if (sprite.x + diagonal >= 0 && sprite.y + diagonal >= 0 && sprite.x <= this.width && sprite.y <= this.height) {
sprite.draw(this.gameConsole.ctx);
}