Search Unity

Feature Request Serialized interface fields

Discussion in 'Editor & General Support' started by lumpn, Feb 11, 2022.

  1. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    7,892
    You honestly don't need to do anything like this at all. It can be distilled down to a simple wrapper class:
    Code (CSharp):
    1. [System.Serializable]
    2. public sealed class InterfaceReference<T> where T : class
    3. {
    4.     [SerializeField]
    5.     private UnityEngine.Object _objectValue;
    6.  
    7.     public UnityEngine.Object ObjectValue => _objectValue;
    8.  
    9.     public T Value => (T)_objectValue;
    10. }
    11.  
    12. // usage
    13. [SerializeField]
    14. private InterfaceReference<ISomeInterface> _interfaceReference = new();
    And if you are so inclined you could use a property drawer to enforce some kind of validation. I've done something similar but using Odin when I want to reference Unity objects via interfaces without the use of Odin serialisation.

    Absolutely does not require you to write a new monobehaviour for every interface type.
     
    Vectrex likes this.
  2. sandolkakos

    sandolkakos

    Joined:
    Jun 3, 2009
    Posts:
    285
    It is a good approach, but I would rather use an abstract class to be attached to the SerializeField instead of creating a system for that.

    Code (CSharp):
    1.  
    2. public interface IEnemy { }
    3.  
    4. // It can also inherit from ScriptableObject
    5. public abstract class AEnemy : MonoBehaviour, IEnemy { }
    6.  
    7. public class Test : MonoBehaviour
    8. {
    9.     [SerializeField]
    10.     private AEnemy[] enemies;
    11.  
    12.     private IEnumerable<IEnemy> Enemies => enemies;
    13. }
    14.  
     
  3. CodeRonnie

    CodeRonnie

    Joined:
    Oct 2, 2015
    Posts:
    530
    I tried to implement this, but was unable to get it working. I had to change (T)_objectValue to (T)(object)_objectValue because that's just what you have to do for casting to generic types, but I get an InvalidCastException when I hit play.
    Code (CSharp):
    1. [System.Serializable]
    2. public sealed class InterfaceReference<T> where T : class
    3. {
    4.     [SerializeField]
    5.     private UnityEngine.Object _objectValue;
    6.  
    7.     public UnityEngine.Object ObjectValue => _objectValue;
    8.  
    9.     public T Value
    10.     {
    11.         get
    12.         {
    13.             // Multiline version of (T)(object)_objectValue
    14.             object obj = _objectValue;
    15.             return (T)obj; // <- InvalidCastException
    16.  
    17.             // This does not compile.
    18.             //return (T)_objectValue;
    19.         }
    20.     }
    21. }
    22.  
    23. public interface ITestInterface
    24. {
    25.     string Foo { get; }
    26. }
    27.  
    28. [CreateAssetMenu(fileName = nameof(InterfaceAsset), menuName = nameof(InterfaceAsset))]
    29. public class InterfaceAsset : ScriptableObject, ITestInterface
    30. {
    31.     [SerializeField]
    32.     string _foo;
    33.     public string Foo => _foo;
    34. }
    35.  
    36. public class InterfaceComponent : MonoBehaviour, ITestInterface
    37. {
    38.     [SerializeField]
    39.     string _foo;
    40.     public string Foo => _foo;
    41. }
    42.  
    43. public class TestScript : MonoBehaviour
    44. {
    45.     [SerializeField]
    46.     private InterfaceReference<ITestInterface> _interfaceReference = new();
    47.  
    48.     private void OnEnable()
    49.     {
    50.         // This throws an InvalidCastException, presumably because a serialized UnityEngine.Object does not implement the interface. (?)
    51.         ITestInterface value = _interfaceReference.Value;
    52.         string message = value.Foo;
    53.         Debug.Log(message);
    54.     }
    55. }
    I can assign MonoBehaviours and ScriptableObjects to the object field in the inspector, but it does not want to cast them to the interface at run time.
     
    Last edited: Nov 14, 2023
  4. CodeRonnie

    CodeRonnie

    Joined:
    Oct 2, 2015
    Posts:
    530
    This works, but you're not serializing an interface. You're serializing a base class that implements an interface. It kind of defeats the purpose of using an interface. Why have the base class implement an interface at all if all serializable types have to derive from the base class?
     
    Last edited: Nov 14, 2023
  5. CodeRonnie

    CodeRonnie

    Joined:
    Oct 2, 2015
    Posts:
    530
    Interesting use of the interface support provided by GetComponent, but with this solution the interface implementation must be a MonoBehaviour, not a ScriptableObject or plain C# class or struct.
     
    Last edited: Nov 14, 2023
  6. sandolkakos

    sandolkakos

    Joined:
    Jun 3, 2009
    Posts:
    285
    Wait, if I got it correctly, in your example the Test class is also not declaring the SerializeField as an interface. You define it as a base class as well: `GroundDetectorPicker` which also extends `ComponentPicker`, right?

    Code (CSharp):
    1. [SerializeField]
    2. private GroundDetectorPicker _groundDetectorPicker;
     
  7. CodeRonnie

    CodeRonnie

    Joined:
    Oct 2, 2015
    Posts:
    530
    I never said anything about that. It is not my example.
     
  8. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    7,892
    Weird, but I did write that in notepad rather than actually reference my own wrapper. I'll double check when I'm at home to see what I got wrong.
     
    CodeRonnie likes this.
  9. sandolkakos

    sandolkakos

    Joined:
    Jun 3, 2009
    Posts:
    285
    hey @CodeRonnie, sorry. I got confused because your question was just after my quote to @vakuor example.
    But yeah, I guess it is better to stick to a C# built-in functionality (the abstract class) other than creating a complex system only for that.
    In the ideal world, I wanna use interface directly on the serialize field, buuuuutttt, Unity is always being Unity.
     
    CodeRonnie likes this.
  10. CodeRonnie

    CodeRonnie

    Joined:
    Oct 2, 2015
    Posts:
    530
    I do agree with the implication that vakuor's example likely results in tight coupling, which limits the usefulness of an interface. However, one key difference in functionality is that in their solution any MonoBehaviour may implement the interface. In your version, only MonoBehaviours that derive from the specific base class may be serialized.
     
    Last edited: Nov 15, 2023
  11. sandolkakos

    sandolkakos

    Joined:
    Jun 3, 2009
    Posts:
    285
    No no, in vakuor's example the SerializeField needs a base class as well:
    Code (CSharp):
    1. public class GroundDetectorPicker : ComponentPicker<IGroundDetector>
    2. {}
    3.  
    4. public class Test : MonoBehaviour
    5. {
    6.     [SerializeField]
    7.     private GroundDetectorPicker _groundDetectorPicker;
    8.  
    9.     private void Update()
    10.     {
    11.         Debug.Log(_groundDetectorPicker.Value.IsGrounded);
    12.     }
    13. }
    14.  
     
  12. CodeRonnie

    CodeRonnie

    Joined:
    Oct 2, 2015
    Posts:
    530
    The key is in the implementation of ComponentPicker<T>.Value. It's not my preferred method, but it is slightly more flexible.
     
  13. vakuor

    vakuor

    Joined:
    Jul 9, 2018
    Posts:
    17
    And that's the reason I gave my example. You don't have to use Odin in my case.
     
  14. vakuor

    vakuor

    Joined:
    Jul 9, 2018
    Posts:
    17
    In your case you need to reference IEnemy. If you have AEnemy and BEnemy you cannot replace A and B values from inspector. You need to rewrite your [SerializeField] in Test class for that.
     
  15. vakuor

    vakuor

    Joined:
    Jul 9, 2018
    Posts:
    17
    You can serialize structs or plain C# classes by default.
    Code (CSharp):
    1. [Serializable]
    2. public struct A
    3. {
    4.     public int i;
    5. }
    6.  
    7. [Serializable]
    8. public class B
    9. {
    10.     public int i;
    11. }
    12.  
    13. public class Test : MonoBehaviour
    14. {
    15.     [SerializeField]
    16.     private A _a;
    17.      
    18.     [SerializeField]
    19.     private B _b;
    20. }
    upload_2023-11-15_23-49-18.png
     
  16. vakuor

    vakuor

    Joined:
    Jul 9, 2018
    Posts:
    17
    @CodeRonnie
    Ah you mean interfaces.
    Yes it is impossible.
    That's why I told that it is just a lifehack :)
     
    CodeRonnie likes this.
  17. vakuor

    vakuor

    Joined:
    Jul 9, 2018
    Posts:
    17
    Test class is serializing a `ComponentPicker` who has an interface under it's `Value`.
    This way you can get an interface while not referencing an interface but still having a reference to interface.
     
  18. vakuor

    vakuor

    Joined:
    Jul 9, 2018
    Posts:
    17
    If you need your class to be f.e. Enemy and Entity you cannot inherit two classes. But you can inherit two interfaces.
    That's the moment when an abstract class cannot help you.
     
  19. vakuor

    vakuor

    Joined:
    Jul 9, 2018
    Posts:
    17
    I am not agree :C
    The only problem here is to have an extra components and reference `ComponentPicker` classes instead of interface.
    Anyway it solves interface serialization problem for unity without Odin support.
     
  20. CodeRonnie

    CodeRonnie

    Joined:
    Oct 2, 2015
    Posts:
    530
    = the definition of tight coupling.

    Also, note that I said "limits" not "negates" the usefulness of an interface.

    It's probably the most difficult thing to escape. I'm really most interested in spiney's solution. I was hoping it was going to work when I tested it. Even in that case, you have to reference the InterfaceReference to get the Value. It's not a big deal. It would be ideal to be able to reference the interface directly without an intermediary class for serialization, but that very well may not be possible without a custom property drawer. However, even in that case you probably need a custom attribute applied to the interface field, at the very least. That, in my opinion, is also a sort of tight coupling to that attribute and the hope that there is a serialization solution that picks up on it. Also, we're in Unity, so we're tightly coupling to everything in UnityEngine. So, don't take it too personally.
     
    Last edited: Nov 15, 2023
  21. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    7,892
    Odin wasn't a requirement in mine either. You could do what I mentioned with a Unity property drawer.

    I just do it with Odin's drawer tools because they're better. I should stress I'm doing so without Odin serialisation.
     
  22. CodeRonnie

    CodeRonnie

    Joined:
    Oct 2, 2015
    Posts:
    530
    My actual concerns with the ComponentPicker solution would be:
    - It uses GetComponent at run time, causing a performance hit that I would prefer to avoid.
    - It only works for MonoBehaviours, not ScriptableObjects. A fully fledged solution would also support plain serializable classes and structs, but I imagine that won't be possible with any solution that doesn't use a custom property drawer. But a solution that at least supports MonoBehaviours and ScriptableOjbects without a drawer would be neat.
    - It provides no guarantee that there is actually even a Value that will be found.

    But, again, don't take it personally. I think it is clever, for what it is worth. If it is working for you, then that's all that matters.
     
  23. vakuor

    vakuor

    Joined:
    Jul 9, 2018
    Posts:
    17
    Gotcha. Anyway you also need to write (or have written by some1) drawer tools.
    That's why I think that my example is good especially for new users. It is easy to understand and accept.

    P.S. Odin's possibility to serialize interfaces is not good since interface reference is not cleared when component destroyed. This way you can have an interface reference that is not null but missing.
     
    CodeRonnie likes this.
  24. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    7,892
    Yes it's called a Unity fake-null object. Nothing really to do with Odin, just how Unity works.
     
    vakuor likes this.
  25. vakuor

    vakuor

    Joined:
    Jul 9, 2018
    Posts:
    17
    Sadly yes. But when you get more in functionality you almost always lose in perfomance.
    Yes, it is designed for MonoBehaviours.
    Same way you can forget to put a reference inside [SerializeField].
    I am just replying on your thoughts. It's ok :)
     
    CodeRonnie likes this.
  26. CodeRonnie

    CodeRonnie

    Joined:
    Oct 2, 2015
    Posts:
    530
    Aha! I figured it out. There was nothing wrong with the code. The problem was that I was trying to drag and drop a GameObject with the interface component on it to the UnityEngine.Object field. So, it was serializing the GameObject, not the MonoBehaviour. I had gotten stuck at that point in my testing and hadn't even moved onto the ScriptableObject.

    It raises probably the most important issue with casting a UnityEngine.Object, that you have no guarantee that what is assigned to that field actually implements the interface. What I had to do was to open two inspector windows, select my test object with the public field, lock one inspector, then select the other object with the interface component and specifially drag the component onto the field.

    I figured this out because I had looked back at the previous posts much earlier in the thread that basically do the exact same thing as spiney's solution (serialize a UnityEngine.Object and cast it as an interface at run time), but they did not have that functionality encapsulated into a generic class. So, the previous posts required doing the boilerplate by hand, like having multiple members for one logical object. In those previous examples they were using the "as" operator to cast to the interface. I could not get that working either! So, I realized I must be doing something wrong and that's when it finally hit me.

    So, the code works fine, I was just not plugging it in properly. I'm happy to have figured that out.

    On the topic of using "as" for casting. That's also what you can do in the InterfaceReference.Value getter, and it's probably better because it won't throw an exception, and then the client can null check on Value before using it. Perhaps it would even be useful to perform an == null check on the UnityEngine.Object in the getter to mitigate the concerns about Unity's fake null. I had said that you have to do (T)(object)_object, but that's not true you can also use as.
     
    Last edited: Nov 16, 2023
    spiney199 likes this.
  27. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    7,892
    Ah, yep, that would've been it.

    I admittedly forgot to check my own implementation but I do remember that my Odin Property Processor (magical stuff if you've never used one) that I use to handle the drawer for this class does check for any components that implement the wanted interface on it if it sees that a GameObject has been assigned.

    It actually handles plain C# classes and Unity objects by having a SerializeReference field for the plain C# object, and a regular SerializeField... field for Unity objects.

    I admittedly don't use the class too much myself.
     
    CodeRonnie likes this.
  28. CodeRonnie

    CodeRonnie

    Joined:
    Oct 2, 2015
    Posts:
    530
    Here is a slightly modified solution that works for both MonoBehaviours and ScriptableObjects, without requiring a custom property drawer or plugin such as Odin.
    Code (CSharp):
    1. using UnityEngine;
    2.  
    3. [System.Serializable]
    4. public sealed class InterfaceReference<T> where T : class
    5. {
    6.     [SerializeField]
    7.     private UnityEngine.Object _objectValue;
    8.  
    9.     public UnityEngine.Object ObjectValue => _objectValue;
    10.  
    11.     // Choose one of the following Value getters:
    12.  
    13.     // Fails silently and returns null on inappropriate object assignment.
    14.     public T Value => (_objectValue == null) ? null : _objectValue as T;
    15.  
    16.     // Fails loudly and throws an exception on inappropriate object assignment.
    17.     public T Value => (_objectValue == null) ? null : (T)(object)_objectValue;
    18. }
    And here it is in use.
    Code (CSharp):
    1. using UnityEngine;
    2.  
    3. public class TestScript : MonoBehaviour
    4. {
    5.     [SerializeField]
    6.     private InterfaceReference<ITestInterface> InterfaceReference = new();
    7.  
    8.     private void Start()
    9.     {
    10.         ITestInterface value = InterfaceReference.Value;
    11.         if(value != null)
    12.             Debug.Log(value.Message);
    13.  
    14.         if(InterfaceReference.ObjectValue is MonoBehaviour mb)
    15.             Destroy(mb.gameObject);
    16.     }
    17.  
    18.     private void Update()
    19.     {
    20.         // Because of the == null check in the Value getter, this properly logs "Destroyed."
    21.         // Without the null check it does incorrectly continue to log the Message value.
    22.         ITestInterface value = InterfaceReference.Value;
    23.         string message = (value != null) ? value.Message : "Destroyed";
    24.         Debug.Log(message);
    25.     }
    26. }
    And here are the other relevant classes for completeness.
    Code (CSharp):
    1. public interface ITestInterface
    2. {
    3.     string Message { get; }
    4. }
    5.  
    6. public class InterfaceComponent : MonoBehaviour, ITestInterface
    7. {
    8.     [SerializeField]
    9.     string _message;
    10.     public string Message => _message;
    11. }
    12.  
    13. [CreateAssetMenu(fileName = nameof(InterfaceAsset), menuName = nameof(InterfaceAsset))]
    14. public class InterfaceAsset : ScriptableObject, ITestInterface
    15. {
    16.     [SerializeField]
    17.     string _message;
    18.     public string Message => _message;
    19. }
    I will probably implement a custom property drawer at some point for myself for a more robust solution, but it's nice to have this option in mind. The danger is that it is easy to assign an inappropriate value, like assigning a GameObject rather than the MonoBehaviour on the GameObject. If you would prefer for those situations to fail loudly, use the (T)(object) cast, if you prefer it to just silently return null us the "as" operator.
     
    sandolkakos and spiney199 like this.
  29. karl_jones

    karl_jones

    Unity Technologies

    Joined:
    May 5, 2015
    Posts:
    8,282
    Can you explain what you mean by this? SerializeReference does serialize by reference, not value.
    The reference is limited to the asset its inside of and can not be a Unity Object, assets solve that problem.
     
  30. vakuor

    vakuor

    Joined:
    Jul 9, 2018
    Posts:
    17
    Hi, @CodeRonnie
    Just checked your example. That's a good solution for interface reference. It is also very inconvenient that to create a link you need to drag the component and not the gameobject, but ok.
    The only thing what worries me and is a big problem is the double casting of the reference when getting the value.

    I did some perfomance tests on our solutions vs default [SerializeField] on component and here are results:

    I put all your and mine code to project. Here are test scripts I used.
    P.S.: ButtonAttribute is from Odin to call method directly from inspector. Can be changed to ContextMenuAttribute.

    1) Script to call a Value.Message directly from component.
    Code (CSharp):
    1. public class TestScriptDefault : MonoBehaviour
    2. {
    3.     [SerializeField]
    4.     private InterfaceComponent ComponentReference = new();
    5.     private Stopwatch sw = new();
    6.     private string str;
    7.     private long callTime;
    8.  
    9.     [Button]
    10.     private void Test()
    11.     {
    12.         GetComponentValue();
    13.         Debug.Log("Default call: " + callTime + " ms.");
    14.     }
    15.  
    16.     private void GetComponentValue()
    17.     {
    18.         sw.Reset();
    19.         sw.Start();
    20.         for (int i = 0; i < 10000000; i++)
    21.         {
    22.             str = ComponentReference.Message;
    23.         }
    24.         sw.Stop();
    25.         callTime = sw.ElapsedMilliseconds;
    26.     }
    27. }
    2) Script to call a InterfaceReference.Value.Message from your implementation.
    Code (CSharp):
    1. public class TestScriptSerializedInterface : MonoBehaviour
    2. {
    3.     [SerializeField]
    4.     private InterfaceReference<ITestInterface> InterfaceReference = new();
    5.     private Stopwatch sw = new();
    6.     private string str;
    7.     private long callTime;
    8.  
    9.     [Button]
    10.     private void Test()
    11.     {
    12.         GetInterfaceValue();
    13.         Debug.Log("Interface call: " + callTime + " ms.");
    14.     }
    15.  
    16.     private void GetInterfaceValue()
    17.     {
    18.         sw.Reset();
    19.         sw.Start();
    20.         for (int i = 0; i < 10000000; i++)
    21.         {
    22.             str = InterfaceReference.Value.Message;
    23.         }
    24.         sw.Stop();
    25.         callTime = sw.ElapsedMilliseconds;
    26.  
    27. }
    3) Script to call Awake (to measure GetComponent collect time) and Value.Message from my implementation.
    Code (CSharp):
    1. public class TestScriptGetComponent : ComponentPicker<ITestInterface>
    2. {
    3.     private string str;
    4.     private Stopwatch sw = new();
    5.     private long callTimeAwake;
    6.     private long callTimeValue;
    7.  
    8.     [Button]
    9.     private void Test()
    10.     {
    11.         CallAwake();
    12.         GetDirectValue();
    13.         Debug.Log("GetComponent Awake: " + callTimeAwake + " ms. Call: " + callTimeValue + " ms. Sum: " + (callTimeAwake + callTimeValue));
    14.     }
    15.  
    16.     private void CallAwake()
    17.     {
    18.         sw.Reset();
    19.         sw.Start();
    20.         for (int i = 0; i < 10000000; i++)
    21.         {
    22.             base.Awake();
    23.         }
    24.         sw.Stop();
    25.         callTimeAwake = sw.ElapsedMilliseconds;
    26.     }
    27.  
    28.     private void GetDirectValue()
    29.     {
    30.         sw.Reset();
    31.         sw.Start();
    32.         for (int i = 0; i < 10000000; i++)
    33.         {
    34.             str = Value.Message;
    35.         }
    36.         sw.Stop();
    37.         callTimeValue = sw.ElapsedMilliseconds;
    38.     }
    39. }
    I test it multiple times and almost always I am getting same results:
    upload_2023-11-18_13-29-35.png

    As you can see I get depressing values.
    Default direct call is ofc most perfomant - ~136ms.
    Your interface call is almost 4-5 times slower - ~508ms.
    My interface call is awfully bad on Awake, but well perfomant on Value call: Awake ~2345ms, Call ~163ms.

    As a result I made the following assumptions.

    Ronnie's option is good when:
    - during development you have a large number of objects actively spawning (units in strategies, for example)
    - you do not access the value often

    vakuor's option is good when:
    - you are not actively re-creating objects OR you are using a pool of objects
    - at the same time you need to frequently access the interface value

    Ronnie's solution: https://forum.unity.com/threads/serialized-interface-fields.1238785/page-2#post-9474235
    vakuor's solution: https://forum.unity.com/threads/serialized-interface-fields.1238785/#post-9469838

    In conclusion, I can say that there are many options for implementing this feature, but anyway we will not be able to make the best solution for this task without the participation of Unity.

    #unitypleasefix #unity #unityhelp
     
    sandolkakos and CodeRonnie like this.
  31. sandolkakos

    sandolkakos

    Joined:
    Jun 3, 2009
    Posts:
    285
    Wow, thanks @vakuor and @CodeRonnie for pushing this.
    Really cool performance Analysis @vakuor, just a hint, those performance results can be different in a compiled build running on your device, so if you want to have a more trustable analysis you can also test it directly on Android or iOS device. :)
     
    vakuor likes this.
  32. vakuor

    vakuor

    Joined:
    Jul 9, 2018
    Posts:
    17
    Thanks for the advice. I don't really want to waste time on this. My opinion is that this is a flaw of the engine.

    I hope that the Unity guys will pay attention to us someday. Some. Day...
     
    sandolkakos likes this.
  33. Ninquiet

    Ninquiet

    Joined:
    Sep 21, 2015
    Posts:
    10
    is this already implemented?
     
  34. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    7,892
    Yes and no?

    We can serialise non-Unity objects via interfaces with
    [SerializeReference]
    since 2019. We still cannot serialise references to Unity objects via interfaces.
     
  35. PascalPieper

    PascalPieper

    Joined:
    Sep 14, 2018
    Posts:
    2
    Still waiting for this feature. Unity really doesn't work on any features that support better architecture anymore.
     
    tantx, gnore, iDerp69 and 2 others like this.
  36. JonathonOH

    JonathonOH

    Joined:
    Mar 22, 2020
    Posts:
    1
    Every few months I end up back at this thread.
    + 1
     
    iDerp69 and Vectrex like this.
  37. mattstromaneveri

    mattstromaneveri

    Joined:
    Oct 19, 2020
    Posts:
    4
    We have been using Unity 2021 for a while now and are in the middle of moving to 2022. We have successfully been able to utilize interfaces in a polymorphic way in the inspector for some time now using [SerializeReference] and a very nice and light weight open source option called Serialize Reference Extensions. Jason Weimann actually just did a great video on this recently that I recommend checking out.
     
    sandolkakos, iDerp69 and CodeRonnie like this.
  38. iDerp69

    iDerp69

    Joined:
    Oct 27, 2018
    Posts:
    49
    That's what's so aggravating... a tiny library adds exactly the feature that should already be in the engine to begin with. Even Jason agrees with this in the video. It would take one engineer at Unity less than a week to implement this.
     
    tantx and sandolkakos like this.
  39. karl_jones

    karl_jones

    Unity Technologies

    Joined:
    May 5, 2015
    Posts:
    8,282
    This is really useful and we did look into adding something similar when the feature first came out but it didn't get very far. We hit some walls with missing functionality which has since been added. Ill bring this up with the team and see if we can look into doing something like this although I suspect it will take longer than a week ;)
     
    Last edited: Feb 15, 2024
  40. iDerp69

    iDerp69

    Joined:
    Oct 27, 2018
    Posts:
    49
    :DIncredible... I'll confess the week thing was a bit out of pocket and optimistic, but uh... thank you! There is clearly a use-case for this and the community would really appreciate it!
     
    tantx, sandolkakos and karl_jones like this.
  41. sandolkakos

    sandolkakos

    Joined:
    Jun 3, 2009
    Posts:
    285
    For sure, I really appreciate having the ability to simply serialize my interfaces used by MonoBehaviors and ScriptableObjects :)
     
  42. Badlek

    Badlek

    Joined:
    Jan 13, 2024
    Posts:
    2
    +1000 Add this feature
     
  43. Katerlad

    Katerlad

    Joined:
    Mar 26, 2015
    Posts:
    19
    Probably Commented on this thread before, but here to bump it again to show that months or years later, this is still something I wish was in Unity natively to this day. Sorry for the bump, but the more people see it, the more people that agree and comment the more unity may put it higher and higher on their priority list.
     
    sandolkakos and In2Play like this.
  44. najati

    najati

    Joined:
    Oct 23, 2017
    Posts:
    42
    Would be nice!
     
    sandolkakos likes this.
  45. phillipeaam

    phillipeaam

    Joined:
    Apr 18, 2017
    Posts:
    1
    Hey Unity Team, ;)

    I hope this message finds you well! I wanted to take a moment to express how much the Unity development community would benefit from having serialization support for interfaces.

    As many of us know, interfaces serve as a crucial tool for building modular, extensible, and maintainable code. However, the current limitation in Unity regarding serialization of interfaces can sometimes pose challenges, especially when it comes to designing complex systems or implementing certain design patterns. :(

    Having serialization support for interfaces would open up a world of possibilities for Unity developers. It would enable us to design more flexible and reusable components, streamline our workflows, and ultimately create better games and applications. :D

    I understand that implementing this feature may require significant and challenging refactoring of the underlying serialization system. Still, I believe that investing in this enhancement would be immensely valuable for the Unity ecosystem in the long run. :rolleyes:

    I wanted to kindly request that serialization support for interfaces be considered as a priority for future Unity releases. I wholeheartedly support and upvote this request, and I'm sure many others in the community share the same sentiment.

    Thank you for considering our feedback, and we look forward to seeing continued improvements in Unity. :cool:
     
    Katerlad, In2Play and sandolkakos like this.
  46. SereDim

    SereDim

    Joined:
    Jul 22, 2019
    Posts:
    1
    why you did not use it?
    Code (CSharp):
    1. using System;
    2. using UnityEngine;
    3.  
    4. [Serializable]
    5. public class LinkInterface<T1> : ISerializationCallbackReceiver where T1 : class
    6. {
    7.     [SerializeField] MonoBehaviour _mono;
    8.     public T1 value;
    9.  
    10.     public void OnBeforeSerialize()
    11.     {
    12.         if (_mono!= null && _mono is not T1)
    13.         {
    14.             _mono= null;
    15.         }
    16.     }
    17.  
    18.     public void OnAfterDeserialize()
    19.     {
    20.        value = _mono as T1;
    21.     }
    22. }
     
    Last edited: Apr 11, 2024
    sandolkakos likes this.
  47. sandolkakos

    sandolkakos

    Joined:
    Jun 3, 2009
    Posts:
    285
    I haven't tested that solution yet, but it looks like a good candidate to be used in my projects before Unity implements it natively. Some points:

    - we have to define every field with `[SerializeField] private LinkInterface<IMyInterface> myObject;`
    but it is better than having to cast every `MonoBehavior` field to `IMyInterface` in every class.

    - we have to access it via `myObject.value`
    but I guess we can avoid that by implementing the implicit/explicit operators

    - we will have a 2nd field level on the inspector due to it being a Seriazable class
    but we create a custom inspector for it.

    Thanks for the hint, @SereDim! I will definitely try it out when I have the chance.
     
    tantx likes this.
  48. jamie_xr

    jamie_xr

    Joined:
    Feb 28, 2020
    Posts:
    67
    @SereDim Generic type classes cannot be serialized without a concrete implementation. `List<T>` is the only exception - but it's exactly that, an exception. Another limitation that would be great to see support for.
     
    sandolkakos likes this.
  49. SisusCo

    SisusCo

    Joined:
    Jan 29, 2019
    Posts:
    1,329
    Actually, the serializer can already handle generic types these days, with both SerializeField (since Unity 2020.1) and SerializeReference (since Unity 2023.1).
     
    spiney199, sandolkakos and CodeRonnie like this.