I am attempting to create a game where objects (currently circles) are randomly generated within a canvas, and the objects and their positions (plus other info) are stored within an array.
The objects are randomly generated and what I want is for them to disappear whenever the mouse touches them. Currently I do this by setting the object’s diameter to 0 when onmousemove is triggered but this does not work; it only removes objects occasionally.
Can anyone point me in the right direction of how I can implement this? This is my first javascript project and I’m a beginner who’s just learning. 🙂
The best I’ve gotten is with attempt 1 (near the bottom of startanimate function) but it only occasionally removes objects the mouse touches.
Attempt 2 does not work at all for me. I get an arrow function misdefined error and the circles do not generate at all.
This is my Javascript code:
// get a refrence to the canvas and its context
var canvas = document.getElementById("canvas");
canvas.height = (window.innerHeight)*0.7;
canvas.width = (window.innerWidth)*0.7;
var ctx = canvas.getContext("2d");
// newly spawned objects start at Y=25
var spawnLineY = 25;
// spawn a new object every 500ms
var spawnRate = 100;
// when was the last object spawned
var lastSpawn = -1;
// this array holds all spawned object
var objects = [];
// save the starting time (used to calc elapsed time)
var startTime = Date.now();
var objcount = -1;
var mouseX = 0;
var mouseY = 0;
/* Attempt 2 which gives errors
function trackMouse (event) {
//mouseX = event.clientX;
//mouseY = event.clientY;
objects.forEach ((object, i) => {
if ((Math.abs(object.x - event.clientX) <= object.diameter/2) && (Math.abs(object.y - event.clientY) <= object.diameter/2)) {
setTimeout (() => { objects.splice (i, 1) }, 0)
}
})
} */
// start animating
//animate();
function spawnRandomObject() {
// select a random type for this new object
var t;
// About Math.random()
// Math.random() generates a semi-random number between 0-1.
var temp = Math.random()
if (temp < 0.25) {
t = "green";
}
else if (temp >= 0.25 && temp < 0.5) {
t = "blue";
}
else if (temp >= 0.5 && temp < 0.75) {
t = "purple";
}
else {
t = "pink";
}
// create the new object
var object = {
// set this objects type
type: t,
// set spawn randomly on the canvas edge
x: Math.random(),
// set y to start on the line where objects are spawned
y: Math.random(),
xspeed: Math.random()*3.5 + 0.5,
yspeed: Math.random()*3.5 +0.5,
diameter: Math.ceil (Math.random()*29+1),
}
// set spawn coordinates so they spawn at the edges using previous x to randomly choose where along edges they spawn
// 4 situtations
if (object.x < 0.50){
if (object.y < 0.5){
object.x = -5; // along left edge and already moving to the right
}
else {
object.x = canvas.width + 5; // along right edge
object.xspeed = object.xspeed*-1; // should move towards left
}
object.y = Math.random() * (canvas.width - 300) + 15;
}
else {
if (object.y >= 0.5){
object.y = -5; // along top edge and already moving down
}
else {
object.y = canvas.height + 5; // along bottom edge
object.yspeed = object.yspeed*-1; // should move down
}
object.x = Math.random() * (canvas.width - 300) + 15;
}
// add the new object to the objects[] array
objects.push(object);
}
function startanimate() {
// get the elapsed time
var time = Date.now();
// see if its time to spawn a new object
if (time > (lastSpawn + spawnRate)) {
lastSpawn = time;
spawnRandomObject();
// spawnRate*=.95; to increase speed
}
// request another animation frame
requestAnimationFrame(startanimate);
// clear the canvas so all objects can be redrawn in new positions
ctx.clearRect(0, 0, canvas.width, canvas.height);
/* draw the line where new objects are spawned
ctx.beginPath();
ctx.moveTo(0, spawnLineY); // starting position of line
ctx.lineTo(canvas.width, spawnLineY); // ending position of line
ctx.stroke();
didnt need a line*/
// move each object down the canvas
for (var i = 0; i < objects.length; i++) {
var object = objects[i];
object.y += object.yspeed;
object.x += object.xspeed;
ctx.beginPath();
ctx.arc(object.x, object.y, object.diameter, 0, Math.PI * 2);
ctx.closePath();
ctx.fillStyle = object.type;
ctx.fill(); // need to fill to see the object
/* ATTEMPT 1 but doesn't work properly, I believe because the forloop takes too long it get an accurate position
if ((Math.abs(object.x - mouseX) <= object.diameter/2) && (Math.abs(object.y - mouseY) <= object.diameter/2)){
console.log ("HELLO");
object.diameter = 0;
*/
}
}
}
This is what I have in HTML:
<html>
<head>
<meta charset="UTF-8"> <! --- To establish proper website layout and properties--->
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- href is case sensitive -->
<link rel="stylesheet" href="../CSS/fishgame.css">
</head>
<body>
<canvas id="canvas" width="1000px" height="500px" onmousemove="trackMouse(event)"></canvas>
<!-- must load javascript only after the html so animation can occur in the canvas-->
<script src="../javascript/fishgame.js" onload="startanimate()"></script>
</body>
</html>
2
Answers
There are these issues:
It is a bad idea to use
objects.splice
while iterating over the same array. You usedsetTimeout
to get around that, but that led to an invalidi
value by the time those callbacks got to execute. It is better to use array filtering (filter
method) to achieve the expected result.event.clientX
andevent.clientY
give coordinates relative to the viewport, not relative to the canvas. So you should substract from these coordinates the offset of the canvas element.You pass the
diameter
to thearc
function, but that method expects a radius, not a diameter. By consequence, your circles are twice as large as you expect and thus the "collision" check is wrong.It makes more sense to filter out "touched" objects before you draw them.
To fix your code, I retained the name
diameter
, but used it in the sense of radius (you should change the name). I used a mix of both methods, because you surely need the keep the mouse coordinates updated, and sotrackMouse
should be enabled. The collision check does not have to happen in that function though. It is enough to do that in thestartanimate
function.Here is your code with only those corrections. I removed your comments and placed comments where I edited, so you can see what changed:
Honestly, I think you already have it. I copied your code just to test out what you have and only updated 2 things.
defer
if you want a script to run after the HTML is loaded. so in your JS you can just runstartAnimation()
(as you initially had)diameter/2
in the condition.Likely the issue was the lack
defer
of the<script>
tag was causing theonmousemove
in your<cavas>
element not to trigger.You can also add the
onmousemove
event listener in the JS which is fairly common