Unity Architecture Pattern: Structured Prefabs

Definitely not a Goomba

This article shows a powerful Unity pattern that I’m calling Structured prefabs. Structured prefabs are a pattern I use to simplify prefab initialization and reduce bugs. They help to indicate the intention of the creator to other developers and lead to overall better maintainability.

What is a structured prefab?

Like I mentioned, a structured prefab is a pattern I use to reduce bugs and increase maintainability for all my prefabs. The idea is to add a component at the root of every prefab to identify it, and give it structure. This component often has the same name as the prefab itself, but not always. Sometimes this component won’t contain any code, sometimes it’ll only contain serialized fields, and sometimes it’ll be a full-blown MonoBehaviour with behaviours.

So let’s explore all the benefits of this pattern, starting with references.

Prefab References

More often than not, I see developers reference prefabs with generic GameObject fields like this:

public class Game : MonoBehaviour {
    public GameObject playerPrefab;
}

There are two problems with this approach. First of all, as a developer working on the project, you’ve given me no indication of what you expect the player prefab to be. Does it require specific components to work? Most likely, yes. What developers should do instead is reference prefabs by a component, like this:

public class Game : MonoBehaviour {
    public Player playerPrefab;
}

When you do it that way, you know that at the very least, the player prefab needs a MonoBehaviour of type Player at its root. What’s more, if you do this, the Inspector won’t even let you assign a prefab that doesn’t have a Player component on its root. You can’t accidentally assign the wrong prefab (well, you can, but the wrong prefab will have a Player component on it as well, at least). By the way, when I talk about the root, I mean the GameObject at the top of the prefab’s hierarchy, rather than buried inside the prefab.

Now I’ll show how this subtle change massively improves your code’s reliability.

Prefab Instantiation

This next bit is my favourite part. When you instantiate a Prefab using a generic GameObject type, the function returns a GameObject. Usually, the next step is to use GetComponent to retrieve the components you care about like this:

public class GameManager : MonoBehaviour
{
	public GameObject playerPrefab;

	void Start()
	{
		GameObject playerGo = Instantiate(playerPrefab);
		Player player = playerGo.GetComponent<Player>();
	}
}

Instantiating prefabs this way is pretty dangerous because there’s no guarantee the Player component will be there, and then your whole game breaks. But, did you know that if you call Instantiate using a reference to a Player component, it’ll return the Player component instead:

public class GameManager : MonoBehaviour
{
	public Player playerPrefab;

	void Start()
	{
		Player player = Instantiate(playerPrefab);
	}
}

When you do it this way, you know with absolute certainty that you have a Player component (unless playerPrefab is null). By the way, if you use the main loop pattern from my previous article, and you avoid unnecessary null checks, you’ll know for sure that a null reference on initialization is due to not assigning something in the Inspector. Think of all the hours of your life you wasted chasing null reference errors. Now you can take that time back for the rest of your life. Spend that newly found time with your family and loved ones.

Now that we’ve solved prefab references and instantiation let’s cover the last piece of the puzzle: prefab initialization.

Prefab Initialization

After instantiating a prefab, you often need to initialize it before using it. You could wait for the Awake and Start methods to be called, but then you have to rely on a collection of loose initialization functions to run in an undefined order before your prefab is fully initialized. In my experience, a much simpler approach is to give your root component an initialization function that you call explicitly. For example:

public class GameManager : MonoBehaviour
{
	public Player playerPrefab;

	void Start()
	{
		Player player = Instantiate(playerPrefab);
		player.OnCreated(this);
	}
}

By the way, you might notice I passed this into my OnCreated function. That’s because I give my objects a reference to their parent object to simplify lifetime management. It’s the owner’s job to manage any GameObjects that it creates, so having a reference to your parent makes it easier to ask to be cleaned up and destroyed. If you don’t know what I’m talking about, I recommend reading my previous article on the main loop pattern to understand how I structure my projects.

Anyway, let’s get back to talking about initialization. We’ve already seen how we can remove GetComponent, Start and Awake, and make our code less error-prone, right? Well, we can take those same approaches and use them on our root component as well. In other words, rather than use GetComponent inside our Player MonoBehaviour, we can reference everything we need directly.

public class Player : MonoBehaviour
{
	GameManager _gameManager;
	public CharacterController characterController;
	public Animator animator;
	public Health health;
	//etc...
	
	public void OnCreated(GameManager gameManager)
	{
		_gameManager = gameManager;
		//call OnCreated on all the custom components...
		health.OnCreated(this);
	}
}

With that:

  • We probably don’t have to use GetComponent ever again. 
  • Any null references on initialization are due to something not being assigned in the Inspector. By the way, this is a reasonable error because it shows us we’re not using the object as intended, so don’t suppress it.
  • We don’t have to deal with Awake and Start methods that create spaghetti code.

So now we’ve seen all the benefits that the Structured Prefab pattern has, from improved references, instantiation and initialization, to cleaner code and greater maintainability.

If you liked this article, join my mailing list to be notified whenever a new post comes out. As a Mailing list subscriber, you could ask me to cover specific topics. So if you’d like me to write about something else, join my mailing list anyway and ask me.

2 thoughts on “Unity Architecture Pattern: Structured Prefabs

  1. David "G3oX"

    Nice article! I will start to approach this kind of architecture. Thanks so much for sharing it! 🙂

    1. bronson

      You’re welcome!

Leave A Comment