Some Best Practices for Unity

Now that I’ve been working with Unity for a few years as both a hobbyist and a professional, I’ve come up with a list of best practices that I find help with large-scale projects.  Please note that I am a programmer/game designer and not an artist, so this will be very code-centric.  I’m sure someone could come up with a list just as long for graphical work and asset file management, but that is not my area of expertise.

Code

  • Avoid using GameObject.Find(), it is slow and the object you are looking for would need to have a unique name.  Having to use GameObject.Find() is an indicator that you may not know how to find something and that your design may need some organization so that you know where all of your crucial components are.  Relying on the GameObject.name field or tag to find objects is generally a poor idea since those fields are public and can change very easily.  It creates functionality that is relying on data in order to function properly.
  • If you are targeting a mobile platform, avoid using GameObject.Instantiate(), pool your objects instead.  This is done by including many of a given object in the scene that are immediately deactivated at runtime and activating and deactivating them individually as needed.  Calling “new” every frame (or worse every OnGUI) can also be a resource strain when memory is allocated on the heap.
  • Rather than exposing a variable to the inspector by making it public, make it protected or private and use the [SerializeField] attribute instead to preserve encapsulation.
  • Using the [RequireComponent()] attribute can save a lot of time and headache.  When the Component is added to a GameObject, all required components will be automatically added.  If subobjects are required to be in a particular format, use the Reset() callback in Monobehaviour to automate the creation and setup of the required structure when the component is placed on a GameObject.  If the RequireCompnent attribute is used, you can usually forgo null checking results from GetComponent.
  • To avoid trying to find the appropriate components within children at runtime, make serialized fields and reference the children from the scene in the inspector.  This way you can see if you’re missing references before runtime.
  • Keep your code organized by controlling component initialization via explicit initialization rather than relying on Unity’s script execution callbacks to do the heavy lifting.  This way you have very precise control over what happens when.  Use inversion of control patterns to achieve this effect.  This will also allow you to maintain references to core components without the need for things like singletons or a lot of component finding.
  • Never use BroadcastMessage().  Just don’t do it.  I can guarantee that you don’t need every object to process a message (doubt you will want to write code for every object to process a single message).  Just like with using GameObject.Find(), using this is an indicator of extreme disorganization and major fixes are required.
  • Don’t use DontDestroyOnLoad() in code.  Make the function its own script in the Start() or Awake() call and attach this to objects you don’t want to destroy when the scene loads.  This way you know exactly what will persist between scenes without scrutinizing code.
  • When using Debug.Log/LogWarning/LogError/LogException() (which you should not be using excessively), always try to use the overload Debug.Log(string, GameObject).  Using this overload causes the referenced GameObject to be pinged in the hierarchy when the message is clicked in the console, so you know who generated the message.
  • Don’t use PlayerPrefs to store save data.  Use Application.PersistentDataPath and write your own file instead.
  • Errors seen in the Unity console that don’t come from Debug.LogError() calls mean that exceptions are being thrown, which means code is being skipped, which is incredibly bad.  Your game should run with zero errors or warnings, and little to no debug messages.  It’s best to operate under the rule that if the console has to warn you about something, it should be fixed immediately.
  • Create a toggleable console and metrics counter that will be rendered as text in game in builds.  Unity release builds have no console and if you are building for mobile devices, you can’t make a log file.  You can also use a plugin that I highly recommend called Touch Console Pro.
  • When working in Unity, learn to separate your logic from the Unity API.  This will allow unit testing to be a lot easier to implement in your project.  Unity Test Tools are available for free on the Asset Store.  If your project is very large and your team is fairly big, use them.
  • If the inspector for a given component has far too many lines/fields, consider a custom inspector, custom editor window, a refactor, or all of the above.  If certain serialized fields need constraints, use custom editor code to enforce them.
  • Avoid excessive use of preprocessor directives that offer alternate functionality in your code.  The potential for difficult to diagnose bugs becomes high.  Code that gets preprocessed out will not be picked up by compilers, will not show errors at certain times, and is left out of “Find all instance” searches.  If you need editor only functionality, make editor extensions that interface with the intended classes naturally.  This will allow the debug code to be automatically stripped when the game is built.

