A Bygone Era
In a bygone era, I developed a game in Sinclair BASIC (with the worst graphics ever seen given limitations of UDGs (User Defined Graphics… remember). I posted my source code on cassette tape to Sinclair User magazine only to never hear back. Unfinished business!
Consequently, I made it a mission in recent years to learn Unity3D: not just coding, but also the creative side: 3D modelling, graphics and animation although there is still much to learn.
My Unity project was to be a modern version of my 'Kung Fu Man’ game comprising a simple 3D assailant you fight first person against a static 2D backdrop of the Great Wall of China. It didn’t stop there…
I spent 3 months creating animations by hand using Unity’s ‘Dope Sheet’ from animation reference videos (regrettably my budget of $zero didn't stretch to motion capture services from Andy Serkis ;-).
Fast forward to today… my game is based on an island (hours of terrain modeling to do), has non-player characters, variations of assailant and an adventure storyline. While Unity Asset Store provided great assets (often pre-animated), I also shopped around on 3D model stores. Furthermore, I spent hundreds of hours scripting my controller classes and state-machine based character controller scripts.
Having proudly finished my game (so I thought) I deployed it to my iPad Mini, only to realize I had hundreds more hours performance-tuning to eliminate crashes, ensure smooth running and minimise device energy consumption.
Essential Tips - it is easier in Hindsight!
My ten recommendations to help others develop a large scale 3D game that runs with efficient performance in Unity:
1. Backup, backup and backup
… frequently. Your Unity project will grow quickly and it is important to protect hours of work at a time and know reliably what is missing if you ever have to restore and reapply edits. It is not uncommon for a large project with many assets and scripts to become corrupted - if you don’t have a working backup, you may never recover.
2. Aim for a minimum viable product (MVP)
It is better to ship a simple working game in a reasonable timeframe. You can then enhance and expand it based on valuable user feedback. Don’t embark on a Christopher Nolan-esque epic you will never finish - you don’t have a Hollywood budget nor production team.
3. Build quality control into your project from the start
I recommend scripting some unit tests to assert the presence and state of your game objects in each scene. Errors thrown by your unit tests then appear in the ‘Console’ window at runtime when new code subtly breaks something or if you have accidentally moved or deleted game objects (easier than you think in a large/complex scene and problems aren’t always apparent until its too late).
4. Establish good scene scale and physics
Do this at the outset (such settings are difficult to change later without impacting everything):
• Create a terrain and paint a texture onto it (‘paint’ icon in your Terrain’s ‘Inspector’ settings)
• Set its resolution and other settings like ‘Tree & Detail Objects’ and Wind Settings’ for Grass (‘gear’ icon)
• Paint some grass (‘flowers’ icon) and add some trees (‘tree’ icon)
• Drag-and-drop assets into your scene and scale these game objects accordingly
• Check graphics, size and physics of all the elements look good and play well together by building/testing on your target device(s)…
Examples of things to check:
- Graphic of the terrain texture doesn’t look low resolution or stretched compared to your game object textures
- Painted grass is the right default size, relative to everything else and sways convincingly in a wind zone
- Near and far details (e.g. trees) look convincing.
5. Warning: when adding features
Whenever you apply a major feature like weather effects, bird flocks or whichever asset floats your boat, you should immediately build and test your game on target devices to ensure it remains stable and performs well. There are many third-party assets claiming to be mobile-ready but unlikely tested well in all cases. Don’t reach a situation where your device testing reveals a stability problem, you’ve no idea what caused it and have to start taking features out to identify the root cause.
6. Optimise animation performance
Use models with simple skeletons (i.e. fewer bones), keep your animation ‘Dope Sheet’ as simple as possible (leave the Mecanim animation system to interpolate across frames and don’t set unnecessary nodes). Most of my animations are merely ‘must-have’ rotation changes. Follow animation performance guidelines like https://docs.unity3d.com/Manual/MecanimPeformanceandOptimization.html
7. If you target mobile, use low poly models
Don’t be seduced by great looking models that bring your game performance to its knees or turn your mobile device into a ball of molten lava. If you must purchase a high poly model (i.e. a complex mesh comprising millions of polygons), use a process called ‘Decimation’ to create your own lower poly version. You can search Unity Asset Store for such tools.
8. GameObject Redraw Performance
Every game object added to a scene will have a redraw cycle running many times per second. This is a common cause for game crashes on low-end devices (my low-end test device is an iPad Mini 2). You know you have a problem if you profile your application in Unity (…choose Windows -> Profiler at game runtime and click ‘Rendering’ performance…) and you see DrawCalls above 400-500. The remedy is a process called ‘Batching’ using tools like 'Draw Call Minimizer’. For instance use its ‘Editor Batcher’ (‘Draw Call Minimiser’ adds this to your Unity ‘Window’ menu) to combine a number of game objects (e.g. individual buildings objects) to create one composite game object (e.g. a town object). Your scene won’t look different and you’re down to one redraw cycle (very efficient).
9. Game Memory Footprint
Minimising the memory footprint of your game is vital (another source of crashes). You should check ‘Editor Log’ just after building your Unity project (File -> Build Settings -> Build). This provides a valuable report on the relative size (versus overall footprint) of your textures, meshes, animations, sounds and other elements. Huge assets in your project will stick out like a sore thumb in the ranking list. Working with low poly models (i.e. simple meshes), reducing the quantity and length of audio files and especially reducing size/resolution of images brings down your overall build size.
A good tip with large dialogue audio files is switched off preloading (won’t be part of the game’s initial loaded memory footprint) and have the Unity engine background-load them on demand.
For mobile games you should consider the cost and memory impact of rendering textures (i.e. rendering images on the surface of your game objects). If you click any image file in your project and inspect it (‘Inspector’ window), you can change Texture Type to ‘Advanced’ and tweak some crucial settings: maybe override image resolution to a lower 'Max Size’ and/or contemplate unchecking ‘Generate Mip Maps’ (these make for sharper details but massively bloat your build/ game footprint).
10. Minimize Running Scripts
You’ll develop many scripts to control characters and game logic. You should always have an overall GameController script (maybe added to your Terrain object?) which should among other things deactivate scripts (.GetComponent<stringName>().enabled = false) and/or game objects (.setActive(false)) when not in use. The more scripts running concurrently (especially with busy OnGui() and Update() methods) means slower in-game performance.
In summary designing incrementally with performance in mind is crucial to creating an immersive smooth running game, especially on mobile devices.
If you fancy a lighter, more visual tutorial - on Animation/ Scripting - why not head to my youtube channel and check out my Animated Deer Tutorial.
I wanted to create a tutorial explaining how to use Asset Store models, adapt their animations and put them to use with coding script.