How to manage multiple ball animations? - javascript

I have made a program that is supposed to make several balls move along a path. So far, I have only been able to make one ball successfully traverse the course because whenever I add another ball (from the array of balls) it begins to flicker and spasmodically disappears. I would appreciate any assistance in solving this problem.
JS bin
<!DOCTYPE html>
<html>
<head>
<style>
* {
padding: 0;
margin: 0;
}
canvas {
background: #eee;
display: block;
margin: 0 auto;
}
</style>
</head>
<body>
<canvas id="Circuit" width="500" height="320"></canvas>
<script>
var dad = [];
var canvas = document.getElementById("Circuit");
var ctx = canvas.getContext("2d");
var bool = false;
var dx1 = 2;
var dx2 = -2;
var dy1 = 0;
var dy2 = 2;
var memes = [{
x: 0,
y: 100,
}, {
x: 0,
y: 100,
}, {
x: 0,
y: 100,
}, {
x: 0,
y: 100,
}];
function drawCircle(index) {
ctx.beginPath();
ctx.arc(memes[index].x, memes[index].y, 10, 0, Math.PI * 2);
ctx.fillStyle = "#0095DD";
ctx.fill();
ctx.closePath();
}
function draw(index) {
if (index == 0) {
ctx.clearRect(0, 0, canvas.width, canvas.height);
}
if (memes[index].x < 490 && memes[index].y < 291 && !bool) {
drawCircle(index);
memes[index].x += dx1;
memes[index].y += dy1;
}
else if (memes[index].x == 490) {
drawCircle(index);
memes[index].x += 1;
}
else if (memes[index].x > 490 && memes[index].y < 291) {
drawCircle(index);
memes[index].y += dy2;
}
else if (memes[index].y == 291) {
drawCircle(index);
memes[index].y += 1;
}
else if (memes[index].y > 291 && memes[index].x > 2) {
drawCircle(index);
bool = true;
memes[index].x -= 2;
}
else if (memes[index].x == 2 && memes[index].y > 291) {
drawCircle(index);
memes[index].x -= 1;
}
else if (memes[index].x < 2) {
drawCircle(index);
memes[index].y -= dy2;
if (memes[index].y < 100) {
clearInterval(dad[index]);
ctx.clearRect(0, 0, canvas.width, canvas.height);
}
}
ctx.strokeStyle = "red";
ctx.strokeRect(2, 101, 490, 190);
ctx.strokeStyle = "blue";
ctx.strokeRect(2, 82, 40, 40);
}
setTimeout(function() {
setTimeout(function() {
dad[1] = setInterval(function() {
draw(1);
}, 20);
}, 1000);
dad[0] = setInterval(function() {
draw(0);
}, 20);
}, 1000);
</script>
</body>
</html>

The flicker happens when the second ball tries to render the frame. You have two sprites (animating things) clearing and drawing the frame. You also have multiple timers and when animating you usually want a nextFrame function that handles movement of the sprites and drawing the frame.
The sprites array is a list of things that need to be moved and drawn. I added some properties to the meme sprites so you can see that their state needs to be internal like with the "bool" value. Without that you end up effecting the other balls. You'll need to figure out how to remove sprites when they're no longer in play.
var dad = [];
var canvas = document.getElementById("Circuit");
var ctx = canvas.getContext("2d");
var bool = false;
var dx1 = 2;
var dx2 = -2;
var dy1 = 0;
var dy2 = 2;
var memes = [{
x: 0,
y: 100,
color: "#0095DD",
draw: drawMeme,
move: moveMeme,
vx: 1.2,
vy: 1.5,
}, {
x: 0,
y: 100,
vx: 1.5,
vy: 1,
color: "#DD9500",
draw: drawMeme,
move: moveMeme
}, {
x: 0,
y: 100,
vx: 2,
vy: 1,
color: "#FF0000",
draw: drawMeme,
move: moveMeme
}, {
x: 0,
y: 100,
vx: 3,
vy: 2,
color: "#009999",
draw: drawMeme,
move: moveMeme
}];
function drawMeme(meme) {
ctx.beginPath();
ctx.arc(meme.x, meme.y, 10, 0, Math.PI * 2);
ctx.fillStyle = meme.color;
ctx.fill();
ctx.closePath();
}
var sprites = [];
function nextFrame () {
ctx.clearRect(0, 0, canvas.width, canvas.height);
var len = sprites.length;
for (var i = 0; i < len; i++) {
var sprite = sprites[i];
sprite.move(sprite);
sprite.draw(sprite);
}
ctx.strokeStyle = "red";
ctx.strokeRect(2, 101, 490, 190);
ctx.strokeStyle = "blue";
ctx.strokeRect(2, 82, 40, 40);
requestAnimationFrame(nextFrame);
}
function moveMeme(meme) {
if (meme.x < 490 && meme.y < 291 && !meme.bool) {
meme.x += dx1 * meme.vx;
meme.y += dy1 * meme.vy;
}
else if (meme.x == 490) {
meme.x += 1 * meme.vx;
}
else if (meme.x > 490 && meme.y < 291) {
meme.y += dy2 * meme.vy;
}
else if (meme.y == 291) {
meme.y += 1 * meme.vy;
}
else if (meme.y > 291 && meme.x > 2) {
meme.bool = true;
meme.x -= 2 * meme.vx;
}
else if (meme.x == 2 && meme.y > 291) {
meme.x -= 1 * meme.vx;
}
else if (meme.x < 2) {
meme.y -= dy2 * meme.vy;
if (meme.y < 100) {
// stop drawing this sprite
meme.draw = function(){};
meme.delete = 1; // for a cleanup function
}
}
}
nextFrame();
function startMeme(index) {
var meme = memes[index];
sprites.push(meme);
}
setTimeout(function() {
setTimeout(function() {
startMeme(1);
}, 1000);
startMeme(0);
startMeme(2);
startMeme(3);
}, 1000);
<canvas id="Circuit" width="500" height="320"></canvas>

