Unity Mobile Optimization
Unity Mobile Optimization
YO U R M O B I L E G A M E
PERFORMANCE
U N I T Y 2 0 2 2 LT S E D I T I O N ⟶ E - B O O K
Contents
Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
Profiling. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
Memory. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
Adaptive Performance. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
Use ScriptableObjects. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
Project configuration. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
Assets . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36
Compress textures. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39
Unity DataTools . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41
GPU optimization. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46
Disable shadows . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50
Bake your lighting into Lightmaps. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51
Optimize SkinnedMeshRenderers. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58
User interface . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59
Animation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 68
Physics . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70
Simplify colliders . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72
Both iOS and Android have active user bases in the billions. If your mobile
game is highly optimized, it has a better chance at passing certification from
platform-specific stores. To maximize your opportunity for success at launch
and beyond, your aim is always twofold: building the slickest, most immersive
experience and making it performant on the widest range of handhelds.
This guide assembles knowledge and advice from Unity’s expert team of
software engineers. Unity’s Accelerate Solutions games team has partnered
with developers across the industry to help launch the best games possible.
Follow the steps outlined here to get the best performance from your mobile
game while reducing its power consumption.
Profile your project early and throughout the development cycle, not just
when you are close to shipping. Investigate glitches or spikes as soon as they
appear to benchmark performance before and after major changes in your
project. As you develop a “performance signature” for your project, you’ll be
able to spot new issues more easily.
Along with the Unity Profiler, you can leverage the Memory Profiler and
Profile Analyzer, as well as these native tools from iOS and Android for further
performance testing on their respective hardware:
Arm Mobile Studio : This suite of tools can help you profile and debug
your games in great detail, catering toward devices running Arm hardware.
Certain hardware can also take advantage of Intel VTune, which helps you to find
and fix performance bottlenecks on Intel platforms (with Intel processors only).
Don’t guess or make assumptions about what is slowing down your game’s
performance. Use the Unity Profiler and platform-specific tools to locate
the precise source of a lag. Profiling tools ultimately help you understand
what’s going on under the hood of your Unity project, but don’t wait
for significant performance problems to start showing before digging into
your detective toolbox.
The built-in Unity Profiler can help you detect the causes of any lags or
freezes at runtime and better understand what’s happening at a specific
frame or point in time.
Begin by enabling the CPU and Memory tracks as your default. You can
monitor supplementary Profiler Modules like Renderer, Audio, and Physics,
as needed for your game (e.g., physics-heavy or music-based gameplay).
To capture profiling data from an actual mobile device within your chosen platform,
check the Development Build and Autoconnect Profiler boxes before you click Build
and Run. Alternatively, if you want the app to start separately from your profiling, you
can uncheck the Autoconnect Profiler box, and then connect manually once the app
is running.
Choose the platform target to profile. The Record button tracks several seconds
of your application’s playback (300 frames by default).
Go to Unity > Preferences > Analysis > Profiler > Frame Count to increase this
as far as 2000 if you need longer captures. While this means that the Unity Editor has
to do more CPU work and take up more memory, it can be useful depending on your
specific scenario.
When using the Deep Profiling setting, Unity can profile the beginning and
end of every function call in your script code, telling you exactly which part
of your application is being executed and potentially causing a delay. However,
deep profiling adds overhead to every method call and may skew the
performance analysis.
Click in the window to analyze a specific frame. Next, use either the Timeline
or Hierarchy view for the following :
— Timeline shows the visual breakdown of timing for a specific frame. This
allows you to visualize how the activities relate to one another and across
different threads. Use this option to determine if you are CPU- or GPU-
bound.
You can find a complete overview of the Unity Profiler here. If you’re new to
profiling, you can also watch this Introduction to Unity Profiling.
Before optimizing anything in your project, save the Profiler .data file. Implement
your changes and compare the saved .data before and after the modification.
Rely on this cycle to improve performance: profile, optimize, and compare. Then,
rinse and repeat.
The Profile Analyzer lets you aggregate multiple frames of Profiler data
and then locate frames of interest. Do you want to see what happens to the
Profiler after you make a change to your project? The Compare view allows
you to load and differentiate two data sets, so you can test changes
and improve their outcome. The Profile Analyzer is available via Unity’s
Package Manager.
Each frame will have a time budget based on your target frames per second
(fps). For an application to run at 30 fps, its frame budget can’t exceed 33.33
ms per frame (1000 ms/30 fps). Likewise, a target of 60 fps leaves 16.66 ms
per frame.
Devices can exceed this budget for short periods of time (e.g., for cutscenes
or loading sequences) but not for a prolonged duration.
Most mobile devices do not have active cooling like their desktop
counterparts. Physical heat levels can directly impact performance.
If the device is running hot, the Profiler might perceive and report poor
performance, even if it is not cause for long-term concern. To combat
profiling overheating, profile in short bursts. This cools the device and
simulates real-world conditions. Our general recommendation is to keep the
device cool for 10-15 minutes before profiling again.
The central processing unit (CPU) is responsible for determining what must be
drawn, and the graphics processing unit (GPU) is responsible for drawing it. When
a rendering performance problem is due to the CPU taking too long to render a
frame, the game becomes CPU bound. When a rendering performance problem is
due to the GPU taking too long to render a frame, it becomes GPU bound.
The Profiler can tell you if your CPU is taking longer than your allotted frame
budget or if the culprit is your GPU. It does this by emitting markers prefixed with
Gfx as follows:
Unity employs automatic memory management for your user-generated code and
scripts. Small pieces of data, like value-typed local variables, are allocated on the
stack. Larger pieces of data and long-term storage are allocated to the managed
or native heaps.
Optimizing your memory usage means being conscious of when you allocate and
deallocate heap memory, and how you minimize the effect of garbage collection.
The Memory Profiler package takes a snapshot of your managed heap memory to
help you identify problems like fragmentation and memory leaks.
Use the Unity Objects tab to identify areas where you can eliminate duplicate
memory entries or find which objects use the most memory. The All of Memory
tab displays a breakdown of all the memory in the snapshot that Unity tracks.
Learn how to leverage the Memory Profiler in Unity for improved memory usage.
If you are certain that a garbage collection freeze won’t affect a specific point
in your game, you can trigger garbage collection with System.GC.Collect.
2
Note that using the GC can add read-write barriers to some C# calls, which come with little overhead that can add up to ~1 ms per frame of scripting call overhead. For optimal
performance, it is ideal to have no GC Allocs in the main gameplay loops and to hide the GC.Collect where a user won’t notice it.
With Unity and Samsung’s Adaptive Performance, you can monitor the device’s
thermal and power state to ensure that you’re ready to react appropriately.
When users play for an extended period of time, you can reduce your level of
detail (or LOD) bias dynamically to help your game continue to run smoothly.
Adaptive Performance allows developers to increase performance in a
controlled way while maintaining graphics fidelity.
While you can use Adaptive Performance APIs to fine-tune your application,
this package also offers automatic modes. In these modes, Adaptive Performance
determines the game settings along several key metrics:
These four metrics dictate the state of the device, and Adaptive Performance
tweaks the adjusted settings to reduce the bottleneck. This is done by providing
an integer value, known as an Indexer, to describe the state of the device.
To learn more about Adaptive Performance, you can view the samples we’ve
provided in the Package Manager by selecting Package Manager > Adaptive
Performance > Samples. Each sample interacts with a specific scaler, so you
can see how the different scalers impact your game. We also recommend
reviewing the end-user documentation to learn more about Adaptive
Performance configurations and how you can interact directly with the API.
The Unity PlayerLoop contains functions for interacting with the core of the
game engine. This tree-like structure includes a number of systems that
handle initialization and per-frame updates. All of your scripts will rely on this
PlayerLoop to create gameplay.
When profiling, you’ll see all of your project’s user code under the PlayerLoop
(with Editor components under the EditorLoop).
The Profiler will show your custom scripts, settings, and graphics in the context of the entire engine’s execution.
Make sure you understand the execution order of Unity’s frame loop. Every
Unity script runs several event functions in a predetermined order. You should
understand the difference between Awake, Start, Update, and other functions
that create the lifecycle of a script. You can utilize the Low-Level API to add
custom logic to the player’s update loop.
Consider whether code must run every frame. Move unnecessary logic out of
Update, LateUpdate, and FixedUpdate. These event functions are convenient
places to put code that must update every frame, but extract any logic that does
not need to update with that frequency. Whenever possible, only execute logic
when things change.
If you do need to use Update, consider running the code every n frames.
This is one way of applying time slicing, a common technique of distributing
a heavy workload across multiple frames. In this example, we run the
ExampleExpensiveFunction once every three frames:
void Update()
{
if (Time.frameCount % interval == 0)
{
ExampleExpensiveFunction();
}
}
When your first scene loads, these functions get called for each object:
— Awake
— OnEnable
— Start
Avoid expensive logic in these functions until your application renders its first
frame. Otherwise, you may encounter longer loading times than necessary.
Refer to the order of execution for event functions for details about the first
scene load.
Use preprocessor directives if you are using these methods for testing:
#if UNITY_EDITOR
void Update()
{
}
#endif
Here, you can freely use the Update in the Editor for testing without
unnecessary overhead slipping into your build.
Generate your log message with your custom class. If you disable the
ENABLE_LOG preprocessor in the Player Settings, all of your Log statements
disappear in one fell swoop.
The same thing applies for other use cases of the Debug Class, such as Debug.
DrawLine and Debug.DrawRay. These are also only intended for use during
development and can significantly impact performance.
Unity does not use string names to address animator, material, and shader
properties internally. For speed, all property names are hashed into property
IDs, and these IDs are actually used to address the properties.
Invoking AddComponent at runtime comes with some cost. Unity must check for
duplicate or other required components whenever adding components at runtime.
void Update()
{
Renderer myRenderer = GetComponent<Renderer>();
ExampleFunction(myRenderer);
}
It’s more efficient to invoke GetComponent only once, as the result of the
function is cached. The cached result can be reused in Update without any
further calls to GetComponent.
void Update()
{
ExampleFunction(myRenderer);
}
Instantiate and Destroy can generate garbage and garbage collection (GC)
spikes and is generally a slow process.
This reduces the number of managed allocations in your project and can prevent
garbage collection problems.
Learn how to create a simple Object Pooling system in Unity here. You can also
see these code examples, from Unity, using Object pooling to inform your own
game development.
A common use case is having many GameObjects that rely on the same
duplicate data that does not need to change at runtime. Rather than having
this duplicate local data on each GameObject, you can funnel it into a
ScriptableObject. Then, each of the objects stores a reference to the shared
data asset, rather than copying the data itself. This can provide significant
performance improvements in projects with thousands of objects.
In this example, a ScriptableObject called Inventory holds settings for various GameObjects.
To learn more about using design patterns in Unity, see the e-book Level up
your code with game programming patterns to learn more about using design
patterns.
There are a few Project Settings that can impact your mobile performance.
Ensure your Accelerometer Frequency is disabled if you are not making use of it in your mobile game.
In the Player settings, disable Auto Graphics API for unsupported platforms
to prevent generating excessive shader variants. Disable Target Architectures
for older CPUs if your application is not supporting them.
If your game is not using physics, uncheck Auto Simulation and Auto Sync
Transforms. These will just slow down your application with no discernible benefit.
Mobile projects must balance frame rates against battery life and thermal
throttling. Instead of pushing the limits of your device at 60 fps, consider
running at 30 fps as a compromise. Unity defaults to 30 fps for mobile.
You can also adjust the frame rate dynamically during runtime with
Application.targetFrameRate. For example, you could even drop below
30 fps for slow or relatively static scenes and reserve higher fps settings
for gameplay.
See “Optimizing the hierarchy” on the Unity Blog and this Unite talk for best
practices with Transforms.
GameObject.Instantiate(prefab, parent);
GameObject.Instantiate(prefab, parent, position, rotation);
For more details about Object.Instantiate, please see the Scripting API.
Mobile platforms won’t render half-frames. Even if you disable Vsync in the
Editor (Project Settings > Quality), Vsync is enabled at the hardware level.
If the GPU cannot refresh fast enough, the current frame will be held,
effectively reducing your fps.
Don’t rely on default settings. Use the platform-specific override tab to optimize
assets such as textures and mesh geometry. Incorrect settings may yield larger
build sizes, longer build times, and poor memory usage. Consider using the
Presets feature to help customize baseline settings for a specific project to
ensure optimal settings.
See this guide to best practices for art assets for more detail or
check out this course about 3D Art Optimization for Mobile Applications
on Unity Learn for more details.
Most of your memory will likely go to textures, so the import settings here are
critical. In general, follow these guidelines:
— Lower the Max Size: Use the minimum settings that produce visually
acceptable results. This is non-destructive and can quickly reduce your
texture memory.
— Atlas your textures: Placing multiple textures into a single texture can
reduce draw calls and speed up rendering. Use the Unity Sprite Atlas
or the third-party Texture Packer to atlas your textures.
— Toggle off the Read/Write Enabled option: When enabled, this option
creates a copy in both CPU- and GPU-addressable memory, doubling
the texture’s memory footprint. In most cases, keep this disabled. If you
are generating textures at runtime, enforce this via Texture2D.Apply,
passing in makeNoLongerReadable set to true.
— Disable unnecessary Mip Maps: Mip Maps are not needed for textures
that remain at a consistent size on-screen, such as 2D sprites and UI
graphics (leave Mip Maps enabled for 3D models that vary their distance
from the camera).
Consider these two examples using the same model and texture. The settings
on the left consume almost eight times the memory as those on the right,
without much benefit in visual quality.
Use Adaptive Scalable Texture Compression (ATSC) for both iOS and Android.
The vast majority of games in development target min-spec devices that
support ATSC compression.
If the quality of compressed formats such as PVRTC and ETC isn’t sufficiently
high, and if ASTC is not fully supported on your target platform, try using 16-bit
textures instead of 32-bit textures.
Much like textures, meshes can consume excess memory if not imported
carefully. To minimize meshes’ memory consumption:
— Disable rigs and BlendShapes: If your mesh does not need skeletal or
blendshape animation, disable these options wherever possible.
— Disable normals and tangents, if possible: If you are certain the mesh’s
material will not need normals or tangents, uncheck these options for
extra savings.
The AssetPostprocessor allows you to hook into the import pipeline and run
scripts prior to or when importing assets. This prompts you to customize
settings before and/or after importing models, textures, audio, and so on in a
way similar to presets but through code. Learn more about the process in our
GDC 2023 talk, “Technical tips for every stage of game creation.”
Unity DataTools
If you split your non-code assets (models, textures, Prefabs, audio, and even
entire scenes) into an AssetBundle, you can separate them as downloadable
content (DLC).
Click here to see how the Addressable Asset System can take
the pain out of asset management.
With each frame, Unity determines the objects that must be rendered and
then creates draw calls. A draw call is a call to the graphics API to draw
objects (e.g., a triangle), whereas a batch is a group of draw calls to be
executed together.
As your projects become more complex, you’ll need a pipeline that optimizes
the workload on your GPU. The Universal Render Pipeline (URP) supports three
options for rendering: Forward, Forward+, and Deferred.
— Octahedron encoding
(accurate normals, might
have significant performance
impact on mobile GPUs)
Learn about moving projects based on the Built-in Render Pipeline to URP with the
e-book Introduction to the Universal Render Pipeline for advanced Unity creators.
Use these best practices for reducing the rendering workload on the GPU.
When profiling, it’s useful to start with a benchmark. A benchmark tells you what
profiling results you should expect from specific GPUs.
Click the Stats button in the top right of the Game view. This window shows you
real-time rendering information about your application during Play mode. Use
this data to help optimize performance:
— CPU Main: Total time to process one frame (and update the Editor
for all windows)
— CPU Render: Total time to render one frame of the Game view
— SetPass calls: The number of times Unity must switch shader passes
to render the GameObjects onscreen; each pass can introduce extra
CPU overhead.
PC and console hardware can push a lot of draw calls, but the overhead of each
call is still high enough to warrant trying to reduce them. On mobile devices,
draw call optimization is vital. You can achieve this with draw call batching.
Draw call batching minimizes these state changes and reduces the CPU cost of
rendering objects. Unity can combine multiple objects into fewer batches using
several techniques:
— SRP Batching: If you are using HDRP or URP, enable the SRP Batcher
in your Pipeline Asset under Advanced. When using compatible
shaders, the SRP Batcher reduces the GPU setup between draw calls
and makes material data persistent in GPU Memory. This can speed up
your CPU rendering times significantly. Use fewer Shader Variants with
a minimal amount of Keywords to improve SRP batching. Consult this
SRP documentation to see how your project can take advantage of this
rendering workflow.
Mark all meshes that never move as Batching Static in the Inspector.
Unity combines all static meshes into one large mesh at build time.
The StaticBatchingUtility also allows you to create these static batches
yourself at runtime (for example, after generating a procedural level of
non-moving parts).
— Dynamic Batching: For small meshes, Unity can group and transform
vertices on the CPU, then draw them all in one go. Note: Do not use this
unless you have enough low-poly meshes (no more than 300 vertices
each and 900 total vertex attributes). Otherwise, enabling it will waste
CPU time looking for small meshes to batch.
— Always bake lightmaps at the largest atlas size possible. Fewer lightmaps
require fewer material state changes, but keep an eye on the memory
footprint.
— Keep an eye on the number of static and dynamic batch counts versus
the total draw call count by using the Profiler or the rendering stats during
optimizations.
Please refer to the Draw Call Batching documentation for more information.
The Frame Debugger shows how each frame is constructed from individual
draw calls. This is an invaluable tool for troubleshooting your shader properties
and can help you analyze how the game is rendered.
The Frame Debugger breaks each frame into its separate steps.
It is crucial to avoid adding too many dynamic lights to your mobile application
when using forward rendering. Consider alternatives like custom shader
effects and light probes for dynamic meshes, as well as baked lighting for
static meshes.
See this feature comparison table for the specific limits of URP and Built-in
Render Pipeline real-time lights.
Disable shadows
Shadow casting can be disabled per MeshRenderer and light. Disable shadows
whenever possible to reduce draw calls.
You can also create fake shadows using a blurred texture applied to a simple
mesh or quad underneath your characters. Alternately, create blob shadows
with custom shaders.
Add dramatic lighting to your static geometry using Global Illumination (GI).
Mark objects with Contribute GI so you can store high-quality lighting in the
form of Lightmaps.
Baked shadows and lighting can then render without a performance hit at
runtime. The Progressive CPU and GPU Lightmapper can accelerate the baking
of Global Illumination.
Adjust the Lightmapping Settings (Windows > Rendering > Lighting Settings) and Lightmap size to limit memory usage.
Follow the manual guide and this article on light optimization to get started with
Lightmapping in Unity.
For complex scenes with multiple lights, separate your objects using layers,
then confine each light’s influence to a specific culling mask.
Light Probes store baked lighting information about the empty space in your
scene while providing high-quality lighting (both direct and indirect). They
use Spherical Harmonics, which calculate quickly compared to dynamic lights.
This is especially useful for moving objects that normally cannot receive baked
lightmapping.
A Light Probe Group with Light Probes spread across the level.
Continue using lightmapping for your prominent level geometry, but use probes
for smaller details. Light Probe illumination does not require proper UVs, saving
you the extra step of unwrapping your meshes. Probes also reduce disk space
since they don’t generate lightmap textures.
See the “Static Lighting with Light Probes” blog post for information about
selectively lighting scene objects with Light Probes.
For more about lighting workflows in Unity, read “Making believable visuals
in Unity.”
As objects move into the distance, Level of Detail (LOD) can switch them to use
simpler meshes with simpler materials and shaders to aid GPU performance.
See the Working with LODs course on Unity Learn for more detail.
Objects hidden behind other objects can potentially still render and cost
resources. Use Occlusion Culling to discard them.
While frustum culling outside the camera view is automatic, occlusion culling is
a baked process. Simply mark your objects as Static Occluders or Occludees,
then bake via Window > Rendering > Occlusion Culling. Though not necessary
for every scene, culling can improve performance in specific cases, so be sure
to profile before and after enabling occlusion culling to check if it has improved
performance.
Check out the Working with Occlusion Culling tutorial for more information.
Phones and tablets have become increasingly advanced, with newer devices
sporting very high resolutions.
Each enabled camera incurs some overhead, whether it’s doing meaningful work
or not. Only use camera components necessary for rendering. On lower-end
mobile platforms, each camera can use up to 1 ms of CPU time.
The Universal Render Pipeline includes several lightweight Lit and Unlit
shaders that are already optimized for mobile platforms. Try to keep your
shader variations as low as possible, as they can have a dramatic effect on
runtime memory usage. If the default URP shaders don’t suit your needs, you
can customize the look of your materials using Shader Graph. Find out how to
build your shaders visually using Shader Graph here.
Rendering skinned meshes is expensive. Make sure that every object using
a SkinnedMeshRenderer requires it. If a GameObject only needs animation
some of the time, use the BakeMesh function to freeze the skinned mesh in
a static pose, and swap to a simpler MeshRenderer at runtime.
A Reflection Probe can create realistic reflections, but this can be very costly
in terms of batches. Use low-resolution cubemaps, culling masks, and texture
compression to improve runtime performance.
You can also leverage the System Metrics Mali package to access low-level
system or hardware performance metrics on devices that use ARM GPUs. This
includes being able to monitor low-level GPU metrics in the Unity Profiler, use
the Recorder API to access low-level GPU metrics at runtime, and automate
performance testing with continuous integration test runs.
Unity offers two UI systems, the older Unity UI and the new UI Toolkit. UI Toolkit
is intended to become the recommended UI system. It’s tailored for maximum
performance and reusability, with workflows and authoring tools inspired by
standard web technologies, meaning UI designers and artists will find it familiar
if they already have experience designing web pages.
However, as of Unity 2022 LTS, UI Toolkit does not have some features that
Unity UI and Immediate Mode GUI (IMGUI) support. Unity UI and IMGUI are more
appropriate for certain use cases and are required to support legacy projects.
See the Comparison of UI systems in Unity for more information.
If you have one large Canvas with thousands of elements, updating a single UI
element forces the whole Canvas to update, potentially generating a CPU spike.
Ensure that all UI elements within each Canvas have the same Z value,
materials, and textures.
You may have UI elements that only appear sporadically in the game
(e.g., a health bar that appears only when a character takes damage).
If your invisible UI element is active, it might still be using draw calls. Explicitly
disable any invisible UI components and re-enable them as needed.
If you only need to turn off the Canvas’s visibility, disable the Canvas component
rather than the whole GameObject. This can prevent your game from having to
rebuild meshes and vertices when you reenable it.
Remove the default GraphicRaycaster from the top Canvas in the hierarchy.
Instead, add the GraphicRaycaster exclusively to the individual elements that
need to interact (buttons, scrollrects, and so on).
Layout Groups update inefficiently, so use them sparingly. Avoid them entirely
if your content isn’t dynamic, and use anchors for proportional layouts instead.
Alternately, create custom code to disable the Layout Group components after
they set up the UI.
If you do need to use Layout Groups (Horizontal, Vertical, Grid) for your dynamic
elements, avoid nesting them to improve performance.
Large List and Grid views are expensive. If you need to create a large List or
Grid view (e.g., an inventory screen with hundreds of items), consider reusing
a smaller pool of UI elements rather than creating a UI element for every item.
Check out this sample GitHub project to see this in action.
Layering lots of UI elements (e.g., cards stacked in a card battle game) creates
overdraw. Customize your code to merge layered elements at runtime into
fewer elements and batches.
With mobile devices now using very different resolutions and screen sizes,
create alternate versions of the UI to provide the best experience per device.
Use the Device Simulator to preview the UI across a wide range of supported
devices. You can also create virtual devices in XCode and Android Studio.
If your pause screen or start screen covers everything else in the scene,
disable the camera rendering the 3D scene. Likewise, disable any background
Canvas elements hidden behind the top Canvas.
Leaving the Event or Render Camera field blank forces Unity to fill
in Camera.main, which is unnecessarily expensive.
When using World Space Render Mode, make sure to fill in the Event Camera.
Though audio is not normally a performance bottleneck, you can still optimize
to save memory.
If you are using 3D spatial audio, author your sound clips as mono (single
channel) or enable the Force To Mono setting. A multichannel sound used
positionally at runtime will be flattened to a mono source, thus increasing CPU
cost and wasting memory.
If you use any compressed format (such as MP3 or Vorbis), then Unity will
decompress it and recompress it during build time. This results in two lossy
passes, degrading the final quality.
Reduce the size of your clips and memory usage with compression:
— Use Vorbis for most sounds (or MP3 for sounds not intended to loop).
— Use ADPCM for short, frequently used sounds (e.g., footsteps, gunshots).
This shrinks the files compared to uncompressed PCM but is fast
to decode during playback.
— Small clips (< 200 kb) should Decompress on Load. This incurs CPU cost
and memory by decompressing a sound into raw 16-bit PCM audio data,
so it’s only desirable for short sounds.
When implementing a mute button, don’t simply set the volume to 0. You can
Destroy the AudioSource component to unload it from memory, provided the
player does not need to toggle this on and off very often.
By default, Unity imports animated models with the Generic Rig, but
developers often switch to the Humanoid Rig when animating a character.
A Humanoid Rig consumes 30–50% more CPU time than the equivalent Generic
Rig because it calculates inverse kinematics and animation retargeting each
frame, even when not in use. If you don’t need these specific features of the
Humanoid Rig, use the Generic Rig instead.
Animators are primarily intended for humanoid characters but are often
used to animate single values (e.g., the alpha channel of a UI element).
Avoid overusing Animators, particularly in conjunction with UI elements.
Whenever possible, use the legacy Animation components for mobile.
Unity’s built-in Physics (Nvidia PhysX) can be expensive on mobile. The following
tips may help you squeeze out more frames per second.
Make sure that you edit your Physics settings (Project Settings > Physics)
as well. Simplify your Layer Collision Matrix wherever possible.
Keep an eye on the Physics module of the Profiler for performance issues.
Mesh colliders can be expensive. Substitute more complex mesh colliders with
simpler primitive or mesh colliders to approximate the original shape.
The default Fixed Timestep in the Project Settings is 0.02 (50 Hz). Change this
to match your target frame rate (for example 0.03 for 30 fps).
Otherwise, if your frame rate drops at runtime, that means Unity would call
FixedUpdate multiple times per frame, potentially creating a CPU performance
issue with physics-heavy content.
Modify the Fixed Timestep to match your target frame rate, and lower the Maximum Allowed Timestep to reduce
performance glitches.
Use the Physics Debug window (Window > Analysis > Physics Debugger)
to help troubleshoot any problem colliders or discrepancies. This shows a
color-coded indicator of what GameObjects should be able to collide with one
another.
The Physics Debugger helps you visualize how your physics objects can interact with each other.
Building an application in Unity is a large endeavor that will often involve many
developers. Make sure that your project is set up optimally for your team.
Everyone should be using some type of version control. Make sure your
Editor Settings have Asset Serialization Mode set to Force Text.
Version control is essential for working as part of a team. It can help you track
down bugs and bad revisions. Follow good practices like using branches
and tags to manage milestones and releases.
For further versioning support, check out Plastic SCM, our recommended
version control solution for Unity game development.
Note that, at runtime, your project can load scenes additively using
SceneManager.LoadSceneAsync passing the LoadSceneMode.Additive
parameter mode.
Watch out for any unused assets that come bundled with third-party plug-ins and
libraries. Many include embedded test assets and scripts, which will become part
of your build if you don’t remove them. Strip out any unneeded resources left over
from prototyping.
The team’s expertise lies in helping you take your game to the next level, no
matter what stage of game development you’re in. Mainly, the optimizations
focus on identifying general and specific performance issues such as frame
rate, memory, and binary size to improve player experiences and/or iteration
times.
— Consulting
During these engagements, the consultant will analyze your project or
workflows and provide guidance and recommendations to your team on
how to achieve your desired outcome.
— Codevelopment
Working alongside your team, a Unity developer and/or team will deep dive
into your project and achieve a desired outcome.
— Custom development
For these engagements, the Accelerate Solutions team will assign and
partner with an internal Unity team that will lead and execute a project on
your behalf, owning it from inception to completion.
Integrated Success also allows you to optionally add read and modification
access to Unity source code. This access is available for development teams
that want to deep dive into Unity source code to adapt and reuse it for other
applications.
For well-architected projects that have low build times (modular scenes, heavy
usage of AssetBundles, etc.), they’ll make adjustments and reprofile to uncover
new issues. In instances where the team is unable to solve problems immediately,
they’ll capture as much information as possible and conduct further investigation
internally, consulting specialized developers across R&D if necessary.
You can find additional optimization tips, best practices, and news on the
Unity Blog and Unity community forums, as well as through Unity Learn and
the #unitytips hashtag.
Create a C# style guide: Write cleaner code that scales assists you with
developing a style guide to help unify your approach to creating a more cohesive
codebase.
Level up your code with game programming patterns highlights best practices for
using the SOLID principles and common programming patterns to create scalable
game code architecture in your Unity project.
Unity Professional Training gives you the skills and knowledge to work more
productively and collaborate efficiently in Unity. Find an extensive training
catalog designed for professionals in any industry, at any skill level, in multiple
delivery formats.
Learn more about how Unity Professional Training can support you and
your team.