# HTML5 - Dynamic canvas grid for scrolling a big map

I've been working on different scrolling algorithms for my JS-based game. My main problem is that I need to draw a huge map and also have it scroll smoothly. I've made a few attempts and while they're efficient (code and framerate wise) they still look jumpy and have to redraw constantly (though not each frame, it's still too much redrawing for my taste).

My latest idea is to implement some kind of 3x3 grid around the camera which will scroll with delay. What do I mean "with delay" ? well, if you have played Diablo II before, try to get a Maphack app and enable the Automap Screen Area feature: You see, Diablo II's map is made through a concept of "rooms" and those adjacent to the player in a specific area (usually screen size) are drawn offscreen to allow the scrolling.

I want to implement that in an orthogonal perspective (this is, 2D zelda-like), just like this:

The blue grid is made out of canvases around my camera's position. I know I can position them with the simple x+(floor(camera.x / tile_size) * tile_size) formula and with some offsets have them move around the camera like this:

THE PROBLEM arises when the drawn offscreen areas must remain in the same position to allow the scrolling. I've made a second image which illustrates this:

Notice how the colored tiles (or offscreen areas of screen size) remain in the same position UNTIL they get "too far" from the camera (the red dot). HOWEVER, while they have the same position, notice the Tile # (below each blue grid tile) change. I can't do it that way because it would require to redraw, and my goal is to avoid such redraw if the position hasn't changed.

This means however, that, taking in example blue tile 6 (located at 0,1 at the first animation frame) it changes to tile 5 and then to tile 1. While this is true, the idea is to keep it at tile 6, and only re-use the farthest canvases in the closest ones that need to be redrawn.

I'm illustrating my problem this way because my previous question didn't even get a single comment ( https://stackoverflow.com/questions/22185858/infinite-scroll-a-canvas-with-a-tiled-map-without-redrawing/22277681 ) so I had to work and answer my own question... something that doesn't even help either because someone else might have had a better idea, but nobody did, it seems.

That's why I'm asing here in gamedev, maybe here are some more skilled people who can have an idea or a shot at what I'm attempting to do. Also, please, I'm not interested in using other game or JS engines. If I were I wouldn't be asking this here as I know they implement tile scrolling and such, but this is both for my own learning and for creating a custom engine suited for my own game's needs.

Any help then, is really welcome.

EDIT: Well alright, after struggling for like 2 days straight on this issue I finally managed to do it. I didn't want to do it with this kind of coords, so I wonder is there any way to optimize this code or to avoid it to do those increments and instead use a formula that would absolutely position them?

Here's the code I'm using (cv = canvas, cx = context("2d"), grid = 3x3 array of canvases. Also, I'm using a 32x32 "grid" just to see how it works. The size should be screen size (or region size, in my engine's case)):

for(ix = 0; ix < grid.length; ix++)
for(iy = 0; iy < grid.length; iy++) {
cv = grid[iy][ix];
if(cam.x - cv.x > 48)
cv.x += 32 * 3;
if(cam.x - cv.x < -48)
cv.x -= 32 * 3;
if(cam.y - cv.y > 48)
cv.y += 32 * 3;
if(cam.y - cv.y <- 48)
cv.y -= 32 * 3;
cx.fillStyle = cv.color;
cx.fillRect(cv.x, cv.y, 32, 32);
}

• Hi and welcome to GameDev SE! Tip: You could post your own answer in the answers section, even if it's your own question. (It's totally OK on StackExchange, even encouraged, to add and accept your own answers.) – Anko Mar 9 '14 at 13:17
• The illustrations are really helpful, but for some more clarification: You want to render all objects that are currently on screen (or some other view rectangle), but not redraw any of the off-screen ones, if possible. That's why your've got your world split into rooms, a lot like spatial hashing. Am I getting this right? – Anko Mar 9 '14 at 13:26
• Hello Anko! thanks for the warm welcome! :) yeah I wanted to post it as an answer, but while it archieves what I want, that whole code is making noise to me. I mean, I bet there is a formula to position them absolutely instead of doing such increments, seems like it'll bug in the future and create gaps in the scrolling and such. About the clarification (and thanks for the useful link!) yes, indeed, in the case of terrain, I want to do the rendering -once- (when grid cells change position), that should be enough for my camera code to handle the scrolling :) – DARKGuy Mar 9 '14 at 21:57
• Alright, after some more struggling I managed to find a way to absolutely position them. My math knowledge sucks, but something was telling me there was a pattern I could convert into a formula. I made this JSFiddle -> jsfiddle.net/B7Jm6/2 working with 32x32 instead of entire screen size just for testing if the formula is right. My idea is to make it work in double-for X-by-Y loop (for y=1 blah blah { for x=1 blah draw(x,y) etc. }), @Anko or someone other, do you happen to know how to simplify it? thanks! – DARKGuy Mar 13 '14 at 8:20

Well fine, after 4 days and 62 views and only ONE comment (thanks @Anko), I managed to do the formula on my own. I don't know if it's as optimized as it should be, but who cares anyways? I can't believe that out of 62 views I only got one user interested in helping...very disappointing.

Anyways, here are the formulas for those interested (ix/iy = absolute world coordinates):

X = ( (SCREEN_WIDTH * 3) * (Math.floor( (ix+(SCREEN_WIDTH*(1-j))) / (SCREEN_WIDTH * 3) ) ) + SCREEN_WIDTH * j ) - ix;
Y = ( (SCREEN_HEIGHT * i) + ( (SCREEN_HEIGHT * 3) * Math.floor( ( (iy+(SCREEN_HEIGHT*(1-i))) / (SCREEN_HEIGHT * 3)) )) ) - iy;


Usage:

for(var i = 0; i < 3; i++)
for(var j = 0; j < 3; j++) {
var cvg = grid[i][j];
cx.fillStyle = cvg.color;
var vy = ( (SCREEN_HEIGHT * i) + ( (SCREEN_HEIGHT * 3) * Math.floor( ( (iy+(SCREEN_HEIGHT*(1-i))) / (SCREEN_HEIGHT * 3)) )) ) - iy;
var vx = ( (SCREEN_WIDTH * 3) * (Math.floor( (ix+(SCREEN_WIDTH*(1-j))) / (SCREEN_WIDTH * 3) ) ) + SCREEN_WIDTH * j ) - ix;
cx.fillRect(vx, vy, SCREEN_WIDTH, SCREEN_HEIGHT);
}
`

Working JSFiddle.

• Concise and clear questions get the most attention. (Many give up at a few hundred words in unless they can immediately follow.) I'm still hazy on what exactly you wanted to do too, but I'm happy if I helped. – Anko Mar 13 '14 at 17:41