A simple approach to the problem of interobject communication is to use Unity's built-in Serialization System. Software design purists tend to get a little combative about this feature, since it breaks Encapsulation; it makes any field (the C# term for a member variable) markedprivateact in a way that treats them like apublicfield. However, it is a very effective tool for improving development workflow. This is particularly true when artists, designers, and programmers are all tinkering with the same product, where each has wildly varying levels of computer science and software programming knowledge, and with some of whom would prefer to stay away from modifying code files. Sometimes, it's worth bending a few rules in the name of productivity.
Whenever we create apublicfield in a MonoBehaviour, Unity automatically serializes and exposes the value in the Inspector window when the Component is selected. However, publicfields are always dangerous from a software design perspective. These variables can be changed through code at anytime from anywhere, making it hard to keep track of the variable and is liable to introduce a lot of unexpected bugs.
A better solution, is to take anyprivateorprotectedmember variable of a class and expose it to the Inspector window with the[SerializeField]attribute. The value will now behave like apublicfield with respect to the Inspector window, allowing us to change it through the Editor interface for convenience, but keeps the data safely encapsulated from other parts of our codebase.
For example, the following class exposes threeprivatefields to the Inspector window:
using UnityEngine;
public class EnemyCreatorComponent : MonoBehaviour { [SerializeField] private int _numEnemies; [SerializeField] private GameObject _enemyPrefab; [SerializeField] private EnemyManagerComponent _enemyManager;
void Start() { for (int i = 0; i < _numEnemies; ++i) { CreateEnemy(); } }
public void CreateEnemy() { _enemyManager.CreateEnemy(_enemyPrefab); } }
Note that theprivateaccess specifiers shown in the preceding code are redundant keywords in C#, since fields and methods default toprivateunless specified otherwise. However, it is often best practice to be explicit about the intended access level.
Looking at this Component in the Inspector window reveals three values, initially given default values of0, ornull, which can be modified through the Editor interface:
We can drag-and-drop a Prefab reference from the Project window into the Enemy Prefab field revealed in the Inspector window.
Note how Unity automatically takes a camel-cased field name and creates a convenient Inspector window name for it._numEnemiesbecomesNum Enemies,_enemyPrefabbecomesEnemyPrefab and so on.
Meanwhile, the_enemyManagerfield is interesting because it is a reference to a specific MonoBehaviour class type. If a GameObject is dragged-and-dropped into this field, then it will refer to theComponenton the given object as opposed to the GameObject itself. Note that if the GameObject does not contain the expected MonoBehaviour, then nothing will be assigned to the field.
A common usage of this Component reference technique is to obtain references to other Components attached to the very same GameObject a Component is attached to. This is an alternative means of caching Components with zero cost, as discussed in the section entitledCache Component references earlier in this chapter.
There is some danger to using this method. Much of our code would assume that a Prefab is assigned to a field that is used like a Prefab and a GameObject is assigned to a field that refers to an instance of a GameObject. However, since Prefabs are essentially GameObjects, any Prefab or GameObject can be assigned to a serialized GameObject reference field, which means we could assign the wrong type by accident.
If we do assign the wrong type then we could accidentally instantiate a new GameObject from an existing GameObject that was previously modified, or we could make changes to a Prefab, which would then change the state of all GameObjects instantiated from it. To make matters worse, any accidental changes to a Prefab become permanent since Prefabs occupy the same memory space whether Play Mode is active or not. This is the case even if the Prefab is only modified during Play Mode.
Therefore, this approach is a very team-friendly way of solving the problem of interobject communication, but it is not ideal due to all of the risks involved with team members accidentally leavingnullreferences in place, assigning Prefabs to references that expect an instance of a GameObject from the Scene, or vice versa.
It is also important to note that not all objects can be serialized and revealed in the Inspector window. Unity can serialize all primitive data types (int,float,string, andbool), various built-in types (Vector3,Quaternion, and so on);enum,class,struct, and various data structures containing other serializable types such as List. However, it is unable to serializestaticfields,readonlyfields, properties, and dictionaries.
Some Unity developers like to implement pseudo-serialization of dictionaries via two separate lists for keys and values, along with a Custom Editor script, or via a single list ofstruct objects, which contain both keys and values. Both of these solutions are a little clumsy, and rarely as performant as a proper dictionary, but they can still be useful.
Another solution to the problem of interobject communication is to try and make use of globally accessible objects in order to minimize the number of custom assignments we need to make.