Search Unity

Bug OnPostprocessPrefab() doesn't serialize changes

Discussion in 'Scripting' started by Noblauch, May 28, 2021.

  1. Noblauch

    Noblauch

    Joined:
    May 23, 2017
    Posts:
    275
    Hey, I just implemented the new function from the unity AssetPostprocessor:
    OnPostprocessPrefab(GameObject root)

    The docs on it are pretty straight forward, do your changes, and the changes will be saved with the prefab.
    However in my case it's not. I'm simply iterating over all childs and if it's an Image, I set the sprite to null.

    In the scene I even see that it worked, but when I open my newly created prefab asset (prefab view) I can indeed see, that the changes were not serialized (the sprite is still in the prefab).

    Ideas?
    Unity Version: 2020.3.4f1
     
    Last edited: May 28, 2021
  2. Just tested real fast, what I found is this:
    - if you change something on the root object, selecting the prefab in the project window will show the changed values in the inspector
    - if you change something in child(ren), opening up the prefab hitting the project window will not show the change in the inspector, but if you instantiate the prefab (drag into the hierarchy) and you unfold the instance, the children will reflect the change in the inspector.

    Unless I did something wrong.
    ---
    The test:
    Code (CSharp):
    1. using TMPro;
    2. using UnityEngine;
    3. using UnityEditor;
    4.  
    5. public class Example : AssetPostprocessor
    6. {
    7.    private void OnPostprocessPrefab(GameObject g) => g.GetComponentInChildren<TextMeshProUGUI>().text = "Hello World!";
    8. }
    Original game object:
    screenshot1.png
    Prefab in prefab view from the project window
    screenshot2.png
    Prefab instantiated in the hierarchy
    screenshot3.png
     
    Last edited by a moderator: May 28, 2021
    Noblauch likes this.
  3. Noblauch

    Noblauch

    Joined:
    May 23, 2017
    Posts:
    275
    Well, lol. I just instantiated my created prefab and indeed: the changes are present (no sprite set). But in the prefab view the sprite is there, this is a mismatch that can not be.
    I just investigated deeper and checked the assets YAML file, and indeed, the sprite is still there:
    m_Sprite: {fileID: 21300000, guid: 35c0e98c7ee3243d6832f2517b63b2c4, type: 3}

    It should look like this if the changes would've persisted correctly:
    m_Sprite: {fileID: 0}


    PS: Laser quick response! :D I'm shocked xP

    I guess that's not the right behaviour then. I'll change the Thread to [Bug] now.
     
    Last edited: May 28, 2021
    Lurking-Ninja likes this.
  4. Just reported as a bug. Case #1339347
     
  5. Noblauch

    Noblauch

    Joined:
    May 23, 2017
    Posts:
    275
    Well, I did too #1339351 :D Seems like you beat me again :'D
     
    Lurking-Ninja likes this.
  6. Unity, slowly, but steadily you push me to the camp who says you're incompetent. This is an issue, it should not be "by design" because no one cares what is your design if it is clearly wrong. Storing information "A" and showing information "B" to the user is super-duper wrong. By all standards. It is plain STUPID. Period.
     
    ajmurdoch96 and Noblauch like this.
  7. Noblauch

    Noblauch

    Joined:
    May 23, 2017
    Posts:
    275
    I escalated this to the Unity Core Support, will try to check back to this thread if I get regression or a workaround.
    Personally I can't think of a reason why you would want to detach your local files from the actual source files. These kind of behaviours is prone to all sorts of hard to find bugs. Kind of puzzled too by this "By Design" answer from Unity.
     
    ajmurdoch96 likes this.
  8. MirceaI

    MirceaI

    Unity Technologies

    Joined:
    Nov 24, 2020
    Posts:
    36
    The AssetPostprocessor is designed to let users hook into the import pipeline and control the importers output - the files created in the Library folder. The Prefab post processor is doing the same thing - its input is a Prefab from the Assets folder (source Prefab) and its output is a Prefab in the Library folder (imported Prefab). When opening a Prefab in Prefab View the source Prefab is used, not the imported Prefab which means that any change made in OnPostprocessPrefab is not visible is Prefab view.

    For example OnPostprocessPrefab can be used to attach scriptable objects to imported Prefabs or to do complicated/time consuming computations that can be done when importing a Prefab instead of doing them at runtime.
    Following is a simple example of a Prefab Post Processor:

    NPCProperties.cs:
    Code (CSharp):
    1. using UnityEngine;
    2.  
    3. public class NPCProperties : MonoBehaviour
    4. {
    5.     public enum Type
    6.     {
    7.         TheGoodOne,
    8.         TheBadOne
    9.     }
    10.  
    11.     public Type type;
    12.     public NPCPropertySheet propertySheet;
    13.     public Vector2[] path;
    14. }
    NPCPropertySheet.cs:
    Code (CSharp):
    1. using UnityEngine;
    2.  
    3. public class NPCPropertySheet : ScriptableObject
    4. {
    5.     public int hp;
    6.     public int damage;
    7. }
    BadGuyPropertySheet.cs:
    Code (CSharp):
    1. public class BadGuyPropertySheet : NPCPropertySheet
    2. {
    3.     public BadGuyPropertySheet()
    4.     {
    5.         hp = 100;
    6.         damage = 10;
    7.     }
    8. }
    GoodGuyPropertySheet.cs:
    Code (CSharp):
    1. public class GoodGuyPropertySheet : NPCPropertySheet
    2. {
    3.     public GoodGuyPropertySheet()
    4.     {
    5.         hp = 100;
    6.         damage = 0;
    7.     }
    8. }

    PrefabPostProcessor.cs:
    Code (CSharp):
    1. using UnityEngine;
    2. using UnityEditor;
    3.  
    4. public class PrefabPostProcessor : AssetPostprocessor
    5. {
    6.     private static Vector2[] ComputeTheGoodGuyPath()
    7.     {
    8.         var points = new Vector2[10];
    9.         /// Complicated and inefficient algorithm that you don't want to implement in Awake/Start
    10.         return points;
    11.     }
    12.  
    13.     private static Vector2[] ComputeTheBadGuyPath()
    14.     {
    15.         var points = new Vector2[10];
    16.         /// Complicated and inefficient algorithm that you don't want to implement in Awake/Start
    17.         return points;
    18.     }
    19.  
    20.     private void OnPostprocessPrefab(GameObject root)
    21.     {
    22.         var npcProperties = root.GetComponentInChildren<NPCProperties>();
    23.         if(npcProperties != null)
    24.         {
    25.             switch(npcProperties.type)
    26.             {
    27.                 case NPCProperties.Type.TheGoodOne:
    28.                     npcProperties.path = ComputeTheGoodGuyPath();
    29.                     npcProperties.propertySheet = ScriptableObject.CreateInstance<GoodGuyPropertySheet>();
    30.                     context.AddObjectToAsset("GoodGuyPropertySheet", npcProperties.propertySheet);
    31.                     break;
    32.  
    33.                 case NPCProperties.Type.TheBadOne:
    34.                     npcProperties.path = ComputeTheBadGuyPath();
    35.                     npcProperties.propertySheet = ScriptableObject.CreateInstance<BadGuyPropertySheet>();
    36.                     context.AddObjectToAsset("BadGuyPropertySheet", npcProperties.propertySheet);
    37.                     break;
    38.             }
    39.         }
    40.     }
    41. }
    If the desired behavior is to alter the source Prefab one can use Editor scripts like this:

    EditPrefabAsset.cs:
    Code (CSharp):
    1. using UnityEngine;
    2. using UnityEngine.UI;
    3. using UnityEditor;
    4.  
    5. public class EditPrefabAsset : MonoBehaviour
    6. {
    7.     static string PrefabPath = "Assets/MyPrefab.prefab";
    8.  
    9.     [MenuItem("Prefabs/Change Prefab Text")]
    10.     static void ChangePrefabText()
    11.     {
    12.         GameObject prefabRoot = AssetDatabase.LoadMainAssetAtPath(PrefabPath) as GameObject;
    13.         prefabRoot.GetComponentInChildren<Text>().text = "Hello World!";
    14.         PrefabUtility.SavePrefabAsset(prefabRoot);
    15.     }
    16. }
     
    Noblauch and PromeshStudio like this.