There are some unique features of Unity’s programming environment that require extra consideration when writing code compared to standard C#/.NET projects. The following is a summary of key issues to be aware of when writing code for Unity applications, along with best practices to help you avoid common pitfalls.
When writing C# in Unity, be careful when comparing objects for equality with other objects or with null. For types that inherit from UnityEngine.Object, Unity uses a custom version of the C# equality and inequality operators. This means the null check myGameObject == null can evaluate true (and conversely myGameObject != null can evaluate false) even if myGameObject technically holds a valid C# object reference. For more information on the specifics of this behavior, refer to Custom equality operators.
Unity’s custom equality behavior and object lifecycle have a few implications for your code:
if (obj == null) and not ReferenceEquals for Unity objects.ReferenceEquals or cast to System.Object first.obj1 == obj2 may return true even if both references are different C# objects, if one or both have been destroyed and recreated, for example through Undo.Object.DestroyImmediate is Editor-only so use Object.Destroy at runtime and let Unity schedule destruction.Don’t use C# finalizers in runtime code, for the following reasons:
One of the most important performance risks to be mindful of in Unity applications is runtime code that allocates memory and increases garbage collector overhead, especially in hot paths.
To avoid this, apply the following coding practices:
WaitForSeconds and other yield instructions when using coroutines.GameObject.GetComponent in Awake and cache references to returned objects, rather than calling repeatedly from Update.Update or FixedUpdate and other hot paths. Methods from the System.Linq namespace can create unnecessary allocations and involve boxing and closures.For more detailed guidance and examples of these issues, refer to Optimizing your code for managed memory.
For information on tracking and reducing garbage collector overhead, refer to Managed memory.
The traditional pattern for many Unity projects involves using MonoBehaviour script components to regularly update the game state through built-in callbacks such as MonoBehaviour.Update, MonoBehaviour.FixedUpdate, and MonoBehaviour.LateUpdate that typically run many times per second.
This is a simple model that can still work well when used appropriately, but it has some key performance risks that commonly catch inexperienced developers out:
Update functions incurs a small overhead from Unity’s internal management and interaction with the native layer. When you have many such MonoBehaviour scriptsA piece of code that allows you to create your own Components, trigger game events, modify Component properties over time and respond to user input in any way you like. More infoUpdate functions that run unnecessarily most of the time, or that are unnecessarily memory-intensive when they do run.To mitigate these risks, consider the following options:
Update functions by using a centralized update manager or customizing the Player loop. For more information, refer to Using a custom update manager and Customizing the Player loop.NativeArray in performance-critical sections of your code, and choose unmanaged alternatives to managed APIs such as those for transform operations.While Unity has multithreaded capabilities, the core runtime is single-threaded and most APIs in the UnityEngine and UnityEditor namespaces can only be called from the main thread. Don’t reference GameObjectsThe fundamental object in Unity scenes, which can represent characters, props, scenery, cameras, waypoints, and more. A GameObject’s functionality is defined by the Components attached to it. More info
See in Glossary, Transforms, Components, or asset APIs from background threads. Never await with a Task.Result or Task.Wait on the main thread as this leads to deadlocks.
When dealing with inherently asynchronous and long-running operations, Unity provides the Awaitable class as a Unity-specific alternative to .NET Task. Awaitable uses object pooling to reduce allocations and is aware of Unity-specific concepts like Update and FixedUpdate, which allows you to await tasks and schedule them to resume at specific points in the Player loop. For more information refer to Asynchronous programming with the Awaitable class.
For shorter-lived but more compuationally-intensive parallelized work, Unity provides the job system, which can be Burst compiled. For more information, refer to Write multithreaded code with the job system.
For optimal performance it’s important to think not just about how you write code but how it’s compiled. Compiling code naively rather than actively defining which contexts certain source files or regions of your code are relevant for imposes the following costs:
Unity provides several mechanisms to help you control which parts of your code are compiled for different platforms and contexts:
#if directives with scripting symbols to exclude specific regions of code from compilation based on the target platform or context. For example, guard Editor-only code behind #if UNITY_EDITOR directives to exclude it from runtime builds. For more information on the various methods Unity offers for conditionally including or excluding code, refer to Conditional compilation.Unity provides a variety of tools to help you identify bottlenecks and write more performant code. The Project Auditor tool can analyze your project code to identify common performance issues and suggest fixes. The ProfilerA window that helps you to optimize your game. It shows how much time is spent in the various areas of your game. For example, it can report the percentage of time spent rendering, animating, or in your game logic. More info
See in Glossary can help you identify runtime performance bottlenecks in your code by providing detailed information about CPU and GPU usage, memory allocation, and more. You can also create Roslyn analyzers to enforce coding standards and identify performance issues specific to your project.
For more information on creating custom Roslyn analyzers and source generators, refer to [Roslyn analyzers and source generators](roslyn-analyzers.
For more information on Unity’s suite of analysis tools, refer to Optimization.