Related

Javascript - 2d Platformer - Bounding Box Collision Detection

I am trying to make a sonic 2d platformer. However I am struggling with making my sonic entity collide with my platform. Right now my sonic is falling through the platform as if my collisionCheck() method does not work at all.
Here is my sonic.js:
class Sonic {
constructor(game) {
this.game = game;
this.game.sonic = this; // special entity
this.position = {
x: 0,
y: 0
}
this.velocity = {
x: 0, //increase as to the right ->
y: 0 // increase as downwards
}
this.speed = 1300;
this.jumpSpeed = 200;
this.spinSpeed = 1400;
this.spritesheet = ASSET_MANAGER.getAsset("./sprites/realSonicSheet.png");
this.updateBB();
this.animations = [];
this.state = 0;
this.direction = 0;
this.loadAnimations();
this.ground = 550;
this.onGround = false;
this.BB = null;
this.lastBB = null;
}
loadAnimations() {
for (var i = 0; i < 4; i++) { // four states (idle, running, jumping and spinning)
this.animations.push([]);
for (var k = 0; k < 2; k++) { // two directions (right or left)
this.animations[i].push([]);
}
}
// idle animation for state = 0
// facing right = 0
this.animations[0][0] = new Animator(this.spritesheet, 5, 723, 45, 45, 1, 0.33, 0, false, true);
// facing left = 1
this.animations[0][1] = new Animator(this.spritesheet, 741, 38, -41, 47, 1, 0.33, 0, true, true); // change to true true if issues
// run animation
// facing right
this.animations[1][0]= new Animator(this.spritesheet, 2, 916, 49, 45, 14, 0.08, 0, false, true);
// facing left
this.animations[1][1] = new Animator(this.spritesheet, 745, 230, -49, 47, 14, 0.08, 0, false, true);
// jump animation
// facing right
this.animations[2][0] = new Animator(this.spritesheet, 340, 1160, 47, 56, 8, 0.08, 0, false, true);
// facing left
this.animations[2][1] = new Animator(this.spritesheet, 410, 480, -47, 56, 8, 0.08, 0, false, true);
// spinning animation
// facing right
this.animations[3][0] = new Animator(this.spritesheet, 1, 1113, 47, 40, 10, 0.08, 0, false, true);
// facing left
this.animations[3][1] = new Animator(this.spritesheet, 746, 427, -47, 40, 10, 0.08, 0, false, true);
}
updateBB() {
this.BB = new BoundingBox(this.position.x, this.position.y, 155, 130, "red");
this.lastBB = new BoundingBox(this.BB.x, this.BB.y, this.BB.width, this.BB.height, this.BB.color);
console.log("This is this.BB", this.BB);
}
update() {
const GRAVITY = 0.5;
this.updateBB();
let standingOnPlatform = false;
let canvasHeight = 768;
// Move left
if (this.game.left) {
console.log(this.game.left);
this.position.x -= this.speed * this.game.clockTick;
this.direction = 1;
this.state = 1;
console.log(this.position.x);
}
// Move right and spinning left
if (this.game.spin && this.game.left) {
this.position.x -= this.spinSpeed * this.game.clockTick;
this.state = 3;
}
// Move right
if (this.game.right) {
console.log(this.game.right);
this.position.x += this.speed * this.game.clockTick;
this.direction = 0;
this.state = 1
console.log(this.position.x)
}
// Jump
if (this.game.jump) {
console.log(this.game.jump)
this.position.y -= 20;
this.state = 2;
this.direction = 0;
}
this.velocity.y += GRAVITY; // Gravity to pull sonic down after jumping
this.position.y += this.velocity.y;
// Spin
if (this.game.spin) {
console.log(this.game.spin)
this.position.x += this.spinSpeed * this.game.clockTick;
this.position.y += 10;
this.state = 3;
}
// Set state back to idle if no actions are being performed
if (!this.game.left && !this.game.right && !this.game.jump && !this.game.spin) {
this.state = 0;
}
// If Sonic is not standing on a platform, check if he has fallen off the screen
if (!standingOnPlatform) {
this.velocity.y += GRAVITY;
this.position.y += this.velocity.y;
// // Check if Sonic has fallen off the screen
// if (this.position.y > canvasHeight) {
// // Reload the game
// window.location.reload();
// this.position.y = 300; // start at y position 300
// }
}
// Update the bounding box for Sonic's new position
this.updateBB();
this.collisionCheck();
}
collisionCheck() {
this.game.entities.forEach(entity => {
if (this !== entity && entity.BB && this.BB.collide(entity.BB)) {
if (entity instanceof Platform) {
if (this.velocity.y > 0 && this.lastBB.bottom <= entity.BB.top) {
this.position.y = entity.BB.top - this.BB.height;
this.velocity.y = 0;
this.onGround = true;
return;
}
if (this.velocity.y < 0 && this.lastBB.top >= entity.BB.bottom) {
this.position.y = entity.BB.bottom;
this.velocity.y = 0;
return;
}
if (this.velocity.x > 0 && this.lastBB.right <= entity.BB.left) {
this.position.x = entity.BB.left - this.BB.width;
this.velocity.x = 0;
return;
}
if (this.velocity.x < 0 && this.lastBB.left >= entity.BB.right) {
this.position.x = entity.BB.right;
this.velocity.x = 0;
return;
}
}
}
});
}
draw(ctx) {
if(this.state < 0 || this.state > 3) this.state = 0;
let done = this.animations[this.state][this.direction].drawFrame(this.game.clockTick, ctx, this.position.x - this.game.camera.x , this.position.y);
if (done) {
this.animations[this.state][this.direction].elapsedTime = 0;
this.state = 0;
}
if (PARAMS.DEBUG) {
this.game.ctx.strokeStyle = "red";
this.game.ctx.strokeRect(this.BB.x - this.game.camera.x, this.BB.y, this.BB.width, this.BB.height);
}
}
}
Here is my platform.js
class Platform {
constructor(game, x, y, width, height) {
this.game = game;
this.position = {
x: x,
y: y
};
this.width = width;
this.height = height;
this.boundingBox = {
x: this.position.x,
y: this.position.y,
width: this.width,
height: this.height
};
this.spritesheet = ASSET_MANAGER.getAsset("./sprites/floor.png");
}
updateBB() {
this.boundingBox.x = this.position.x;
this.boundingBox.y = this.position.y;
}
update() {
this.updateBB();
}
draw(ctx) {
ctx.drawImage(this.spritesheet, this.position.x - this.game.camera.x, this.position.y, this.width, this.height);
if (PARAMS.DEBUG) {
ctx.strokeStyle = "lime";
ctx.strokeRect(
this.boundingBox.x - this.game.camera.x,
this.boundingBox.y,
this.boundingBox.width,
this.boundingBox.height
);
}
}
}
I have tried debugging my collisionCheck method (line 142 of sonic.js)and seeing if my bounding boxes are drawn correctly around my sonic and platforms and they are drawn correctly.

