All Unity projects must solve the problem of initializing instantiated prefabs.
PrefabController
is my attempt to demonstrate a standard solution to this problem in Unity.
When a prefab is instantiated, OnEnterScene
should be called immediately to inject necessary scene data, such as cameras, lighting info, and other dependencies.
All prefab controllers ought to be unit tested, and OnEnterScene
provides a clear interface to pass initialization data within a test scenario.
Scenes and prefabs are similar concepts in Unity.
Both are modular assets intended to be reused.
Both contain a tree structure of GameObjects.
But despite having completely separate creation workflows in the Unity editor, the key difference between the two is subtle and rarely alluded to: scenes are self contained singletons, while prefabs are not.
Prefabs cannot exist in their own, they are always instantiated into a scene, and PrefabController
only attempts to formalize this relationship.
Although many engines define their own concept of a “scene”, these paradigms rarely transfer from engine to engine. For example, Godot’s mental model of a fractal tree of nodes, where each node could be treated as its own scene, is extremely satisfying conceptually, but here a “scene” is more like Unity’s “prefab.” In Bevy, scenes are more concerned with serializing and deserializing configurations of entities for composition, which also sounds a lot like prefabs. Experienced developers should never grow too attached to any single way of doing things, and the best solution is usually the one that works with the engine, not against it.
PrefabController
is implemented as a generic, abstract class that contains a scene context object defined by the developer.
using UnityEngine;
public abstract class PrefabController<TSceneContext> : MonoBehaviour {
public TSceneContext CurrentContext => _sceneContext;
protected TSceneContext _sceneContext;
/// <summary>
/// Initializes the prefab controller by setting the scene context object and calling PostEnterScene().
/// Can be called multiple times to re-initialize the prefab controller with new or modified scene context.
/// </summary>
/// <param name="sceneContext"></param>
public void OnEnterScene(TSceneContext sceneContext) {
_sceneContext = sceneContext;
PostEnterScene();
}
protected virtual void PostEnterScene() { }
}
ExampleCardSlotController is an abbreviated example from a personal project of mine that shows how PrefabController is used to inject scene data into a prefab, like the camera to use for raycasts.
using UnityEngine;
public class ExampleCardSlotController : PrefabController<UICardSlotController.SceneContext>, IDragHandler,
{
public class SceneContext {
public Camera camera;
}
public void OnDrag(PointerEventData eventData)
{
var screenPos = eventData.position;
var ray = _sceneContext.camera.ScreenPointToRay(screenPos);
var numHits = Physics.RaycastNonAlloc(ray, _hitCache);
if (numHits == 0) {
return;
}
if(_hitCache[0].collider.TryGetComponent<PlayerAreaController>(out var playerAreaController)) {
playerAreaController.ShowPlacementPreview(_hitCache[0].point);
}
}
}