Ive been having a really baffling slow down issue with the game I am working on probably because I am unsure how to handle graphics (most likely responsible for the slow down) in javascript without using a third party framework like Phaser/ImapactJS/EaselJS etc)*. The following is the low down on how I am approaching my graphics. I would be very thankful for some tips or methods on how to do this right.
My game is tile based - using tiles designed at 500x500 px because I want them to display decently on high definition devices.
I am using a spritesheet to load all (most of) my tiles before the main loop is run. This image is roughly 4000 x 4000 (keeping it below 4096 because the GPU cant handle texture sizes larger than that).
I then use the drawImage function to cycle through and draw each tile on a part of the canvas using information (w, h, x, y) stored in the tile array. I do this on every cycle of the main loop using my drawMap function.
The map is currently 6x6 tiles in size
A character spritesheet is also loaded and drawn on to the canvas after the map has been drawn. The character displays a different frame of the animation on every cycle of the main loop. There are sets of animations for the character each contained in the same spritesheet.
The character sprite sheet is roughly 4000x3500
The character is roughly 350x250 px
Other objects also use the same sprite sheet. Currently there is only one object.
Possibly helpful questions:
Am I using too many spritesheets or too few?
Should I only draw something if it's coordinates are in bounds of the screen?
How should I go about garbage collection? Do I need to set image objects to null when no longer in use?
Thanks in advance for input. I would just like to know if I am going about it the right way and pick your brains as how to speed it up enough.
*Note that I plan to port the JS game to cocoonJS which provides graphics acceleration for the canvas element on mobile.
** If interested please visit my Patreon page for fun!
You have asked lots of questions here, I'll address the ones I've run into.
I would like to start out by saying very clearly,
Use a profiler
Find out whether each thing you are advised to do, by anybody, is making an improvement. Unless we work on your code, we can only give you theories on how to optimise it.
How should I go about garbage collection? Do I need to set image objects to null when no longer in use?
If you are no longer using an object, setting its reference to null will probably mean it gets garbage collected. Having nulls around is not necessarily good but this is not within the scope of this question.
For high performance applications, you want to avoid too much allocation and therefore too much garbage collection activity. See what your profiler says - the chrome profiler can tell you how much CPU time the garbage collector is taking up. You might be OK at the moment.
I then use the drawImage function to cycle through and draw each tile on a part of the canvas using information (w, h, x, y) stored in the tile array. I do this on every cycle of the main loop using my drawMap function.
This is quite slow - instead consider drawing the current on screen tiles to a background canvas, and then only drawing areas which were previously obscured.
For example, if your player walks to the left, there is going to be a lot of tiles on the left hand side of the screen which have come into view; you will need to draw the background buffer onto the screen, offset to account for the movement, and then draw the missing tiles.
My game is tile based - using tiles designed at 500x500 px because I want them to display decently on high definition devices
If I interpret this right, your tiles are 500x500px in diameter, and you are drawing a small number of these on screen. and then for devices without such a high resolution, the canvas renderer is going to be scaling these down. You really want to be drawing pixels 1:1 on each device.
Would you be able, instead, to have a larger number of smaller tiles on screen - thereby avoiding the extra drawing at the edges? Its likely that the tiles around the edges will sometimes draw only a few pixels of one edge, and the rest of the image will be cropped anyway, so why not break them up further?
Should I only draw something if it's coordinates are in bounds of the screen?
Yes, this is a very common and good optimisation to take. You'll find it makes a big difference.
Am I using too many spritesheets or too few?
I have found that when I have a small number of sprite sheets, the big performance hit is when I frequently switch between them. If during one draw phase, you draw all your characters from character_sheet.png and then draw all the plants from plant_sheet.png you'll be ok. Switching between them can cause lots of trouble and you'll see a slow down. You will know this is happening if your profiler tells you that drawImage is taking a big proportion of your frame.
Related
I intend to have a FabricJS canvas that will display potentially dozens of different graphics, which could either be served up as individual image files or a sprite. For each graphic, it's possible that particular graphic would be displayed on the canvas 0 to dozens of times. I know that FabricJS has the clipping ability, which would make the use of sprites possible, and I know that generally speaking, sprites on the web are highly preferred over individual images, such as for CSS.
However, I've also heard of browser behaviors/quirks surrounding canvas where some optimizations that you might expect to take place, don't -- I can't find the link at the moment, but one that I recall was how large canvas-content elements out of view in Chrome would still take a considerable amount of time to be calculated/"rendered", despite not being relevant to on-screen appearance.
So, are there any known expectations on whether n individual non-clipped images would be preferable/unpreferable to a sprite that's clipped n times for a FabricJS canvas? In case it matters, this would not be a heavily animated FabricJS canvas; things may move when dragged, for example, but constant animation is not what's involved in this case.
Just do a test yourself to find out what slows down your PC. It depends on the PC and browser and graphics card.
I don't know anything about Fabric.js, but computer CPUs and graphics cards can handle clipping/graphic manipulation easily. You're not displaying millions of polygons like a 3D game, so you should be fine.
Here's a website that explains the CSS sprites vs individual images network performance increases:
https://medium.com/parlay-engineering/emoji-at-scale-render-performance-of-css-sprites-vs-individual-images-f0a0a2dd8039
We are developing a web-based game. The map has a fixed size and is procedually generated.
At the moment, all these polygons are stored in one array and checked whether they should be drawn or not. This requires a lot of performance. Which is the best rendering / buffering solution for big maps?
What I've tried:
Quadtrees. Problem: Performance still not as great because there are so many polygons.
Drawing sections of the map to offscreen-canvases. A test run: http://norizon.ch/repo/buffered-map-rendering/ Problem: The browser crashes when trying to buffer that much data and such big images (maybe 2000x2000) still seem to perform badly on a canvas.
(posting comments as an answer for convenience)
One idea could be, when the user is translating the map, to re-use the part that will still be in view, and to draw only the stripe(s) that are no longer corrects.
I believe (do you confirm ?) that the most costly operation is the drawing, not to find which polygon to draw.
If so, you should use your QuadTree to find the polygons that are within the strips. Notice that, given Javascript's overhead, a simple 2D bucket that contains the polygons that are within a given (x,y) tile might be faster to use (if the cost of the quadtree is too high).
Now i have a doubt about the precise way you should do that, i'm afraid you'll have to experiment / benchmark, and maybe choose a prefered browser.
Problems :
• Copying a canvas on itself can be very slow depending on devices/Browsers. (might require to do 2 copy, in fact)
• Using an offscreen canvas can be very slow depending on devices/Browsers. (might not use hardware acceleration when off-screen).
If you are drawing things on top of the map, you can either use a secondary canvas on top of the map canvas, or you'll be forced to use an off-screen canvas that you'll copy on each frame.
I have tried a lot of things and this solution turned out to be the best for us.
Because our map has a fixed size, it is calculated server-side.
One big image atlas with all the required tiles will be loaded at the beginning of the game. For each image on the atlas, a seperate canvas is created. The client loads the whole map data into one two-dimensional array. The values determine, which tile has to be loaded. Maybe it would be even better if the map was drawn on a seperate canvas, so that only the stripes have to be painted. But the performance is really good, so we won't change that.
Three conclusions:
Images are fast. GetImageData is not!
JavaScript has not yet great support for multi threading, so we don't calculate the map client-side in game-time.
Quadtrees are fast. Arrays are faster.
I've recently started learning to work with HTML5 Canvas and I've got a bunch of questions really. All related to performance. I understand that everything with this is quite subjective to what I'm doing. But I'm just hoping to gain some clarity.
In the example of creating game with an aerial view of the player. all 2d with a map that scrolls around on both X and Y axis.
Question 1: In my understanding, As long as there are no moving objects in my map canvas. it's more efficient to do something like create a large canvas element that I draw once and then use JS to scroll the page / move the canvas. rather than create a canvas that is the size of my screen, translate the canvas and redraw the map with each movement.
Question 2: If I use the above method of having a large canvas that scrolls about to move my map. will there be much performance difference between a simple drawing on the canvas and a complex drawing? Example: simple canvas with a couple rows of single colored blocks compared to complex canvas with thousands of lines, circles, gradients, patterns and detail. If the two canvases are the same width and height there shouldn't be a huge performance difference for just having the browser scroll them right? (no redrawing).
Question 3: Is there a preferred method for dealing with a map that is bigger than a canvas object can be? I know various browsers will limit the physical size of the canvas. Is it better to design the map in several big canvas blocks. Load an adjacent block into a brand new canvas element when the player gets near the edge. or will this cause issues. if I'm working with canvas elements that are say 10,000px by 10,000px and I start placing several of these side by side am i just asking for trouble? am I better off designing it to run in a single canvas that just redraws the map as the player moves and keep the complexity of the map down. and program it so it doesn't think about parts of the map that aren't close to the player.
I've read a bunch about tricks to increase performance like layering canvases and not redrawing the canvas more than the browser is refreshing. but I've been having trouble finding info on good practice for dealing with large maps and performance of moving around them.
I'm really interested to hear everyone's thoughts.
Question 1 is not actually a question, but it's best to create a canvas that is the size of your screen, translate the canvas and redraw PART OF the map with each movement. Do not redraw the whole map, but add methods to redraw a portion of the map that is slightly larger than the viewport, possibly excluding part of the map that is already drawn. Otherwise your browser might crash from memory stress.
Question 2: Right. There will be no difference at all. The browser will simply move the already drawn canvas about behind the viewport.
Question 3: Play around with the element inspector of Chrome on a google map. This is a very good example of a huge map that runs on a tiny phone. You will see that it uses a grid of images (you could uses canvasses). You could cache these blocks outside the viewport for if the user returns back. Play with the cache size to keep the memory usage sane.
What I am trying to do is create a game that has an extreme amount of zoom-ability on a canvas element. I would like to make use of the advantage that vector graphics have insofar as being able to be programmatically created at runtime, with the high performance of bitmap images.
What I would like to do is programmatically create the first-frame image of a game "sprite"... this would be a vector image. After the first frame though, I do not want to keep wasting CPU cycles on drawing the image though.. i would like to cache it as a bitmap/high performance image for that zoom level.
Following this, if the user zooms in by >20%, I then redraw the image with a higher level of detail vector image. As above, this vector image would then be cached and optimized.
As you can see here, this would be a pretty basic space ship.. I would first render it programmatically as a vector and then.. raster it I guess? Goal is to avoid wasting CPU.
If the user zooms in...
A new vector image of the same shape would be drawn, albeit with a much higher level of detail. This is basically a Level Of Detail system. In this case as well, after the initial programmatic draw, I would "raster" the image for maximum performance.
Does anyone have ideas on what tools I would need to make this a reality inside of a HTML canvas? (The rest of the game will be running inside of the canvas element..)
Thank you very much for your thoughts.
**Edit: I wanted to add... perhaps the route of rendering an image via SVG (programmatically), then pushing that png file into the canvas using drawimage(), might provide some success? Something similar? Hmm...
Check out that article , but it seems there is no standard method to do what you want and it may fail in IE.
http://svgopen.org/2010/papers/62-From_SVG_to_Canvas_and_Back/#svg_to_canvas
You should perhaps go with an all SVG game , or provide a maximum zooming rate to your game and use big images as sprite assets. it would not have been a problem using flash,but i guess you wont go with flash anyway.
Maybe there is a framework that can translate SVG into a "canvas drawing sequence" but i would not bet on high performances in that case.
I managed to answer my own question.
The way to do this is to first create an SVG file, and then convert it to a PNG file on the client using "canvg". The PNG can be created at different levels of details based on what you want, and in this way you could create a dynamic LOD system.
Flash does something similar automatically by cashing a bitmap image of the SVG file... it's called "pre-rendering". If the SVG isn't scaled or the alpha isn't changed, flash will just use the bitmap instead (much faster then continuously re-rendering the SVG file, in complex cases). Size (and thus detail) of the PNG output can be modified however you like, and so pre-rendering could be done based on events as well.
From this information, I have decided to implement the LOD system such that SVG is used whilst the user is actively zooming (scaling the target "sprite"), and then as the zoom slows down, compute a PNG pre-render. Also, at extremely high levels of zoom, I simply use the SVG, as it is much easier for the CPU to compute SVG's at high resolution, then bitmap images that cover most of the screen. (just take a look at some of the HTML5 icon tests that put lots of icons on the screen... the bigger the icons are, the slower it runs).
Thanks very much to everyone's comments here and I hope that my question/answer has helped someone.
Is there a quick and efficient way to move lots of objects in canvas? Basically if there are around 1000 objects and I want to move all of them at once to emulate scrolling, it is very slow to redraw every single object by calling drawImage() 1000+ times.
Is there anyway to optimize this? I have an example link of the problem (and that's only with 100 objects): http://craftyjs.com/isometric/
Since canvas doesn't provide fast low level bitmap copying it's hard to do stuff in multiple layers and scroll for example the whole background at once and then only render the edges.
So what can you do? In short, nothing. Especially not when scrolling, sure you can do tricks with multiple canvases when you have a more or less static background but for moving objects there are hardly any performance improving tricks.
So, you've go to wait for Hardware Acceleration shipping in all majors browsers, I know this sounds ridiculous but I'm too waiting for that :/
The problem is that the canvas was never designed for game stuff. It was designed as, well, basically some kind of on the fly drawing thing, guess the designers had Photoshop clones in mind, but definitely not games, let alone the fact that there's no fast clear operation proves that, there's not even optimization in place when clearing the whole canvas with the same color.
If the images are already composited, not moving relative to one another, and defined by a rectangular region, then using canvas.drawImage() with a canvas as the first parameter and drawing to a sub-region should be significantly faster than re-drawing all the objects.
You could also just layer multiple canvases and slide the top canvas with the objects in HTML to scroll them.
Edit: Having really looked at your example, it seems to me that it should be implemented similar to Google Maps: create tiles of canvases and slide them left/right on the HTML page; once a canvas has been slid off the screen entirely (for example, off the left edge), move it to the other side (to the right edge) and re-use it for drawing. With this you will only need to re-draw whatever objects overlap the canvases that are moving on the edges.
You can draw all objects on a second, off-screen canvas and then only blit the whole canvas (drawImage() accepts canvas element).
However, if you're targeting desktop browsers, then this shouldn't be necessary. I've implemented tile engine (source) that simply redraws whole scene and naive implementation turned out to be pretty fast.
What I did to solve this problem was I had 10 squares on my screen and I wanted to animate them on a white background. So I drew a white rectangle over the canvas to clear the canvas so the animation would work. Does that make sense?
#Ivo By the way I read on http://www.w3.org/TR/html5/the-canvas-element.html that canvas was made for applications like games because it was a solution to get rid of the dependency on a external engine. Canvas is built in so it's kind of like a flash player built into your browser powered by JavaScript. I think it's fascinating.
You can use tiled rendering.
http://www.gamesfrommars.fr/demojsv2/ (better viewed with Chrome)