This is Unity for Software Engineers, a series for folks familiar with software development best practices seeking an accelerated introduction to Unity as an engine and editor. More is coming over the next few weeks, so consider subscribing for updates.
The Unity Engine runtime is primarily written in C++, and much of the Engine’s primitives (such as the game objects and their components) live in C++ land. You’ll also know that the Unity Engine API is in C#. The API gives you access to all of Unity’s native objects in a way that—save for a few pitfalls we’ll discuss today—feels like intuitive, idiomatic C#.
At the top of the Unity Object hierarchy sits
UnityEngine.Object. For the
most part provides a
name string, an
int GetInstanceID() method, and a bunch
of equality comparers.
The class also provides a
static void Destroy(Object obj) method (and some
overloads) that destroys a
UnityEngine.Object and any of its subclasses. When
an Object is destroyed, the native part of the object is freed from memory,
and the smaller managed part will be garbage collected at some point after
there are no more references to it.
Because your valid reference to a
UnityEngine.Object can point to a destroyed
UnityEngine.Object overrides C#‘s
to make a destroyed Object appear null. Simply accessing methods on a
destroyed object will return
NullReferenceException, albeit with a friendlier
error message that tells you which object you were trying to access.
A GameObject derives from Object and represents anything in your scene.
Let’s start at a high-level: A GameObject inherits a name and instance ID from its parent. Otherwise, conceptually, a GameObject
A GameObject’s state
- is the product of all of its Components’ state, and
- whether an object is active or not.
Let’s dig a bit deeper. When starting, most of the interesting stuff in a
GameObject is in its Components. A GameObject has at least one Component:
Transform describes the position and rotation of the GameObject. A Transform
includes helper properties that show an object’s absolute world position and
rotation, as well as the position and rotation relative to its parent. In the
Editor, the Transform position and rotation are set from the parent relative
Since every GameObject has a Transform (and also, given that a Transform is
frequently needed/accessed), the GameObject directly exposes a
Transform transform public property.
You can access individual components from
T GetComponent<T>(), or lists of
T GetComponents<T>(), etc. These methods search through all
components on a GameObject and return ones with a compatible type (or null, if
none exist in the singular case). Since these methods search through components
and check type compatibility, it is often recommended to cache this lookup.
If you are building/extending a GameObject by hand, you can always use
T AddComponent<T>(). In most cases, however, you’re better off using the
Individual Objects (a Component or ScriptableObject) might refer to other
GameObjects in a few ways:
By reference. By exposing a
GameObjectserialized field that you then set from the inspector.
We have discussed serialization extensively throughout the series: as a fundamental concept and in our tour of the Editor, when describing the Inspector, and the practice of using the Inspector as an injection framework.
Using tags. Every Game Object can have a tag string. You can find objects in the scene using that tag through the static functions
GameObject.FindGameObjectWithTag. A GameObject also exposes a public
bool CompareTag(string tag)method.
This is a quick-and-dirty way to get the job done, but is still a popular way. A common use of this in the wild is to have a
"Player"tag to find the Player. Ideally, these methods should not be called every frame, so if you have to use them, consider caching the result.
Using layers. A layer is an
intbetween 0 and 31. Every Game Object is in exactly one layer.
While you can’t directly look up all objects in a layer, if you already have a reference to a GameObject (e.g., in a collision event), you can check a GameObject against a
LayerMaskis typically used in functions like
Physics.Raycast(). This allows you to find objects with colliders intersecting with a given ray. Passing a
Physics.Raycast()will only return objects within the specified set of layers.
Inside the Unity Engine, Cameras make heavy use of layers. E.g., you can have one camera that renders “everything but UI”, and overlay another camera for an in-game HUD, etc.
Using indirect references. There are many reasons why the methods above might be insufficient: you might not want to use tags to avoid depending on copy-pasted strings, and layers might not fit your use case. If referencing a fellow object in-scene is not an option (e.g., you’re dealing with a dynamic set of objects or don’t have access to the current scene objects in the context you need this reference, etc.), then you might want to look further.
For this, an increasingly popular concept is runtime sets Scriptable Objects. You can read more about this in Unity’s how-to article on architecting your game with ScriptableObjects, based on the talk by Ryan Hipple. If you have an hour to spare, you might want to watch the whole thing.
A GameObject also exposes a
SendMessage functions that
propagate messages (described in the Component section) to
all components in or under it.
Every behavior on a GameObject is driven through its Components.
User-implemented Components will usually extend the
(more on that later).
A Component inherits a name and instance ID from its parent. Otherwise, conceptually, a Component
- always belongs to a single GameObject, exposed as a public
GameObject gameObjectproperty, and
- can receive messages, driving much of its behavior.
The state of a component on an active GameObject lies entirely in its implementation.
In addition to its
GameObject, a component exposes shorthand properties and
methods such as
T GetComponent<T>(), etc. These are
simply convenience shorthands for accessing those same methods on the
The most important functionality of a Component is driven through Unity
Messages (also sometimes called Unity Event Functions when referring to
built-in messages). These are effectively callbacks functions triggered by the
Engine in certain situations. Every Component will receive a
Update() and other messages, for example. The Unity Docs on the
Order of Execution of
these messages is a convenient resource.
To have your component receive a particular message, simply add a
private void method with the appropriate message name. The runtime will use
reflection to call these messages, when applicable. This is why you don’t see an
override directive on these messages. Messages like
FixedUpdate are inspected once per type, so don’t worry about reflection
being used in every frame. See more details in the
”10000 Update() calls”
Unity blog post for more information.
Behaviour is a type of component that can be enabled or disabled. When
Behaviour is disabled,
OnDisable messages are not called.
MonoBehaviour is a
Behaviour that also enables using
A Note on Inactive Objects and Disabled Components
A GameObject in a loaded scene will exist in memory until the object is
Destroyed explicitly or the scene is unloaded. A GameObject can be set to
inactive, which will cause it to stop receiving
Update (and related) events.
When an object is created, the messages called on a component depend on if: (1) the GameObject is active, and (2) the component is enabled:
|GameObject is active||GameObject is inactive|
|Component is Enabled||Component implicitly disabled|
|Component is Disabled|
When an object is set to active or a
Behaviour is set to enabled:
OnEnablewill be called.
Starthas never been called on this
Behaviour, it will be called exactly once.
Some takeaways of all this:
- A Unity object might appear to become
== nullchecking does more than you think.
- As a result, null-coalescing operators (
??=) and null-conditional operators (
?) don’t work as expected.
- Yes, your Unity Messages can be private!
- Don’t create abstract classes that unnecessarily declare
Updateor other messages to make overriding easier; that’ll result in the engine always calling these events.
- Disabling an Object or Component is a great way to limit its game logic or save on CPU-bound effort, but these objects still have a memory overhead.