Unity PCConsole Optmization Final 28mb
Unity PCConsole Optmization Final 28mb
O P T I M I Z E YO U R
GAME PERFORMANCE
F O R CO N SO L ES A N D PC
U N I T Y 2 0 2 2 LT S E D I T I O N
Contents
Introduction. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
Profiling. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
Deep Profiling . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
Project Auditor. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
Memory. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20
Heap Explorer. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
Use ScriptableObjects. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
Project configuration. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35
Switch to IL2CPP . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35
Assets. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37
Compress textures. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37
The AssetPostprocessor . . . . . . . . . . . . . . . . . . . . . . . . . . 43
Unity DataTools. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43
Async texture buffer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43
Use Addressables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44
Graphics . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46
Bake lightmaps. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57
Disable shadows. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58
GPU optimization . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61
Culling. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72
Dynamic resolution. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 74
RenderObjects in URP . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75
CustomPassVolumes in HDRP. . . . . . . . . . . . . . . . . . . . . . 75
User interface . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 78
Audio. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 82
Physics . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 86
Simplify colliders . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 86
Use Physics.BakeMesh . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 90
Animation. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 95
Optimize workflow. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97
Getting your game to run with fewer resources and at higher frame rates
ensures that it can reach more potential players – and keep them engaged.
While your audience may take it for granted that your game runs at silky-
smooth 60+ frames per second (fps), achieving your performance goals across
multiple platforms is not always easy. It requires effort to make both your code
architecture and art assets more efficient.
This guide assembles knowledge and advice from Unity’s expert software
engineers. Our Accelerate Solutions games team has tested these best
practices with our industry partners in real-world scenarios. We’re here to help
you identify key areas for optimization in your Unity project.
The Unity Profiler provides performance information about your application – but
it can’t help you if you don’t use it.
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
and make sure 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.
While profiling in the Editor can give you an idea of the relative performance of
different systems in your game, profiling on each device gives you the opportunity
to gain more accurate insights. Profile a development build on target devices
whenever possible. Remember to profile and optimize for both the highest- and
lowest-spec devices that you plan to support.
Along with the Unity Profiler, you can leverage the Memory Profiler and Profile
Analyzer. See Profiling Applications Made with Unity and Working with the
Profiler for more information.
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.
Of course, not every optimization described here will apply to your application.
Something that works well in one project may not translate to yours. Identify
genuine bottlenecks and concentrate your efforts on what benefits your work.
To learn more about how to plan your profiling workflows see the Ultimate guide
to profiling Unity games.
The built-in Unity Profiler can help you detect the causes of any bottlenecks 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 (for example, profiling for physics-heavy or music-based gameplay).
However, only enable what you need so you don’t impact performance and skew
your results.
To capture profiling data from an actual device within your chosen platform,
check the Development Build before you click Build and Run. Then, connect the
Profiler to your application manually once it’s running.
You can optionally check Autoconnect Profiler in the Build Options. Sometimes
this is useful if you specifically want to capture the first few frames of the
application. Be warned that this option can add 5–10 seconds of startup
time, so only use it when necessary.
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:
You can find a complete overview of the Unity Profiler here. If you’re new to
profiling, you can also watch 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.
Deep Profiling
You can also enable Deep Profiling Support in the Build Settings. When the built
Player starts, the Deep Profiler profiles every part of your code, not just code
timings explicitly wrapped in ProfilerMarkers.
When Deep Profiling is enabled, Unity can profile the beginning and end of every
function call in your script code. This can help you identify exactly which part of
your application is causing a slowdown.
If you want to see more details on samples with markers such as GC.Alloc or
JobHandle.Complete, navigate to the Profiler window toolbar and enable the
Call Stacks setting. This provides the sample’s full call stack, which gives
you the information you need without incurring the overhead of Deep Profiling.
In general, only use Deep Profiling when it’s necessary, since your application
runs significantly slower when it’s in use.
The Profile Analyzer lets you aggregate multiple frames of Profiler data, 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.
Take an even deeper dive into frames and marker data with the Profile Analyzer, which complements the existing Profiler.
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.
A common way that gamers measure performance is with frame rate, or frames per
second. This can be a deceptive metric when gauging your application’s performance.
If your application is running at 900 fps, this translates into a frame time of
1.111 milliseconds per frame. At 450 fps, this is 2.222 milliseconds per frame.
This represents a difference of only 1.111 milliseconds per frame, even though
the frame rate appears to drop by one half.
If you look at the differences between 60 fps and 56.25 fps, that translates into
16.666 milliseconds per frame and 17.777 milliseconds per frame, respectively.
This also represents 1.111 milliseconds extra per frame, but here, the drop-in
frame rate feels far less dramatic percentage-wise.
This is why developers use the average frame time to benchmark game speed
rather than fps.
Don’t worry about fps unless you drop below your target frame rate. Focus on frame
time to measure how fast your game is running, then stay within your frame budget.
Read the original article, “Robert Dunlop’s FPS versus Frame Time,” for more
information.
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:
Start your profiling with Unity’s tools, and if you need greater detail, reach for
the native profiling and debugging tools available for your target platform.
Intel
— Intel VTune: Quickly find and fix performance bottlenecks on Intel
platforms with this suite of tools for Intel processors only.
— Intel GPA suite: This suite of graphics-focused tools can help you improve
your game’s performance by quickly identifying problem areas.
— AMD μProf: AMD uProf is a performance analysis tool for understanding and
profiling performance for applications running on AMD hardware.
— NVIDIA NSight: This tooling enables developers to build, debug, profile, and
develop class-leading and cutting-edge software using the latest visual
computing hardware from NVIDIA.
— Superluminal: Superluminal is a high-performance, high-frequency profiler
that supports profiling applications on Windows, Xbox One, and PlayStation®
written in C++, Rust and .NET. It is a paid product, though, and must be
licensed for use.
PlayStation
— CPU profiler tools are available for PlayStation hardware. For more details, you
need to be a registered PlayStation developer: Start here.
WebGL
— Firefox Profiler: Dig into the call stacks and view flame graphs for Unity
WebGL builds (among other things) with the Firefox Profiler. It also features a
comparison tool to look at profiling captures side by side.
— Chrome DevTools Performance: This web browser tool can be used to
profile Unity WebGL builds.
Some are platform-specific and offer closer platform integration. Take a look at
the tools relevant to your platforms of interest:
Project Auditor
The Project Auditor is an experimental tool capable of performing static analysis
of a project’s scripts and settings. It offers a great way to track down the causes
of managed memory allocations, inefficient project configurations, and possible
performance bottlenecks.
The Project Auditor is a free, unofficial package for use with the Editor.
For information, please refer to the Project Auditor documentation.
The Build Report Inspector (in Preview) is an Editor script that lets you access
information about your last build so you can profile the time spent building your
project and the build’s disk size footprint.
This script allows you to inspect this information graphically in the Editor UI,
making it more easily accessible than the script APIs would.
The build report displays statistics on included resources and generated code size.
Optimizing your memory usage means being conscious of when you allocate
and deallocate managed 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.
— Strings: In C#, strings are reference types, not value types. This means
that every new string will be allocated on the managed heap, even if it’s
only used temporarily. Reduce unnecessary string creation or manipulation.
Avoid parsing string-based data files such as JSON and XML, and store
data in ScriptableObjects or formats like MessagePack or Protobuf instead.
Use the StringBuilder class if you need to build strings at runtime.
— Unity function calls: Some Unity API functions create heap allocations,
particularly ones which return an array of managed objects. Cache
references to arrays rather than allocating them in the middle of a loop.
Also, take advantage of certain functions that avoid generating garbage.
For example, use GameObject.CompareTag instead of manually comparing
a string with GameObject.tag (as returning a new string creates garbage).
For more information, see the manual page on Garbage Collection Best Practices.
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.
Rather than creating a single, long interruption during your program’s execution,
incremental garbage collection uses multiple, much shorter interruptions
that distribute the workload over many frames. If garbage collection is impacting
performance, try enabling this option to see if it can reduce the problem of
GC spikes. Use the Profile Analyzer to verify its benefit to your application.
Heap Explorer
Note that using the GC can add read-write barriers to some C# calls, which come with little overhead that can
1
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.
Though it overlaps in functionality with Unity’s Memory Profiler, some still prefer
Heap Explorer for its easy to understand UI/UX.
The Unity PlayerLoop contains functions for interacting with the core of
the game engine. This 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 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.
Refer to the Script Lifecycle Flowchart for event functions’ specific order of
execution.
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, while extracting 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 to apply 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();
}
}
The trick is to interleave this with other work that runs on the other frames.
In this example, you could “schedule” other expensive functions when
Time.frameCount % interval == 1 or Time.frameCount % interval == 2.
Frequently calling these methods can contribute to CPU spikes. Wherever possible,
run expensive functions in the initialization phase (i.e., MonoBehaviour.Awake
and MonoBehaviour.Start). Cache the needed references and reuse them later.
Use preprocessor directives if you are employing these methods for testing:
#if UNITY_EDITOR
void Update()
{
}
#endif
Here, you can freely use the Update in-Editor for testing without unnecessary
overhead slipping into your build. This blog post on 10,000 Update calls will help
you understand how Unity executes Monobehaviour.Update.
A common usage pattern for Update or LateUpdate is to run logic only when
some condition is met. This can lead to a lot of per-frame callbacks that effectively
run no code except for checking this condition.
Every time Unity calls a Message method like Update or LateUpdate, it makes
an interop call, a call from the C/C++ side to the managed C# side. For a small
number of objects, this is not an issue. When you have thousands of objects,
this overhead starts becoming significant.
[System.Diagnostics.Conditional(“ENABLE_LOG”)]
static public void Log(object message)
UnityEngine.Debug.Log(message);
}
}
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.
Use the Stack Trace options in the Player Settings to control what type of log
messages appear.
If your application is logging errors or warning messages in your release build (e.g.,
to generate crash reports in the wild), disable stack traces to improve 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
duplicates or other required components whenever adding components at runtime.
Instantiate and Destroy can generate garbage and garbage collection (GC)
spikes, and this is generally a slow process
In this example, the ObjectPool
creates 20 PlayerLaser
instances for reuse.
The key function of object pooling is to create objects in advance and store them
in a pool, rather than have them created and destroyed on demand. When an
object is needed, it’s taken from the pool and used. When it’s no longer needed, it’s
returned to the pool rather than being destroyed.
Rather than regularly instantiating and destroying GameObjects (e.g., shooting bullets
from a gun), use pools of preallocated objects that can be reused and recycled.
Learn how to create a simple object pooling system in Unity using the Pooling
API as of 2021 LTS.
GameObject.Instantiate(prefab, parent);
GameObject.Instantiate(prefab, parent, position, rotation);
Use ScriptableObjects
Store unchanging values or settings in a ScriptableObject instead of a
MonoBehaviour. The ScriptableObject is an asset that lives inside of the project
that you only need to set up once. It cannot be directly attached to a GameObject.
A common use case is having many GameObjects that rely on the same
duplicate data, which 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 is a benefit that 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.
To learn more about using ScriptableObjects in your project, see the e-book
Create modular architecture in Unity with ScriptableObjects.
A lambda expression can simplify your code, but that simplification comes
at a cost. Calling a lambda creates a delegate as well. Passing context
(e.g., this, an instance member, or a local variable) into the lambda invalidates
any caching for the delegate. When that happens, invoking it frequently
can generate significant memory traffic.
Modern CPUs have multiple cores, but your application needs multithreaded
code to take advantage of them. Unity’s Job System allows you to split large
tasks into smaller chunks that run in parallel on those extra CPU cores, which
can improve performance significantly.
In traditional
multithreaded
programming, threads
are created and
destroyed. In the C# Job
System, small jobs run
on a pool of threads.
Timeline view in the Profiler shows jobs running on the worker threads.
These jobs go into a queue, which schedules them to run on a shared pool of
worker threads. JobHandles help you create dependencies, ensuring that the
jobs run in the correct order.
— Change classes to be structs. A job is any struct that implements the IJob
interface. If you’re performing the same task on a large number of objects,
you could also use IJobParallelFor to run across multiple cores.
— Data passed into a job must be blittable. Remove reference types and pass
only the blittable data into the job as a copy.
— Because the work within each job remains isolated for safety, you send the
results back to the main thread using a NativeContainer. A NativeContainer
from the Unity Collections package provides a C# wrapper for native
memory. Its subtypes (e.g., NativeArray, NativeList, NativeHashMap,
NativeQueue, etc.)2 work like their equivalent C# data structures.
2
These are part of the com.unity.collections package. Some of these structures are currently in Preview.
The Burst compiler complements the Job System. Burst translates IL/.NET
bytecode into optimized native code using LLVM. To access it, simply add the
com.unity.burst package from the Package Manager.
[BurstCompile]
public struct MyFirstJob : IJob
{
ToNormalize[i] = math.normalize(ToNormalize[i]);
Here is an example Burst job that runs over an array of float3’s and normalizes
the vectors. It uses the Unity Mathematics package, as mentioned above.
Both the C# Job System and the Burst compiler form part of Unity’s
Data-Oriented Tech Stack (DOTS). However, you can use them equally
with ‘classic’ Unity GameObjects or the Entity Component System.
Refer to the latest documentation to see how Burst can accelerate your
workflow when combined with the C# Job System.
There are a few project settings that can affect your performance.
Using this option, Unity converts IL code from scripts and assemblies to C++
before creating a native binary file (e.g., .exe, .apk, .xap) for your target platform.
You can also read the Introduction to IL2CPP Internals blog post for additional
detail or consult the Compiler options manual page to see how the various
compiler options affect runtime performance.
See Optimizing the Hierarchy and this Unite talk for best practices for Transforms.
Don’t rely on default settings. Use the platform-specific override tab to optimize
assets such as textures and mesh geometry. Incorrect settings might yield
larger build sizes, longer build times, poor GPU performance, and poor memory
usage. Consider using the Presets feature to help customize baseline settings
that will enhance a specific project.
See this guide to best practices for importing art assets. For a mobile-specific
guide (with many general tips as well), check out the Unity Learn course on
3D art optimization for mobile applications. And watch the GDC 2023 session
“Technical tips for every stage of game creation” to learn more about how to
leverage Presets.
Compress textures
Consider these two examples using the same model and texture. The settings
on the top consume more than five times the memory compared to those on the
bottom, without much benefit in visual quality.
This can result in faster load times, a smaller memory footprint, and dramatically
increased rendering performance. Compressed textures only use a fraction of
the memory bandwidth needed for uncompressed 32-bit RGBA textures.
Refer to this recommended list of texture compression formats for your target platform:
Textures can potentially use a lot of resources. Import settings here are critical.
In general, try to 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.
— Use powers of two (POT): Unity requires POT texture dimensions for
texture compression formats.
— 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 (only
— Disable unnecessary mipmaps: Mipmaps are not needed for textures that
remain at a consistent size on-screen, such as 2D sprites and UI graphics
(leave mipmaps enabled for 3D models that vary their distance from the
camera).
Proper texture import settings will help optimize your build size.
For 2D projects, you can use a Sprite Atlas (Asset > Create > 2D > Sprite Atlas)
rather than rendering individual Sprites and Textures.
For 3D projects, you can use your digital content creation (DCC) package of
choice. Several third-party tools like MA_TextureAtlasser or TexturePacker also
can build texture atlases.
Combine textures and remap UVs for any 3D geometry that doesn’t require
high-resolution maps. A visual editor gives you the ability to set and prioritize
the sizes and positions in the texture atlas or sprite sheet.
The texture packer consolidates the individual maps into one large texture. Unity
can then issue a single draw call to access the packed Textures with a smaller
performance overhead.
Consider cutting down models in your DCC package of choice. Delete unseen
polygons from the camera’s point of view. For example, if you never see the back
of a cupboard resting against a wall, the model should not have any faces there.
Be aware that the bottleneck is not usually polygon count on modern GPUs,
but rather polygon density. We recommend performing an art pass across
all assets to reduce the polygon count of distant objects. Microtriangles can
be a significant cause of poor GPU performance.
Reduce pixel complexity by baking as much detail into the Textures as possible.
For example, capture the specular highlights into the Texture to avoid having to
compute the highlight in the fragment shader.
Much like textures, meshes can consume excess memory if not imported
carefully. To minimize meshes’ memory consumption:
— Disable Read/Write:
Enabling this option
duplicates the mesh in
memory, which keeps one
copy of the mesh in system
memory and another in
GPU memory. In most
cases, you should disable it Check your mesh import settings.
(in Unity 2019.2 and earlier,
this option is checked by
default).
— Disable rigs and BlendShapes: If your mesh does not need skeletal or
blendshape animation, disable these options wherever possible.
— Disable normals and tangents: If you are absolutely certain the mesh’s material
will not need normals or tangents, uncheck these options for extra savings.
In the Player Settings, you can also apply a couple of other optimization to your
meshes:
Vertex Compression sets vertex compression per channel. For example, you
can enable compression for everything except positions and lightmap UVs. This
can reduce runtime memory usage from your meshes.
Note that the Mesh Compression in each mesh’s Import Settings overrides the
vertex compression setting. In that event, the runtime copy of the mesh
is uncompressed and may use more memory.
Optimize Mesh Data removes any data from meshes that is not required by the
material applied to them (such as tangents, normals, colors, and UVs).
By automating the asset audit process, you can avoid accidentally changing
asset settings. A couple tools can help both to standardize your import settings
or analyze your existing assets.
The AssetPostprocessor
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 that’s similar to presets but through code. Learn more about the process in
the GDC 2023 talk “Technical tips for every stage of game creation.”
Unity DataTools
Unity DataTools is a collection of open source tools provided by Unity that aim
to enhance the data management and serialization capabilities in Unity projects.
It includes features for analyzing and optimizing project data, such as identifying
unused assets, detecting asset dependencies, and reducing build size.
Learn more about the tools here and read more about Asset Auditing in the
Understanding Optimization in Unity section of the best practice guide.
Unity uses a ring buffer to push textures to the GPU. You can manually adjust
this async texture buffer via QualitySettings.asyncUploadBufferSize.
If either the upload rate is too slow or the main thread stalls while loading
several Textures at once, adjust the Texture buffers. Usually you can set the
value (in MB) to the size of the largest texture you need to load in the Scene.
Note: Be aware that changing the default values can lead to high memory pressure.
Also, you cannot return ring buffer memory to the system after Unity allocates it.
If GPU memory overloads, the GPU unloads the least-recently used Texture
and forces the CPU to reupload it the next time it enters the camera frustum.
Read more about memory restrictions in Texture buffers when using time-
slice awake in the Memory Management in Unity guide. Also, refer to the post
Optimizing loading performance to investigate how you can improve your
loading times with the Async Upload Pipeline.
The Mipmap Streaming system gives you control over which mipmap levels load
into memory. To enable it, go to Unity’s Quality Settings (Edit > Project Settings
> Quality) and check Texture Streaming. Enable Streaming Mipmaps in the
Texture’s Import Settings under Advanced.
Texture Streaming
settings
Streaming Mipmaps
is enabled.
This system reduces the total amount of memory needed for Textures because
it only loads the mipmaps necessary to render the current Camera position.
Otherwise, Unity loads all of the textures by default. Texture Streaming trades a
small amount of CPU resources to save a potentially large amount of GPU memory.
You can use the Mipmap Streaming API for additional control. Texture Streaming
automatically reduces mipmap levels to stay within the user-defined Memory Budget.
Use Addressables
The Addressable Asset System simplifies how you manage the assets that make
up your game. Any asset, including scenes, Prefabs, text assets, and so on, can
be marked as “addressable” and given a unique name. You can then call this
alias from anywhere.
Adding this extra level of abstraction between the game and its assets can
streamline certain tasks, such as creating a separate downloadable content
pack. Addressables makes referencing those asset packs easier as well,
whether they’re local or remote.
Install the Addressables package from the Package Manager. Each asset or Prefab
in the project has the ability to become “addressable” as a result. Checking the
option under an asset’s name in the Inspector assigns it a default unique address.
In the Addressables Groups, you can see each asset’s custom address paired with its location.
Whether the asset is hosted elsewhere or stored locally, the system will locate
it using the Addressable Name string. An addressable Prefab does not load into
memory until needed and automatically unloads its associated assets when no
longer in use.
The “Tales from the optimization trenches: Saving memory with Addressables”
blog post demonstrates an example of how to organize your Addressable
Groups in order to be more efficient with memory. You can also see the
Addressables: Introduction to Concepts Learn module for a quick overview of
how the Addressable Asset system can work in your project.
Unity’s graphics tools let you create beautiful, optimized graphics across a range
of platforms, from mobile to high-end consoles and desktop. Because lighting
and effects are quite complex, we recommend that you thoroughly review the
render pipeline documentation before attempting to optimize.
Optimizing scene lighting is not an exact science. Your process usually depends
on your artistic direction and render pipeline.
Before you begin lighting your scenes, you will need to choose one of the
available render pipelines. A render pipeline performs a series of operations that
take the contents of a scene to display them onscreen.
Unity provides three prebuilt render pipelines with different capabilities and
performance characteristics, or you can create your own.
HDRP targets high-end hardware such as PC, Xbox, and PlayStation3. Use it
to create realistic games, automotive demos, or architectural applications.
HDRP uses physically based lighting and materials and supports improved
debugging tools.
URP and HDRP work on top of the Scriptable Render Pipeline (SRP). This is a
thin API layer that lets you schedule and configure rendering commands using
C# scripts. This flexibility allows you to customize virtually every part of the
pipeline. You can also create your own custom render pipeline based on SRP.
See Render Pipelines in Unity for a more detailed comparison of the available
pipelines.
3
HDRP is not currently supported on mobile platforms or Nintendo Switch. See the Requirements and compatibility page for more details.
— PlayStation 4: com.unity.render-pipelines.ps4
— PlayStation 5: com.unity.render-pipelines.ps5
— Xbox: com.unity.render-pipelines.gamecore
The pipeline passes each object, one at a time, to the graphics API. Forward
rendering comes with a cost for each light. The more lights in your scene, the
longer rendering will take.
The Built-in Render Pipeline’s forward renderer draws each light in a separate
pass per object. If you have multiple lights hitting the same GameObject, this can
create significant overdraw, where overlapping areas need to draw the same pixel
more than once. Minimize the number of real-time lights to reduce overdraw.
Rather than rendering one pass per light, the URP culls the lights per-object.
This allows for the lighting to be computed in one single pass, resulting in fewer
draw calls compared to the Built-In Render Pipeline’s forward renderer.
In the first pass, or the G-Buffer geometry pass, Unity renders the GameObjects.
This pass retrieves several types of geometric properties and stores them in a
set of textures. G-buffer textures can include:
— surface smoothness
— occlusion
Though choosing a rendering path is not an optimization per se, it can affect how
you optimize your project. The other techniques and workflows in this section may
vary depending on what render pipeline and which rendering path you’ve chosen.
Both HDRP and URP support Shader Graph, a visual interface for shader creation.
This allows some users to create complex shading effects that may have been
previously out of reach. Use the 150+ nodes in the visual graph system to create
more shaders. You can also make your own custom nodes with the API.
Use Shader Graph to create shaders via a visual interface rather than through code.
Begin each Shader Graph with a compatible master node, which determines the
graph’s output. Add nodes and operators with the visual interface, and construct
the shader logic.
This Shader Graph then passes into the render pipeline’s backend. The final
result is a ShaderLab shader, functionally similar to one written in HLSL or Cg.
Optimizing a Shader Graph follows many of the same rules that apply to
traditional HLSL/Cg Shaders. The more processing your Shader Graph does,
the more it will impact the performance of your application.
If you are CPU-bound, optimizing your shaders won’t improve frame rate,
but may improve your battery life for mobile platforms.
— Decimate your nodes: Remove unused nodes. Don’t change any defaults
or connect nodes unless those changes are necessary. Shader Graph
compiles out any unused features automatically.
When possible, bake values into textures. For example, instead of using a node
to brighten a texture, apply the extra brightness into the texture asset itself.
— Use a smaller data format: Switch to a smaller data structure when possible.
Consider using Vector2 instead of Vector3 if it does not impact your project. You
can also reduce precision if the situation allows (e.g., half instead of float).
— Reduce math operations: Shader operations run many times per second,
so optimize any math operators when possible. Try to blend results instead
of creating a logical branch. Use constants, and combine scalar values before
applying vectors. Finally, convert any properties that do not need to appear
in the Inspector as in-line Nodes. All of these incremental speedups can help
your frame budget.
If the branch is not connected to the master node, you can safely leave
the preview branch in your graph. Unity removes nodes that do not affect
the final output during compilation.
Create a new HLSL/Cg Shader and then paste in the copied Shader Graph.
This is a one-way operation, but it lets you squeeze additional
performance with manual optimizations.
Remove every shader that you don’t use from the Always Included list of
shaders in the Graphics Settings (Edit > ProjectSettings > Graphics).
Add shaders here needed for the lifetime of the application.
You can use the Shader compilation pragma directives to compile the shader differently
for target platforms. Then, use a shader keyword (or Shader Graph Keyword node)
to create shader variants with certain features enabled or disabled.
Shader variants can be useful for platform-specific features but increase build
times and file size. You can prevent shader variants from being included in your
build, if you know that they are not required.
Parse the Editor.log for shader timing and size. Locate the lines that begin with
“Compiled shader” and “Compressed shader.”
— Unity compresses the shader included in the game data to roughly the
sum of the compressed sizes: 0.14+0.12+0.20+0.15 = 0.61MB.
After a build, Project Auditor can parse the Editor.log to display a list of all shaders,
shader keywords, and shader variants compiled into a project. It can also
analyze the Player.log after the game is run. This shows you what variants the
application actually compiled and used at runtime.
Employ this information to build a scriptable shader stripping system and reduce
the number of variants. This can improve build times, build sizes, and runtime
memory usage.
Read the Stripping scriptable shader variants blog post to see this process in detail.
Unity includes two particle simulation solutions for smoke, liquids, flames, or
other effects:
— The VFX Graph moves calculations on the GPU using compute shaders. This
can simulate millions of particles in large-scale visual effects.
The workflow includes a highly customizable graph view. Particles can also
interact with the color and depth buffer.
When selecting one of the two systems, keep device compatibility in mind.
Most PCs and consoles support compute shaders, but many mobile devices do not.
If your target platform does support compute shaders, Unity allows you to use
both types of particle simulation in your project.
Learn more about creating high-end visual effects with the e-book The
definitive guide to creating advanced visual effects in Unity.
If you are using Forward Rendering with the Built-in Render Pipeline, Multisample
Anti-aliasing (MSAA) is available in the Quality Settings. MSAA produces high-
quality anti-aliasing, but it can be expensive. The MSAA Sample Count from the
drop-down menu (None, 2X, 4X, 8X) defines how many samples the renderer
uses to evaluate the effect.
In HDRP, you can also use FXAA and SMAA in the Post Anti-aliasing on the
Camera. HDRP also offers an additional option:
While lighting is a vast subject, these general tips can help you to optimize
your resources.
Bake lightmaps
The fastest option to create lighting is one that doesn’t need to be computed
per-frame. To do this, use Lightmapping to “bake” static lighting just once,
instead of calculating it in real-time.
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.
Adjust the Lightmapping Settings (Windows > Rendering > Lighting Settings) and Lightmap size to limit memory usage.
Follow the manual guide and this article on optimizing lighting to get started
with lightmapping in Unity.
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. Use Type: Baked to avoid
per-frame updates.
If using Type: Realtime is necessary in HDRP, use On Demand mode. You can
also modify the Frame Settings in Project Settings > HDRP Default Settings.
Reduce the quality and features under Realtime Reflection for improved performance.
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. Otherwise, you can create blob
shadows with custom shaders.
In particular, avoid enabling shadows for point lights. Each point light with
shadows requires six shadow map passes per light – compare that with a single
shadow map pass for a spotlight. Consider replacing point lights with spotlights
where dynamic shadows are absolutely necessary. If you can avoid dynamic
shadows, use a cubemap as a Light.cookie with your point lights instead.
In some cases, you can apply simple tricks rather than adding multiple extra
lights. For example, instead of creating a light that shines straight into the
camera to give a rim lighting effect, use a Shader which simulates rim lighting
(see Surface Shader examples for an implementation of this in HLSL).
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 very quickly compared to dynamic lights.
This is especially useful for moving objects, which normally cannot receive
baked lightmapping.
You can also use Light Probes for smaller details where lightmapping is less noticeable.
A Light Probe Group with Light Probes spread across the level.
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.
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:
— SetPass calls: The number of times Unity must switch shader passes
to render the GameObjects on-screen; each pass can introduce extra
CPU overhead.
To draw a GameObject, Unity issues a draw call to the graphics API (e.g.,
OpenGL, Vulkan, or Direct3D). Each draw call is resource intensive. State
changes between draw calls, such as switching materials, can cause
performance overhead on the CPU side.
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 allows you to freeze playback on a single frame and step
through how Unity constructs a scene to identify optimization opportunities.
Look for GameObjects that render unnecessarily, and disable those to reduce
draw calls per frame.
Note: The Frame Debugger does not show individual draw calls or state changes.
Only native GPU profilers give you detailed draw call and timing information.
However, the Frame Debugger can still be very helpful in debugging pipeline
problems or batching issues.
One advantage of the Unity Frame Debugger is that you can relate a draw call to
a specific GameObject in the scene. This makes it easier to investigate certain
issues that may not be possible in external frame debuggers.
For more information, read the Frame Debugger documentation, and see the
section “Use native profiling and debugging tools” for a list of platform-specific
debugging tools.
If your game is limited by fill rate, this means that it’s trying to draw more pixels
per frame than the GPU can handle.
Drawing on top of the same pixel multiple times is called overdraw. Overdraw
decreases fill rate and costs extra memory bandwidth. The most common
causes of overdraw are:
HDRP controls the render queue slightly differently. To calculate the order of the
render queue, the HDRP:
The resulting queue is a list of GameObjects that are first sorted by their
material’s Priority, then by their individual Mesh Renderer’s Priority. This page on
Renderer and Material Priority illustrates this in more detail.
Though developing for Xbox and PlayStation does resemble working with their
PC counterparts, those platforms do present their own challenges. Achieving
smooth frame rates often means focusing on GPU optimization.
This section outlines the process of porting the Book of the Dead environment project to PlayStation 4.
To begin, locate a frame with a high GPU load. Microsoft and Sony provide
excellent tools for analyzing your project’s performance on both the CPU and on
the GPU. Make PIX for Xbox and PlayStation profiler tools part of your toolbox
when it comes to optimization on these platforms.
Use your respective native profiler to break down the frame cost into its specific
parts. This will be your starting point to improve graphics performance.
The view was GPU-bound on a PS4 Pro at roughly 45 milliseconds per frame.
As with other platforms, optimization on console will often mean reducing draw
call batches. There are a few techniques that might help.
— GPU instancing can also reduce your batches if you have many objects
that share the same mesh and material. Limiting the number of models in
your scene can improve performance. If it’s done artfully, you can build a
complex scene without making it look repetitive.
— The SRP Batcher can reduce the GPU setup between DrawCalls by batching
Bind and Draw GPU commands. To benefit from this SRP batching, use
as many materials as needed, but restrict them to a small number of
compatible shaders (e.g., Lit and Unlit shaders in URP and HDRP).
Enable this option in Player Settings > Other Settings to take advantage of the
PlayStation’s or Xbox’s multi-core processors. Graphics Jobs (Experimental)
allows Unity to spread the rendering work across multiple CPU cores, removing
pressure from the render thread. See Multithreaded Rendering and Graphics
Jobs tutorial for details.
Be sure to use post-processing assets that are optimized for consoles. Tools from
the Asset Store that were originally authored for PC may consume more resources
than necessary on Xbox or PlayStation. Profile using native profilers to be certain.
Tessellation subdivides shapes into smaller versions of that shape. This can
enhance detail through increased geometry. Though there are examples where
tessellation does make sense (e.g., Book of the Dead’s realistic tree bark),
in general, avoid tessellation on consoles. They can be expensive on the GPU.
Like tessellation shaders, geometry and vertex shaders can run twice per frame
on the GPU – once during the depth pre-pass, and again during the shadow pass.
If you want to generate or modify vertex data on the GPU, a compute shader
is often a better choice than a geometry shader. Doing the work in a compute
When you send a draw call to the GPU, that work splits into many wavefronts
that Unity distributes throughout the available SIMDs within the GPU.
Each SIMD has a maximum number of wavefronts that can be running at one
time. Wavefront occupancy refers to how many wavefronts are currently in
use relative to the maximum. This measures how well you are using the GPU’s
potential. PIX and Razor show wavefront occupancy in great detail.
In this example from Book of the Dead, vertex shader wavefronts appear in
green. Pixel shader wavefronts appear in blue. On the bottom graph, many
vertex shader wavefronts appear without much pixel shader activity. This shows
an underutilization of the GPU’s potential.
If you’re doing a lot of vertex shader work that doesn’t result in pixels, that may
indicate an inefficiency. While low wavefront occupancy is not necessarily bad,
it’s a metric to start optimizing your shaders and checking for other bottlenecks.
For example, if you have a stall due to memory or compute operations,
increasing occupancy may help performance. On the other hand, too many in-
flight wavefronts can cause cache thrashing and decrease performance.
If your project uses HDRP, take advantage of its built-in and custom passes.
These can assist in rendering the scene. The built-in passes can help you
optimize your shaders. HDRP includes several injection points where you can
add custom passes to your shaders.
The High Quality setting of HDRP defaults to using a 4K shadow map. Reduce the
shadow map resolution and measure the impact on the frame cost. Just be aware that
you may need to compensate for any changes in visual quality with the light’s settings.
In this example from Book of the Dead, several optimizations shaved several
milliseconds off the shadow mapping, lighting pass, and atmospherics. The resulting
frame cost allowed the application to run at 30 fps on a PlayStation®4 Pro.
Unity culls by layer first, keeping GameObjects only on layers the Camera
uses. Afterwards, frustum culling removes any GameObjects outside the
camera frustum. Frustum culling is performed as a series of jobs to take
advantage of available worker threads.
Each layer culling test is very fast (essentially just a bit mask operation).
However, this cost could still add up with a very large number of
GameObjects. If this becomes a problem for your project, you may need
to implement some system to divide your world into “sectors” and disable
sectors that are outside the Camera frustum in order to relieve some of
the pressure on Unity’s layer/frustum culling system.
— Occlusion culling removes any GameObjects from the Game view if the
Camera cannot see them. Use this feature to prevent rendering of objects
hidden behind other objects since these can still render and cost resources.
For example, rendering another room is unnecessary if a door is closed and
the Camera cannot see into the room.
Enabling occlusion culling can significantly increase performance but can also
require more disk space, CPU time, and RAM. Unity bakes the occlusion data
during the build and then needs to load it from disk to RAM while loading a
scene.
Unity triggers this scaling if performance data suggests that the frame rate is
about to decrease as a result of being GPU-bound. You can also preemptively
trigger this scaling manually with script. This is useful if you are approaching a
GPU-intensive section of the application. If scaled gradually, dynamic resolution
can be almost unnoticeable.
Refer to the dynamic resolution manual page for additional information and a list
of supported platforms.
Sometimes you may need to render from more than one point of view during
your game. For example, it’s common in an FPS game to draw the player’s
weapon and the environment separately with different fields of view (FOV).
This prevents the foreground objects from feeling too distorted viewed through
the wide-angle FOV of the background.
Camera Stacking in URP. In this example, the gun and background render with different camera settings.
You could use Camera Stacking in URP to render more than one camera view.
However, there is still significant culling and rendering done for each camera.
Each camera incurs some overhead, whether it’s doing meaningful work or not.
Only use Camera components required for rendering. On mobile platforms,
each active camera can use up to 1 ms of CPU time, even when rendering nothing.
The Unity CPU Profiler shows the main thread in the timeline view and indicates that there are multiple Cameras.
Unity performs culling for each Camera.
and LayerMask
CustomPassVolumes in HDRP
Using Custom Passes in HDRP can help you avoid using extra Cameras
and the additional overhead associated with them. Custom passes have
extra flexibility in how they can interact with shaders. You can also extend
the Custom Pass class with C#.
Profile your post-processing effects to see their cost on the GPU. Some fullscreen
effects, like Bloom and depth of field, can be expensive, but experiment until
you find a happy balance between visual quality and performance.
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, which can potentially generate a CPU spike.
Ensure that all UI elements within each Canvas have the same Z value, materials,
and textures.
You might have UI elements that only appear sporadically in the game
(e.g., a health bar that appears 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 reenable 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.
Input events like onscreen touches or clicks require the GraphicRaycaster component.
This simply loops through each input point on screen and checks if it’s within a
UI’s RectTransform. You need a Graphic Raycaster on every Canvas that requires
input, including sub-canvases.
While this is not really a raycaster (despite the name), there is some expense
for each intersection check. Minimize the number of Graphic Raycasters by not
adding them to non-interactive UI Canvases.
In addition, disable Raycast Target on all UI text and images that don’t need it.
If the UI is complex with many elements, all of these small changes can reduce
unnecessary computation.
Layout Groups update inefficiently, so use them sparingly. Avoid them entirely
if your content isn’t dynamic, and use anchors for proportional layouts instead.
Otherwise, 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.
If your pause or start screen covers everything else in the scene, disable the
camera that is rendering the 3D scene. Likewise, disable any background
Canvas elements hidden behind the top Canvas.
UI Toolkit offers improved performance over Unity UI, is tailored for maximum
performance and reusability, and provides workflows and authoring tools
inspired by standard web technologies. One of its key benefits is that it uses a
highly optimized rendering pipeline that is specifically designed for UI elements.
Although audio is not normally a performance bottleneck, you can still optimize
to save memory, disk space, or CPU usage.
Start with your sound assets in a lossless file format like WAV or AIFF.
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.
— Preload Audio Data ensures that Unity will load any referenced AudioClips
before initializing the scene. However, this may increase scene loading
times.
— Set the Sample Rate Setting to Optimize Sample Rate or Override Sample Rate.
For mobile platforms, use Vorbis for most sounds (or MP3 for sounds
not intended to loop). Use ADPCM for short, frequently used sounds
(e.g., footsteps, gunshots).
For PC and Xbox®, use the Microsoft XMA codec instead of Vorbis or MP3.
Microsoft recommends a compression ratio between 8:1 and 15:1.
For Playstation, use the ATRAC9 format. This has less CPU overhead than
Vorbis or MP3.
OR
Set to Compressed
In Memory and set
Compression Format to
ADPCM. This offers a
fixed 3.5:1 compression
ratio and is inexpensive to
decompress in real-time.
In addition to your AudioClip settings, be aware of these issues with the AudioMixer.
— The SFX Reverb Effect is one of the most expensive audio effects in the
AudioMixer. Adding a mixer group with SFX Reverb (and a mixer group
sending to it) increases CPU cost.
— Avoid parents with a single child group. Whenever possible, combine the
two mixer groups into one.
Physics can create intricate gameplay, but this comes with a performance cost.
When you know these costs, you can tweak the simulation to manage them
appropriately. These tips can help you stay within your target frame rate and
create smooth playback with Unity’s built-in physics (NVIDIA PhysX).
Simplify colliders
Mesh colliders can be expensive. Substitute more complex mesh colliders with
primitive or simplified mesh colliders to approximate the original shape.
Make sure that you edit your Physics settings (Project Settings > Physics) as well.
Simplify your Layer Collision Matrix wherever possible.
Physics engines work by running on a fixed time step. To see the fixed rate that
your project is running at, go to Edit > Project Settings > Time.
The default Fixed Timestep in the Project Settings is 0.02 seconds (50 frames per second).
The Fixed Timestep field defines the time delta used by each physics step. For
example, the default value of 0.02 seconds (20 ms) is equivalent to 50 fps, or 50 Hz.
Because each frame in Unity takes a variable amount of time, it is not perfectly
synced with the physics simulation. The engine counts up to the next physics
time step. If a frame runs slightly slower or faster, Unity uses the elapsed time to
know when to run the physics simulation at the proper time step.
In the event that a frame takes a long time to prepare, this can lead to performance
issues. For example, if your game experiences a spike (e.g., instantiating many
GameObjects or loading a file from disk), the frame could take 40 ms or more to run.
With the default 20 ms Fixed Timestep, this would cause two physics simulations
to run on the following frame in order to “catch up” with the variable time step.
Extra physics simulations, in turn, add more time to process the frame.
On lower-end platforms, this potentially leads to a downward spiral of performance.
Eventually the time between physics updates could exceed the Maximum
Allowed Timestep. After this cutoff, Unity starts dropping physics updates,
and the game stutters.
— Decrease the Maximum Allowed Timestep. Using a smaller value (like 0.1 s)
sacrifices some physics simulation accuracy, but also limits how many
physics updates can happen in one frame. Experiment with values to find
something that works for your project’s requirements.
— Simulate the physics step manually if necessary. You can disable Auto Simulation
in the Physics Settings and instead directly invoking Physics.Simulate during
the Update phase of the frame. This allows you to take control when to
run the physics step. Pass Time.deltaTime to Physics.Simulate in order to
keep the physics in sync with the simulation time.
Meshes used in physics go through a process called cooking. This prepares the
mesh so that it can work with physics queries like raycasts, contacts, and so on.
A MeshCollider has several CookingOptions to help you validate the mesh for
physics. If you are certain that your mesh does not need these checks, you can
disable them to speed up your cook time.
Use Physics.BakeMesh
If you are generating meshes procedurally during gameplay, you can create
a Mesh Collider at runtime. Adding a MeshCollider component directly to the
mesh, however, cooks/bakes the physics on the main thread. This can consume
significant CPU time.
Use Physics.BakeMesh to prepare a mesh for use with a MeshCollider and save
the baked data with the mesh itself. A new MeshCollider referencing this mesh
will reuse this prebaked data (rather than baking the mesh again). This can help
reduce Scene load time or instantiation time later.
To optimize performance, you can offload mesh cooking to another thread with
the C# Job System. Refer to this example for details on how to bake meshes
across multiple threads.
— the narrow phase, where the engine actually computes the collisions
If your scene is large and mostly flat, avoid this issue and switch to
Automatic Box Pruning or Multibox Pruning Broadphase. These options divide
the world into a grid, where each grid cell performs sweep-and-prune.
Multibox Pruning Broadphase allows you to specify the world boundaries and the
number of grid cells manually, while Automatic Box Pruning calculates that for you.
If you want to simulate a specific physics body more accurately, increase its
Rigidbody.solverIterations.
To optimize your physics simulations, set a relatively low value in the project’s
defaultSolveIterations. Then apply higher custom Rigidbody.solverIterations
values to the individual instances that need more detail.
When you update a Transform, Unity does not automatically sync it to the
physics engine. Unity accumulates transformations and waits for either the
physics update to execute or for the user to call Physics.SyncTransforms.
If you want to sync physics with your Transforms more frequently, you can set
Physics.autoSyncTransform to true (also found in Project Settings > Physics >
Auto Sync Transforms). When this is enabled, any Rigidbody or Collider on that
Transform or its children automatically update with the Transform.
In the Unity Console, there is a single collision instance on Collision Entered and Collision Stay.
Static colliders are GameObjects with a Collider component but without a Rigidbody.
Note that you can move a static collider, contrary to the term “static.” To do
so, simply modify the position of the physics body. Accumulate the positional
changes and sync before the physics update. You don’t need to add a Rigidbody
component to the static collider just to move it.
However, if you want the static collider to interact with other physics bodies in
a more complex way, give it a kinematic Rigidbody. Use Rigidbody.position and
Rigidbody.rotation to move it instead of accessing the Transform component.
This guarantees more predictable behavior from the physics engine.
Note: In 2D physics, do not move static colliders because the tree rebuild
is time consuming.
To reduce this overhead, use the NonAlloc versions of those queries. For example,
if you are using OverlapSphere to collect all potential colliders around a point,
use OverlapSphereNonAlloc instead.
This allows you to pass in an array of colliders (the results parameter) to act as
a buffer. The NonAlloc method works without generating garbage. Otherwise,
it functions like the corresponding allocating method.
Note that you need to define a results buffer of sufficient size when using a
NonAlloc method. The buffer does not grow if it runs out of space.
You can run raycast queries with Physics.Raycast. However, if you have a large
number of raycast operations (e.g., calculating line of sight for 10,000 agents),
this may take a significant amount of CPU time.
Use RaycastCommand to batch the query using the C# Job System. This
offloads the work from the main thread so that the raycasts can happen
asynchronously and in parallel.
The Physics Debugger helps you visualize how your physics objects can interact with one another.
Use the Physics Debug window (Window > Analysis > Physics Debugger)
to help troubleshoot any problem colliders or discrepancies. This shows a
color-coded indicator of the GameObjects that can collide with one another.
— A humanoid rig gives you the ability to retarget bipedal animation from any
source (e.g., motion capture, the Asset Store, or some other third-party
animation library) to your own character model. Unity’s Avatar system maps
humanoid characters to a common internal format, making this possible.
Animator component
These guidelines will help you when working with animation in Unity.
— With generic rigs, using root motion is Generic rigs use less CPU time than humanoid rigs.
more expensive than not using it. If your
animations don’t use root motion, do not
specify a root bone.
The current animation system is optimized for animation blending and more
complex setups. It has temporary buffers used for blending, and there is
additional copying of the sampled curve and other data.
Also, if possible, consider not using the animation system at all. Create easing
functions or use a third-party tweening library where possible (e.g., DOTween).
These can achieve very natural-looking interpolation with mathematical expressions.
Note: This does not apply to constant curves (curves that have the same value
for the length of the animation clip). Constant curves are optimized, and these
are less expensive than normal curves.
Set the animators’s Culling Mode to Based on Renderers, and disable the
skinned mesh renderer’s Update When Offscreen property. This saves Unity
from updating animations when the character is not visible.
Optimize workflow
— Use State Tags to easily match your AI state machine to the Unity state
machine.
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.
To help with version control merges, make sure your Editor settings have Asset
Serialization Mode set to Force Text. This is less space efficient but makes
Unity store scene files in a text-based format.
Asset Serialization Mode
If you’re using an external version control system (such as Git) in the Version
Control settings, verify that the Mode is set to Visible Meta Files.
Version Control Mode
Most Unity projects include a sizable amount of art assets in addition to the
script code. If you want to manage these assets with version control, consider
switching to Unity Version Control (formerly known as Plastic SCM). Even with
Git LFS, Git does not perform as well as Plastic SCM with larger repositories
which offers superior speed when working with large binary files (>500 MB).
Additionally, Unity Version Control helps you centralize your development with
excellent visualization tools. Artists especially will appreciate the user-friendly
workflows that encourage tighter integration between development and art teams.
Note that at runtime, your project can load scenes additively using
SceneManager.LoadSceneAsync passing the LoadSceneMode.Additive
parameter mode.
The team’s expertise lies in helping you take your game to the next level at
any stage of game development. The optimizations mainly focus on identifying
general and specific performance issues such as frame rate, memory, and
binary size to improve player experiences and/or iteration times. Services
offered range from consulting to full game development.
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.
Integrated Success is our most complete Success Plan – ideal for your most
complex projects and helping your games reach their full potential. From strategic
planning to unforeseen circumstances, we’ve got you covered. Get insight, hands-
on guidance, and premium technical support to ensure your project’s success.
This plan provides access to advanced features including our fastest response
times, dedicated strategic support from a Partner Relations Manager, prioritized
bug handling and LTS backporting, and an annual deep-dive project review.
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.
The team familiarizes themselves with your projects and then uses various
profiling tools to detect performance bottlenecks, factoring in existing
requirements and design decisions. They also try to identify points where
performance could be optimized for greater speed, stability, and efficiency.
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.
Though deliverables can vary depending on your needs, findings are summarized
with recommendations provided in a written report. The team’s goal is to always
provide the greatest value to you by helping to identify potential blockers, assess
risk, validate solutions, and ensure that best practices are followed moving
forward.
To learn more about our Integrated Success package, Project Reviews, and PRMs,
please reach out.
Unity’s team is always here to help you find the right tools and services
for support throughout your game development journey, from concept
to commercialization. If you’re ready to get going, you can access Unity Pro today
or talk to one of our experts to learn about all the ways we’re ready
to help you realize your vision.
More resources
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.
unity.com