Search Unity

I found the solution for checking if a gameobject is "prefab ghost" or not!!!

Discussion in 'Scripting' started by Xtro, Oct 9, 2014.

  1. Xtro

    Xtro

    Joined:
    Apr 17, 2013
    Posts:
    608
    * You can go to Edit 3 directly for solution.
    * Read Edit 2 for explanation.

    Edit:

    Today I found out about GameObject.hideFlags so it can be used for finding if the gameobject is prefab or not. if hideFlags is HideInHierarchy then it's a prefab.
    Please ignore the rest of this post...


    Edit 2:
    1) I take back my first edit because hideFlags can't be used for this purpose. The real gameobjects can also be hidden in hierarchy and that doesn't mean they are prefabs.

    2) I have to make some clarifications. The code hereby has nothing to do with the prefab files in your project or prefab instances in your scene. It solves another problem about the ghost prefabs which lives only in runtime. As we all know, there is no prefab concept in runtime. Evertything is regular gameobject in runtime including the prefabs in your project folder. Well, they are not so regular because they don't live inside the scene, they are not visible, they don't receive the events like start, update, etc.. but still, they are just hidden gameobjects which are waiting to be instantiated by your command. So they are like ghost objects and my code below tries to distinguish those ghost objects from the real scene objects. From now on, I'll call them as "prefab ghosts" and I'll update the title of this post.

    3) Unity has a bug about GetSiblingIndex method. It works fine in editor (both edit and play modes) but it always returns zero in build runtime only for scene root objects. It works fine for non-root game objects in runtime too. So the code below doesn't work for root objects in build runtime until Unity team fixes this bug.

    Edit 3:

    Code (CSharp):
    1.  
    2.         /// <summary>
    3.         /// Play mode only!
    4.         /// </summary>
    5.         public static bool IsPrefab(this Transform This)
    6.         {
    7.             if (Application.isEditor && !Application.isPlaying) throw new InvalidOperationException(Strings.MethodIsOnlyAllowedInPlayMode("IsPrefab"));
    8.  
    9.             return This.gameObject.scene.buildIndex < 0;
    10.         }
    11.  
    How to use...

    Code (CSharp):
    1.  
    2.        if (transform.IsPrefab()) print("this is a prefab ghost which is invisible and lives in runtime silently");
    3.  
    ---------------------
    Original post(Obsolete):

    I finally found a better way than searching through all scene objects... (Play mode only)

    Code (CSharp):
    1.  
    2.     static class TransformExtensions
    3.     {
    4.         internal static bool IsPrefabGhost(this Transform This)
    5.         {
    6.             var TempObject = new GameObject();
    7.             try
    8.             {
    9.                 TempObject.transform.parent = This.parent;
    10.  
    11.                 var OriginalIndex = This.GetSiblingIndex();
    12.  
    13.                 This.SetSiblingIndex(int.MaxValue);
    14.                 if (This.GetSiblingIndex() == 0) return true;
    15.  
    16.                 This.SetSiblingIndex(OriginalIndex);
    17.                 return false;
    18.             }
    19.             finally
    20.             {
    21.                 Object.DestroyImmediate(TempObject);
    22.             }
    23.         }
    24.     }
    25.  
    How to use...

    Code (CSharp):
    1.  
    2.        if (transform.IsPrefabGhost()) print("this is a prefab ghost which is invisible and lives in runtime silently");
    3.  
     
    Last edited: Apr 6, 2016
  2. ThermalFusion

    ThermalFusion

    Joined:
    May 1, 2011
    Posts:
    906
    So I gave this a try using:
    Code (csharp):
    1.  
    2.     [MenuItem("Test/Selection Is Prefab")]
    3.     public static void CheckPrefab() {
    4.         Debug.Log(Selection.activeTransform.IsPrefab());
    5.     }
    Selecting a non prefab in the scene and running this logged False.
    Selecting a prefab in the scene logged False.
    Selecting a prefab in the project view caused a null reference exception.

    How is this supposed to work?
     
  3. hpjohn

    hpjohn

    Joined:
    Aug 14, 2012
    Posts:
    2,190
  4. Xtro

    Xtro

    Joined:
    Apr 17, 2013
    Posts:
    608
    hpjohn, PrefabUtility is editor only. My version is for runtime.
     
    Malbers likes this.
  5. Xtro

    Xtro

    Joined:
    Apr 17, 2013
    Posts:
    608
    ThermalFusion, I'm happy you tested it. I'll check what's wrong...
     
  6. Xtro

    Xtro

    Joined:
    Apr 17, 2013
    Posts:
    608
    ThermalFusion, I see the problem with your test. My code is for play mode only because in editor mode we have other options for a long time. But I didn't see any solution for play mode until now.

    In play mode, there is no "REAL" prefabs. Everything is gameobject. Even the prefab instances in your scene becomes regular gameobjects when you start play mode. And also, your prefabs in the project tree are created as regular gameobjects in play mode. But they don't appear in the scene. So my function can distinguish the difference between the gameobjects in the scene and gameobject off the screen (formerly prefabs in edit mode).

    I checked your tested myself...

    1) Non-prefab in scene logs as false and this is correct.
    2) Your second test returns false because any object in the scene is not a prefab even if they are in blue color. They are instances of prefabs and instances are gameobject actually. This is also correct.
    3) When you select a prefab in the project window, Selection.activeTransform is null. that's why you got Null exception. Not because of my code.
     
  7. KelsoMRK

    KelsoMRK

    Joined:
    Jul 18, 2010
    Posts:
    5,539
    I'm curious what problem you're solving by determining if something is a prefab or not while the game is playing.
     
  8. Xtro

    Xtro

    Joined:
    Apr 17, 2013
    Posts:
    608
    I'm developing a small plugin which sends message to all objects even if they are inactive in the scene. Because default SendMessage doesn't work on inactive objects. For the purpose of my plugin, In play mode, I should know the difference between real scene objects and "off scene" objects (which were prefabs in project folder in edit mode). So I don't send the message to prefabs because they should be notified in any way. They are just off scene objects.

    It's actually more complicated but I don't list all the details here.

    When I finish this, It will be free in asset store and I think many people will be happy to be able to send messages to inactive scene objects :) :)
     
  9. ThermalFusion

    ThermalFusion

    Joined:
    May 1, 2011
    Posts:
    906
    There was no mention in the original post that it was during runtime only. I'll call these runtime instances of prefabs "prefab" from now on.
    So I see what you're doing now, you're forcibly changing the hierarchy order and seeing if it changes, which it can't if it's not an object in the scene.
    Here's a variation I tried that uses the root transform of an object instead and doesn't touch the "prefab":
    Code (csharp):
    1.     internal static bool IsPrefab(this Transform This)
    2.     {
    3.         bool result = false;
    4.         GameObject tempObject = new GameObject();
    5.         Transform rootObject = This.root;
    6.         tempObject.transform.SetAsFirstSibling();
    7.         if (rootObject.GetSiblingIndex() == 0) result = true;
    8.         Object.DestroyImmediate(tempObject);
    9.         return result;
    10.     }
    This should not throw an exception on trying to modify a "prefab".

    A couple of notes:
    As per the documentation, one should not call DestroyImmediate in runtime, only in editor.
    Modifying the hierarchy order of the scene could possible have overhead, one would have to profile this.
     
  10. Xtro

    Xtro

    Joined:
    Apr 17, 2013
    Posts:
    608
    If the scene object is the only child of a parent (or in a scene root), SetSiblingIndex doesn't work. This is why I am creating the temp object as a potential sibling to the actual object (which is "This").

    I don't see any link between the temp object and the actual object in your code because you didn't set its parent to actual object's parent. They are not siblings and I don't understand why are you creating it.

    I was hesitant to user DestroyImmediate but since I created the temp object in the same method, it should give any trouble I suppose.

    Yes I'm very well aware of SetSiblingIndex may have an overhead in runtime but it is much much better than searching through all the objects in a big fat scene.

    Thank you for your comments and suggestions. That's the only way I can improve my code and my self.
     
  11. ThermalFusion

    ThermalFusion

    Joined:
    May 1, 2011
    Posts:
    906
    You see I always compare the root transform of This. A root in a "prefab" will never have any siblings where a root in the scene can. So I create a new object in the scene, make it the first sibling, then see if this.root still is alone, then we can guess its a prefab.
     
  12. Xtro

    Xtro

    Joined:
    Apr 17, 2013
    Posts:
    608
    What if the scene object is child of another object and still have no siblings? Root can help here but it's overhead is bigger I guess. Both methods should be tested.
     
  13. Kushulain

    Kushulain

    Joined:
    Sep 20, 2012
    Posts:
    19
    FindObjectsOfType let you collect all scene objects doesn't it ? If you afraid of performance cost, you could convert it to a HashSet to get quick check.

    Code (CSharp):
    1. HashSet<UnityEngine.Object> sceneObjects = new HashSet<UnityEngine.Object>();
    2.  
    3. void Start()
    4. {
    5.     foreach (UnityEngine.Object obj in FindObjectsOfType<UnityEngine.Object>())
    6.         sceneObjects.Add(obj);
    7. }
    8.  
    9. public bool IsPrefab(UnityEngine.Object obj)
    10. {
    11.     return !sceneObjects.Contains(obj);
    12. }
    What do you think ?
     
  14. Xtro

    Xtro

    Joined:
    Apr 17, 2013
    Posts:
    608
    I wouldn't even think about storing all the scene objects in a data structure for a big game. For small games, your approach is doable. But I wanted to support very big scenes.
     
  15. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,531
    This looks to be a bit inneficient if you're going to create a new GameObject and destroy it every time you test if its a prefab or not...
     
    ThermalFusion likes this.
  16. Xtro

    Xtro

    Joined:
    Apr 17, 2013
    Posts:
    608
    Yes but it's still better than using FindObjectsOfType. Right ?
     
  17. vexe

    vexe

    Joined:
    May 18, 2013
    Posts:
    644
    I could be missing something, but this small script worked for me so far very well
    Code (csharp):
    1.  
    2. [DisallowMultipleComponent, ExecuteInEditMode]
    3. public class PrefabMarker : MonoBehaviour
    4. {
    5.    private bool _isInScene;
    6.  
    7.    public bool IsInScene
    8.    {
    9.      get { return _isInScene; }
    10.    }
    11.  
    12.    void Awake()
    13.    {
    14.      _isInScene = true;
    15.    }
    16. }
    17.  
    Came across the idea in Mike's Unity Serializer. You should attach this script to a prefab object from your project window, i.e. not a prefab instance. It seems that even though the script has ExecuteInEditMode on it, Awake doesn't get called when you attach the script to a prefab. The flag becomes true immediately when you instantiate the prefab (in edit or playmode). You can make the flag public just to see how the value changes yourself.
     
  18. xuanyusong

    xuanyusong

    Joined:
    Apr 10, 2013
    Posts:
    49
    Code (CSharp):
    1.     void Start () {
    2.         Debug.Log(transform.IsPrefab());
    3.     }
    I tested.
    runtime add this script to a prefab. but It's all return false ?
     
  19. xuanyusong

    xuanyusong

    Joined:
    Apr 10, 2013
    Posts:
    49
    oh I konw.
    It's must before Instantiate
    thank you ~
     
  20. Xtro

    Xtro

    Joined:
    Apr 17, 2013
    Posts:
    608
    I updated the original post for more information.
     
  21. codeage

    codeage

    Joined:
    Feb 7, 2015
    Posts:
    14
    The problem is, FindObjectsOfType() will return active objects only.
     
  22. codeage

    codeage

    Joined:
    Feb 7, 2015
    Posts:
    14
    This does not work on Android. Calling GetSiblingIndex() on root objects will return 0 always.
     
  23. Xtro

    Xtro

    Joined:
    Apr 17, 2013
    Posts:
    608
  24. winxalex

    winxalex

    Joined:
    Jun 29, 2014
    Posts:
    166
    Hide flags might work but not on GO but GO.transform.hideFlags==HideFlags.HideInHierarchy = is Prefab. Not working in IOS, as probably something is different there...
     
  25. Xtro

    Xtro

    Joined:
    Apr 17, 2013
    Posts:
    608
    I updated my method long time ago. I will add it in the original post up there now.
     
  26. winxalex

    winxalex

    Joined:
    Jun 29, 2014
    Posts:
    166
    What is the link of original post?
     
  27. Xtro

    Xtro

    Joined:
    Apr 17, 2013
    Posts:
    608
    It's in this thread. Check the Original post(Obsolete) section in the first post.
     
  28. n00bmind

    n00bmind

    Joined:
    Sep 5, 2017
    Posts:
    3
    Just posting a few years later to thank you because this worked beautifully :)
     
  29. Deleted User

    Deleted User

    Guest

    Now it doesn't work correctly. Immortalized objects (marked as DontDestroyOnLoad) are placed into DontDestroyOnLoad scene which has build index -1. So this method is not suitable for such objects. Probably it would be better to use Scene.IsValid() function. But I'm not sure about anything now.

    Ah, does anybody know analogous solution for ScriptableObjects?
     
    Last edited by a moderator: Mar 28, 2019
  30. TheDemiurge

    TheDemiurge

    Joined:
    Jul 26, 2010
    Posts:
    42
    This also won't work if you're just playing a scene in the editor that isn't in the build list
     
  31. Deleted User

    Deleted User

    Guest

    I've just checked that and it works fine. The buildIndex property returns non-zero value. But what will happen if you do this in runtime for scenes loaded from asset bundles?
     
  32. TheDemiurge

    TheDemiurge

    Joined:
    Jul 26, 2010
    Posts:
    42
    But do they return a positive value? I haven't tried this, but I would assume that in a scene that's not in the build list, every single object would have a negative buildIndex value, which would break the intended behavior since they're not "prefab ghosts" but scene objects. If they return any non-negative number, isn't that a bug?
     
  33. aklgupta

    aklgupta

    Joined:
    Jun 9, 2014
    Posts:
    29
    Isn't it better to use
    PrefabAssetType

    I haven't tried it, but I think this should work
    Code (CSharp):
    1. using UnityEngine;
    2. using UnityEditor;
    3. // ...
    4.  
    5. if(PrefabUtility.GetPrefabAssetType(GO) == PrefabAssetType.NotAPrefab){
    6.     //.. Whatever you want to do
    7. }
     
  34. KelsoMRK

    KelsoMRK

    Joined:
    Jul 18, 2010
    Posts:
    5,539
    From the 4th post in this thread (which was also 5 years ago)

     
  35. aklgupta

    aklgupta

    Joined:
    Jun 9, 2014
    Posts:
    29
    Oh, my bad. Didn't notice that.