add delay to iteraction

I'm building an animation with multiple drawn circles going left to right. However, the animation starts at the same time for all circles, and I need them to have a timed interval (for example 1 second) between each other. Any idea on how to manage this?
I've tried setInterval without success.
The drawing of circles is as follows:
function isPrime(num) {
for(var i = 2; i < num; i++)
if(num % i === 0) return false;
return num > 1;
}
const settings = {
width: 300,
height: 930,
radius: 13,
gap: 5,
circles: 30,
};
const canvas = document.getElementById("canvas");
canvas.width = settings.width;
canvas.height = settings.height;
const ctx = canvas.getContext("2d");
var randomNumber = [];
const circles = [...Array(settings.circles).keys()].map((i) => ({
number: randomNumber[i],
x: settings.radius,
y: settings.radius + (settings.radius * 2 + settings.gap) * i,
radius: settings.radius,
dx: 100, // This is the speed in pixels per second
dy: 0,
isPrime: isPrime(randomNumber[i]),
}));
function drawCircle(circle) {
i = 0;
if (circle.number > 0 && circle.number <= 10) {
ctx.strokeStyle = "#0b0bf1";
} else if (circle.number > 10 && circle.number <= 20) {
ctx.strokeStyle = "#f10b0b";
} else if (circle.number > 20 && circle.number <= 30) {
ctx.strokeStyle = "#0bf163";
} else if (circle.number > 30 && circle.number <= 40) {
ctx.strokeStyle = "#f1da0b";
} else if (circle.number > 40 && circle.number <= 50) {
ctx.strokeStyle = "#950bf1";
} else if (circle.number > 50 && circle.number <= 60) {
ctx.strokeStyle = "#0bf1e5";
}
ctx.beginPath();
ctx.arc(circle.x, circle.y, circle.radius, 0, Math.PI * 2, false);
ctx.stroke();
ctx.fillText(circle.number, circle.x - 5, circle.y + 3);
}
function updateCircle(circle, dt) {
circle.x = clamp(
circle.x + circle.dx * dt,
circle.radius + 1,
settings.width - circle.radius - 1
);
circle.y = clamp(
circle.y + circle.dy * dt,
circle.radius + 1,
settings.height - circle.radius - 1
);
}
function animate() {
ctx.clearRect(0, 0, settings.width, settings.height);
circles.forEach(drawCircle);
requestAnimationFrame(animate);
}
animate();
update((dt) => circles.forEach((circle) => updateCircle(circle, dt)), 50);
I think you want to do like
let i = 0, l = circles.length;
let intv = setInterval(()=>{
drawCircle(circles[i++]);
if(i === l){
clearInterval(intv); intv = undefined;
}
}, 100); // change interval as desired
instead of circles.forEach(drawCircle);.
That requestAnimationFrame looks pretty useless like that too.

