Richard Hamilton
Richard Hamilton

Reputation: 26434

HTML5 Canvas - Redrawing new circles after erasing them with clip

I have a unique problem.

I am creating a game of snake with HTML5 and Canvas

I have a function that generates apples on the board randomly and removes them after a set period of time. In order to remove circles, you have to use the clip() function followed by clearRect().

However, after you use the clip function, you can no longer draw new circles.

The solution I found was using ctx.save() and ctx.restore(). However, if you play the game, you will learn that the snake acts crazy when circles disappear and new circles appear.

I suspect this has to do with my use of the save and restore functions.

Here's the specific code in question

var width = canvas.width;
var height = canvas.height;
var applesArray = [];            // Store the coordinates generated randomly

// Generates a random coordinate within the confines of the canvas and pushes it to the apples array
function randCoord() {
    var coord = Math.floor(Math.random() * height);
    applesArray.push(coord);
    return coord;
}

function generateApples() {
    ctx.beginPath();
    ctx.fillStyle = "green";
    ctx.arc(randCoord(),randCoord(),3,0, 2 * Math.PI);
    ctx.fill();
    ctx.save();           // To redraw circles after clip, we must use save
    ctx.clip();           // Allows only the circle region to be erased
    setTimeout(function() {
        ctx.clearRect(0, 0, width, height);
    },3000);
    ctx.restore();       // We must restore the previous state.
}

setInterval(function() {
    generateApples();
},4000);

You can play the game here

https://jsfiddle.net/2q1svfod/9/

Can anyone explain this weird behavior? I did not see it coming?

Upvotes: 0

Views: 78

Answers (1)

Bobby Orndorff
Bobby Orndorff

Reputation: 3335

The code has multiple issues.

The code that draws the snake (e.g. upArrow function) simply extends the current path. This is a problem because the code that draws the apple starts a new path. Note that save/restore in apple drawing code does not help because path is not part of the state that is saved/restored. The code that draws the snake will need to start a new path. For example...

function upArrow() {
    if (direction == "up") {
        goUp = setInterval(function() {
            ctx.beginPath();
            ctx.moveTo(headX, headY);
            ctx.lineTo(headX, headY - 10);
            ctx.stroke();
            headY -= 10;
        }, 400);
    }
}

The save/clip/restore calls are in the code that draws the apple. These methods need to be moved into the timeout callback function that erases the apple. Also, the code that erases the apple will need to recreate the path (because the snake drawing could have changed the path between when the apple is drawn and when the apple is erased). For example...

function generateApples() {
    var cx = randCoord();
    var cy = randCoord();
    ctx.beginPath();
    ctx.fillStyle = "green";
    ctx.arc(cx, cy,3,0, 2 * Math.PI);
    ctx.fill();
    setTimeout(function() {
        ctx.beginPath();
        ctx.arc(cx,cy,3,0, 2 * Math.PI);
        ctx.save();
        ctx.clip();
            ctx.clearRect(0, 0, width, height);
        ctx.restore();
    },40000);
}

These changes will get you close to what you intended. However, there will still be a couple minor issues.

When drawing the apple, there will be some anti-aliasing occuring around the edge of the apple's path. The clear operation can miss clearing some of these pixels. After the clear operation, you might see a semi-transparent outline of where the apple was. You could work around this issue by using a slightly larger circle radius when clearing the apple.

Another issue is that apples could be drawn on top of the snake. Erasing the apple will also erase the snake. There is not an easy fix for this issue. You would need to store all the coordinates for the snake and then redraw all or part of the snake.

In the long term, you may want to consider the suggestions in the comments about restructuring your logic to track all objects and redraw everything each frame (or redraw everything after each change).

Upvotes: 1

Related Questions