Issue with spritesheet size for pixel art games

Not favoritedFavorited Favorited 0 favourites
  • 8 posts
From the Asset Store
Vintage steam pixels for your UI! 125 unique assets in a single pack!
  • Hey Ashley

    Today, I was looking into the spritesheet system C3 uses, and I just realised that my pixel art game, despite having max spritesheet size set to 2048 still generates spritesheet sizes of at most 512 by 512 and often much smaller.

    In the end, the game exports with 71 spritesheets, but I ran some numbers and it turns out that had the game respected my setting and generated as many 2048 by 2048 sheets, all of the assets could fit into a single sheet.

    I was wondering; Is there any good reason why C3 doesn't pack everything into spritesheets as big as possible?

    Of course, I assume that the complexity of spritesheet making is specifically that testing out all combinations (especially now that sprites can be rotated) gets exponentially more complicated if you add more images to a single sheet, but then would it be possible to run that thing in 2 steps instead? Do whatever C3 does right now and create all 71 sheets, and then run it again a second time and generate far fewer sheets by merging them together and then merging the data. I would honestly even be fine if it gave me two 1024 by 1024 sheets or four 512 by 512, but SEVENTY ONE is a big excessive IMO.

  • I noticed there was some recent discussion about it on github so I'll move this there instead:

    github.com/Scirra/Construct-bugs/issues/8427

  • It's best to discuss such things on the forum - generally closed issues are considered resolved, and we prefer not to have extended discussions on the issue tracker anyway (as it actually makes it harder to fix issue reports).

    The spritesheeting algorithm has been tweaked and refined over the years, and there are a lot of subtleties and complications that probably aren't obvious. That's not to say there aren't further improvements that could be made, but it is a very good example of a part of Construct that may look simple but is actually very complicated.

    For example you may try using an external packing tool to compare it to Construct's output. But if it packs tiled background images on to the same sheet, then it is not possible to use a tiling texture mode. Perhaps it could be done with a custom shader, but when you take in to account mipmaps then you get color bleed issues when downscaling. So Construct exports all tiled background images as separate images with no padding so it can apply the tiling texture mode to those images in the engine, because it has to. If you then compare that to some tool that has packed those images all on to the same sheet, you might think Construct looks needlessly inefficient. But it's impossible for it to do what your external tool has done, because that hasn't taken in to account the necessary texture parameters that the engine will need to apply to different images used in different ways.

    Imagine another 10-20 such cases of various subtleties and complications, not to mention that optimal rectangle packing is NP-hard, and you probably are starting to get in to the ballpark of how complicated this area is.

    Is there any good reason why C3 doesn't pack everything into spritesheets as big as possible?

    In short, the answer is because a bunch of games will then crash due to running out of memory, and that is worse than some projects having a somewhat sub-optimal use of spritesheets. The max spritesheet size setting gives you some control over this, but Construct still avoids using unnecessarily large spritesheets where it's likely to waste a lot of memory, and will prefer to split spritesheets in to smaller chunks.

    If you just fill up spritesheets as much as possible, then it's possible a sprite animation will spill over from one sheet to another. At 4096x4096 every spritesheet takes 64 MB of memory. This spilling then means anything that uses that single sprite animation must use a minimum of 128 MB memory for the two sheets. Repeat that in a large project a few times and you can quite possibly double the memory requirement and hit out-of-memory errors. Cue lots of reports of how Construct wastes loads of memory and that it obviously needs to be improved. Spilling also reduces the efficiency of image compression as it spreads similar image content across two different images. Construct tends to put sprites with a lot of animation frames on their own spritesheets specifically to mitigate those kinds of problems.

    I guess we could have a bunch of settings to micro-manage how spritesheets are generated, but we have always wanted to avoid such complexity in Construct, as it makes it less beginner-friendly and it is hard to explain what all those settings do, other than "try random combinations to see what works best for you". I suppose we could have one extra setting along the lines of "prefer filling up spritesheets", as for pixel art games perhaps that will produce a better result. But for pixel art games, is the situation really that bad? Modern web serving will serve multiple files in parallel over the same connection probably involving a nearby CDN. So whether it is downloading 40 files or 4 shouldn't really be that big a difference.

    Anyway, in short, I just want to highlight that if you think there is one easy way to make everything optimal for all projects, it's really much more complicated.

  • Tiled backgrounds are also packed separately in the packer Skymen made, linked on the github issue.

    Though afaik tbg could be wrapped correctly even if packed by using partial derivates (which are also availaible in webGL 1 with extensions), though I have not tested this myself. Alternatively the texture could be generated at runtime similar to 9patch.

  • if you think there is one easy way to make everything optimal for all projects, it's really much more complicated

    Don't get me wrong, I totally understand why the packer is the way it is, and why it needs to be designed in ways that take into account things that are far beyond the concerns of my projects.

    What I'm highlighting here is that I have noticed a tendency for Pixel art games made with Construct on Poki to load slower for players, to the point of significantly affecting the player bounce rates, and I have separately noticed a tendency for Pixel art games made with Construct to generate an excessive amount of spritesheets, sometimes far more than with a bigger project that has bigger assets.

    The solution I am proposing here is not a complete revamp of the spritesheeting algorithm, but it is instead to provide an optional secondary step that packs the currently generated sheets again following a similar logic to find ways to reduce the number of generated sheets the project exports with.

    The github repo I provided is on one hand a way for me to test wether the problem would actually be solved if it was implemented (I am currently running tests on Poki) but also to serve as a naïve proof of concept for how I see the implementation.

    In this case, TBGs would still be left alone, and large animations are still packed in smart ways since you already run an initial smart packing step. If the large animation happens to have been split over multiple sheets, then it's very likely it's already reached max sheet size anyway.

    Again, in the use case I am defending, memory usage is NOT the bottleneck. We are talking projects that (were the sheets packed into 2048x2048 sheets) would have less than 6 of them in total, so that's 96MB of ram, which is perfectly fine on any modern device. Web Games need to ship with build sizes below 10MB after compression and need to have initial load times below 5MB, so it is very unlikely to ship a game on the web platforms and have image memory usage be a bottleneck, and instead often the load times and compression efficiency become far more important.

    Now, I also completely understand that you wouldn't want to implement that kind of feature set, and instead I would find it very useful if you could give us a new option that exports a new separate file that gives some amount of information on every exported image (wether it can be packed, wether it should be lossy or lossless, what animation it's a part of) and I will be perfectly happy writing a custom algorithm that runs using that data and does the secondary packing manually.

    Right now I read the data.json file and I try my best to guess as many of these information as possible and it works fine so far as long as I keep exporting with a few known parameters.

    I will also let you know the result of my testing with Poki. If it turns out that the number of spritesheet is somehow totally decorrelated from the loading issues that we are facing, I'll stop looking into this as thoroughly although I would still like for that feature to be considered in the future.

  • What I'm highlighting here is that I have noticed a tendency for Pixel art games made with Construct on Poki to load slower for players, to the point of significantly affecting the player bounce rates

    Do you have measurements you can share that prove this? I find this surprising - pixel art games should not have heavy art assets by any stretch, and a good CDN should not have a problem with something like 100 small files - with modern HTTP 2+ those all just go down the same connection efficiently without having lots of per-request overhead. For example the Construct editor is 1000+ separate requests to load the editor - mostly lots of small files - but it's served cached via CloudFlare CDN over HTTP/3. A cold load takes <1 second on my system (with a good connection, but still). So I have to ask, what measurements have to made and what has led to identifying spritesheeting specifically as the cause of it?

    Again, in the use case I am defending, memory usage is NOT the bottleneck.

    I know, but we have to make something that works for all sorts of kinds of projects, not just specific ones. If we add a setting, sometimes people change the setting, forget about it, find their project starts crashing, and then leave us poor reviews or end up contacting customer support. So we also try to avoid features that make it easy to shoot yourself in the foot like that.

  • Try Construct 3

    Develop games in your browser. Powerful, performant & highly capable.

    Try Now Construct 3 users don't see these ads
  • Do you have measurements you can share that prove this? I find this surprising

    Yes so do I. I am currently investigating it, but so far I have no conclusive evidence to say that it is beyond any doubt, however here is some data I was able to gather

    I was given a relatively short list of games that has bad Conversion To Play (C2P) and I added a few games that had no C2P issues at all.

    I gathered some data for all of these games that might take in part in loading like

    - number of fonts

    - number of assets

    - number of audio files

    - total size of assets

    - wether preload sounds was enabled

    - list of plugins, behaviors and effects (including built in ones)

    - wether worker mode was enabled or not

    C2P is measured by Poki as the number of users who clicked on the game (aka opened it on the website) vs the number of users who ended up triggering a gameplay start event (aka actually played the game).

    Now, a number of things can cause a user to not interact (long intro sequence, long loading times, crash on load, etc) but it is relatively common for games on Poki to have very light build sizes, and to load straight to gameplay so a gameplay start is quite often the first ever interaction the user has with the game (clicking, hitting a key on the keyboard etc) since it would trigger an action in game as long as the game has finished loading.

    In my short analysis so far, what I can tell is that while having preload sounds enabled does affect the C2P, it doesn't justify low C2P on its own (also, this one was fairly obvious) and the number of fonts and the number of audio files used don't seem to affect the C2P in general (most games with good C2P had more fonts than games with bad C2P).

    However, one point of data that tells a different story is the total asset size, and most specifically the number of assets, and the average size per asset. From the few games I have analysed it does seem to me like while games with bad C2P tend to have smaller build sizes (usually caused by the devs trying to optimise the games as much as possible to fix the C2P issues), they have a bigger number of assets and the average size per assets is much smaller than average.

    This does seem to vaguely track with pixel art titles made with Construct 3 since pixel art games do tend to have a very large amount of very small assets that then get packed into a large amount of very small spritesheets by Construct 3 which then causes this "large amount of small files" issue that we can see.

    I must say that this is not conclusive evidence still, and the dataset is still pretty limited (13 games) but I am actively working with Poki to find wether the issue can be fixed on their end or on the Construct end. I know very little about CDNs and how they function, and I know even less about what kind of setup Poki has internally, how they serve games, and why it is the way it is. The difference with a company like Scirra is that you guys only serve a single known product and can optimise for it, but Poki needs to serve thousands of games on a very wide range of devices, in a very wide range of countries with very varying amounts of connections, and all of it is data they don't control since they don't make the games themselves, and thus would need to serve them in a way that accomodates for as many games as possible.

    If it turns out that the issue would be fixed by changing some server config on their end but that change would in turn make a lot of other games load slower, that might not be a viable solution either.

  • Small update;

    After deploying the quick fix I made on a few games, it doesn't seem like it has solved the C2P issue at all despite the fact that there was a good chance it could.

    I'm still investigating other possible avenues.

Jump to:
Active Users
There are 1 visitors browsing this topic (0 users and 1 guests)