skip to Main Content

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


  1. There are these issues:

    • It is a bad idea to use objects.splice while iterating over the same array. You used setTimeout to get around that, but that led to an invalid i 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 and event.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 the arc 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 so trackMouse should be enabled. The collision check does not have to happen in that function though. It is enough to do that in the startanimate 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:

    var canvas = document.getElementById("canvas");
    canvas.height = (window.innerHeight)*0.7;
    canvas.width = (window.innerWidth)*0.7;
    var ctx = canvas.getContext("2d");
    
    var spawnLineY = 25;
    var spawnRate = 100;
    var lastSpawn = -1;
    var objects = [];
    var startTime = Date.now();
    var objcount = -1;
    var mouseX = 0;
    var mouseY = 0;
    
    // Enable this function:
    function trackMouse(event) {
        // Need to subtract the coordinates of the canvas
        mouseX = event.clientX - canvas.offsetLeft; 
        mouseY = event.clientY - canvas.offsetTop;
        // No need to perform the "collision" check here.
    }   
    
    function spawnRandomObject() {
        var t;
        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";
        }
        var object = {
            type: t,
            x: Math.random(),
            y: Math.random(),
            xspeed: Math.random()*3.5 + 0.5,
            yspeed: Math.random()*3.5 +0.5,
            diameter: Math.ceil (Math.random()*29+1),
        }
        if (object.x < 0.50){
            if (object.y < 0.5){
                object.x = -5;
            }
            else {
                object.x = canvas.width + 5;
                object.xspeed = object.xspeed*-1;
            }
            object.y = Math.random() * (canvas.width - 300) + 15;
        }
        else {
            if (object.y >= 0.5){
                object.y = -5;
            }
            else {
                object.y = canvas.height + 5;
                object.yspeed = object.yspeed*-1;
            }
            object.x = Math.random() * (canvas.width - 300) + 15;
        }
        objects.push(object);
    }
    
    function startanimate() {
        var time = Date.now();
        if (time > (lastSpawn + spawnRate)) {
            lastSpawn = time;
            spawnRandomObject();
        }
        requestAnimationFrame(startanimate);
        ctx.clearRect(0, 0, canvas.width, canvas.height);
    
        // Use filter method to remove objects that are touched:
        // This should happen before the objects are moved and drawn. 
        objects = objects.filter(object => {
            // Don't divide by 2, as "diameter" is actually the radius
            return !((Math.abs(object.x - mouseX) <= object.diameter) && (Math.abs(object.y - mouseY) <= object.diameter));
        });
    
        for (const object of objects) { // Use modern for..of syntax (no need for 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();
        }
    }
    startanimate();
    <canvas id="canvas" width="1000px" height="500px" onmousemove="trackMouse(event)"></canvas>
    Login or Signup to reply.
  2. Honestly, I think you already have it. I copied your code just to test out what you have and only updated 2 things.

    1. in your HTML you can just use defer if you want a script to run after the HTML is loaded. so in your JS you can just run startAnimation() (as you initially had)
    2. The circles were disappearing only when the mouse was half way into the circle in your Attempt 2 so i got rid of the diameter/2 in the condition.
    // 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 &&
                Math.abs(object.y - event.clientY) <= object.diameter
            ) {
                setTimeout(() => {
                    objects.splice(i, 1);
                }, 0);
            }
        });
    }
    
    // start animating
    startanimate();
    
    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.5) {
            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;
            */
        }
    }
    <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="index.js" defer></script>
      </body>
    </html>

    Likely the issue was the lack defer of the <script> tag was causing the onmousemove in your <cavas> element not to trigger.

    You can also add the onmousemove event listener in the JS which is fairly common

    document.addEventListener("mousemove", trackMouse);
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search