Remove an item that is drawn on to a canvas from an array. Where each item has a different purpose

I'm currently making a game and I am at the point where I'm trying to implement powerups. For the power ups I use a for loop that creates an object which allows me to give them different characteristics.
var type;
Powerups = [];
for(var j = 0; j < 325; j++) {
if (type="undefined"){
type = "scoreUp";
} else if(type="scoreUp"){
type = "horizonUp"
} else if (type="horizonUp"){
type = "newBall"
} else if (type="newBall") {
type = "scoreUp"
}
Powerups.push({
"x": randInt(20,360),
"y": 345 + (j * 240),
"width": 10,
"type": type
})
}
I then use rect circle collision that allows me to detect if the player collides with a powerup. If it does, the following line of code runs Powerups.splice(j, 1)
One thing that I've noticed is that the powerups never change and always increase score when a collision occurs. I think that this is because of the code that I use to remove the power up when colliding.
Is there a way where I could make it work as intended
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var isMenu = true;
var isPlaying = false;
var testing = true;
var gameOver = false;
var pause;
var pressingLeft = false;
var pressingRight = false;
var pressingP = false;
var Platforms = [];
var Powerups = [];
var difficulty = 1;
var playerGravity = 1;
var Player = { color: "red", radius: 7.5, stepY: 1.5, x: 175, y: 75 };
var Score = 0;
var speed = 1;
var type;
var moveR = 2;
var moveL = 2;
function PlayerColliding(circle, rect) {
var distX = Math.abs(circle.x - rect.x - rect.width / 2);
var distY = Math.abs(circle.y - rect.y - 20 / 2);
if (distX > (rect.width / 2 + circle.radius) && (distX) - 70 < (rect.width / 2 + circle.radius)) return false;
if (distY > (20 / 2 + circle.radius)) return false;
if (distX <= (rect.width / 2)) return true;
if (distY >= (20 / 2)) return true;
var dx = distX - rect.width / 2;
var dy = distY - 20 / 2;
return (dx * dx + dy * dy <= (circle.radius * circle.radius));
}
function PowerColliding(circle, rect) {
var distX = Math.abs(circle.x - rect.x - rect.width / 2);
var distY = Math.abs(circle.y - rect.y - 20 / 2);
if (distX > (rect.width / 2 + circle.radius)) return false;
if (distY > (20 / 2 + circle.radius)) return false;
if (distX <= (rect.width / 2)) return true;
if (distY >= (20 / 2)) return true;
var dx = distX - rect.width / 2;
var dy = distY - 20 / 2;
return (dx * dx + dy * dy <= (circle.radius * circle.radius));
}
function drawBackground(Player) {
ctx.fillStyle = "black"; ctx.fillRect(0, 0, canvas.width, canvas.height);
if (isMenu && !isPlaying) {
Score = 0;
createText("60px monospace", "white", "FallDown", 45, 130);
createText("34px Arial", "white", "PLAY", 130, 240);
createText("34px Arial", "white", "LEADERBOARD", 50, 300);
createText("34px Arial", "white", "SETTINGS", 90, 360);
createText("34px Arial", "white", "INFO", 130, 420);
}
if(isMenu && isPlaying) {
createText("60px monospace", "white", "Game Mode", 40, 130);
createText("34px Arial", "white", "CLASSIC", 110, 240);
createText("34px Arial", "white", "VERSUS", 113.5, 300);
}
else if(pause) {
isPlaying = false;
createText("60px monospace", "white", "PAUSED", 90, 130);
createText("34px Arial", "white", "RESUME", 115, 260);
createText("34px Arial", "white", "SETTINGS", 100, 340);
createText("34px Arial", "white", "QUIT", 145, 420);
}
else if(!isMenu && isPlaying) {
if (testing) {
Platforms = [];
for (var i = 0; i < 1300; i++) {
Platforms.push({
"x": 10,
"y": 300 + (i * 60),
"width": (Math.random() * canvas.width) - 60
});
}
testing = false;
Powerups = [];
for(var j = 0; j < 325; j++) {
if (type="undefined"){
type = "scoreUp";
} else if(type="scoreUp"){
type = "horizonUp"
} else if (type="horizonUp"){
type = "newBall"
} else if (type="newBall") {
type = "scoreUp"
}
Powerups.push({
"x": randInt(20,360),
"y": 345 + (j * 240),
"width": 10,
"type": type
})
}
}
if(Player.y<=0) {
restartGame()
}
var playerCollided;
for (var i in Platforms) {
ctx.fillStyle = "#00ffff";
ctx.fillRect(10, Platforms[i].y, Platforms[i].width, 20);
var totalTest = Platforms[i].width + 60;
ctx.fillRect(totalTest + 30, Platforms[i].y, canvas.width - totalTest, 20);
Platforms[i].y -= 1;
if (!playerCollided) {
if (PlayerColliding(Player, Platforms[i])) {
playerGravity = -1;
playerCollided = true;
} else {
playerGravity = 2.5;
}
}
}
var powerCollided;
for (var j in Powerups) {
ctx.fillStyle = "#ff00ff";
ctx.fillRect(Powerups[j].x, Powerups[j].y, Powerups[j].width, 10);
Powerups[j].y -= 1;
if (!powerCollided) {
if (PowerColliding(Player, Powerups[j])) {
powerCollided = true;
Powerups.splice(j, 1)
if(type="scoreUp") {
Score += 75
} else if (type="horizonUp") {
moveR+= 0.5;
moveL+=0.5;
console.log("hup")
}
}
}
}
displayScore();
detectBorderCollision();
detectPlayerCollision();
drawPlayer();
drawBorder();
}
if (Platforms.length === 7) Platforms = [];
}
function displayScore() {
ctx.beginPath();
Score +=1;
ctx.font = "15px arial black";
ctx.fillStyle = 'white';
ctx.strokeStyle = 'black';
ctx.fillText(Score, 180, 50);
ctx.lineWidth = 0.25;
ctx.strokeText(Score, 180, 50);
ctx.fill();
ctx.stroke();
ctx.closePath();
}
function detectBorderCollision() {
if (Player.x > 370 - Player.radius) {
Player.x = 370 - Player.radius;
} else if (Player.x < 3.8 + Player.radius * 2) {
Player.x = 3.8 + Player.radius * 2
}
}
function detectPlayerCollision() {
}
function randInt(min, max) {
return ~~(Math.random() * (max - min + 1) + min);
}
function drawPlayer() {
ctx.beginPath();
ctx.fillStyle = Player.color;
ctx.arc(Player.x, Player.y, Player.radius, 0, 2 * Math.PI);
ctx.fill();
ctx.closePath();
Player.y += playerGravity;
if (pressingRight) {
Player.x += 2;
} else if (pressingLeft) {
Player.x -= 2;
}
/*
ctx.fillStyle = "#00ffff"; ctx.fillRect(10, 160, 300, 20); */ }
function drawBorder() {
ctx.beginPath();
ctx.strokeStyle = "#00ffff";
ctx.lineWidth = 10;
ctx.moveTo(5, 0);
ctx.lineTo(5, 640);
ctx.moveTo(375, 0);
ctx.lineTo(375, 640);
ctx.stroke();
ctx.closePath();
}
function createText(font, color, value, posX, posY) {
ctx.font = font;
ctx.fillStyle = color;
ctx.fillText(value, posX, posY)
}
function isInside(realX, realY, x1, x2, y1, y2) {
return (realX > x1 && realX < x2) && (realY > y1 && realY < y2)
}
function drawGame() {
drawBackground(Player);
}
function startDrawing() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
drawGame();
requestAnimationFrame(startDrawing);
}
function restartGame() {
isPlaying = false;
isMenu = true;
pause = false;
Player.y= 75;
Player.x = 175
Platforms = [];
var Score = 1;
testing = true;
pressingLeft = false;
pressingRight = false;
var moveR = 2;
var moveL = 2;
}
function Init() {
requestAnimationFrame(startDrawing);
canvas.addEventListener("click", function(evt) {
var rect = canvas.getBoundingClientRect();
var mouseX = evt.clientX - rect.left;
var mouseY = evt.clientY - rect.top;
if (isMenu && !isPlaying) {
if (isInside(mouseX, mouseY, 115, 230, 220, 270)) {
isPlaying = true;
isMenu = false;
} else if (isInside(mouseX, mouseY, 35, 320, 300, 345)) {
console.log("Leaderboard");
} else if (isInside(mouseX, mouseY, 75, 270, 380, 430)) {
console.log("Settings");
}
} else if (pause) {
if(isInside(mouseX, mouseY, 110, 270, 230, 260)) {
pause = false;
isPlaying = true;
} else if (isInside(mouseX, mouseY, 95, 270, 310, 345)) {
console.log('settings')
} else if (isInside(mouseX, mouseY, 140, 230, 390, 425)) {
console.log('quit')
restartGame();
}
}
});
window.addEventListener("keydown", function(evt) {
if (!isMenu && isPlaying || pause) {
if (evt.keyCode === 39) { // right
pressingRight = true;
} else if (evt.keyCode === 37) { // left
pressingLeft = true;
} else if (evt.keyCode === 80) {
pressingP = true;
pause = !pause;
if(!pause) {
isPlaying = true;
}
}
}
});
window.addEventListener("keyup", function(evt) {
if (!isMenu && isPlaying) {
if (evt.keyCode === 39) { // right
pressingRight = false;
} else if (evt.keyCode === 37) { // left
pressingLeft = false;
}
}
});
}
Init();
<html>
<head>
<title>Falldown</title>
</head>
<body>
<canvas id="canvas" width = "380" height= "640"></canvas>
<script src="beta.js"></script>
</body>
</html>

