I've developed a small game that requires the player to "eat" only the green balls in an HTML5-canvas in order to succeed.
I've come to the part where when you hover on the ball, it gets eaten. But some REALLY weird behavior arises. Instead of only one ball getting eaten, A WHOLE LOT OF THEM JUST SUDDENLY DISAPPEAR.
Here's my code:
body {
overflow: hidden;
background: rgba(82, 80, 78, 0.75)
}
#gameCanvas {
border: 0.25em solid black;
background-color: rgba(255,235,205,0.5);
margin-top: 1.25vh;
}
* {
margin: 0;
padding: 0;
border: 0;
outline: 0;
font-size: 100%;
vertical-align: baseline;
background: transparent;
}
#flexContainer {
margin-top: 0.5em;
font-family: Verdana;
font-size: 2.5em;
display: flex;
justify-content: space-around;
}
#flexContainer span{
background: rgba(255, 180, 0, 0.50);
transition-duration: 0.5s;
padding: 0.1em;
border-radius:0.25em;
}
#flexContainer span:hover {
background: rgba(255, 180, 0, 0.8);
transition-duration: 0.5s;
border-radius: 1em;
cursor:pointer;
}
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
<head>
<link type="text/css" rel="stylesheet" href="EatTheBall.css" />
<meta charset="utf-8" />
<title>EatTheBall!</title>
</head>
<body>
<div id="flexContainer">
<span id="49" onclick="changeNumBalls();">Level 1</span>
<span id="30" onclick="changeNumBalls();">Level 2</span>
<span id="20" onclick="changeNumBalls();">Level 3</span>
<span id="10" onclick="changeNumBalls();">Level 4</span>
<span id="0" onclick="changeNumBalls();">Level 5</span>
</div>
<canvas id="gameCanvas"></canvas>
<!--Start of JavaScript code-->
<script>
var XPos = 0;
var YPos = 0;
var n = 40;
var gameCanvas = document.body.querySelector("#gameCanvas");
var ctx = gameCanvas.getContext("2d");
var colorArray = ["red", "orange", "yellow", "green", "blue", "indigo", "violet"];
var balls = [];
window.onload = function initial() {
gameCanvas.height = 0.9 * window.innerHeight;
gameCanvas.width = 0.995 * window.innerWidth;
window.addEventListener("resize", function (evt) {
gameCanvas.height = 0.9 * window.innerHeight;
gameCanvas.width = 0.995 * window.innerWidth;
gameCanvas.style.marginTop = (1.25 * window*innerHeight) +"vh";
})
movePlayer();
MainLoop();
addBalls();
}
function MainLoop() {
ctx.clearRect(0, 0, gameCanvas.width, gameCanvas.height);
drawBalls(balls);
animateBalls(balls);
requestAnimationFrame(MainLoop);
}
function addBalls() {
while (n < 50) {
n++;
var b = {
x: window.innerWidth / 2,
y: window.innerHeight / 2,
radius: Math.random() * 30 + 15,
speedX: -7.5 + Math.random() * 15,
speedY: -7.5 + Math.random() * 15,
color : colorArray[Math.round(Math.random() * 6)]
}
balls.push(b);
}
}
function drawBalls(ballArray) {
ballArray.forEach(function (b) {
drawOneBall(b);
});
drawPlayer();
}
function animateBalls(ballArray) {
ballArray.forEach(function (b) {
b.x += b.speedX;
b.y += b.speedY;
testCollisionBallWithWalls(b);
});
}
function drawOneBall(b) {
ctx.save();
ctx.beginPath();
ctx.arc(b.x, b.y, b.radius, 0, Math.PI * 2);
ctx.fillStyle = b.color;
ctx.fill();
ctx.restore();
}
function testCollisionBallWithWalls(b) {
if ((b.x + b.radius > gameCanvas.width) || (b.x - b.radius < 0)) {
b.speedX = -b.speedX;
};
if ((b.y + b.radius > gameCanvas.height) || (b.y - b.radius < 0)) {
b.speedY = -b.speedY;
};
if (XPos > b.x - b.radius && XPos < b.x + b.radius && YPos > b.y - b.radius && YPos < b.y + b.radius) {
balls.splice(b, 1);
}
if (XPos < b.x - b.radius && XPos > b.x + b.radius && YPos < b.y - b.radius && YPos > b.y + b.radius) {
balls.splice(b, 1);
}
}
function changeNumBalls() {
n = event.currentTarget.id;
balls = [];
addBalls();
drawBalls(balls);
animateBalls(balls);
}
function drawPlayer() {
ctx.save();
ctx.fillStyle = "red";
ctx.fillRect(XPos, YPos, 30, 30);
ctx.restore();
}
function movePlayer() {
gameCanvas.addEventListener("mousemove", function (evt) {
XPos = evt.clientX - 15;
YPos = evt.clientY - 100;
})
}
</script>
</body>
</html>
CODE EXPLANATION TIME!
The idea is that I declare a variable named "balls" at the beginning, and I assign a value of an empty array to it. Then the balls are created by using a while loop. The while loop keeps adding new object literals, which describe the properties of the ball (aka. the position of the center of the ball, the length of the radius, etc.), to the empty array. Next I call 2 functions, one to actually draw the balls using the properties in each object literal that has been added to the array. The other one is used to animate the balls. I've used the forEach() method with both of the functions.
As for the player, I created a fillRect() in the canvas. I set the x1 and y1 properties* to the position of the cursor using a canvas.addEventListener('mouseover', ...)
Whenever the player moves into one of the balls, the ball should automatically disappear using the array.splice() method. HOWEVER, that does not happen. Instead of only one ball disappearing, as mentioned beforehand, more than one disappear.
I've actually got insight onto how exactly is the problem produced. When the ball collides with the player, if instead of using an array.splice(), you just display a random message in the console (console.log(random shit)), the message will not be only displayed once, it will get displayed multiple times.
The splice() method needs index as the first parameter at which to start changing the array. In that case, balls.indexOf(b) can give the position of ball in the array balls. So it will work, if you slightly change testCollisionBallWithWalls function:
function testCollisionBallWithWalls(b) {
if ((b.x + b.radius > gameCanvas.width) || (b.x - b.radius < 0)) {
b.speedX = -b.speedX;
};
if ((b.y + b.radius > gameCanvas.height) || (b.y - b.radius < 0)) {
b.speedY = -b.speedY;
};
if (XPos > b.x - b.radius && XPos < b.x + b.radius && YPos > b.y -
b.radius && YPos < b.y + b.radius) {
balls.splice(balls.indexOf(b), 1);
}
}