Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Destroying a GameObject inside a Prefab instance is not allowed

Discussion in 'Prefabs' started by stephero, Sep 15, 2018.

  1. Aka_ToolBuddy

    Aka_ToolBuddy

    Joined:
    Feb 25, 2014
    Posts:
    536
    With the following code, the prefab is indeed updated, but the update is considered by Unity (i.e: impacted on instances of said prefab) only when I Alt+Tab twice to force Unity to check files modifications
    I am using this but the updated prefab file is considered only when Alt+tabing.

    Code (CSharp):
    1. PrefabUtility.prefabInstanceUpdated += instance =>
    2. {
    3.     string prefabPath = PrefabUtility.GetPrefabAssetPathOfNearestInstanceRoot(instance.gameObject);
    4.     Scene previewScene = EditorSceneManager.NewPreviewScene();
    5.     //This method is supposed to return the root, as stated in its documentation. It returns void.
    6.     PrefabUtility.LoadPrefabContentsIntoPreviewScene(prefabPath,
    7.         previewScene);
    8.     GameObject prefabContent = previewScene.GetRootGameObjects()[0];
    9.     bool wasModified = DeleteUnwantedGameObjects();
    10.     if (wasModified)
    11.     {
    12.         PrefabUtility.SaveAsPrefabAsset(prefabContent, prefabPath);
    13.     }
    14.     EditorSceneManager.ClosePreviewScene(previewScene);
    15.     AssetDatabase.SaveAssets();
    16.     AssetDatabase.Refresh();
    17. };
    I thought the calls to AssetDatabase that I did should flush any changes. Any help?

    Edit: The problem appears only when updating the prefab from within PrefabUtility.prefabInstanceUpdated. I couldn't solve this issue, so I had to avoid using that event.
     
    Last edited: May 9, 2021
  2. SteenLund

    SteenLund

    Unity Technologies

    Joined:
    Jan 20, 2011
    Posts:
    639
  3. Aka_ToolBuddy

    Aka_ToolBuddy

    Joined:
    Feb 25, 2014
    Posts:
    536
    Since I am not familiar with the new prefab API, I thought that the safest would be to load the prefab's content in a preview scene instead of the current scene, to avoid any possible impact on the current scene (such as code executed in OnEnable or Awake of some component in the prefab). But while trying to avoid the issue explained above (the Alt+tab related problem), I tried all the possible ways to load a prefab's content, including the ones you referenced.
    About EditPrefabContentsScope, I missed it until l saw on the forum a post using it. While exploring the API, I thought it was just some type used as a parameter for some method.
     
  4. SteenLund

    SteenLund

    Unity Technologies

    Joined:
    Jan 20, 2011
    Posts:
    639
    LoadPrefabContents
    actually loads into a preview scene internally to avoid polluting the current scene, so you don't have to be concerned about this.
    EditPrefabContentsScope
    automatically takes care of unloading everything to avoid leaking preview scenes.
     
  5. Aka_ToolBuddy

    Aka_ToolBuddy

    Joined:
    Feb 25, 2014
    Posts:
    536
    The naming of both LoadPrefabContents and
    LoadPrefabContentsIntoPreview made me think only the second one loaded in a preview scene. I know that the documentation of LoadPrefabContents states that it loads the prefab in an isolated scene, but when you are discovering an API, at least the way I do it, the first thing you base your judgment on is the name of classes/members/method parameters. Documentation comes next only when the names are not self explanatory.
    Thanks for having answered me
     
  6. Mayarmoawad

    Mayarmoawad

    Joined:
    May 19, 2021
    Posts:
    1
    I tried ti unpack prefab on the gameobject and it worked
     
  7. Dhruvgreat

    Dhruvgreat

    Joined:
    Jul 8, 2021
    Posts:
    3
    where to put this code or what to do with this code.
    i dont even know which prefab is not destroying i am using asset cscape city builder.
    what to do now please help.
    i spend a lot of money into this asset.
    please help
     
  8. paulatwarp

    paulatwarp

    Joined:
    May 17, 2018
    Posts:
    135
    LoadPrefabContentsIntoPreviewScene doesn't return the object. But I need to use a custom created preview scene or Unity destroys the RectTransform on the root object when I SaveAsPrefabAsset. How do I find the prefab object once it has loaded into the preview scene?
     
  9. Dhruvgreat

    Dhruvgreat

    Joined:
    Jul 8, 2021
    Posts:
    3
    here i know how to solve my problem, I just unpack the prefab completely.
    now it can be modify through script without showing any error.
    thanks
     
  10. unity_FC1EEF5588E76CE80417

    unity_FC1EEF5588E76CE80417

    Joined:
    Jan 25, 2022
    Posts:
    11
    So what is the flow for deleting a game object of a base prefab?
    I want to load the prefab content, see if the game object I want to delete is part of the variant, if not, load the BASE prefab content and (check if the game object belongs to the base) and delete the game object. save, unload.

    Can you please give a code example for this?
     
  11. geer-v

    geer-v

    Unity Technologies

    Joined:
    Jun 8, 2022
    Posts:
    6
    Hope this helps:

    Code (CSharp):
    1.         // Load prefab variant content
    2.         GameObject prefabVariant = PrefabUtility.LoadPrefabContents(pathToVariant);
    3.  
    4.         // Find the GameObject to delete in the prefab variant
    5.         Transform objectToDeleteInVariant = prefabVariant.transform.Find("GameObjectToDelete");
    6.  
    7.         // If it's not part of the prefab variant...
    8.         if (objectToDeleteInVariant == null)
    9.         {
    10.             // Load prefab base content
    11.             string pathToBase = AssetDatabase.GetAssetPath(PrefabUtility.GetCorrespondingObjectFromSource(prefabVariant));
    12.             GameObject prefabBase = PrefabUtility.LoadPrefabContents(pathToBase);
    13.  
    14.             // Find the GameObject to delete in the prefab base
    15.             Transform objectToDeleteInBase = prefabBase.transform.Find("GameObjectToDelete");
    16.  
    17.             // If it is part of the prefab base...
    18.             if (objectToDeleteInBase != null)
    19.             {
    20.                 // Delete the GameObject, save and unload
    21.                 GameObject.DestroyImmediate(objectToDeleteInBase.gameObject);
    22.  
    23.                 PrefabUtility.SaveAsPrefabAsset(prefabBase, pathToBase);
    24.  
    25.                 AssetDatabase.Refresh();
    26.             }
    27.  
    28.             PrefabUtility.UnloadPrefabContents(prefabBase);
    29.         }
    30.  
    31.         PrefabUtility.UnloadPrefabContents(prefabVariant);
     
  12. unity_FC1EEF5588E76CE80417

    unity_FC1EEF5588E76CE80417

    Joined:
    Jan 25, 2022
    Posts:
    11
    Thanks a lot!
     
  13. Yany

    Yany

    Joined:
    May 24, 2013
    Posts:
    96
    If @jessiep1 or somebody could give an "official" guide on how to approach this, that could be extremely helpful.

    The situation in general: how to manage child objects when they are just the output of a component that generates them? How to make it prefab instance compatible?

    I give some details, what I mean, imagine the following component:

    Code (CSharp):
    1. public class CubeGenerator : MonoBehaviour
    2. {
    3.     [SerializeField]
    4.     private int _count = 1;
    5.  
    6.     [SerializeField]
    7.     private Vector3 _direction = Vector3.forward;
    8.  
    9.     private void OnValidate()
    10.     {
    11.         GenerateChildCubes();
    12.     }
    13.  
    14.     private void GenerateChildCubes()
    15.     {
    16.         // Destroy all the previously generated child cube gameobjects using DestroyImmediate().
    17.         // Create cube gameobjects next to each other...
    18.     }
    19. }
    This script just runs fine, when put on a regular GameObject. Also works fine if you create a prefab of it and open the prefab in its dedicated edit scene.

    But it won't work if you want to modify an instance of a prefab having this component.

    My question: the workflow (for the users using this component) should be to carry only the useful information with the prefab and whenever a scene is opened with an object having this component, they should generate the child cubes on-the-fly. The cubes in fact are only just the result of the script.

    What is the recommended approach for this? How to make this component not carry the cubes, but still have them whenever I open a scene in the editor or in an actual build? Or how to carry those cubes but with the possibility to destroy and recreate them whenever it wants to, regardless of being a prefab instance or a regular object?

    In a prefab variant, I just want to vary the two properties, not the cubes. Modifying the cubes would make no sense as they are generated, so whenever I touch any of the properties they will be replaced by new cubes aligning with the new property values.

    The answer to this question could help everybody who is creating a component that creates child objects based on its properties.

    This cube generation is just a meta example, you can put crowd generation, prefab spawner along a spline, whatever in its place.

    Thank you.
     
  14. Yany

    Yany

    Joined:
    May 24, 2013
    Posts:
    96
    As I've learnt, from the version 2022.2 it's possible to destroy a child gameobject of a prefab instance: https://blog.unity.com/technology/prefabs-whats-new-2022-2

    It's worth checking the source code of the Splines asset package Unity provides through the package manager.

    Edit: a bit offtopic, but relates: When you open the original SplineInstantiate example project (v2.0.1 package) and select the gameobject, and duplicate it, the hidden children become visible for some reason with an odd position beside all the hidden and correctly generated instances. So all this object generation is full of weird, unstable stuff. Let's say at least: there's some room for improvement in this use-case. :D
     
    Last edited: Jan 23, 2023
    NibbleByte3 and Thaina like this.
  15. Aka_ToolBuddy

    Aka_ToolBuddy

    Joined:
    Feb 25, 2014
    Posts:
    536
    I have a similar case in one of my assets*. What I ended up doing is to, when deleting the generated objects, to check if said objects are part of a prefab instance, and if so, delete them from the prefab. Here is the code I wrote:

    Code (CSharp):
    1.         /// <summary>
    2.         ///  Delete, from the associated prefab if any, all the managed resources acting as an output. One example of such resources is the generated meshes by the <see cref="FluffyUnderware.Curvy.Generator.Modules.CreateMesh"/> module
    3.         /// <remarks>Prefabs instances are not allowed to do some operations, such as deleting a game object. Such operations are done by the Curvy Generator. So run this method before doing any of those operations</remarks>
    4.         /// </summary>
    5.         /// <returns>Whether an associated prefab was modified</returns>
    6.         public bool DeleteAllOutputManagedResourcesFromAssociatedPrefab()
    7.         {
    8. #if UNITY_EDITOR
    9.             string prefabPath = PrefabUtility.GetPrefabAssetPathOfNearestInstanceRoot(this);
    10.             if (String.IsNullOrEmpty(prefabPath))
    11.                 return false;
    12.  
    13.             GameObject prefabContentsRoot = PrefabUtility.LoadPrefabContents(prefabPath);
    14.             CurvyGenerator[] prefabGenerators = prefabContentsRoot.GetComponentsInChildren<CurvyGenerator>();
    15.  
    16.             bool modified = false;
    17.             foreach (CurvyGenerator prefabGenerator in prefabGenerators)
    18.                 foreach (CGModule module in prefabGenerator.Modules)
    19.                     modified |= module.DeleteAllOutputManagedResources();
    20.  
    21.             if (modified)
    22.             {
    23.                 GameObject savedPrefab = PrefabUtility.SaveAsPrefabAsset(prefabContentsRoot, prefabPath);
    24.                 if (savedPrefab == null)
    25.                 {
    26.                     DTLog.LogError($"[Curvy] The prefab asset '{prefabPath}' containing the generator '{name}' needs to be modified to delete generator output objects. Attempt to modify it failed. See other console messages to know what caused this failure.", this);
    27.                     modified = false;
    28.                 }
    29.                 else
    30.                 {
    31.                     object message = $"[Curvy] The prefab asset '{prefabPath}' containing the generator '{name}' was modified to delete generator output objects.";
    32.  
    33.                     PrefabStage currentPrefabStage = PrefabStageUtility.GetCurrentPrefabStage();
    34.  
    35. #if UNITY_2020_1_OR_NEWER
    36.                     if (currentPrefabStage != null && currentPrefabStage.assetPath == prefabPath)
    37. #else
    38.                     if (currentPrefabStage != null && currentPrefabStage.prefabAssetPath == prefabPath)
    39. #endif
    40.                     {
    41.                         message += " This might happen when you save modifications to the prefab asset. You might want to disable Auto Save in Prefab Mode to make this happen less frequently.";
    42.                         DTLog.LogWarning(message, this);
    43.                     }
    44.                     else
    45.                     {
    46.                         message += " This might lead to the refreshing of the associated generator in the prefab instance.";
    47.                         DTLog.Log(message, this);
    48.                     }
    49.  
    50.                 }
    51.             }
    52.             PrefabUtility.UnloadPrefabContents(prefabContentsRoot);
    53.             return modified;
    54. #else
    55.             return false;
    56. #endif
    57.         }
    I hope this code will help you. I wish things could be done in a simpler manner, as the current code has probably some bugs appearing on edge cases, due to its complexity.

    *namely: Curvy Generator component in the Curvy Splines asset.
     
    Yany likes this.
  16. Aka_ToolBuddy

    Aka_ToolBuddy

    Joined:
    Feb 25, 2014
    Posts:
    536
    To clarify: the code shared above is meant to work for Unity versions starting from 2019.4. If your target only Unity 2022.2, you can surely do something simpler.
     
    Yany likes this.