Setup Overview
Two key nodes exist in this scenario: a Unit Manager node (equipped with Transform and UnitFactory components) and a Unit prefab node (equipped with Tarnsform and Unit components). The objective involves retrieving the Unit component from the instantiated prefab.
Direct Component Reference Approach
A common novice implementation looks like this:
using UnityEngine;
public class UnitFactory : MonoBehaviour
{
[SerializeField] private GameObject _unitPrefab;
private GameObject _spawnController;
private Unit _spawnedUnit;
private void Start()
{
_spawnController = Instantiate(_unitPrefab);
_spawnedUnit = _spawnController.GetComponent<Unit>();
}
}
This approach carries performance overhead. A more efficient method references the component directly:
using UnityEngine;
public class UnitFactory : MonoBehaviour
{
[SerializeField] private Unit _unitPrefab;
private Unit _spawnedUnit;
private void Start()
{
_spawnedUnit = Instantiate(_unitPrefab);
}
}
Any script inheriting from MonoBehaviour can leverage this pattern.
Managing Multiple Units with a Collection
To manage multiple units, introduce a private list within the UnitFactory class:
private List<Unit> _activeUnits = new List<Unit>();
Ensure the proper namespace is included at the top of the script:
using System.Collections.Generic;
Populate the collection in the Start method:
for (int i = 0; i < 10; i++)
{
_activeUnits.Add(Instantiate(_unitPrefab));
}
To control unit positions dynamically, update the logic within the Update method:
foreach (var unit in _activeUnits)
{
unit.transform.position = Random.insideUnitSphere;
}
Exposing Data Through a Static Singleton
When multiple scripts need access to unit data, a static singleton pattern provides convenient access.
Add the following to UnitFactory:
public static UnitFactory Instance { get; private set; }
private void Awake() => Instance = this;
Modify the collection's visibility and expose it:
public List<Unit> Units { get; private set; } = new List<Unit>();
For preserving serialized data when renaming fields, use the Unity attribute:
[FormerlySerializedAs("_activeUnits")]
public List<Unit> Units { get; private set; } = new List<Unit>();
Remember to include the required namespace:
using UnityEngine.Serialization;
Create an empty GameObject in the Hierarchy and attach a MapManager script. Use this implementation:
using UnityEngine;
public class MapManager : MonoBehaviour
{
void Update()
{
foreach (var unit in UnitFactory.Instance.Units)
{
unit.transform.position = Random.insideUnitSphere;
}
}
}
Read-Only Property Access Pattern
To allow external scripts to read the collection without mdoification, expose the field as a property:
public List<Unit> Units { get; private set; } = new List<Unit>();
Collision Detection Best Practices
When handling collisions, check whether the colliding object contains a specific component:
A less efficient pattern:
using UnityEngine;
public class Unit : MonoBehaviour
{
private void OnCollisionEnter(Collision other)
{
var targetUnit = other.gameObject.GetComponent<Unit>();
if (targetUnit != null)
{
//Do something.
}
}
}
The TryGetComponent method provides cleaner syntax and better performance:
using UnityEngine;
public class Unit : MonoBehaviour
{
private void OnCollisionEnter(Collision other)
{
if (other.gameObject.TryGetComponent(out Unit targetUnit))
{
//Do something.
}
}
}
Avoiding Reflection-Based Method Invocation
The SendMessage approach invokes methods via reflection, which carries significant performance penalties:
using UnityEngine;
public class Unit : MonoBehaviour
{
public void HandleCollision(int damage){}
private void OnCollisionEnter(Collision other)
{
other.gameObject.SendMessage("HandleCollision", 10);
}
}
This technique should be avoided in production code.