简体   繁体   中英

Large 2D world rendering in HTML5 Canvas

I have a world made up of randomly generated blocks (black being on, white being off). When zoomed out, it essentially looks like white noise. However, instead of each block being 1 pixel, they are 40 pixels and drawn as an image texture. My game works in a camera basis, so you can only see a fraction of the map at a time and you must move the character around to explore the rest.

Currently, I have my game simply render each image (block texture) that is in range of the canvas. This results in drawing 80-100 images every single frame. While it works fine on a desktop computer, it doesn't do very well on mobile.

Considering the map look doesn't change throughout the game, I wanted to try a different approach. I created a canvas the size of the world, which ended up being 1600x24000 pixels large. I drew all textures onto an external, hidden canvas. This was done once upon initialization. Then I would use the clipping attributes in drawImage to take the subsection that I needed. While it worked , it was extremely laggy and made things very much worse than they were before. In addition, image quality dropped to a more blurred look, which is undesirable.

Now I'm looking for ways to better go about this. So my question is, how should I go about this? Thank you.

Two more Ideas could increase your performance:

  • Check if your whole world is rendered, or just the visible images (on the stage). For example double the world size and see, if it impacts the performance. It shouldn't, if you only render the relevant images.

  • Use CocoonJS to compile your application. It promises to speed up your application speed by 10 times for mobile devices. But be aware that it implies some serious restrictions on your html around your canvas.


obsolete answer, which assumed that the problem is caused by zooming out too far:

In 3D graphics Mipmaps can be used to avoid this problem. Essentially smaller images (ie less pixel) are used, when the object is more distant to the camera.

Maybe you can find something appropriate if you google something like html5 canvas 2D Mipmaps . Or you could build a simple mipmapping algorithm yourself.

But before investing the work, try how performant this approach is, by simple changing all block images, with 1x1-pixel images. Maybe your performance problem is not caused by slow rendering, as you assume. Learn to use a profiler, if it doesn't solve the problem.

When you're using a huge canvas, you can't be sure the renderer won't load the whole texture to render even a part of it. Since you see a huge performance drop, that might well be happening.

A few things i would do :
• try only with fillRect to see how much drawImage is to blame.
• try to set-up once and for all the context then only use drawImage with its simplest flavor :

var topLeft = { col:12, row : 6 }; // shift of the left-most rect (indexes)
context.save();
context.scale( scale, scale); 
for  column = 0 to columnSeenCount 
     for row = 0 to rowSeenCount
         image = the image of  ( topLeft.col + column , topLeft.row + row )
         context.drawImage( image, column, row) ;
context.restore(); 

this way you avoid to re-compute a transform matrix for every drawImage. Much less math involved for the renderer.

• if you do the drawImage by yourself, try to use only rounded coordinates, it will be faster.
• You must round also the scale to prevent artifacts. You can round on 1, but for the scale it might be too much a limit : you can easily 'round' to 0.5 or 0.25 or... by doing :

  var precision     = 2 ;     //  0 => floor ; 1 => at 0.5 ; 2 => 0.25 ; .... 
  var factor        = 1 << precision ;
  var roundedFigure = Math.floor( figure * factor) / factor ;

• if the way your application is done makes it easy to draw tile type per tile type, do it and you might win some time (you'll benefit from the fact that image in cache ).

• After that your only resort will be to use webGL or a webGL based renderer...

A couple of questions and thoughts:

I would ditto @GameAlchemist's tip that using the clipping version of drawImage is slower than "blitting" a separate tile image onto the canvas. Use separate images instead when you have such an overly large map image.

24000 pixels is too much width to contain in any 1 image.

It looks like you're panning horizontally. You could slice your 24000 pixel wide image into individual images of a more reasonable size. Each image might be 3X the screen width. Exchange the image when the user pans beyond the edge of the current image.

How many unique block image tiles are you using?

Perhaps reduce the number of unique tiles when you detect a mobile user. Then put each unique tile on a separate image or canvas.

Is your map largely 1 tile type (eg. white/off)?

If so, you could make 1 single image of a grid of enough white tiles to fill the entire canvas. Then add black tiles where necessary. This reduces your drawing to 1 white grid image plus any required black images.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM