Things I Learned in my First Year of Unity

After a year of using Unity at work, I’ve learned a lot of new techniques, and my coding habits have changed over time.

When I started with Unity I read a bunch of tutorials and recommended practices, but with practical use I learned other techniques.

Some of these techniques may be blindingly obvious to you, others might have slipped you by.

If you have any tips of your own please add them in the comments section!

Make Most of your MonoBehaviour Variables Public

Programming 101 always teaches you to only make member variables public if you really need them to be modified by other classes. Consequently it feels dirty and dangerous to make everything public.

However after a year I think the benefit of being able to view variable values in the inspector outweighs the danger of exposing private variables.

Personally I make the interface of the class with accessors, and make member variables public for the inspector. By prefixing member variables with m_, even if they’re public it’s clear that you’re accessing a member variable.

public float m_width;
public float Width { get { return m_width; } }

Thing.m_width.... // Bad!
Thing.Width  // Good!

Select GameObjects from Debug.Log and Use Color

I didn’t notice there was an optional second parameter for Debug.Log (and LogWarning, LogError). It lets you specify a gameObject that is automatically selected when you click on the log message. Also you can add colors to your log. If you have messages that are specifically for different parts of your team, like artists and designers, it can help to highlight them in specific colours.

Debug.Log("<color=#magenta>♡ ART ♡ - Failed to load model, must be named Character.fbx</color>", modelRoot.gameObject);

Make Everything a Subclass of MonoBehaviour for Visual Debugging

Everything might be too strong a word, but most things that can be made a subclass of MonoBehaviour, should be. Even for classes that you think will have no renderers, making them subclass MonoBehaviour and adding them to a GameObject somewhere means you can visually debug them from the inspector and scene views.

This applies for singletons too. Use a singleton instead of a static class, and make the singleton a subclass of MonoBehaviour means you can have the instance of your singleton be a component on a GameObject in your scene. Beyond the usual ability to use the inspector to view values, you can add OnDrawGizmos to it and visualise its current state better.

public class KittenManager : MonoBehaviour {
  static KittenManager m_instance;
  public static KittenManager Instance { get {
    if (m_instance == null)
      m_instance = Create();
    return m_instance;
  } }

  KittenManager Create() {
    GameObject go = new GameObject("KittenManager");
    return go.AddComponent<KittenManager>();
  }

  void Awake() {
    if (m_instance == null) {
      m_instance = this;
    } else {
      if (m_instance != this) {
        Debug.Log("Instance already exists, destroying this object");
        Destroy(gameObject);
      }
    }
  }
}

Deal with Unity’s 90-degree FBX Rotation

Unity imports most models with a rotation of -90 in X. Most modelling programs use a Z-up coordinate system, whereas Unity uses the Y-up convention. For whatever reason, their attempt to fix this is to give a -90 X rotation to any GameObjects created from model files. With this rotation the models look correct in Unity. However if you child any objects to them, or set rotations based on the assumption that 0,0,0 is “unrotated”, you can get unexpected rotations.

The only way to hack around this is to work with your modelling software and build your model with a baked-in rotation of +90 in X on the root node. That way the model is imported with a rotation of 0,0,0, and looks correct. There are tutorials online that go into more detail about this, but it’s something you need the artists to understand and work with from day 1.

Know About Unity’s Model Animation Quirks

This will only apply to you if you’re using imported models with animation. Unity’s treatment of model animations is most charitably described as “unique”. Animation curves are forced to be regular bezier curves. Meaning that 0-frame instant jump your animator put on the Camera node for animation, does not actually take zero frames In fact there will be at least one frame where the camera is partway between the start and end points of the curve. While Unity’s in-engine animation system supports a variety of animation curve types,

Resources.Load is Slower Than you Think, AssetBundle.AsyncLoad is the Solution

For loading a very small number of very simple objects, Resources.Load() is perfectly fine. However it can get out of hand surprisingly fast.

We had to load around sixteen 256px images and create some complex GameObjects, and the slowest parts of the whole process were but the 16 calls to Resources.Load(). Each one was synchronous and in total they caused the game to freeze for half a second. Changing the process to use a placeholder image, load each image with AsyncLoad(), and replace the image when AsyncLoad completed, stopped any freezing.

Instantiate is not Free, You Need to Work to make it Asnyc

GameObject.Instantiate() is another cuprit for freezing. There’s no async version of it, but the slow part in calling it is not usually the instantiation itself but the code that gets called on instantiation - Awake() and Start() If your Awake(), Start(), OnEnable() or any other functions called on startup are heavy and synchronous, every time you create an instance of that GameObject your game will freeze. Split up the work needed into smaller asynchronous chunks by using coroutines.

Don’t Put Everything in the Resources Directory

As a programmer it’s frustrating to have assets outside of the Resources directory as it means you can’t load them at runtime. You’re stuck with using the inspector to set up links to GameObjects in other parts of the file directory.

Our first reaction to this was to put nearly everything in the Resources directory. The problem with this is if you stop using assets, if they’re in the resources directory they’re still put into every build you make. If they’re in the regular Assets directory, they will only be added to a build if they’re used by something included in the build (another GameObject, Scene).

Pick the Right File Format for your Designers and Artists

This isn’t a Unity-specific tip but I think it’s important nonetheless. Our game required both artists and designers to input a lot of data for setting up the game’s visual content. In the early prototype stage we chose JSON as we had an existing importer and it was what we as programmers were used to. This was not a good idea. The artists and designers put up with it very well but a mixture of picky syntax and verbosity the files quickly became unmanageable. We should have built the tool on their terms. They were most comfortable with Excel, so we should have written an exporter and converter for Excel. It would have taken longer on our part at first, but we would have saved hundreds of hours of debugging in the long-run.

GameObject.Find and Transform.Find should be used Sparingly

When I first started using Unity, the lead programmer and I tried to do as much work as we could through code. We had been burned by SVN conflicts for prefabs and scenes where we had to choose whose work would be preserved, and whose work would be lost. The logic was that if we did everything in code, we could easily merge the results and not lose work. To set up objects, I used Transform.Find("ObjectName") in Awake and Start to find objects higher and lower in the object hierarchy. However some objects were not yet set up in Awake(), so I moved the Find() calls to Start(), but sometimes even then the target object hadn’t been created yet.

Even after untangling that spaghetti, I started noticing how much the calls to Find() were slowing down instantiation.

The solution to this was to get used to Prefabs and Scenes and set up the objects to avoid calls to Find(). There was no golden bullet with SVN conflicts but clearly separating responsibilities and confirming with other team members before making major changes to scenes seemed to work out.

Set up your Directory Structure by Use, Not Filetype

This might be more controversial, and is more of a personal preference than something I can empircally prove is better. One file structure I see recommended in Unity tutorials is based around file format. Separating materials, textures etc. into different subdirectories like below:

/Materials/
/Materials/PetShop/
/Materials/Menus/
/Textures/
/Models/
/Shaders/

The benefit of this is it’s immediately clear where a resource should go. The downside is you often have to jump around the directory tree to find related resources. You find Puppy.png and Kitty.png, but you have to jump to the Materials directory to find the material that might use them.

The alternative looks something like this:

/
/PetShop/
/WorldMap/
/Menus/
/Menus/Options/

Each directory contains a mix of filetypes; materials, textures, models.

We kept shaders and scripts outside of this structure in their own dedicated directories, but maybe mixing them with the assets they use might work.

Conclusion

There’s a lot to learn when starting Unity. Some of the lessons are just things you have to experience yourself, but I hope this post saves you a few hours of pain.