I am currently building a replication of Atari's breakout for university. I currently have a working ball, a paddle and (potentially) some bricks. At the minute, the bricks are rendering incorrectly as the canvas remains blank at run-time. Could anyone please tell me where I'm going wrong (either with the 'bricks' variable or with the bricks section of the render function). The code is below:
var canvas = document.getElementById("breakout");
var ctx = canvas.getContext("2d");
var PADDLE_WIDTH_PX = canvas.width / 5;
var PADDLE_HEIGHT_PX = 10;
var PADDLE_SPEED = 450;
var ball = {
x: canvas.width / 2, //pixels
y: canvas.height / 2, //pixels
xSpeed: 500, //pixels per second
ySpeed: 500, //pixels per second
radius: 100 //pixels
}
var paddle = {
//radius: 5,
/*speed: 500,
TopRight: ctx.moveTo(canvas.width / 1.35, canvas.height - (canvas.height / 12.5)),
TopSide: ctx.lineTo(canvas.width / 2, canvas.height - (canvas.height / 12.5)),
RightSide: ctx.lineTo(canvas.width / 1.35, canvas.height - (canvas.height / 27.5)),
BottomLeft: ctx.moveTo(canvas.width / 2, canvas.height - (canvas.height / 27.5)),
LeftSide: ctx.lineTo(canvas.width / 2, canvas.height - (canvas.height / 12.5)),
BottomSide: ctx.lineTo(canvas.width / 1.35, canvas.height - (canvas.height / 27.5))*/
xSpeed: 450,
x: (canvas.width - PADDLE_WIDTH_PX) / 2,
y: canvas.height - PADDLE_HEIGHT_PX
}
var Brick = function() {
canvas.width / 30 = x;
canvas.height / 50 = y;
var bricks(750);
var location_X(750);
var location_Y(750);
for (int b = 0; b <= 749; b++)
{
if (b == 0)
{
location_X[b] = 0;
location_Y[b] = 0;
}
if (b != 0)
{
while (b <= 29)
{
location_X[b] = (location_X[b - 1]) + Brick.x;
}
while (b <= 49)
{
location_Y[b] = (location_Y[b - 1]) + Brick.y;
}
}
switch (b % 10) {
case 0:
bricks[b] = firebrick;
break;
case 1:
bricks[b] = gold;
break;
case 2:
bricks[b] = yellow;
break;
case 3:
bricks[b] = green;
break;
case 4:
bricks[b] = fuschia;
break;
case 6:
bricks[b] = palevioletred;
break;
case 7:
bricks[b] = goldenrod;
break;
case 8:
bricks[b] = turquoise;
break;
case 9:
bricks[b] = white;
break;
default:
bricks[b] = thistle;
break;
}
}
}
}
var keysDown = {};
window.addEventListener("keydown",function(e) {
keysDown[e.keyCode] = true;
});
window.addEventListener("keyup",function(e) {
delete keysDown[e.keyCode];
});
function render() {
//clear the canvas
ctx.clearRect(0, 0, canvas.width, canvas.height)
// draw the ball
ctx.fillStyle = "white";
ctx.beginPath();
ctx.arc(ball.x, ball.y, ball.radius, 0, Math.PI * 2);
ctx.closePath();
ctx.fill();
ctx.beginPath();
//ctx.fillStyle = "red";
/*ctx.moveTo(canvas.width - (2*paddle.x), canvas.height - (2*paddle.y));
/*ctx.lineTo(canvas.width / 2, canvas.height - (canvas.height / 12.5));
ctx.lineTo(canvas.width / 1.35, canvas.height - (canvas.height / 27.5));
ctx.moveTo(canvas.width / 2, canvas.height - (canvas.height / 27.5));
ctx.lineTo(canvas.width / 2, canvas.height - (canvas.height / 12.5));
ctx.lineTo(canvas.width / 1.35, canvas.height - (canvas.height / 27.5));
ctx.fill();
ctx.closePath();*/
/*ctx.lineTo(canvas.width - (2*paddle.x), canvas.height - paddle.y);
ctx.moveTo(canvas.width - paddle.x, canvas.height - paddle.y);
ctx.lineTo(canvas.width - paddle.x, canvas.height - (2*paddle.y));
ctx.lineTo(canvas.width - (2*paddle.x), (canvas.height -paddle.y));*/
ctx.fillRect(paddle.x, paddle.y, PADDLE_WIDTH_PX, PADDLE_HEIGHT_PX);
for (int b = 0; b <= 749; b++)
{
ctx.fillStyle = "Brick.bricks[b]";
ctx.fillRect(Bricks.location_X[b], Bricks.location_Y[b], Bricks.x, Bricks.y);
}
/*ctx.closePath();
ctx.fill();*/
}
function update(elapsed) {
//update the ball position according to the elapsed time
ball.y += ball.ySpeed * elapsed;
ball.x += ball.xSpeed * elapsed;
/*paddle.TopRight += paddle.speed * elapsed;
paddle.BottomLeft += paddle.speed * elapsed;
paddle.RightSide += paddle.speed * elapsed;
paddle.LeftSide += paddle.speed * elapsed;
paddle.TopSide += paddle.speed * elapsed;
paddle.BottomSide += paddle.speed * elapsed;*/
/*paddle.x += paddle.xSpeed * elapsed;
paddle.y += paddle.xSpeed * elapsed;*/
//bounce the ball of all edges
if (37 in keysDown && paddle.x > 0)
paddle.x -= PADDLE_SPEED * elapsed;
if (39 in keysDown && paddle.x + PADDLE_WIDTH_PX < canvas.width)
paddle.x += PADDLE_SPEED * elapsed;
if (ball.x+(ball.x/7) >= canvas.width) {
ball.x -= 5;
ball.xSpeed *= -1;
}
if (ball.x-(ball.x/7) <= 0) {
ball.x += 5;
ball.xSpeed *= -1;
}
if (ball.y+(ball.y/100) <= 0) {
ball.y += 5;
ball.ySpeed *= -1;
}
if (ball.y+(ball.y/3) >= canvas.height) {
ball.y -= 5;
ball.ySpeed *= -1;
}
/*
The problem here is that sometimes the ball gets 'stuck' to an edge.
This can occur when the ball passes beyond an edge in a frame when the
elapsed time is relatively large. In some cases, when the elapsed time in the
next frame is relatively short, the ball doesn't reach the edge to get back
into play. This results in another flip of the velocity and the ball becomes
'trapped' on the edge.
e.g.
xSpeed = -500, x = 10, elapsed = 0.2 => xSpeed = 500, x = -90 (xMovement = -100)
xSpeed = 500, x = -90, elapsed = 0.1 => xSpeed = -500, x = -40 (xMovement = +50)
xSpeed = -500, x = -40, elapsed = 0.1 => xSpeed = 500, x = -40 (xMovement = -50)
and so on ...until a larger elapsed time occurs in the right direction
The fix for this is to move the ball to the edge when the velocity is flipped.
*/
}
var previous;
function run(timestamp) {
if (!previous) previous = timestamp; //start with no elapsed time
var elapsed = (timestamp - previous) / 1000; //work out the elapsed time
update(elapsed); //update the game with the elapsed time
render(); //render the scene
previous = timestamp; //set the (globally defined) previous timestamp ready for next time
window.requestAnimationFrame(run); //ask browser to call this function again, when it's ready
}
//trigger the game loop
window.requestAnimationFrame(run);
Thanks for taking the time to read this,
--ConfusedStudent
The first thing that jumps out is there are a lot of syntax errors (ex. some variable assignments are backwards), so your script isn't going to execute. If you run your JavaScript through JSLint, you'll see several errors that need to be fixed first. If you clean up those errors and update your post, someone will be able to help you more easily.
Related
I am working on a little platformer game. The player is a circle, and I am going to implement the obstacles as rectangles. Does anyone know how to make sure the player can't go through the obstacle? I have tried some methods, but they come out glitchy.
Here is what I have so far (It is a bit long, so the method that I need is inside the resolveCollision function):
var canvas = document.createElement("CANVAS");
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
canvas.id = "CANVAS";
document.body.appendChild(canvas);
document.body.style.margin = "0px 0px 0px 0px";
document.body.style.overflow = "hidden";
document.body.style.backgroundColor = "#202020";
var ctx = canvas.getContext("2d");
var objects = [];
var player = {
x: window.innerWidth / 2 - 25,
y: 0,
height: 25,
width: 25,
old: {
x: 0,
y: 0,
},
velocity: {
x: 0,
y: 0,
},
jumping: true,
color: "#ff0000",
};
var obstacle = {
x: 50,
y: 300,
width: 50,
height: 50
}
var control = {
left: false,
right: false,
up: false,
keyListener: function (event) {
var keyState;
if (event.type == "keydown") {
keyState = true;
} else {
keyState = false;
}
switch (event.keyCode) {
case 37:
control.left = keyState;
break;
case 38:
control.up = keyState;
break;
case 39:
control.right = keyState;
break;
}
}
};
var checkCollision = function (object) {
var distX = Math.abs(player.x - object.x - object.width / 2);
var distY = Math.abs(player.y - object.y - object.height / 2);
if (distX > (object.width / 2 + (player.width + player.height) / 2)) { return false; }
if (distY > (object.height / 2 + (player.width + player.height) / 2)) { return false; }
if (distX <= (object.width / 2)) { return true; }
if (distY <= (object.height / 2)) { return true; }
var dx = distX - object.width / 2;
var dy = distY - object.height / 2;
return (dx * dx + dy * dy <= ((player.width + player.height) / 2 * (player.width + player.height) / 2));
}
var resolveCollision = function (object) {
// Add Resolve Collision Code
if (checkCollision(object)) {
throw new SyntaxError("Couldn't resolve collision between player and object!");
}
}
var renderFrame = function () {
if (control.up && player.jumping == false) {
player.velocity.y -= 20;
player.jumping = true;
}
if (control.left) {
player.velocity.x -= 0.5;
}
if (control.right) {
player.velocity.x += 0.5;
}
player.velocity.y += 1.5;
player.x += player.velocity.x;
player.y += player.velocity.y;
player.velocity.x *= 0.95;
player.velocity.y *= 0.95;
player.old.x = player.x - player.velocity.x * 1.2;
player.old.y = player.y - player.velocity.y * 1.2;
if (player.y > window.innerHeight - 25) {
player.jumping = false;
player.y = window.innerHeight - 25;
player.velocity.y = 0;
}
ctx.clearRect(0, 0, window.innerWidth, window.innerHeight);
ctx.fillStyle = player.color;
ctx.beginPath();
ctx.globalAlpha = 0.5;
ctx.ellipse(player.old.x, player.old.y, player.width, player.height, Math.PI / 4, 0, 2 * Math.PI);
ctx.fill();
ctx.beginPath();
ctx.globalAlpha = 1;
ctx.ellipse(player.x, player.y, player.width, player.height, Math.PI / 4, 0, 2 * Math.PI);
ctx.fill();
ctx.fillRect(obstacle.x, obstacle.y, obstacle.width, obstacle.height);
if (checkCollision(obstacle)) {
resolveCollision(obstacle)
}
window.requestAnimationFrame(renderFrame);
};
renderFrame();
window.addEventListener("keydown", control.keyListener);
window.addEventListener("keyup", control.keyListener);
Thanks in advance!
EDIT: I didn't make it clear enough before, but I need a way to resolve collision once they are detected. I have a method to detect collisions already.
You're talking about bounding box and bounding circle calculations.
Here is a real simple explanation of the algorithms for each type of geometry:
https://developer.mozilla.org/en-US/docs/Games/Techniques/2D_collision_detection
Of course if they're all the same shape that may simplify it, one solution would be to convert the circles to boxes:
https://math.stackexchange.com/questions/2360970/how-to-calculate-a-bounding-box-of-a-circle
Then just use bounding box collision detection.
But I think if you want to support both then the general strategy will be to attempt to detect if the Circle intersects with the box.
Here is a description of that algorithm:
https://www.lazyfoo.net/tutorials/SDL/29_circular_collision_detection/index.php
To check if a box and circle collided we need to find the closest point on the box...
Here we find the closest y position much like we did the x position. If the distance squared between the closest point on the box and the center of the circle is less than the circle's radius squared, then there is a collision.
I am creating particle animation using Canvas & JavaScript. Currently, particles are generating from an emitter randomly & falling in a direction (top right corner to bottom left corner). collision detection effect is also working on mouse hover.
problem is:
I want to create separate color-wise multiple lines of particles (e.g:- blue colored particles should be in separate a line & red in a separate line).
how could I achieve this?
Sorry for bad drawing, any reference will be very useful. Thank you in advance.
(function() {
var requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || window.msRequestAnimationFrame ||
function(callback) {
window.setTimeout(callback, 1000 / 60);
};
window.requestAnimationFrame = requestAnimationFrame;
})();
var particleArr = [],
canvas = document.getElementById("canvas"),
ctx = canvas.getContext("2d"),
flakeCount = 700,
mouseX = -100,
mouseY = -100,
xMultiplier = 0.015
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
var rate = 0.07788656582264941;
function getRandomColor() {
// Random Color Generate
const colorArr = ["rgba(215,88,69, 1)", "rgba(117, 161, 199, 1)"]; // Blue & Orange Color
const randomColor = colorArr[Math.floor(Math.random() * colorArr.length)];
return randomColor;
}
function flow() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
for (var i = 0; i < flakeCount; i++) {
var flake = particleArr[i],
x = mouseX,
y = mouseY,
minDist = 150,
x2 = flake.x,
y2 = flake.y;
var dist = Math.sqrt((x2 - x) * (x2 - x) + (y2 - y) * (y2 - y)),
dx = x2 - x,
dy = y2 - y;
if (dist < minDist) {
var force = minDist / (dist * dist),
xcomp = (x - x2) / dist,
ycomp = (y - y2) / dist,
deltaV = force / 2;
// deltaV = 0.03960597468566516
flake.velX -= deltaV * xcomp;
flake.velY -= deltaV * ycomp;
// flake.velX -= deltaV * xcomp + rate;
// flake.velY -= deltaV * ycomp + rate;
} else {
flake.velX *= .98;
if (flake.velY <= flake.speed) {
flake.velY = flake.speed
}
flake.velX += Math.cos(flake.step += .05) * flake.stepSize;
}
flake.y += flake.velY;
flake.x += flake.velX;
if (flake.y >= canvas.height || flake.y <= 0) {
reset(flake);
}
if (flake.x >= canvas.width || flake.x <= 0) {
reset(flake);
}
ctx.fillStyle = particleArr[i].color;
ctx.beginPath();
ctx.arc(flake.x, flake.y, flake.size, 0, Math.PI * 2);
ctx.fill();
}
requestAnimationFrame(flow);
// setTimeout(() => {
// requestAnimationFrame(flow);
// }, 1000 / 40);
};
function reset(flake) {
let temp = (Math.random() * 1) + 0.5;
flake.x = canvas.width;
flake.y = 50;
// flake.size = (Math.random() * 3) + 5;
flake.size = 6.692053245649504;
flake.speed = (Math.random() * 7) + 0.5;
flake.velY = flake.speed;
flake.velX = -xMultiplier * canvas.width * temp;
// flake.opacity = (Math.random() * 0.5) + 0.3;
}
function init() {
for (var i = 0; i < flakeCount; i++) {
var x = canvas.width,
y = 50,
size = 6.692053245649504,
speed = 0;
// size = (Math.random() * 3) + 5, // can change 3 to 4
// speed = (Math.random() * 1) + 0.5;
// opacity = (Math.random() * 0.5) + 0.3;
particleArr.push({
speed: speed,
velY: speed,
velX: -xMultiplier * canvas.width * speed,
x: x,
y: y,
size: size,
stepSize: (Math.random()) / 30,
step: 0,
angle: 360,
color: getRandomColor()
// opacity: opacity
});
}
requestAnimationFrame(flow);
// flow();
};
canvas.addEventListener("mousemove", function(e) {
mouseX = e.clientX,
mouseY = e.clientY
});
window.addEventListener( 'resize', onWindowResize, false );
function onWindowResize() {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
}
init();
canvas{
background-color: #000000 !important;
}
body {
margin: 0;
overflow: hidden;
}`
<canvas id="canvas"></canvas>
How would one go about adding a mouseover event to this code in order to change the speed of the snow falling. This is not my code, but I took it from http://codepen.io/loktar00/pen/CHpGo, to use as an example.
(function() {
var requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || window.msRequestAnimationFrame ||
function(callback) {
window.setTimeout(callback, 1000 / 60);
};
window.requestAnimationFrame = requestAnimationFrame;
})();
var flakes = [],
canvas = document.getElementById("canvas"),
ctx = canvas.getContext("2d"),
flakeCount = 400,
mX = -100,
mY = -100
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
function snow() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
for (var i = 0; i < flakeCount; i++) {
var flake = flakes[i],
x = mX,
y = mY,
minDist = 150,
x2 = flake.x,
y2 = flake.y;
var dist = Math.sqrt((x2 - x) * (x2 - x) + (y2 - y) * (y2 - y)),
dx = x2 - x,
dy = y2 - y;
if (dist < minDist) {
var force = minDist / (dist * dist),
xcomp = (x - x2) / dist,
ycomp = (y - y2) / dist,
deltaV = force / 2;
flake.velX -= deltaV * xcomp;
flake.velY -= deltaV * ycomp;
} else {
flake.velX *= .98;
if (flake.velY <= flake.speed) {
flake.velY = flake.speed
}
flake.velX += Math.cos(flake.step += .05) * flake.stepSize;
}
ctx.fillStyle = "rgba(255,255,255," + flake.opacity + ")";
flake.y += flake.velY;
flake.x += flake.velX;
if (flake.y >= canvas.height || flake.y <= 0) {
reset(flake);
}
if (flake.x >= canvas.width || flake.x <= 0) {
reset(flake);
}
ctx.beginPath();
ctx.arc(flake.x, flake.y, flake.size, 0, Math.PI * 2);
ctx.fill();
}
requestAnimationFrame(snow);
};
function reset(flake) {
flake.x = Math.floor(Math.random() * canvas.width);
flake.y = 0;
flake.size = (Math.random() * 3) + 2;
flake.speed = (Math.random() * 1) + 0.5;
flake.velY = flake.speed;
flake.velX = 0;
flake.opacity = (Math.random() * 0.5) + 0.3;
}
function init() {
for (var i = 0; i < flakeCount; i++) {
var x = Math.floor(Math.random() * canvas.width),
y = Math.floor(Math.random() * canvas.height),
size = (Math.random() * 3) + 2,
speed = (Math.random() * 1) + 0.5,
opacity = (Math.random() * 0.5) + 0.3;
flakes.push({
speed: speed,
velY: speed,
velX: 0,
x: x,
y: y,
size: size,
stepSize: (Math.random()) / 30,
step: 0,
opacity: opacity
});
}
snow();
};
canvas.addEventListener("mousemove", function(e) {
mX = e.clientX,
mY = e.clientY
});
window.addEventListener("resize",function(){
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
})
init();
We can not modify speed of each flakes on the fly, as it would impact performance. What we can do is maintain a minimum speed and access the speed of every flake after adding this minimum speed to the original one.
See the code below:
flakes.push({
speed: speed,
getSpeed: function () {
var localSpeed = minSpeed + this.speed;
return localSpeed;
},
velY: speed,
velX: 0,
x: x,
y: y,
size: size,
stepSize: (Math.random()) / 30,
step: 0,
opacity: opacity
});
Now we will use getSpeed() method to get the speed of snowflake instead of speed attribute.
Now modifying the minSpeed value will modify the speed of all snoflakes in one go.
Here's working codepen fork. Speed according to the speed of mouse pointer.
I'm currently trying to make a version of Breakout for university. Thanks for helping me to draw the paddle. I now find myself unable to cause the ball to bounce on the edge of the canvas - except in the middle. I've tried both adding and subtracting fractions to ball.x and ball.y (greater than or equal to canvas.width and canvas.height both work) but for less than or equal to 0, nothing seems to be successful. Here's the javascript code:
var canvas = document.getElementById("breakout");
var ctx = canvas.getContext("2d");
var PADDLE_WIDTH_PX = canvas.width / 5;
var PADDLE_HEIGHT_PX = 10;
var PADDLE_SPEED = 450;
var ball = {
x: canvas.width / 2, //pixels
y: canvas.height / 2, //pixels
xSpeed: 500, //pixels per second
ySpeed: 500, //pixels per second
radius: 100 //the ball is exceptionally large so that I can see what part of the ball is surpassing the canvas edge before the motion is reversed
}
var paddle = {
//radius: 5,
/*speed: 500,
TopRight: ctx.moveTo(canvas.width / 1.35, canvas.height - (canvas.height / 12.5)),
TopSide: ctx.lineTo(canvas.width / 2, canvas.height - (canvas.height / 12.5)),
RightSide: ctx.lineTo(canvas.width / 1.35, canvas.height - (canvas.height / 27.5)),
BottomLeft: ctx.moveTo(canvas.width / 2, canvas.height - (canvas.height / 27.5)),
LeftSide: ctx.lineTo(canvas.width / 2, canvas.height - (canvas.height / 12.5)),
BottomSide: ctx.lineTo(canvas.width / 1.35, canvas.height - (canvas.height / 27.5))*/
xSpeed: 450,
x: (canvas.width - PADDLE_WIDTH_PX) / 2,
y: canvas.height - PADDLE_HEIGHT_PX
}
var keysDown = {};
window.addEventListener("keydown",function(e) {
keysDown[e.keyCode] = true;
});
window.addEventListener("keyup",function(e) {
delete keysDown[e.keyCode];
});
function render() {
//clear the canvas
ctx.clearRect(0, 0, canvas.width, canvas.height)
// draw the ball
ctx.fillStyle = "white";
ctx.beginPath();
ctx.arc(ball.x, ball.y, ball.radius, 0, Math.PI * 2);
ctx.closePath();
ctx.fill();
ctx.beginPath();
//ctx.fillStyle = "red";
/*ctx.moveTo(canvas.width - (2*paddle.x), canvas.height - (2*paddle.y));
/*ctx.lineTo(canvas.width / 2, canvas.height - (canvas.height / 12.5));
ctx.lineTo(canvas.width / 1.35, canvas.height - (canvas.height / 27.5));
ctx.moveTo(canvas.width / 2, canvas.height - (canvas.height / 27.5));
ctx.lineTo(canvas.width / 2, canvas.height - (canvas.height / 12.5));
ctx.lineTo(canvas.width / 1.35, canvas.height - (canvas.height / 27.5));
ctx.fill();
ctx.closePath();*/
/*ctx.lineTo(canvas.width - (2*paddle.x), canvas.height - paddle.y);
ctx.moveTo(canvas.width - paddle.x, canvas.height - paddle.y);
ctx.lineTo(canvas.width - paddle.x, canvas.height - (2*paddle.y));
ctx.lineTo(canvas.width - (2*paddle.x), (canvas.height -paddle.y));*/
ctx.fillRect(paddle.x, paddle.y, PADDLE_WIDTH_PX, PADDLE_HEIGHT_PX);
/*ctx.closePath();
ctx.fill();*/
}
function update(elapsed) {
//update the ball position according to the elapsed time
ball.y += ball.ySpeed * elapsed;
ball.x += ball.xSpeed * elapsed;
/*paddle.TopRight += paddle.speed * elapsed;
paddle.BottomLeft += paddle.speed * elapsed;
paddle.RightSide += paddle.speed * elapsed;
paddle.LeftSide += paddle.speed * elapsed;
paddle.TopSide += paddle.speed * elapsed;
paddle.BottomSide += paddle.speed * elapsed;*/
/*paddle.x += paddle.xSpeed * elapsed;
paddle.y += paddle.xSpeed * elapsed;*/
//bounce the ball of all edges
if (37 in keysDown && paddle.x > 0)
paddle.x -= PADDLE_SPEED * elapsed;
if (39 in keysDown && paddle.x + PADDLE_WIDTH_PX < canvas.width)
paddle.x += PADDLE_SPEED * elapsed;
if (ball.x+(ball.x/7) >= canvas.width) {
ball.x -= 5;
ball.xSpeed *= -1;
}
if (ball.x-(ball.x/7) <= 0) {
ball.x += 5;
ball.xSpeed *= -1;
}
if (ball.y+(ball.y/100) <= 0) {
ball.y += 5;
ball.ySpeed *= -1;
}
if (ball.y+(ball.y/3) >= canvas.height) {
ball.y -= 5;
ball.ySpeed *= -1;
}
/*
The problem here is that sometimes the ball gets 'stuck' to an edge.
This can occur when the ball passes beyond an edge in a frame when the
elapsed time is relatively large. In some cases, when the elapsed time in the
next frame is relatively short, the ball doesn't reach the edge to get back
into play. This results in another flip of the velocity and the ball becomes
'trapped' on the edge.
e.g.
xSpeed = -500, x = 10, elapsed = 0.2 => xSpeed = 500, x = -90 (xMovement = -100)
xSpeed = 500, x = -90, elapsed = 0.1 => xSpeed = -500, x = -40 (xMovement = +50)
xSpeed = -500, x = -40, elapsed = 0.1 => xSpeed = 500, x = -40 (xMovement = -50)
and so on ...until a larger elapsed time occurs in the right direction
The fix for this is to move the ball to the edge when the velocity is flipped.
*/
}
var previous;
function run(timestamp) {
if (!previous) previous = timestamp; //start with no elapsed time
var elapsed = (timestamp - previous) / 1000; //work out the elapsed time
update(elapsed); //update the game with the elapsed time
render(); //render the scene
previous = timestamp; //set the (globally defined) previous timestamp ready for next time
window.requestAnimationFrame(run); //ask browser to call this function again, when it's ready
}
//trigger the game loop
window.requestAnimationFrame(run);
Thanks for taking the time to read this
--ConfusedStudent
There is a number of issues with the paddle.
First of all you probably want it to be of a fixed size, so let's define
its dimensions at the beginning of the file (put it after the first two lines of your code because it uses the canvas to set the paddle width to 1/5th of its width - I think that's what you tried to do):
var PADDLE_WIDTH_PX = canvas.width / 5;
var PADDLE_HEIGHT_PX = 10;
With this you can initialize the paddle to be at the bottom of the canvas and in the middle:
var paddle = {
x: (canvas.width - PADDLE_WIDTH_PX) / 2,
y: canvas.height - PADDLE_HEIGHT_PX
}
x and y are the top-left corner of the paddle, so the right side is at x + PADDLE_WIDTH_PX and the bottom side is at y + PADDLE_HEIGHT_PX.
Knowing this, you can draw a path throught all the four corners like this:
ctx.beginPath();
ctx.moveTo(paddle.x, paddle.y);
ctx.lineTo(paddle.x + PADDLE_WIDTH_PX, paddle.y);
ctx.lineTo(paddle.x + PADDLE_WIDTH_PX, paddle.y + PADDLE_HEIGHT_PX);
ctx.lineTo(paddle.x, paddle.y + PADDLE_HEIGHT_PX);
ctx.lineTo(paddle.x, paddle.y);
ctx.closePath();
But since the paddle is just a rectangle, it's easier to use a method for drawing rectangles - fillRect, like this:
ctx.fillRect(paddle.x, paddle.y, PADDLE_WIDTH_PX, PADDLE_HEIGHT_PX);
Either way, all the four corners of the paddle move toggether so it doesn't grow or shrink.
So if you put it in your render function, it looks like this:
function render() {
//clear the canvas
ctx.clearRect(0, 0, canvas.width, canvas.height)
// draw the ball
ctx.fillStyle = "white";
ctx.beginPath();
ctx.arc(ball.x, ball.y, ball.radius, 0, Math.PI * 2);
ctx.closePath();
ctx.fill();
// draw the paddle
ctx.fillStyle = "red";
ctx.fillRect(paddle.x, paddle.y, PADDLE_WIDTH_PX, PADDLE_HEIGHT_PX);
}
The last thing is to get the paddle to move when the left and right arrow keys are pressed.
The paddle only moves when either arrow left or arrow right is pressed. Otherwise its speed is 0 and it sits in its place. Therefore the paddle object doesn't need the xSpeed member variable.
Also, the paddle only moves horizontally so only its x variable changes, y is always the same.
Let's first define the speed of the paddle at the beginning of the file:
var PADDLE_SPEED = 300;
and then let's put the movement logic in the update function:
if (37 in keysDown && paddle.x > 0)
paddle.x -= PADDLE_SPEED * elapsed;
else if (39 in keysDown && paddle.x + PADDLE_WIDTH_PX < canvas.width)
paddle.x += PADDLE_SPEED * elapsed;
You can notice that the paddle position is only changed if an arrow key is pressed and the paddle is not at the edge.
All the other code that deals with the paddle should be removed from the update function so that it looks like this (I have removed most of the comments):
function update(elapsed) {
//update the ball position according to the elapsed time
ball.y += ball.ySpeed * elapsed;
ball.x += ball.xSpeed * elapsed;
if (37 in keysDown && paddle.x > 0)
paddle.x -= PADDLE_SPEED * elapsed;
else if (39 in keysDown && paddle.x + PADDLE_WIDTH_PX < canvas.width)
paddle.x += PADDLE_SPEED * elapsed;
//bounce the ball of all edges
if/*(*/(ball.x /*- (ball.x / 2))*/<= 0) {
ball.x = 1;
ball.xSpeed *= -1;
}
if /*(*/(ball.x /*+ (ball.x / 2))*/>= canvas.width) {
ball.x = ball.x -1;
ball.xSpeed *= -1;
}
if/*(*/(ball.y /*- (ball.y / 2))*/<= 0) {
ball.y = 1;
ball.ySpeed *= -1;
}
if /*(*/(ball.y /*+ (ball.y / 2))*/>= canvas.height) {
ball.y = ball.y -1;
ball.ySpeed *= -1;
}
}
I'm making a javascript game in which I want enemy ships to be able to rotate towards a specified point. Their movement can then be calculated from their angle. The following code for the rotation works only if the ship is below and to the right of its target. If the ship is to the left, it rotates to ~0 degrees and jitters there. To the top right it continuously rotates counterclockwise.
What the heck am I doing wrong here? Any suggestions as to a better method?
obj.angle %= 360;
// Calculate angle to target point
var targetAngle = Math.atan2(obj.mode.dest.y - obj.y, obj.mode.dest.x - obj.x) * (180 / Math.PI) + 90;
// Get the difference between the current angle and the target angle
var netAngle = Math.abs(obj.angle - targetAngle) % 360;
// Turn in the closest direction to the target
netAngle > 180 ? obj.angle += obj.shipType.turnSpeed : obj.angle -= obj.shipType.turnSpeed;
if(obj.angle < 0) obj.angle += 360;
if(obj.angle > 360) obj.angle -= 360;
My question is very similar to this one, which explains it better but unfortunately is in C#.
EDIT: Here's the working code, for anyone who might find it useful:
obj.angle %= 360;
var targetAngle = Math.atan2(obj.mode.dest.y - obj.y, obj.mode.dest.x - obj.x) * (180 / Math.PI) + 90;
targetAngle = (targetAngle + 360) % 360;
if(obj.angle != targetAngle)
{
var netAngle = (obj.angle - targetAngle + 360) % 360;
var delta = Math.min(Math.abs(netAngle - 360), netAngle, obj.shipType.turnSpeed);
var sign = (netAngle - 180) >= 0 ? 1 : -1;
obj.angle += sign * delta + 360;
obj.angle %= 360;
}
With little change to your code it appears to work:
http://jsfiddle.net/57hAk/11/
This can be done without the conversion to degrees.
The steps are:
Compute the angle theta between the source and destination vectors with Math.atan2.
Normalize theta to the range 0..Tau.
Take the difference between the source angle and theta, taking care of wraparound below -PI during the subtraction.
This produces a diff value between -Pi..+Pi. If it's negative, turn clockwise, otherwise turn counterclockwise.
The formula only works if the player angle is constrained to the range -Pi..+Pi.
Here's a working example.
const {PI} = Math;
const TAU = 2 * PI;
const canvas = document.querySelector("canvas");
canvas.width = canvas.height = 240;
const ctx = canvas.getContext("2d");
const player = {
x: canvas.width / 2,
y: canvas.height / 2,
angle: 0,
radius: 20,
};
const mouse = {x: canvas.width / 2, y: canvas.height / 2};
canvas.addEventListener("mousemove", e => {
mouse.x = e.offsetX;
mouse.y = e.offsetY;
});
(function rerender() {
requestAnimationFrame(rerender);
let theta = Math.atan2(mouse.y - player.y, mouse.x - player.x);
theta = theta < 0 ? theta + TAU : theta;
let diff = player.angle - theta;
diff = diff < -PI ? diff + TAU : diff;
if (Math.abs(diff) > 0.02) {
player.angle -= 0.04 * Math.sign(diff);
if (player.angle > PI) {
player.angle -= TAU;
}
else if (player.angle < -PI) {
player.angle += TAU;
}
}
ctx.clearRect(0, 0, canvas.width, canvas.height);
// draw player
ctx.save();
ctx.strokeStyle = "black";
ctx.lineWidth = 8;
ctx.translate(player.x, player.y);
ctx.rotate(player.angle);
ctx.beginPath();
ctx.arc(0, 0, player.radius, 0, TAU);
ctx.stroke();
ctx.beginPath();
ctx.moveTo(0, 0);
ctx.lineTo(player.radius * 2, 0);
ctx.stroke();
ctx.restore();
// draw crosshair
ctx.strokeStyle = "red";
ctx.lineWidth = 4;
ctx.beginPath();
ctx.moveTo(mouse.x - 10, mouse.y);
ctx.lineTo(mouse.x + 10, mouse.y);
ctx.stroke();
ctx.beginPath();
ctx.moveTo(mouse.x, mouse.y - 10);
ctx.lineTo(mouse.x, mouse.y + 10);
ctx.stroke();
})();
canvas {
border: 2px solid black;
}
<canvas></canvas>
Another option:
const theta = Math.atan2(mouse.y - player.y, mouse.x - player.x);
let diff = player.angle - theta;
diff = diff > PI ? diff - TAU : diff;
diff = diff < -PI ? diff + TAU : diff;
This can probably be simplified further; feel free to leave a comment.