Search Unity

  1. Good news ✨ We have more Unite Now videos available for you to watch on-demand! Come check them out and ask our experts any questions!
    Dismiss Notice

Prefab Properties (CustomEditor) Dissappear After Quit/Open of Unity

Discussion in 'Immediate Mode GUI (IMGUI)' started by yumupdate, Jul 30, 2013.

  1. yumupdate

    yumupdate

    Joined:
    Nov 20, 2012
    Posts:
    29
    All of my serialization appears to work fine when I attach the following to in-game/scene objects. I can save the scene, quit, return and all the data is still there in the scene hierarchy. The problem comes when I make changes to a prefab using the same script.

    First of all, I notice that it doesn't update any of the scenes with the object's new prefab data when I save the prefab (nor when I "revert"). Second, it appears to be retaining the prefab properties in the Project folder when I select the prefab, but when I quit out of Unity and come back in, the data from a piece of the prefab is gone.

    Again, this doesn't happen with in-game objects, so maybe I've missed something important specific to Unity's ability to store prefab data.

    Operation.cs (removed a few lines that were objects that retain their properties)

    Code (csharp):
    1. using UnityEngine;
    2. using System.Collections;
    3. #if UNITY_EDITOR
    4. using UnityEditor;
    5. #endif
    6.  
    7. [System.Serializable]
    8. public class Operation
    9. {
    10.     public MonoScript operationScript = null; // this part stays after quit/load
    11.     public CustomOperation operation = null; // a part of this does not
    12. }
    CustomOperation.cs (nothing removed, and I never use this class specifically in the Operation, only objects that inherit it)

    Code (csharp):
    1. using UnityEngine;
    2. using System.Collections;
    3. #if UNITY_EDITOR
    4. using UnityEditor;
    5. #endif
    6.  
    7. [System.Serializable]
    8. public class CustomOperation : ScriptableObject // tried removing this inheritance ... didn't help
    9. {
    10.     protected bool isCompleted = false;
    11.     protected bool isWorking = false;
    12.     public bool IsCompleted { get { return isCompleted; } set { isCompleted = value; } }
    13.     public bool IsWorking { get { return isWorking; } set { isWorking = value; } }
    14.    
    15.     public virtual IEnumerator TriggerOperation()
    16.     {
    17.         yield return null;
    18.     }
    19.    
    20.     public virtual void OnGUI()
    21.     {
    22.     }
    23.  
    24.         public void OnEnable()
    25.     {
    26.         hideFlags = HideFlags.HideAndDontSave;
    27.     }
    28. }
    ThisOrThatCustomOperation.cs
    I have a large list of things like this that need to be modular, yet operate similarly ... and these are the objects that get assigned to CustomOperation in my first class. I do casting based on what MonoScript the user selects to produce a custom GUI output. I suspect this is where the problem might be but have no idea why prefabs specifically would be a problem.

    Code (csharp):
    1. using UnityEngine;
    2. using System.Collections;
    3. #if UNITY_EDITOR
    4. using UnityEditor;
    5. #endif
    6.  
    7. [System.Serializable]
    8. public class ThisOrThatCustomOperation : CustomOperation
    9. {
    10.     public int numberOfPoints = 0;
    11.    
    12.     public override IEnumerator TriggerOperation()
    13.     {
    14.         //GameManager.score += numberOfPoints; <-- not important to the problem, just illustrative
    15.         yield return null;
    16.         this.isCompleted = true;
    17.     }
    18.    
    19.     #if UNITY_EDITOR
    20.     public override void OnGUI()
    21.     {
    22.         this.numberOfPoints = EditorGUILayout.IntField("Add Points:", this.numberOfPoints, new GUILayoutOption[0]);
    23.         EditorUtility.SetDirty(this);
    24.     }
    25.     #endif
    26. }
    27.  
    Any ideas? Thanks guys (and gals)!

    Edit: I guess it would help if I included my CustomEditor snippet.

    Code (csharp):
    1. using UnityEngine;
    2. using System;
    3. using System.Collections;
    4. using System.Collections.Generic;
    5. using System.Reflection;
    6. #if UNITY_EDITOR
    7. using UnityEditor;
    8. #endif
    9.  
    10. [CustomEditor(typeof(Operations))]
    11. public class OperationsEditor : Editor
    12. {
    13.     Operations targetObj = null;
    14.    
    15.     public void OnEnable()
    16.     {
    17.         targetObj = (Operations)target;
    18.        
    19.         if (targetObj.operationList.Count == 0) //targetObj.operationList.Add(ScriptableObject.CreateInstance<Operation>());
    20.             targetObj.operationList.Add(new Operation());
    21.     }
    22.    
    23.     public override void OnInspectorGUI()
    24.     {
    25.         BuildOperation(targetObj.operationList[0]);
    26.         EditorUtility.SetDirty(target);
    27.     }
    28.  
    29.     public void BuildOperation(Operation operation)
    30.     {              
    31.         EditorGUILayout.BeginVertical();
    32.         EditorGUIUtility.LookLikeControls(115.0f);
    33.         operation.operationScript = EditorGUILayout.ObjectField("Script to Use:", operation.operationScript, typeof(MonoScript), false, new GUILayoutOption[0]) as MonoScript;
    34.         EditorGUILayout.EndVertical();
    35.        
    36.         // Make sure we reset operation if the operationScript changes (or if their types/classes don't match up)
    37.         if (operation.operationScript == null  operation.operation != null ||
    38.             operation.operation != null  operation.operationScript != null
    39.              operation.operationScript.GetClass() != operation.operation.GetType())
    40.         {
    41.             ScriptableObject.DestroyImmediate(operation.operation);
    42.             operation.operation = null;
    43.         }
    44.        
    45.         // Check to make sure it's a script that can be used by the event system (inherits CustomOperation), if not, clear the fields.
    46.         if (operation.operationScript != null  operation.operation == null)
    47.         {
    48.             System.Reflection.Assembly assembly = typeof(CustomOperation).Assembly;
    49.             System.Type type = assembly.GetType(operation.operationScript.GetClass().ToString());
    50.             if (type != null  typeof(CustomOperation).IsAssignableFrom(type)  operation.operation == null)
    51.             {
    52.                 operation.operation = (CustomOperation)ScriptableObject.CreateInstance(type);
    53.                 Debug.Log("Assigned");
    54.             }
    55.             else
    56.             {
    57.                 ScriptableObject.DestroyImmediate(operation.operation);
    58.                 operation.operationScript = null;
    59.                 operation.operation = null;
    60.                 Debug.Log("Reference removed");
    61.             }
    62.         }
    63.        
    64.         // FINALLY ... build the GUI for this component
    65.         if (operation.operationScript != null  operation.operation != null)
    66.         {
    67.             operation.operation.OnGUI();
    68.         }
    69.        
    70.         // I manually edited the operation.operation field, so mark dirty?
    71.         if (operation != null  operation.operation != null) EditorUtility.SetDirty(operation.operation);
    72.        
    73.         // House-keeping
    74.         EditorGUIUtility.LookLikeControls();
    75.     }
    76. }
     
    Last edited: Jul 31, 2013
  2. yumupdate

    yumupdate

    Joined:
    Nov 20, 2012
    Posts:
    29
    I've researched serialization, seen the new video from Unite 2013 that covers it and am still having issues. I did manage to get the prefab to sync up with the object in the scene (revert, apply and updating the prefab.) But then it still just drops all of the Prefab's "CustomOperation" objects during reassembly. My script then realizes that the CustomOperation object turns up "null" but sees that we still have the Operation's MonoScript that survived the reassembly, so it just does what I've coded it to do (assign the CustomOperation based on the MonoScript) but, of course, now the fields are all blank because it's a new CustomOperation altogether.

    I no longer think that the use of a ScriptableObject is the problem, since Unite 2013's presentation sold it left and right as "the solution". I've setup my base class as was recommended and used the proper hideFlags and setup the creation and destruction of the ScriptableObject the way they recommend, but I'm still floundering for reasons unknown due to my lack of understanding of how to troubleshoot the problem.
     
    Last edited: Jul 31, 2013
  3. yumupdate

    yumupdate

    Joined:
    Nov 20, 2012
    Posts:
    29
    I managed to make a simple project using the code I posted above. I added a MonoBehaviour class to hold a list of Operations and add a new Operation to the list if the list is null during my Editor's OnEnable function. Well, the simple example allows me to edit prefab fields, save it, drop the prefab (a cube with the script attached) into an empty scene, save the scene, and load a different scene while seemingly keeping the prefab properties for both the instances and the prefab. BUT when I quit Unity and reopen, they're gone again.

    Code (csharp):
    1. using UnityEngine;
    2. using System.Collections;
    3. using System.Collections.Generic;
    4.  
    5. public class Operations : MonoBehaviour {
    6.    
    7.     public List<Operation> operationList = new List<Operation>();
    8.    
    9.     // Use this for initialization
    10.     void Start () {
    11.    
    12.     }
    13.    
    14.     // Update is called once per frame
    15.     void Update () {
    16.    
    17.     }
    18. }
    Frustrating.
     
  4. dkozar

    dkozar

    Joined:
    Nov 30, 2009
    Posts:
    1,410
    I believe this is not due to reopening Unity, but recompiling.

    Unity re-compiles your code each time you changes something in your source. Try changing a line in your code and then watch Unity recompiling and reloading the app. After the reload it should de-serialize the saved stuff. Btw are you sure you saved the project?
     
  5. yumupdate

    yumupdate

    Joined:
    Nov 20, 2012
    Posts:
    29
    I actually found out that prefabs just don't support containing ScriptableObjects and it requires a clumsy workaround involving the AssetDatabase. I'm disappointed to hear it, really. I thought since Unity pushed prefabs, pushed their own serialization for editors and object data, and pushed ScriptableObjects for easier maintenance of the object data via the editor ... that maybe prefabs would also support that solution. Alas, not so much. Looks like there's no efficient or simple way to do what I want currently, so I'll either undo a week worth of work to fall back to a massive structure that doesn't use inheritance or drudge on and manually add these new assets that seem to be required. :|

    Thank you, though, dkozar!
     
unityunity