To reference many instances, you must create a collection to hold them in. It's useful to understand:
You can only serialize Arrays and Lists. Dictionaries are not serializable without extra work.
The field must be marked with SerializeField:
[SerializeField] private Transform[] _targets;or can be public:
public Transform[] Targets;This example uses the Transform type, it will need to be replaced with the target type.
As serialized references are set in the inspector, it's a good to only set them there to avoid confusion. With this in mind, serializing Arrays instead of Lists reduces the ways code can modify the data.
Some people suggest only using Lists to avoid thinking about which is which.
The practical differences are few, and a good IDE will make it easy to work with both, so make a choice and set some guidelines with your team to be consistent.
Do not directly reference the script asset. The target component must be an instance added to an object in the scene.
UnassignedReferenceException.Dragging a GameObject from the Hierarchy into the field will reference the first matching Component found on the object.
You can drag multiple instances into the array at a time by either locking the inspector, or by opening a temporary inspector via the Properties... menu in the Component's context menu.
public members on the individual instances can be accessed via a loop. Or by directly indexing into the collection.
foreach (var target in _targets)
{
// Variables and properties
var variable = target.Variable;
target.Variable = variable;
// Methods
target.Method();
}If you don't have autocomplete, configure your IDE to easily find member names and get error highlighting.
The usage must be at a method or block level scope.
Use a list when you have a number of instances to spawn, and don't need to look up individual instances.
[SerializeField] private Transform _prefab;
// An initialised list to associate spawned players with indices.
private readonly List<Transform> _targets = new();
/// <summary>
/// Gets the spawned targets.
/// </summary>
public IReadOnlyList<Transform> Targets => _targets;
/// <summary>
/// Spawns a new target at <see cref="position"/>.
/// </summary>
public void SpawnNewTarget(Vector3 position)
{
Transform instance = Instantiate(_prefab);
instance.localPosition = position;
_targets.Add(instance);
}Use a dictionary when your instances must be associated with a key. Dictionaries provide a fast lookup, avoiding loops when trying to find data.
[SerializeField] private Player _playerPrefab;
// An initialised dictionary to associate spawned players with indices.
private readonly Dictionary<int, Player> _players = new();
/// <summary>
/// Spawns a player and associates it with an index.
/// </summary>
/// <param name="index">The index to associate with the spawned player.</param>
/// <returns>The newly spawned player.</returns>
/// <exception cref="ArgumentException">Exception thrown if there already a player associated with the index.<br/>Use <see cref="GetRequiredPlayer"/> if you are unsure whether it was spawned.</exception>
public Player SpawnPlayer(int index)
{
// Check if the player already exists
if (_players.ContainsKey(index))
throw new ArgumentException($"Player at index {index} has already been spawned.");
// Spawn our player.
Player playerInstance = Instantiate(_playerPrefab);
// Cache our new player instance so TryGetPlayer and GetRequiredPlayer will return it.
_players.Add(index, playerInstance);
// Initialise the player with the index, it's helpful to find that information on the player too.
playerInstance.Initialise(index);
// Return the newly spawned player.
return playerInstance;
}
/// <summary>
/// Gets a player if they were already spawned.
/// </summary>
/// <param name="index">The index associated with the player</param>
/// <param name="player">The returned player that was associated with the index.</param>
/// <returns>True if the player was found.</returns>
public bool TryGetPlayer(int index, out Player player)
=> _players.TryGetValue(index, out player);
/// <summary>
/// Gets a player, spawning it if required.
/// </summary>
/// <param name="index">The index associated with the player.</param>
/// <returns>The player associated with the index, found or spawned.</returns>
public Player GetRequiredPlayer(int index)
// If the player already exists, return it, otherwise spawn a new instance.
=> TryGetPlayer(index, out Player player) ? player : SpawnPlayer(index);If you have a fixed, small number of players you may consider using an array, just checking whether the index is occupied (not null).