Why does the ball in pong get stuck at the bottom?

I recently made a JS Pong game. It works well, but the ball rarely gets stuck at the bottom or top. It looks like it is halfway through the wall and constantly bouncing. Video of the issue happening. You can try the game here. I do not know why this issue is happening because the logic seems right and works 90% of the time correctly. Here are the main two functions of my program:
function moveAll() {
if (showingWinScreen) {
return;
}
computerMovement();
ballX += ballSpeedX;
ballY += ballSpeedY;
if (ballY <= 10) {
ballSpeedY = -ballSpeedY;
} else if (ballY >= HEIGHT - 10) {
ballSpeedY = -ballSpeedY;
}
if (ballX >= WIDTH - 10) {
if ((ballY > paddleY) && (ballY < paddleY + 100)) {
ballSpeedX = -ballSpeedX;
var deltaY = ballY - paddleY - 50;
ballSpeedY = deltaY / 5;
} else {
player1Score++;
ballReset();
}
} else if (ballX <= 10) {
if ((ballY > mouseY - 50) && (ballY < mouseY + 50)) {
ballSpeedX = -ballSpeedX;
deltaY = ballY - mouseY;
ballSpeedY = deltaY / 6;
} else {
player2Score++;
ballReset();
}
}
}
function drawAll() {
if (showingWinScreen) {
colorRect(0, 0, WIDTH, HEIGHT, "black");
canvas.fillStyle = "yellow";
canvas.fillText("Click to continue!", 300, 300);
if (player1Score == WINNING_SCORE) {
canvas.fillText("You won!", 360, 500);
} else if (player2Score == WINNING_SCORE) {
canvas.fillText("The computer beat you!", 280, 500);
}
return;
}
colorRect(0, 0, WIDTH, HEIGHT, "black");
drawNet();
makeCircle(ballX, ballY, 10, 0, Math.PI * 2, "red");
colorRect(790, paddleY, 10, 100, "cyan");
colorRect(0, mouseY - 50, 10, 100, "yellow");
canvas.fillStyle = "white";
canvas.fillText(player1Score + " " + player2Score, 360, 100);
}
Thank you for your help!
I think there's only one case in which this could happen: when, in a colliding frame, you decrease the speed.
When the speed remains the same, no matter what, your ball will always bounce back to the previous' frames position:
var cvs = document.querySelector("canvas");
var ctx = cvs.getContext("2d");
var balls = [
Ball(50, 50, 0, 5, 5, "red"),
Ball(100, 50, 0, 5, 10, "blue"),
Ball(150, 50, 0, 5, 15, "green"),
Ball(200, 50, 0, 5, 20, "yellow")
];
var next = () => {
updateFrame(balls);
drawFrame(balls);
}
var loop = () => {
requestAnimationFrame(() => {
next();
loop();
});
}
next();
function Ball(x, y, vx, vy, r, color) {
return {
x: x,
y: y,
vx: vx,
vy: vy,
r: r,
color: color
}
};
function updateBall(b) {
b.x += b.vx;
b.y += b.vy;
if (b.y <= b.r ||
b.y >= cvs.height - b.r) {
b.vy *= -1;
}
};
function drawBall(b) {
ctx.beginPath();
ctx.fillStyle = b.color;
ctx.arc(b.x, b.y, b.r, 0, 2 * Math.PI, false);
ctx.fill();
}
function updateFrame(balls) {
balls.forEach(updateBall);
}
function drawFrame(balls) {
ctx.clearRect(0, 0, cvs.width, cvs.height);
balls.forEach(drawBall);
};
<canvas width="300" height="150" style="background: #454545"></canvas>
<button onclick="next()">next</button>
<button onclick="loop()">run</button>
But when the speed changes, things get stuck:
var cvs = document.querySelector("canvas");
var ctx = cvs.getContext("2d");
var balls = [
Ball(50, 50, 0, 10, 5, "red"),
Ball(100, 50, 0, 10, 10, "blue"),
Ball(150, 50, 0, 10, 15, "green"),
Ball(200, 50, 0, 10, 20, "yellow")
];
var next = () => {
updateFrame(balls);
drawFrame(balls);
}
var loop = () => {
requestAnimationFrame(() => {
next();
loop();
});
}
next();
function Ball(x, y, vx, vy, r, color) {
return {
x: x,
y: y,
vx: vx,
vy: vy,
r: r,
color: color
}
};
function updateBall(b) {
b.x += b.vx;
b.y += b.vy;
if (b.y <= b.r ||
b.y >= cvs.height - b.r) {
b.vy *= -0.5;
}
};
function drawBall(b) {
ctx.beginPath();
ctx.fillStyle = b.color;
ctx.arc(b.x, b.y, b.r, 0, 2 * Math.PI, false);
ctx.fill();
}
function updateFrame(balls) {
balls.forEach(updateBall);
}
function drawFrame(balls) {
ctx.clearRect(0, 0, cvs.width, cvs.height);
balls.forEach(drawBall);
};
<canvas width="300" height="150" style="background: #454545"></canvas>
<button onclick="next()">next</button>
<button onclick="loop()">run</button>
In your case, I'm thinking this can only happen when there's a paddle collision AND a wall collision simultaneously.
A quick-to-implement solution would be to check if the new position is valid before translating the ball position. If you don't want the precise location, you can place the ball at the point of collision. Note that this will produce a slightly off frame.
E.g.:
var newY = ballY + ballSpeedY;
// Top wall
if(newY <= 10) {
ballY = 10;
ballSpeedY = -ballSpeedY;
}
// Bottom wall
else if(newY >= HEIGHT-10){
ballY = HEIGHT - 10;
ballSpeedY = -ballSpeedY;
}
// No collision
else {
ballY = newY;
}
Update: a more detailed description of what can happen
Let's say your ball collides with the top border of your canvas and with your paddle in the same frame.
First, you move the ball to the colliding position: ballY += ballSpeedY; Say your ballY is 4, and your ballSpeedY is -5, you'll position the ball to -1, inside the wall.
If this were to be the only collision, you should be okay. You flip the speed (ballSpeedY = -ballSpeedY), so in the next frame, your ball should be back at -1 + 5 = 4, so ballY will be 4 again, and your ball will move towards 4 + 5 = 9 in the next frame.
Now a problem arises, when in the -1 positioned frame, you collide with the paddle as well! When the paddle hits the ball, you modify the ballspeed: ballSpeedY = deltaY / 5;. If this turns out to be < 1, your ball won't be able to exit the wall in the next frame. Instead of -1 + 5 = 4, your ball will, for example, move to: -1 + 0.5 = -0.5.
Now, your ball won't be able to get back in to play, since the next frame will, again, calculate a collision and flip the speed. This results in the bouncy, trembling effect you see when the ball gets stuck.
A naive but pretty decent solution, is to only update the position of the ball to a valid position. I.e.: never to a colliding coordinate.
var animate = window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || function (callback) {
window.setTimeout(callback, 1000 / 60)
};
var canvas = document.createElement("canvas");
var width = 400;
var height = 600;
canvas.width = width;
canvas.height = height;
var context = canvas.getContext('2d');
var player = new Player();
var computer = new Computer();
var ball = new Ball(200, 300);
var keysDown = {};
var render = function () {
context.fillStyle = "#FF00FF";
context.fillRect(0, 0, width, height);
player.render();
computer.render();
ball.render();
};
var update = function () {
player.update();
computer.update(ball);
ball.update(player.paddle, computer.paddle);
};
var step = function () {
update();
render();
animate(step);
};
function Paddle(x, y, width, height) {
this.x = x;
this.y = y;
this.width = width;
this.height = height;
this.x_speed = 0;
this.y_speed = 0;
}
Paddle.prototype.render = function () {
context.fillStyle = "#0000FF";
context.fillRect(this.x, this.y, this.width, this.height);
};
Paddle.prototype.move = function (x, y) {
this.x += x;
this.y += y;
this.x_speed = x;
this.y_speed = y;
if (this.x < 0) {
this.x = 0;
this.x_speed = 0;
} else if (this.x + this.width > 400) {
this.x = 400 - this.width;
this.x_speed = 0;
}
};
function Computer() {
this.paddle = new Paddle(175, 10, 50, 10);
}
Computer.prototype.render = function () {
this.paddle.render();
};
Computer.prototype.update = function (ball) {
var x_pos = ball.x;
var diff = -((this.paddle.x + (this.paddle.width / 2)) - x_pos);
if (diff < 0 && diff < -4) {
diff = -5;
} else if (diff > 0 && diff > 4) {
diff = 5;
}
this.paddle.move(diff, 0);
if (this.paddle.x < 0) {
this.paddle.x = 0;
} else if (this.paddle.x + this.paddle.width > 400) {
this.paddle.x = 400 - this.paddle.width;
}
};
function Player() {
this.paddle = new Paddle(175, 580, 50, 10);
}
Player.prototype.render = function () {
this.paddle.render();
};
Player.prototype.update = function () {
for (var key in keysDown) {
var value = Number(key);
if (value == 37) {
this.paddle.move(-4, 0);
} else if (value == 39) {
this.paddle.move(4, 0);
} else {
this.paddle.move(0, 0);
}
}
};
function Ball(x, y) {
this.x = x;
this.y = y;
this.x_speed = 0;
this.y_speed = 3;
}
Ball.prototype.render = function () {
context.beginPath();
context.arc(this.x, this.y, 5, 2 * Math.PI, false);
context.fillStyle = "#000000";
context.fill();
};
Ball.prototype.update = function (paddle1, paddle2) {
this.x += this.x_speed;
this.y += this.y_speed;
var top_x = this.x - 5;
var top_y = this.y - 5;
var bottom_x = this.x + 5;
var bottom_y = this.y + 5;
if (this.x - 5 < 0) {
this.x = 5;
this.x_speed = -this.x_speed;
} else if (this.x + 5 > 400) {
this.x = 395;
this.x_speed = -this.x_speed;
}
if (this.y < 0 || this.y > 600) {
this.x_speed = 0;
this.y_speed = 3;
this.x = 200;
this.y = 300;
}
if (top_y > 300) {
if (top_y < (paddle1.y + paddle1.height) && bottom_y > paddle1.y && top_x < (paddle1.x + paddle1.width) && bottom_x > paddle1.x) {
this.y_speed = -3;
this.x_speed += (paddle1.x_speed / 2);
this.y += this.y_speed;
}
} else {
if (top_y < (paddle2.y + paddle2.height) && bottom_y > paddle2.y && top_x < (paddle2.x + paddle2.width) && bottom_x > paddle2.x) {
this.y_speed = 3;
this.x_speed += (paddle2.x_speed / 2);
this.y += this.y_speed;
}
}
};
document.body.appendChild(canvas);
animate(step);
window.addEventListener("keydown", function (event) {
keysDown[event.keyCode] = true;
});
window.addEventListener("keyup", function (event) {
delete keysDown[event.keyCode];
});
http://jsfiddle.net/kHJr6/2/

