How can I detect prefabs edited in the prefab editor in OnValidate?


In short

I want two functions:

  • Is input GameObject in a prefab asset (in Project or prefab editor)?
  • Is input GameObject in a prefab instance (in scene but not prefab editor)?

Details

I like to do validation on my objects, but I don’t understand how to do it with the new prefab system. I need to be able to check whether an object is a prefab instance/asset from OnValidate (to ignore missing data that’d be filled on an instance or skip adding scene-specific data on assets), so I want two kinds of validation:

  • validate objects in the prefab editor (prefab assets)
  • validate objects in the scene (prefab instances)

Research

I wrote some code (below) to print the object, the scene, and what seemed like relevant PrefabUtility calls.

From prefab editor editing “Sphere”:

'Sphere'     Sphere AnyPrefab=False PrefabAsset=False PrefabInstance=False NonAssetPrefabInstance=False is_stage_scene= True NotAPrefab None
'Sphere'            AnyPrefab=False PrefabAsset=False PrefabInstance=False NonAssetPrefabInstance=False is_stage_scene=False NotAPrefab None
'Sphere'            AnyPrefab=False PrefabAsset=False PrefabInstance=False NonAssetPrefabInstance=False is_stage_scene=False NotAPrefab None
'Sphere'            AnyPrefab= True PrefabAsset= True PrefabInstance=False NonAssetPrefabInstance=False is_stage_scene=False Regular Prefab
'Sphere'   Gameplay AnyPrefab= True PrefabAsset=False PrefabInstance= True NonAssetPrefabInstance= True is_stage_scene=False Regular PrefabInstance

From my “Gameplay” scene:

'Sphere'   Gameplay AnyPrefab= True PrefabAsset=False PrefabInstance= True NonAssetPrefabInstance= True is_stage_scene=False Regular PrefabInstance

Presumably touching a prefab in the prefab editor will trigger validation in
the scene, so that explains the “Gameplay” result from prefab editor. Clicking
the IsPartOfAnyPrefab=True result without a scene reveals the asset in my
Project tab.

I don’t understand the other things with no scene.

We can distinguish three cases:

scene:  AnyPrefab= True PrefabAsset=False PrefabInstance= True NonAssetPrefabInstance= True is_stage_scene=False Regular    PrefabInstance
asset:  AnyPrefab= True PrefabAsset= True PrefabInstance=False NonAssetPrefabInstance=False is_stage_scene=False Regular    Prefab
editor: AnyPrefab=False PrefabAsset=False PrefabInstance=False NonAssetPrefabInstance=False is_stage_scene= True NotAPrefab None

It seems that GetPrefabAssetType is an unreliable way to tell prefab assets
from instances and not as useful as the deprecated GetPrefabType. Instead, we
must check multiple things:

bool is_prefab_instance = IsPartOfAnyPrefab(obj) && !IsPartOfPrefabAsset(obj) &&  IsPartOfNonAssetPrefabInstance(obj)
bool is_prefab_asset    = IsPartOfAnyPrefab(obj) &&  IsPartOfPrefabAsset(obj) && !IsPartOfNonAssetPrefabInstance(obj)
var stage = UnityEditor.Experimental.SceneManagement.PrefabStageUtility.GetCurrentPrefabStage();
bool is_prefab_editor   = stage != null && stage.scene == obj.scene;

Is there a simpler way to detect prefabs in the prefab editor? Are there flaws with my approach?

Code I’m using:

using UnityEngine;
using UnityEditor;
public class WhatIsPrefab : MonoBehaviour
{
    public bool ToggleToValidate;
    void OnValidate()
    {

        var stage = UnityEditor.Experimental.SceneManagement.PrefabStageUtility.GetCurrentPrefabStage();
        bool is_stage_scene = stage != null && stage.scene == gameObject.scene;
        Debug.Log(string.Format("'{0}' {1,10} AnyPrefab={2,5} PrefabAsset={3,5} PrefabInstance={4,5} NonAssetPrefabInstance={5,5} is_stage_scene={6,5} {7} {8}",
                    name,
                    gameObject.scene.name,
                    PrefabUtility.IsPartOfAnyPrefab(gameObject),
                    PrefabUtility.IsPartOfPrefabAsset(gameObject),
                    PrefabUtility.IsPartOfPrefabInstance(gameObject),
                    PrefabUtility.IsPartOfNonAssetPrefabInstance(gameObject),
                    is_stage_scene,
                    PrefabUtility.GetPrefabAssetType(gameObject),
                    PrefabUtility.GetPrefabType(gameObject)), // deprecated, but just in case
                this);
    }
}

Here’s the complete code using my above method. I suspect I’ll have problems with nested prefabs (are they assets or instances?).

    // In scene.
    public static bool IsPrefabInstance(GameObject obj)
    {
        bool is_prefab_instance = false;
#if UNITY_EDITOR
        is_prefab_instance = UnityEditor.PrefabUtility.IsPartOfAnyPrefab(obj)
            && !UnityEditor.PrefabUtility.IsPartOfPrefabAsset(obj)
            && UnityEditor.PrefabUtility.IsPartOfNonAssetPrefabInstance(obj);
#endif
        return is_prefab_instance;
    }

    // In project or prefab editor.
    public static bool IsPrefabAsset(GameObject obj)
    {
        bool is_prefab_asset = false;
#if UNITY_EDITOR
        var stage = UnityEditor.Experimental.SceneManagement.PrefabStageUtility.GetCurrentPrefabStage();
        is_prefab_asset = (stage != null
                && stage.scene == obj.scene)
            || (UnityEditor.PrefabUtility.IsPartOfAnyPrefab(obj)
                    && UnityEditor.PrefabUtility.IsPartOfPrefabAsset(obj)
                    && !UnityEditor.PrefabUtility.IsPartOfNonAssetPrefabInstance(obj));
#endif
        return is_prefab_asset;
    }

This is my take on identifying prefab properties using new API.
There is example project to download. But here is core logic that check everything. (Kinda long)