WORK IN PROGRESS
Best practices for optimisation:
- Test on low end target hardware.
- Make measurements in an easy and consitently repeatable way.
- Check if your project actually has any performance issues.
- Check if these performance issue are caused by the CPU (mostly game logic) or GPU (rendering).
- Use profilers to find the heaviest elements of the game. (built-in debugger, chrome dev tools etc.)
- Only optimise the bottlenecks and low hanging fruits. There is very little benefit in optimising something that doesn't contribute significantly to the performance cost.
- Weigh performance improvements against maintenance burden. Sometimes optimisations can make code much more complex, this should be avoided unless the gains are massive.
- Compare the performance before and after each change, to be sure it really helps performance. Some things seem like a good idea but end up making performance worse.
CPU BOUND
COLLISION CELLS
The default collision cell size matches the viewport size, this is larger than needed in the vast majority of cases. Try lowering the collision cells size and see if it improves performance, a good rule of thumb is setting it to roughly a 4th of the viewport size.
ONLY RUN LOGIC WHEN NESCESSARY
Running logic much more fequently than needed is one of the main performance pitfalls.
For example a health bar only needs to be updated when taking damage or healing. Use a function you call from the heal and damage events for updating the bar.
This strategy can be used for most systems, ideally the majority of the game logic is in triggers, functions and custom actions.
FRAMERATE STABILITY AND JANK
A game with a stable framerate is perceived as smoother than a game at a higher average framerate with widely varying frametimes. This plays into the following point:
UPDATING AT REGULAR INTERVALS
A common strategy is changing heavy events from every tick to every 0.1 second or every other tick (using tickcount % 2). While it improves the average framerate it has the potential to introduce framerate instability (see above). The heavy thing doesn't happen every tick, but instead happens at a fixed interval causing consistent jank.
Instead I would suggest the following strategies:
- Alternate heavy tasks, i.e. running one heavy task one one frame and a different heavy task on the next frame. This spreads out the load more evenly, so both of those heavy tasks don't happen in the same tick.
- Run logic for a part of the instances at a time, i.e. time-slice it so that each tick only some enemies update and over the course of a certain amount of ticks all have been updated. This could be implemented like this for example: pick Sprite by evaluate
sprite.IID >= (tickcount % updateRate) * (sprite.Count / updateRate) & sprite.IID < ((tickcount % updateRate) + 1) * (sprite.Count / updateRate) Here updateRate is the amount of ticks it takes to go through all instances.
- Run the heavy update only for the instances that require it, for example with pathfinding you could check if the last target position is within a threshold of the current target position.
GPU BOUND
FULLSCREEN QUALITY LOW
With fullscreen quality high (the default setting) the game is rendered at the resolution of the screen, instead of the resolution you set in the viewport size property. This can lead to a orders of magnitude higher GPU usage. I suggest using fullscreen quality low for most games. If needed you can add a resolution setting.
Full screen quality high can be especially probelmatic on low end mobile devices with a 4k screen.
WORK IN PROGRESS