Search Unity

Resolved Pull a serialized property struct, as a struct?

Discussion in 'Scripting' started by Heptagram064, Nov 26, 2022.

  1. Heptagram064

    Heptagram064

    Joined:
    Feb 22, 2022
    Posts:
    99
    Hello,

    So i was working on my custom editor window, when i encounter the following inconvenience. When using a serialized property on a scriptable object, you can pull any datatype from it using e.g. SerializedObject.FindProperty("myProp").floatValue. However there appears to be no way of achieving this for pulling a struct, as a struct.

    [EDIT] Quick anwere is available in the bottom reply of this thread

    For what i could find the closest i got was using;
    as a scriptable object;
    Code (CSharp):
    1. using System.Collections.Generic;
    2. using UnityEngine;
    3.  
    4. [System.Serializable]
    5. public class MyScriptableObject : ScriptableObject
    6. {
    7.     [serializeField] public MyStruct myStruct;
    8.     [System.Serializable]
    9.     public struct MyStruct
    10.     {
    11.         public float myValueOne;
    12.         public float myValueTwo;
    13.     }
    14. }
    And as a function class;
    Code (CSharp):
    1. using System.Collections.Generic;
    2. using UnityEngine;
    3.  
    4. namespace MyNamespace {
    5.     public class MyClass
    6.     {
    7.         private SerializedObject scriptableObject = new SerializedObject(AssetDatabase.LoadAssetAtPath("Assets/MyScriptableObject.asset", typeof(MyScriptableObject)));
    8.  
    9.         public static void MyFunction() {
    10.             scriptableObject.Update();
    11.             MyScriptableObject.MyStruct myStruct = (MyScriptableObject.MyStruct)(scriptableObject.FindProperty("myStruct").objectReferenceValue as System.Object);
    12.             Debug.Log("Value 1: " + myStruct.myValueOne + ", Value 2: " + myStruct.myValueTwo);
    13.         }
    14.     }
    15. }
    Essentialey getting the objectReferenceValue and converting it from Unity.Object => System.Object => MyStruct

    The VS-Code tells me this is a perfectly fine solution, however, when i actually use MyFunction() in unity, unity gives me the error: "type is not a supported pptr value". (line 12 on the function class)

    Is anyone aware of a way of getting a serialized property struct as a struct? This would be very convenient, especially when you are building functions that require pulling structs from scriptable objects to local lists for logic purposes. It'd be very tedious having to pull every variable from a struct individually, and piecing it into a struct list/array one variable at a time.
     
    Last edited: Nov 28, 2022
  2. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    7,923
  3. Heptagram064

    Heptagram064

    Joined:
    Feb 22, 2022
    Posts:
    99
    It's a simple namespace containing a simple custom function to be used as;
    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5. using MyNamespace;
    6.  
    7. public class SomeScript : MonoBehavour
    8. {
    9.     void Start()
    10.     {
    11.         // Function from the MyClass class in the MyNamespace namespace, that logs the values of the myStruct struct in the scriptable object MyScriptableObject to the console
    12.         MyClass.MyFunction();
    13.     }
    14. }
    , Maybe the function is more clear to understand if i comment the code;
    Code (CSharp):
    1. using UnityEngine;
    2. using UnityEditor;
    3.  
    4.     namespace MyNamespace {
    5.     public class MyClass
    6.     {
    7.         // Get a reference to the scriptable object MyScriptableObject
    8.         private static SerializedObject scriptableObject = new SerializedObject(AssetDatabase.LoadAssetAtPath("Assets/MyScriptableObject.asset", typeof(MyScriptableObject)));
    9.  
    10.         public static void MyFunction() {
    11.             // Update the reference of MyScriptableObject to the latest values
    12.             scriptableObject.Update();
    13.             // Create a new MyStruct, and call it 'myStruct'
    14.             MyScriptableObject.MyStruct myStruct =
    15.                 // And try to assign it the struct from the scriptable object
    16.                 (MyScriptableObject.MyStruct)(scriptableObject.FindProperty("myStruct").objectReferenceValue as System.Object);
    17.                 // 'SerialzedObject.FindProperty("property").objectReferenceValue' returns a Unity.Object
    18.                 // using 'SerialzedObject.FindProperty("property").objectReferenceValue as System.Object' converts it from a Unity.Object to a Sytem.Object
    19.                 // We do this, because Unity.Object cannot be converted into anything except System.Object
    20.                 // System.Object can than be converted into the datatype it is holding using a implicit convertion like (int)mySystemObjectHoldingAintValue
    21.                 // In our case, we are assinging datatype 'MyStruct' (which is a struct) to the System.Object, which we then try to convert to
    22.                 // a MyStruct struct using the method (MyStruct)mySystemObjectHoldingA_MyStruct_Value
    23.             // Log the 2 variables contained in the pulled struct;
    24.             Debug.Log("Value 1: " + myStruct.myValueOne + ", Value 2: " + myStruct.myValueTwo);
    25.         }
    26.     }
    27. }
    Ofcorse you can use the EditorGUILayout.PropertyField function if what your going for is a custom inspector, and you can use a unity gui property field to display and change a struct manualey. However what im trying to do is, pulling data from a scriptable object 'As a Struct', for the structs im using have a extensive amount of data in them, that for logic reasons need to be processed using a script, not a property field.

    e.g. We have a array of structs on a scriptable object. We use a function to filter out a specific struct containing a data pattern. The struct we filtered out we want to add to a local List of structs, to use them later in the script.

    Having to get each item in the struct from the struct array individually is a tedious work, which becomes near impossible if this struct also contains a couple of Lists in it. In this case, we would need to, for each variable in the struct,
    move it over to a local struct, one value at a time AND
    decompose each List in the struct, item per item, and copy it over to the lists held in the local struct,
    and then add the local struct to the local structs list
     
    Last edited: Nov 27, 2022
  4. Baste

    Baste

    Joined:
    Jan 24, 2013
    Posts:
    6,336
    Not from a SerializedProperty, can't do that.

    Think of the SerializedProperty more as a JSON object or whatever that reflects the layout of your original type. It doesn't actually have a copy of the struct, just a "there's a thing here with this name and a type tag, here's it's sub-values".

    For a specific case, you can just walk the hierarchy of the object and build the correct struct. For the general case, you'll need to grab the original object instead, and use reflection to get the actual struct. This is also the case with non-UnityEngine.Object objects.
     
    Heptagram064 likes this.
  5. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    7,923
    Right, my bad.

    So just cast the UnityEditor.Editor.target and get it from that? Or is this an EditorWindow?
     
  6. Heptagram064

    Heptagram064

    Joined:
    Feb 22, 2022
    Posts:
    99
    Im afraid im working on a EditorWindow yes...

    However i found a workaround for what im trying to achieve, its just not verry performance friendly (Forttunateley for my usecase it doesn't need to be);

    For solemly pulling data from a scriptable object, one can apearently manualey load the whole scriptable object into a variable, and pull the needed variables, structs, and nested structs from this;
    Code (CSharp):
    1. // Load the whole scriptable object
    2. MyScriptableObject myScriptableObject = (MyScriptableObject)AssetDatabase.LoadAssetAtPath("Assets/MyScriptableObject.asset", typeof(MyScriptableObject));
    3. // Create a new list of structs
    4. List<MyScriptableObject.MyStruct> newStructList = new List<>(MyScriptableObject.MyStruct);
    5. // Cherry pick specific items out of the struct array
    6. newStructList.Add(myScriptableObject.myStructArray[0]);
    7. newStructList.Add(myScriptableObject.myStructArray[3]);
    8. newStructList.Add(myScriptableObject.myStructArray[7]);
    9. // Some example math
    10. for (int i = 0; i < newStructList.Count; i++)
    11. {
    12.     if (newStructList[i].myValueOne >= 100f)
    13.     {
    14.         Destroy(UnityEngine)
    15.     }
    16. }
    Trying to do it 60 frames per second will most likely cause mayor lagg (especialey as datasets in the scriptable object increse), but as used in my custom editor window, the script only needs to do it ~3 times for each button click. Which is acceptable.
     
    Last edited: Nov 28, 2022
  7. Heptagram064

    Heptagram064

    Joined:
    Feb 22, 2022
    Posts:
    99
    Incase anyone ever gets across this thread, here is the answer to the original question;
    e.g.
    Code (CSharp):
    1. MyScriptableObject myScriptableObject = (MyScriptableObject)AssetDatabase.LoadAssetAtPath("Assets/MyScriptableObject.asset", typeof(MyScriptableObject));
    2.  
    3. Debug.Log(myScriptableObject.myStruct.someVariable);
     
    Last edited: Nov 28, 2022
  8. Baste

    Baste

    Joined:
    Jan 24, 2013
    Posts:
    6,336
    If you have a SerializedProperty already, it's a lot easier to do:

    Code (csharp):
    1. MyScriptableObject myScriptableObject = (MyScriptableObject) serializedProperty.serializedObject.targetObject;
    2.  
    3. Debug.Log(myScriptableObject.myStruct.someVariable);
    As you already have access to the object! Since you were creating the SerializedObject yourself, that won't be the case, but since you're making sure that people who find the thread gets the info that's asked for, it might help.