How can I restart my function?

I have made a simple game but when its game over the game is just over, I want it to restart when I press ENTER. Can someone help me with this?
I do not want to reload the site but only the function.
When I press enter now the speed increases.
You can se the the game by clicking the link DEMO press ENTER to start the game
Code:
var canvas = document.getElementById("canvas");
ctx = canvas.getContext("2d");
var tileldig = Math.floor((Math.random() * 300) + 1);
var tekst = document.getElementById("tekst")
var kuler = [{
r: 10,
x: canvas.width / 2,
y: canvas.height - 100,
f: "red",
dy: 0
}, ]
var fiender = [{
r: 20,
x: tileldig,
y: -20,
vx: 0,
vy: 1,
}, ]
var snd = new Audio("Skudd.m4a");
var poeng = 0;
var høyre = 0;
var venstre = 0;
var opp = 0;
var ned = 0;
document.onkeydown = function tast(e) {
if (e.keyCode == 39) { // høyre
høyre = 1;
}
if (e.keyCode == 37) { // venstre
venstre = 1;
}
if (e.keyCode == 38) { // opp
opp = 1;
}
if (e.keyCode == 40) { // ned
ned = 1;
}
if (e.keyCode == 32) {
newskudd();
snd.play();
console.log("hit space")
}
if (e.keyCode == 13) {
spill();
}
}
document.onkeyup = function tast2(e) {
if (e.keyCode == 39) { // høyre
høyre = 0;
}
if (e.keyCode == 37) { // venstre
venstre = 0;
}
if (e.keyCode == 38) { // opp
opp = 0;
}
if (e.keyCode == 40) { // ned
ned = 0;
}
}
function spill() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
for (var i = 0; i < kuler.length; i++) {
kuler[i].x += 0;
kuler[i].y += kuler[i].dy;
ctx.fillStyle = kuler[i].f;
ctx.beginPath();
ctx.arc(kuler[i].x, kuler[i].y, kuler[i].r, 2 * Math.PI, 0);
ctx.closePath();
ctx.fill();
if (kuler[0].x >= canvas.width - kuler[0].r) {
kuler[0].x = canvas.width - kuler[0].r
};
if (kuler[0].x <= 0 + kuler[0].r) {
kuler[0].x = 0 + kuler[0].r
};
if (kuler[0].y >= canvas.height - kuler[0].r) {
kuler[0].y = canvas.height - kuler[0].r
};
if (kuler[0].y <= 0 + kuler[0].r) {
kuler[0].y = 0 + kuler[0].r
};
for (var j = 0; j < fiender.length; j++) {
ctx.fillStyle = "blue";
ctx.beginPath();
ctx.arc(fiender[j].x, fiender[j].y, fiender[j].r, 2 * Math.PI, 0);
ctx.closePath();
ctx.fill();
if (fiender[j].x >= canvas.width - fiender[j].r) {
fiender[j].x = canvas.width - fiender[j].r;
};
if (fiender[j].x <= 0 + fiender[j].r) {
fiender[j].x = 0 + fiender[j].r;
};
if (fiender[j].vy >= 2) {
fiender[j].vy = 2;
};
var distanceFromCenters = Math.sqrt(Math.pow(Math.abs(fiender[j].x - kuler[i].x), 2) + Math.pow(Math.abs(fiender[j].y - kuler[i].y), 2)); // you have a collision
if (distanceFromCenters <= (fiender[j].r + kuler[i].r)) {
fiender.splice(j, 1);
kuler.splice(i, 1);
poeng += 1;
} else if (fiender[j].y > canvas.height) {
fiender.splice(j, 1)
}
if (j > 1) {
fiender.splice(j, 1)
}
tekst.innerHTML = ("Poeng: " + poeng)
}
}
for (var j = 0; j < fiender.length; j++) {
fiender[j].y += fiender[j].vy;
}
if (venstre == 1) {
kuler[0].x -= 4;
}
if (høyre == 1) {
kuler[0].x += 4;;
}
if (opp == 1) {
kuler[0].y -= 4;
}
if (ned == 1) {
kuler[0].y += 4;
}
requestAnimationFrame(spill);
return;
}
function newskudd() {
var nyttskudd = {
x: kuler[0].x,
y: kuler[0].y,
r: 5,
dy: -5,
f: "white"
};
kuler.push(nyttskudd);
};
setInterval(function() {
fiender.push({
r: 20,
x: Math.floor((Math.random() * 300) + 1),
y: -20,
vx: 0,
vy: 1,
f: "green"
});
}, 1000);
An easy way to do this would be to simply refresh the page with location.reload() when a key is pressed.
You could also create a gameIsInProgress variable to change to false when the game ends, and test for that value before allowing the page to reload.
First, initialize then run spill ...
if (e.keyCode == 13) {
init();
spill();
}
Here's an init ...
function init() {
kuler = [{
r: 10,
x: canvas.width / 2,
y: canvas.height - 100,
f: "red",
dy: 0
}, ];
fiender = [{
r: 20,
x: tileldig,
y: -20,
vx: 0,
vy: 1,
}, ];
poeng = 0;
høyre = 0;
venstre = 0;
opp = 0;
ned = 0;
}
Running here ... jsFiddle

Categories