Scene

  • If you have an immediate need to make more than one of an object in a scene that need to be identical, make a prefab sooner rather than later.
  • Every scene in your project should be runnable from the editor without major errors or weird behavior.  Achieving this can be tricky depending on your project, but I will explain how I accomplished this.  I created a set of prefabs that are required for the game at all times and placed them all in a scene that I called ProductionScene.  All of these prefabs have DontDestroyOnLoad.  I then created a script called ProductionSceneLoader, attached it to an empty object, and made it into a prefab.  This script has a static bool that is checked on Awake() and if satisfied, loads ProductionScene using LoadScene additive and toggles the boolean.  The object then destroys itself.  This prefab is then dropped into every other scene in the project.  ProductionScene should also contain the only AudioListener and EventSystem in the project since Unity will complain if you have more than one of either.
  • Every scene you create should have at most 10(ish) unparented objects.  If you have a lot of objects in the scene, try grouping them under a common parent for the sake of organization.
  • Within an object hierarchy, logical components should generally be parents to presentational components.
  • If you are working on a team, only one person should be working on a given scene file at a time if you are using Unity 4.  Unfortunately concurrent scene development (scene file merging) is only available in Unity 5.  If you are on a team and are using Unity 5+, make sure your source control is absolutely using the YAML smart merge tool.
  • Never let a single scene grow to gargantuan proportions.  Learn to creatively use LoadScene additive, LoadSceneAsync additive, or object instantiation to bring things into the scene at runtime.
  • One of the best ways to control configuration variables is through the use of ScriptableObjects.  This can allow you to create a complex configuration and serialize it into a scene with a single object.  This also allows you to create multiple configurations and through some potential polymorphism and component pattern trickery, be able to achieve very distinct configuration sets that can be easily hot-swapped.

Project

  • Don’t share active code among parallel Unity projects.  Making sure it always plays nice between projects is extremely cumbersome and often not feasible.  Reusing code from one project to the next is, however, almost always a good idea.
  • If you are doing a project that will be under version control with more than one developer, make sure that the editor settings are set to Visible Meta Files version control mode and Force Text asset serialization.  .meta files should always be committed to source control with their corresponding asset.  Force Text allows you to merge changes to certain files if needed.  If you can swing it, invest in the Unity Asset Server and make use of it.
  • When moving or deleting existing files within the project, always do so within the editor to prevent meta file and scene reference confusion.
  • The scene files are your “data files” for the game.  Keep all other data files (txt, csv, etc) outside of the Assets folder and populate the scene from your data files using an editor script.
  • Never mess with the code project files generated by Unity.  They are automatically managed by the editor.  Don’t do anything in your code editor but debug and modify code text.
  • Deployment of a build should be a one button process, meaning you press one button and you get the exact package that will be pushed to a device for testing or delivered to distribute.  Custom build scripts will typically be necessary to facilitate this.
  • If you need to create temporary files to help the editor along, be sure they exist outside of the Assets folder so that Unity doesn’t spend any time serializing them.

Art

  • Do not invest heavily in art assets (sprites, textures, models) from the asset store for a serious commercial project that you plan to distribute via sales.  Things like shaders, editor plugins, and scripting helpers are ok though.  It’s better to have an artist maintain a cohesive art style through your project and avoid issues with copyrights.
  • When creating particle effects or purchasing them, keep in mind that there is no simple method for scaling particle effects to match your scene.
  • This is more of a general game dev thing, but it still rings true in Unity: never bake text into your art assets.  MAKE FONTS and do text programmatically.  You will regret not having done this when it comes time to do localization.  I highly recommend the Textmesh Pro plugin.

Tools

  • There is no excuse to use Monodevelop instead of Visual Studio Community Edition when developing on Windows or Visual Studio Code when developing on Mac.  They’re free and their feature set blows Monodevelop out of the water.
  • The default editor layout typically has the scene view covering the game view so you can only see one at a time.  This is not optimal.  Create a new layout so that you can always see both.
  • Complex data-defined tasks can and should be automated within the editor.
  • Things you should commit to version control: the Assets folder (including all .meta files), all of the ProjectSettings folder, any files you created personally from the project folder.
  • Things you should NOT commit to version control: the Library folder, the Temp folder, the obj folder (generated by Visual Studio), any code project or solution files in the project folder, game builds.
  • A few must-have Unity plugins: Textmesh Pro, Touch Console Pro, Final IK, DOTween.
  • When using a new plugin, WRAP IT.  You never know when you may have to swap out a 3rd party technology.  You will regret it if you let a plugin creep all over your code.

Hardware

  • Invest in a second or third monitor for your workstation.  Two monitors is sometimes not enough.  Having only one monitor for Unity development is downright terrible.  I use three.
  • If your project is extremely large, get a solid state drive, hook it up, and get your project on there.
  • Depending on how resource-intensive your game is, you will need to invest in a development machine that can run your game along with the overhead of the Unity editor.  The specs of a good, modern gaming PC are a good baseline.
  • Just because Unity can build to multiple platforms doesn’t mean you can build for those platforms on other platforms.  For example, if you are building for OSX or iOS, you must build from a Mac.
  • Just because Unity can build for multiple platforms doesn’t mean that a port is a completely effortless or quick process.  Plan on investing time and resources in porting a Unity game between platforms.