10 Common Unity Mistakes
Before you use Unity’s straightforward and intuitive tool for multi-platform development, become familiar with and avoid these common Unity mistakes.
Before you use Unity’s straightforward and intuitive tool for multi-platform development, become familiar with and avoid these common Unity mistakes.
Tomas has more than a decade of experience with C# and Unity3D. His specialty is AR and VR, as well as 2D production.
Editor’s note: This article was updated on 01/10/2023 by our editorial team. It has been modified to include recent sources and to align with our current editorial standards.
Unity is a straightforward and intuitive tool for multi-platform development. While its principles are simple to understand, it’s all too easy to make fundamental mistakes in Unity. Such errors can slow down development progress, especially the transitions from the initial prototype phase or a final release.
By learning about the common Unity issues that developers face, you will be better prepared to apply strategies to avoid these pitfalls. The considerations outlined here focus primarily on 3D application development, but are also valid for and equally applicable to 2D development.
Unity Mistake No. 1: Underestimating the Project Planning Phase
To build support for a project’s marketing efforts, it’s crucial to be clear about its business model. You need to know which platforms your application releases target, and the minimal supported device specifications: Will you support older, low-end devices, or just more recent models? These considerations will deeply impact the performance and visuals of the app. Development cost is key to each topic discussed below.
On the technical side, the project manager needs to document and share the project’s workflow details (from inception through delivery) with the team. This workflow should take into account the iterative development process (e.g., updating models, refining code). The desired frame rate and vertex budgets must also be established. An analysis of these figures will enable you to better assign maximal resolutions and level-of-detail (LOD) specs for the models. You’ll want to institute and consistently use a single measurement system, which may require adjusting models during their import phase. (More on this in Mistake No. 2.)
Level design and division influence performance and are therefore crucial for future work. Consider performance when designing new levels and ensure you stay within the project’s requirements and constraints. Ask yourself: “Can this level be reasonably achieved?” If it cannot, move on—unless achieving that level is a necessity for your business requirements.
Unity Mistake No. 2: Working With Unoptimized Models
By the time a model is used in a scene, it should be ready to use and require no further modification. Its scale must be consistent with your application. If your 3D modeling software does not readily allow for this, perhaps it uses different units. You should specifically set the scale factor in the app’s models import settings (leave 0.01 for 3dsMax and Modo, set 1.0 for Maya). You may need to re-import objects after you update the scale. These settings allow the team to use the basic scale 1,1,1 in scenes, and achieve consistent behavior with no physics problems. With these settings, it is also likely that dynamic batching will work correctly. Apply this rule to each subobject in the model—not just the main one.
When you need to tweak object dimensions, do it in relation to the objects in the 3D modeling application—not Unity. Experiment with scale in Unity to determine appropriate values. Then, in the final application and for consistent workflow, you will have everything well prepared before importing your models to Unity.
Ensure that your models are well divided and keep subobjects to a minimum in case you need to reuse them: the fewer subobjects, the better.
The pivot of each object and its subobjects should properly align and rotate with regard to the object’s main function. The main object’s Z axis should point forward and pivot at the bottom of the object, allowing for better placement in the scene. Use as few materials on objects as possible.
The name of each asset should clearly describe its type and functionality. Always keep these naming conventions consistent as you work.
Unity Mistake No. 3: Building Interdependent Code Architecture
You can easily prototype and implement functionality in Unity by dragging and dropping object references that simplify addressing objects within a scene and accessing their associated components.
As easy as it looks, this drag-and-drop implementation has the potential to be dangerous. You might, for instance, create an interdependency in which a unit of code is entirely dependent on another part of the code—or even on another system or script within the application—or even on the current scene or scenario. if you are not careful, you might also degrade application performance , because finding objects in a hierarchy and accessing components has overhead.
Take a modular approach and create parts that can be used in other portions of your application, or even shared across an entire application portfolio. Build your framework and libraries on top of the Unity API the same way you build your knowledge base. If you find that a particular component needs to communicate with another system within the application, use an interface to make the relevant parts of your system more abstract and thereby reusable.
Alternatively, you can use an event-driven approach to react to external events. You can either create a messaging system, or register as a listener directly to the other system.
It can be challenging to separate gameObject
properties from program logic (at least for something like the model-controller principle), as it’s tough to identify which objects are modifying gameObject
’s transform properties (e.g., position, rotation). Transformation should be the controller’s exclusive responsibility.
Approach documentation from the perspective that, should you need to revisit your code after an extended absence, it should be quick and easy to understand and identify the code’s functionality. But don’t go overboard when it comes to documenting your work—sometimes, an appropriate class, method, or property name is sufficient.
Unity Mistake No. 4: Squandering Performance
Focusing on performance is a requirement to be taken seriously. A heavy-handed app that runs too many calculations or requires a too-detailed resolution in its user interface can drain the battery of even a powerful new phone, console, or desktop computer. Therefore, you should invest in performance optimization, as this makes all the difference in how your game or application looks in comparison to the competition. When you make one portion of your app more performant, you can use those extra cycles to polish other parts of your application.
Optimization opportunities abound, and even if we devoted an entire article to the topic, we would only scratch the surface. For now, here are the core optimization targets:
Optimization | Description |
---|---|
Update Loops | Instead of using high-cost operations in update loops, use caching. An example would be when you are designing access to a component or another object in a scene, or writing expensive calculations in a script. If possible, cache everything in Awake methods, or design your architecture as event-driven to only invoke functionality when triggered. |
Instantiations | Pre-initialize a pool for high-instantiation cost objects (e.g., bullets in an FPS game). Then, when one is needed, activate the object. When the object is no longer needed, deactivate it and return it to the pool. |
Rendering | Use occlusion culling or LOD techniques to limit scene rendering. Using an optimized model, you’ll be able to keep the scene’s vertex count down. Note: Vertex count isn’t just the number of vertices in a model; a vertex count is influenced by things like normals (hard edges), UV coordinates (UV seams), and vertex colors. Also, a scene’s dynamic light count dramatically influences overall performance, so try to bake everything in advance, whenever possible. |
Draw Calls | Reduce the draw calls count. In Unity, you can use static batching for still objects and dynamic batching for the moving ones. You must first prepare your scenes and models (batched objects have to share the same materials). The batching of dynamic objects only works for low-resolution models. Alternatively, you can combine meshes by the script into one ( Mesh.CombineMeshes ). If you’re combining meshes instead of batching, be careful not to create objects too large to take advantage of view frustum culling on some platforms. Use as few materials as possible and share them across a scene. You may need to create an atlas from a texture to be able to share one material between distinct objects. A good tip is to use higher resolution scene lightmap textures (not generated resolution, but texture output resolution) to lower the texture total when you are baking light in a larger environment. |
Overdraw Problems | Use a transparent texture only when necessary, as it will cause fill rate problems. A transparent texture is appropriate for rendering complex and more distant geometric objects, particularly trees or bushes. To apply a transparent texture, favor alpha-blended shaders over those with alpha testing or cutout shaders for mobile platforms. For general identification of these problems, lower the resolution of your application as this should make issues with fill rate or unoptimized shaders more apparent. Otherwise, an overdraw problem can be caused by a memory problem. |
Shaders | Optimize your shaders for better performance by reducing the number of passes, using variables with lower precision, and replacing complicated math with pre-generated lookup textures. Always use a profiler to determine where bottlenecks may arise. For rendering, you can also use the awesome Frame Debugger. This tool will assist developers in understanding how things work while decomposing rendering processes with it. |
Unity Mistake No. 5: Ignoring Garbage Collection Problems
Despite the fact that Garbage Collector (GC) helps us to be more efficient and focused on programming matters, you should also be aware that GC usage isn’t free.
Generally, you should avoid unnecessary memory allocations to prevent GC from firing itself too often and thus spoiling performance with frame rate spikes. Ideally, new memory allocations should not happen regularly within each frame.
So, how can we achieve this goal? Ultimately, your application’s architecture has a lot to do with it, but here are some helpful rules to follow:
- Avoid unnecessary allocations in update loops.
- Use structs for simple property containers, as these are not allocated on the heap.
- Preallocate arrays, lists, or other collections of objects, instead of creating them inside update loops.
- Avoid using problematic Mono language constructs or libraries (e.g., LINQ expressions,
foreach
loops). Unity uses an older version of Mono. - Cache strings in
Awake
methods, or in events. - If the update of a string property in an update loop is necessary, use the
StringBuilder
object instead of a string. - Use a profiler to identify potential problems.
Unity Mistake No. 6: Optimizing Memory and Space Usage Last
It is necessary to stay on top of your application’s memory and space usage from the get-go. It is significantly more complicated to optimize just prior to release. This is even more important to do for mobile devices with their limited resources.
Also, many companies limit application downloads over their cellular networks. You can lose a significant number of customers if you exceed these size limits. A customer will better appreciate an app that does not waste their time or precious phone resources; they will be more likely to download or buy your app if it is smaller.
To locate a resource drainer, review the editor log. After each new build, you’ll see the sizes of resources divided into categories (e.g., audio, textures, DLLs). For better orientation, pick up some editor extensions at the Unity Asset Store. These will provide you a detailed summary with referenced resources and files in your file system.
Actual memory consumption can also be seen in the profiler, but it is recommended to test consumption when the app is connected to a build on your target platform because testing on anything other than your target platform will yield unexpected results.
Textures are often the biggest memory consumers. It’s preferable to employ compressed textures that use relatively less space and memory. Make all textures squared, ideally. Make the length of both sides a power of two (POT), but keep in mind that Unity can also scale non-power of (NPOT) textures to POT automatically. Also, textures can be compressed in the POT form.
Atlas textures together to fill the whole texture. Sometimes you can even use the texture alpha channel for some extra information for your shaders to save additional space and performance.
And, of course, try to reuse scene textures as much as possible. Use a repeating texture if it does not hamper visual appearance. For low-end devices, lower the resolution of textures in Unity’s Quality Settings. Use the compressed audio format for longer audio clips, (e.g., background music).
When dealing with multiple platforms, resolutions, or localizations, use asset bundles to package different texture sets for different devices or users. Asset bundles can be loaded dynamically from the internet after the application is installed to limit the initial application download size and foist that load onto background processes.
Unity Mistake No. 7: Making the Physics Awkward
When moving objects in the scene, we don’t necessarily realize that an object has a collider, and that changing its position will force Unity to recalculate the whole physical world all over again.
In such cases, add the Rigidbody
component to your app. Set Rigidbody
to non-kinematic if you don’t want to involve external forces.
To modify the position of an object associated with Rigidbody
, set Rigidbody.position
when a new position doesn’t follow the previous one, or set Rigidbody.MovePosition
when it is a continuous movement, which will take interpolation into account. When modifying position, apply operations in FixedUpdate
functions, rather than in Update
functions. This will assure consistent physics behaviors.
Physics can be an application performance bottleneck. CPU overhead and collisions between primitive colliders are much faster to calculate. If possible, use primitive colliders on gameObjects
like sphere, box, or cylinder, and not mesh colliders. Compose your final collider from more than one of these shapes. You can also adjust the Fixed Timestep setting in Time Manager to reduce the frequency of physics fixed updates when the accuracy of physics interaction isn’t so necessary.
Unity Mistake No. 8: Testing All Functionality Manually
We sometimes tend to test functionality manually by experimenting in play mode. It’s fun, plus everything we need is under our direct control.
But this fun factor can fade fast. The more complex an application becomes, the more the programmer has to tediously repeat and analyze the results of tasks to ensure that the application behaves per its original intention. These iterations are easily the least appealing part of the development process. When something is no longer fun, our engagement is diminished and we get sloppy, increasing the chances that bugs will make it through in spite of our testing.
You can automate this testing with Unity’s tools. Coupled with appropriate architectural and code design, unit tests will validate isolated functionality, and integration tests will confirm more complex scenarios. You can dramatically reduce the hunt-and-peck approach when you’re logging actual data and comparing it with its desired state.
Limited manual testing is, without a doubt, a critical part of development. When there’s no possible way to automate a test, prepare your test scenes such that you can quickly focus on the exact issue that needs solving. Ideally, this is a few frames after the player presses the Play button. Implement shortcuts or cheats to set the desired state for testing. Be sure to isolate the testing situation to better identify a problem’s root cause.
Time adds up. The aggregated amount of time spent in play mode counts. When testing, the bigger the initial bias of testing the problem, the more likely you won’t test the problem at all, in the hope that all will work just fine—but that is not very likely.
Unity Mistake No. 9: Expecting Unity Asset Store Plugins to Solve All Your Problems
I’ve worked with clients who—perhaps out of habit—relied heavily on asset store plugins. Not to imply that there are no useful Unity extensions at the Unity Asset Store; there are many. So many, in fact, that sometimes it’s hard to decide which one to choose. But if you introduce plugins that do not play well together, you risk destabilizing the app.
To introduce new functionality to your app, a well-tested product from the asset store can save you hours, if not days, of development. To ensure the safety of your project, look for top-rated reviews on any asset/product before you download it.
If the functionality you’re looking to add is quick and simple enough, consider writing it yourself, growing your personal (or the company’s) library for reuse in the future—plus expanding your knowledge and toolset at the same time.
Unity Mistake No. 10: Having No Need to Extend Unity Basic Functionality
The Unity Editor environment appears ideal for basic game testing and level design. Some developers think that extending it is a waste of time. But trust me, it is not.
Unity’s magic stems from its ability to adapt to specific problems. Use this strength to your advantage, either to improve the developer experience, or dramatically speed up the entire development and level design workflow.
You would be remiss not to use handy features, like built-in or custom Property Drawers, Decorator Drawers, custom component inspector settings, or even not to build whole plugins with Unity’s own Editor Windows.
Anticipating Mistakes
By familiarizing yourself with, recognizing, or anticipating these common Unity mistakes that developers have been known to make, you can be proactive in taking steps to avoid these pain points. You might have different opinions on or procedures for solving these problems in your projects. The most important thing is to be consistent during your project and to work transparently with your team and stakeholders.
Further Reading on the Toptal Blog:
Understanding the basics
What is Unity used for?
Unity produces a cross-platform engine for computer gaming and simulations whose output works on desktops, consoles, and mobile clients.
What are the disadvantages of Unity?
Unity faces forward compatibility issues with some of its asset library, making engine updates challenging.
Why is Unity so popular?
Unity allows developers to generate applications with high-quality visual effects. Unity’s customizable engine is straightforward to learn and its asset library provides a solid foundation of tools with which to create feature-rich solutions.
How do I become a better Unity developer?
Unity developers are able to learn and upgrade their skills through easily accessible online learning resources in both video and book formats.
Chur, Switzerland
Member since March 30, 2016
About the author
Tomas has more than a decade of experience with C# and Unity3D. His specialty is AR and VR, as well as 2D production.