Skip to content

Instantly share code, notes, and snippets.

@njelly
Last active April 8, 2025 03:31
Show Gist options
  • Save njelly/25c6d1bea095960d57815912bf5018f2 to your computer and use it in GitHub Desktop.
Save njelly/25c6d1bea095960d57815912bf5018f2 to your computer and use it in GitHub Desktop.
PrefabController: how to grok scenes and prefabs in Unity

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);
        }
    }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment