I'm having trouble with a movable canvas that adjusts as the 'player' moves around the map. As drawing 600 tiles, 60 times a second is very inefficient, I switched over to using translate3d and only draw once the player crossed a full tile -- but it keeps glitching and not moving around smooth. How would I achieve this properly?
const ctx = canvas.getContext('2d');
canvas.height = 200;
canvas.width = 600;
const tileSize = canvas.height/6;
const MAIN = {position:{x: 120, y: 120}};
const canvasRefresh = {x: 0, y: 20};
document.body.onmousemove = e => MAIN.position = {x: e.clientX, y: e.clientY};
const tiles = {x: 20, y: 20}
function update(){
moveMap();
requestAnimationFrame(update);
}
function drawMap(){
for(var i = 0; i < tiles.x; i++){
for(var j = 0; j < tiles.y; j++){
ctx.fillStyle = ['black', 'green','orange'][Math.floor((i+j+canvasRefresh.x1+canvasRefresh.y1)%3)];
ctx.fillRect(tileSize * i, tileSize * j, tileSize, tileSize);
}
}
}
function moveMap(){
const sector = {
x: Math.round(-MAIN.position.x % tileSize),
y: Math.round(-MAIN.position.y % tileSize)
};
const x2 = Math.floor(MAIN.position.x/tileSize);
const y2 = Math.floor(MAIN.position.y/tileSize);
if(canvasRefresh.x1 != x2 || canvasRefresh.y1 != y2){
canvasRefresh.x1 = x2;
canvasRefresh.y1 = y2;
requestAnimationFrame(drawMap);
}
$('#canvas').css({
transform: "translate3d(" + sector.x + "px, " + sector.y + "px, 0)"
});
}
update();
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<canvas id=canvas></canvas>
There are a few things going on:
Immediately invoking
drawMapinstead of usingrequestAnimationFrameAs ggorlen mentioned in the comments, using
requestAnimationFramemultiple times in an update cycle is an unusual practice. When you userequestAnimationFrame, you're calling the function on the next frame update, meaning there will be a frame where the map isn't redrawn, causing a slight flicker. Instead, if you invoke it immediately, it'll redraw the map for that frame. Also, it's a good idea to consolidate all your painting and updating to one invocation ofrequestAnimationFrame, since it makes it clearer what order things are updated.So you should change
requestAnimationFrame(drawMap);todrawMap();Finding remainders using non integers
Modulo arithmetic (i.e. the
%operator) generally works with integers. In the case where you haveMAIN.position.x % tileSize, it glitches out every so often becausetileSizeisn't an integer (200 / 6). To find remainders using non-integer numbers, we can use a custom function:and replace instances of modulo arithmetic with our new function (e.g. changing
MAIN.position.x % tileSizetoremainder(MAIN.position.x, tileSize))Math.roundvsMath.floorFinally, you probably want to use
Math.floorinstead ofMath.round, becauseMath.roundreturns 0, both for ranges between (-1, 0) and (0, 1), whileMath.floorreturns -1, and 0.Using a container and css to hide shifting parts of the canvas
You may want to using a containing div and corresponding css to hide the edges of the canvas that are being redrawn:
In the HTML:
In the CSS:
All together
All together it looks like this: