Search Unity

Feature Request Serialized interface fields

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

  1. lumpn

    lumpn

    Joined:
    Apr 8, 2020
    Posts:
    6
    Problem

    It is not possible to assign an interface-type field in the inspector. The field does not show up.
    Code (CSharp):
    1. public interface ICharacterData { ... }
    2.  
    3. [CreateAssetMenu]
    4. public class CharacterData : ScriptableObject, ICharacterData { ... }
    5.  
    6. public class Demo : MonoBehaviour
    7. {
    8.   [SerializeField] private CharacterData data1; // works
    9.   [SerializeField] private ICharacterData data2; // does not work
    10. }

    Why are interface-type fields not serializable?

    Unity's serialization system needs to know the type of the serialized field, because it will do something different depending on the type. Basic types, structs, enums, built-in types, and serializable types gets serialized by value, similar to how JsonUtility would serialize them. Anything derived from UnityEngine.Object (including ScriptableObject, MonoBehaviour, and any asset in the project) gets serialized by reference instead, i.e. like this:

    data1: {fileID: 11400000, guid: 961cd3964a6354efb9ad01cbfae84d63, type: 2}
    In order for the serialization system to determine whether to serialize a field by value or by reference it needs to know whether the type is derived from UnityEngine.Object or not.

    In our example, ICharacterData could be anything. We could have a serializable class implementing the interface. We could also have a ScriptableObject asset implementing it. The serialization system can not know which case we're dealing with and the inspector can not know whether to show an object field or a value field.


    But interface-type fields are serializable!

    Since Unity 2019.3 interface-type fields are serializable. Instead of the SerializeField attribute, we have to use the SerializeReference attribute.

    Problem solved, right? ...
    Code (csharp):
    1. public class Demo : MonoBehaviour
    2. {
    3.   [SerializeField] private CharacterData data1; // works
    4.   [SerializeReference] private ICharacterData data2; // still does not work
    5. }
    Contrary to what the name of the attribute implies, SerializeReference serializes the field by value, not by reference.
    It does solve an important problem related to serializing polymorphic types by value, but it does not solve our problem.


    Just use ScriptableObject!
    The documentation suggests that we should use ScriptableObject here. That's a great suggestion. In fact, we are using ScriptableObject here. We just want to use an interface as the field type for it.
    Why though? Why are we being so stubborn? Why not use the concrete type instead?


    What is so special about interfaces?

    Remember the diamond problem? It leads to tricky ambiguities. That's why C# only allows single inheritance. In C# a class can not inherit from multiple base classes at the same time. It can, however, implement multiple interfaces at the same time. That's because interfaces don't have the diamond problem. Interfaces have superpowers.

    Programming in C# means programming with interfaces. If you don't do that then you're seriously limiting the kinds of inheritance patterns that can be used. Chances are you won't notice the problem until the codebase is very large, because concrete types and abstract base classes go a long way. Until they don't. At which point the only way forward is to build a parallel hierarchy of interfaces and replacing all uses of concrete types with their respective interface type.

    Except doing so breaks serialized fields.

    I can give you multiple examples of real world codebases of real games where using interfaces became a necessity, but those examples are all big, very specific, and hard to understand without context. For now, please just take my word for it: interfaces are important. Being able to refer to a type by interface is important. Other programmers reading this might want to confirm.


    What can we do about it?

    Code (CSharp):
    1. public class Demo : MonoBehaviour
    2. {
    3.   [SerializeField, Restrict(typeof(ICharacterData))]
    4.   private UnityEngine.Object _data; // works
    5.  
    6.   public ICharacterData data => _data as ICharacterData;
    7. }
    For our field we want Unity to show an object field in the inspector. So we might as well use UnityEngine.Object as the type. However, we only want to be able to assign objects that implement our interface. We can restrict what can be assigned to the object field by implementing a custom attribute and property drawer.

    That essentially solves our problem. It's a bit clunky and the extra get-only property with cast smells bad, but it works. Without help from Unity, this is as good as it gets.


    Feature request
    Code (CSharp):
    1. public class Demo : MonoBehaviour
    2. {
    3.   [SerializeField] private ICharacterData data; // this should just work
    4. }
    Unity can serialize references to assets. In fact, Unity can serialize references to assets via base type. Assigning a CharacterData asset to a field of type UnityEngine.Object via the inspector works just fine. Unity serializes the reference to the asset and on deserialization Unity looks up the concrete asset by reference (fileId, guid, type) and assigns it to the field.

    I don't see any reason why it would be impossible to do the same for interface-type fields.

    It wouldn't even require a new attribute. Just use SerializeField. The semantics are unambiguous:
    1. If the type is an interface or inherited from UnityEngine.Object, serialize by reference.
    2. In all other cases, serialize by value.
    It wouldn't even clash with SerializeReference. Users who want to have their interface-typed field serialized by value can still do so.

    TL;DR: Please support serializing interface-type fields the same as UnityEngine.Object type fields.
     
  2. Tautvydas-Zilys

    Tautvydas-Zilys

    Unity Technologies

    Joined:
    Jul 25, 2013
    Posts:
    10,680
    While this doesn't address the feature request (I'm the wrong person for that), you could do this:

    Code (csharp):
    1. class CharacterData : ICharacterData
    2. {
    3. }
    4.  
    5. class ICharacterData : ScriptableObject, ICharacterDataBase
    6. {
    7. }
    8.  
    9. interface ICharacterDataBase
    10. {
    11.      // Actual interface methods
    12. }
    13.  
    14. public class Demo : MonoBehaviour
    15. {
    16.     [SerializeField] private ICharacterData data; // this works now.
    17. }
     
  3. FunctionOverflu

    FunctionOverflu

    Joined:
    Jul 20, 2015
    Posts:
    7
    I think the point is to be able to reference any UnityEngine.Object subclass instance (not just ScriptableObject) with an interface.
    PLUS in your code, ICharacterData is not even an interface but a class. And you can only inherit from "one" class.
    It needs to be an interface so another class can implement it as one of the many interfaces it wants to implement.
     
    Last edited: Feb 12, 2022
  4. FunctionOverflu

    FunctionOverflu

    Joined:
    Jul 20, 2015
    Posts:
    7
    * Hence the alternative solution you provided wouldn't solve what OP's proposed feature is trying to solve/can provide.
     
  5. lumpn

    lumpn

    Joined:
    Apr 8, 2020
    Posts:
    6
    Yes, that would "solve" the example I used to illustrate the problem. However, as pointed out, this solution is not an option, because ICharacterData is not an interface.

    Here is an equally contrived example that is less easy to "solve" using existing functionality:

    Code (CSharp):
    1.  
    2. public interface IValueProvider
    3. {
    4.   float value { get; }
    5. }
    6.  
    7. [CreateAssetMenu]
    8. public class FixedValueProvider : ScriptableObject, IValueProvider
    9. {
    10.   [SerializeField] private float _value;
    11.  
    12.   public float value => _value;
    13. }
    14.  
    15. public class DynamicValueProvider : MonoBehaviour, IValueProvider
    16. {
    17.   private float _value;
    18.  
    19.   public float value => _value;
    20.  
    21.   void Update()
    22.   {
    23.     _value += Time.deltaTime;
    24.   }
    25. }
    26.  
    27. public class ValueUI : MonoBehaviour
    28. {
    29.   [SerializeField] private IValueProvider _valueProvider; // does not work!
    30.   [SerializeField] private Text _text;
    31.  
    32.   void Update()
    33.   {
    34.     _text.text = string.Format("Value: {0}", _valueProvider.value);
    35.   }
    36. }
    37.  
    As you can see, we have two classes derived from different base classes, and we want to be able to assign an instance of either one of them to a field. The only way to unify them is through an interface. This is why interfaces are important.
     
  6. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    7,935
    My input, rather than waiting for Unity to implement this (if ever), just invest in Odin Inspector which can serialise interfaces, including that of interfaces implemented by UnityEngine.Objects.
     
    phillipeaam and trombonaut like this.
  7. mitaywalle

    mitaywalle

    Joined:
    Jul 1, 2013
    Posts:
    253
    Odin serializer doesnt work with nested prefabs
     
    phillipeaam and Nit_Ram like this.
  8. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    7,935
    Thank you for pointing out things I already know.
     
  9. mitaywalle

    mitaywalle

    Joined:
    Jul 1, 2013
    Posts:
    253
    Sorry, this was for everyone, as your previous post
     
    nickbezhok, tantx and trombonaut like this.
  10. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    7,935
    Yeah I was being a bit of an ass.

    In any case Odin serialisation is currently deprecated for prefabs in general, BUT, the Odin devs are actively working with Unity's prefab team to bring full support back.

    None of this is on topic, mind you.
     
    mitaywalle likes this.
  11. Rabadash8820

    Rabadash8820

    Joined:
    Aug 20, 2015
    Posts:
    94
    +1000 on this feature request. My use case is dependency injection. It's way easier to test a component when its dependencies (public fields that reference other components) are declared as interfaces rather than concrete types. Then I can simply mock those interfaces in tests and not have to build out an entire scene hierarchy. Without interfaces being serialized, I have had to build my own custom dependency injection library from scratch. While I understand that this is all way easier said than done, for all of the aforementioned reasons, this is really something that Unity should support natively.
     
    Chubzdoomer, KuanMi, andyz and 9 others like this.
  12. sandolkakos

    sandolkakos

    Joined:
    Jun 3, 2009
    Posts:
    285
    I don't really understand why we are still here asking for that feature. This is one of the main missing features for people that wants to create good project architectures.

    C'mon Unity team, that should not be a big effort for you to implement, or am I missing something?
     
    tvtyler and leni8ec like this.
  13. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    7,935
    It would require a completely different method of serialisation to accomplish, so definitely not a small task. Considering how completely divergent SerializeReference is to SerializeField, I would I imagine to serialisation required to allow either referenced types or managed Unity objects to be serialised by the same field is it's own barrel of fish.

    FYI the Odin serialiser is open source and completely free to use: https://github.com/TeamSirenix/odin-serializer

    No inspector support, but it shouldn't be hard to get your own field working.
     
    sandolkakos likes this.
  14. IAmTheQueenOfEngland

    IAmTheQueenOfEngland

    Joined:
    Mar 6, 2015
    Posts:
    1
    If you don't want to use Odin for some reason, I've created small drawer that allows to use interfaces with [SerializeReference] that have serializable-s as implementations(not UnityEngine.Object). It will generate drop down in UI, so you can select concrete implementation in inspector.
    https://github.com/ArseniiRudenko/unity_serialize_interface
     
    Last edited: Dec 2, 2022
    _met44 and sandolkakos like this.
  15. sandolkakos

    sandolkakos

    Joined:
    Jun 3, 2009
    Posts:
    285
    I see, thanks for the explanation. But it should definitely have a way for Unity to do it as a built-in feature with no need of that big change since we can find some repos where people create workarounds such as Odin and @IAmTheQueenOfEngland, for instance.

    in 2021 I tried using the one from Odin, but I did not like it since the serialized data does not work well when you need to rename the class or namespace of the object added to the inspector. So I gave up and changed it to serialize `Object` or `ScriptableObject` and then having a property to cast it to my interface like:

    Code (CSharp):
    1. [SerializeField]
    2. private ScriptableObject[] controllers;
    3.  
    4. private IEnumerable<IController> Controllers => controllers.OfType<IController>();
     
  16. RogueStargun

    RogueStargun

    Joined:
    Aug 5, 2018
    Posts:
    296
    +1000 on this feature request. Composition over inheritance is almost always better eh
     
    KuanMi, tvtyler and sandolkakos like this.
  17. KevinNorth

    KevinNorth

    Joined:
    Mar 4, 2019
    Posts:
    1
    I would also like to see this feature implemented.
     
    tvtyler and sandolkakos like this.
  18. Fizi

    Fizi

    Joined:
    Nov 19, 2014
    Posts:
    3
    +1
     
    tvtyler and sandolkakos like this.
  19. MehdiZarei

    MehdiZarei

    Joined:
    Mar 22, 2019
    Posts:
    2
    +1
    Everyone who loves clean architecture knows the importance of interfaces
     
  20. FranciscoFontes

    FranciscoFontes

    Joined:
    Jul 12, 2014
    Posts:
    3
    Hi, for me it worked like this:


    Code (CSharp):
    1. using UnityEngine;
    2.  
    3. public class Demo : MonoBehaviour
    4. {
    5.     public Object characterDataObject;
    6.     private ICharacterData characterData;
    7.  
    8.     private void Start()
    9.     {
    10.         characterData = characterDataObject as ICharacterData;
    11.     }
    12. }
    13.  
    With the "Object" type, the scriptable object can be attached. Then just do a casting for the interface.

    upload_2023-1-25_16-12-46.png

    Hope it's useful!
     

    Attached Files:

    leni8ec and sandolkakos like this.
  21. sandolkakos

    sandolkakos

    Joined:
    Jun 3, 2009
    Posts:
    285
    Yep, that is the better approach at the moment and is the one I am using, but not ideal in my opinion since:
    1. we need to declare 2 fields and then cast (this is annoying and noisy) :(
    2. we can drag and drop any Object in the serialized field (which can fail the cast if the wrong one is attached)
     
  22. FranciscoFontes

    FranciscoFontes

    Joined:
    Jul 12, 2014
    Posts:
    3

    I agree, the ideal would be to have this feature native to Unity without this additional work.
     
    sandolkakos likes this.
  23. mitaywalle

    mitaywalle

    Joined:
    Jul 1, 2013
    Posts:
    253
    About two-fields solution:

    https://github.com/mitay-walle/SerializedInterfaceReference

    1. problem - allow drag wrong-typed references
    solution: clear reference in OnBeforeSerialize and in property setters if wrong-typed

    2. problem - need cast value somewhere
    solution: cast value in OnAfterDeserialize and in property setter whe assign

    3. problem - UnityEngine.Object references can't be saved to same field as C# plain class.
    solution: use SerializeReference with interface and create plain class-container, including field with reference and inherited from interface

    Basic Code
    Code (CSharp):
    1.  
    2. using System;
    3. using UnityEngine;
    4.  
    5. [Serializable]
    6. public class SerializedInterfaceReference<T1, T2> : ISerializationCallbackReceiver where T1 : UnityEngine.Object
    7. {
    8.     [SerializeField] T1 _classField;
    9.  
    10.     public T1 ClassField
    11.     {
    12.         get => _classField;
    13.         set
    14.         {
    15.             if (value is T2 field)
    16.             {
    17.                 InterfaceValue = field;
    18.                 _classField = value;
    19.             }
    20.             else if (value == default)
    21.             {
    22.                 InterfaceValue = default;
    23.                 _classField = value;
    24.             }
    25.             else
    26.             {
    27.                 throw new InvalidCastException($"Can't assign {value.GetType()} to {typeof(T2)}");
    28.             }
    29.         }
    30.     }
    31.  
    32.     T2 _interfaceValue;
    33.  
    34.     public T2 InterfaceValue
    35.     {
    36.         get => _interfaceValue;
    37.         set
    38.         {
    39.             _interfaceValue = value;
    40.             ClassField = value as T1;
    41.         }
    42.     }
    43.  
    44.     public void OnBeforeSerialize()
    45.     {
    46.         if (ClassField != null && ClassField is not T2)
    47.         {
    48.             ClassField = null;
    49.         }
    50.     }
    51.  
    52.     public void OnAfterDeserialize()
    53.     {
    54.         InterfaceValue = ClassField is T2 field ? field : default;
    55.     }
    56.     public static implicit operator T1(SerializedInterfaceReference<T1, T2> d) => d._classField;
    57. }
    58.  
    Usage Example.

    1. We can drag 'OtherScriptableObject'-value to '_referenceExample', cause saved ReferenceExample.ClassField type is ScriptableObject, but it would be nullified in OnBeforeSerialize.

    2. _referenceExample.Value is available after deserialization and no need to execute cast somewhere

    3. we can store C# plain class ( ClassValueExample ) and Container (ReferenceExample) with reference to ScriptableObject (CorrectScriptableObject) at same IInterface serialized field

    SerializedInterfaceReference.gif
    Code (CSharp):
    1. public interface IInterface
    2. {
    3.     void Execute();
    4. }
    5.  
    6. [Serializable]
    7. public class ReferenceExample : SerializedInterfaceReference<ScriptableObject, IInterface>, IInterface
    8. {
    9.     void IInterface.Execute() => InterfaceValue.Execute();
    10. }
    11.  
    12. [Serializable]
    13. public class ClassValueExample : IInterface
    14. {
    15.     public float floatField = 5;
    16.  
    17.     void IInterface.Execute() { }
    18. }
    19.  
    20. [CreateAssetMenu]
    21. public class OtherScriptableObject : ScriptableObject
    22. {
    23.     [SerializeReference] public IInterface _referenceExample;
    24.  
    25.     void ExecuteAnywhere()
    26.     {
    27.         IInterface value = _referenceExample;
    28.         ScriptableObject scriptableObject = _referenceExample as ReferenceExample;
    29.     }
    30. }
    31.  
    32. [CreateAssetMenu]
    33. public class CorrectScriptableObject : ScriptableObject, IInterface
    34. {
    35.     void IInterface.Execute() { }
    36. }
     
    Last edited: Jan 28, 2023
    sandolkakos likes this.
  24. cloud02468

    cloud02468

    Joined:
    Oct 7, 2020
    Posts:
    15
    +1 need this feature
     
  25. iDerp69

    iDerp69

    Joined:
    Oct 27, 2018
    Posts:
    49
    +1 would make life so much easier
     
  26. mitaywalle

    mitaywalle

    Joined:
    Jul 1, 2013
    Posts:
    253
  27. najati

    najati

    Joined:
    Oct 23, 2017
    Posts:
    42
    Curious that this is (still?) an issue given how generally mature Unity is. Seems like it would be a fairly fundamental use case for object properties.

    I'm curious how developers that don't find this an issue design around it. Would love to leverage some basic OO best practices but having to cram everything into an inheritance hierarchy is kinda a nonstarter.
     
  28. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    7,935
    It's probably never going to happen. Unity's serialisation is basic because it needs to be fast. SerializeReference is already slower than standard serialisation, and anything more complex would probably be slower yet.

    There are a number of assets out there that add this functionality should you require it.

    Otherwise you can still absolutely use interfaces, you will just have to change up how you use them in the context of Unity. Most often I use an interface + base class arrangement. More or less gets the same result.

    Also I just noticed this in the original post:
    This is false. SerializeReference serialises a reference to a section of managed serialised data. You can absolutely serialise reference-based hierarchies within the same UnityEngine.Object.
     
    iDerp69 likes this.
  29. najati

    najati

    Joined:
    Oct 23, 2017
    Posts:
    42
    Definitely appreciate the performance concerns. Mostly just wanting to add my voice to those hoping this gets a first-class solution at some point. I'm holding out hope that Unity's still open to some more considerable—even potentially breaking—changes if that's what it takes to keep it feeling modern.

    I'd happily pay a reasonable performance tax to be able to elect into a little more serialization flexibility. Maybe extend SerializeReference, maybe another attribute.

    I'm a big fan of Odin, in the Editor it is indispensable.

    Yet, all the "You're on your own." messages you have to click to use what seems like should be basic features along with the nested prefab scariness make it a bit more a minefield at runtime than I'm comfortable with. As we get closer to release I'm looking to remove our runtime dependency on it. Interface references to MonoBehaviours would get me most of the way there.

    You mention "a number of assets". Is there one that's generally regarded at the best solution to this? I have yet to find one that doesn't include boilerplate in my behaviours. Thanks!
     
  30. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    7,935
    Looks like you're already using the best option:
    You only see the 'you're on your own' messages in relation to prefabs, which have their entirely own serialisation format that Odin (as of yet) can't emulate well enough to call it a supported featured. The devs have said the might be on the verge of solving this, however.

    Otherwise in non-prefab components and scriptable objects, it's been 100% reliable in my experience. That said, I only use Odin serialisation where absolutely necessary, as - even as fast as it is - it has a performance overhead as well.
     
  31. jamie_xr

    jamie_xr

    Joined:
    Feb 28, 2020
    Posts:
    67
    @spiney199 Why do you think it will be slower for interfaces than concrete types?
     
  32. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    7,935
    Anything that isn't in line with Unity's super basic serialisation is going to be slower. There's no getting around that.

    Case in point: SerializeReference is slower than standard serialisation (as noted in the docs).
     
  33. DragonCoder

    DragonCoder

    Joined:
    Jul 3, 2015
    Posts:
    1,700
    Ran yesterday for the first time into this problem. Just wanted to have a list of components which have one common method but inherit from different classes. In code I can nicely define an Interface for this usecase but there's no way to then assign the components to a List<iInterface> in the editor.

    Ended up making sure that there's only one of the relevant components per game object and then used a List<GameObject> together with GetComponent<iInterface>.

    Works but it's a hassle that adds unnecesary GOs.
     
    chriseborn and sandolkakos like this.
  34. sandolkakos

    sandolkakos

    Joined:
    Jun 3, 2009
    Posts:
    285
    Welcome to the team :D
     
  35. Ukutura

    Ukutura

    Joined:
    Jan 16, 2022
    Posts:
    10
    +1 I'm too dumb for custom property drawers
     
  36. sandolkakos

    sandolkakos

    Joined:
    Jun 3, 2009
    Posts:
    285
    Do not punish yourself, if it was a matter of creating custom property drawers, we would already have resolved it and shared it with everybody. I mean, there are plenty of ideas, but every one of them has downsides, mainly related to doing way more stuff than simply declaring, initializing, and using the serialized field as we would expect :(
     
  37. marcuslelus

    marcuslelus

    Joined:
    Jun 18, 2018
    Posts:
    67
    Can't wait until this is properly implemented in Unity. For now, I personnally use SerializableInterface (which I contributed on ;)) which solve a lot of issues. One being that it properly checks null references for the interface, something you wouldn't get by simply using an interface.

    What do I mean about that? Let's say you have this code:
    Code (CSharp):
    1.  
    2. public interface IMyInterface
    3. {
    4.     void Greet();
    5. }
    6.  
    7. public class MyBehaviour : MonoBehaviour
    8. {
    9.     public IMyInterface mySerializableInterface;
    10.  
    11.     private void Awake()
    12.     {
    13.         if (mySerializableInterface != null)
    14.             mySerializableInterface.Greet();
    15.     }
    16. }
    If your mySerializableInterface is a C# class, then it will behave correctly. But let's say it points to a MonoBehaviour with the IMyInterface interface and the object gets destroyed in the scene. Then, because it's an interface and not an Object, the not-null check will not use Unity's operator-override for == and will do a normal ReferenceEquals(mySerializableInterface, null) operation, which bypasses Unity's lifetime. That means that the not-null check will return true (meaning it is not null in the native layer), but the mySerializableInterface.Greet(); will throw an NullReferenceException, since mySerializableInterface is null in the managed layer. SerializableInterface offers a simple method to check that:
    Code (CSharp):
    1. public class MyBehaviour : MonoBehaviour
    2. {
    3.     public SerializableInterface<IMyInterface> mySerializableInterface;
    4.  
    5.     private void Awake()
    6.     {
    7.         if (mySerializableInterface.IsDefined(out IMyInterface value))
    8.             value.Greet();
    9.     }
    10. }
    The package also lets you points to plain C# classes, ScriptableObjects, prefabs and components in the scene.
     
  38. In2Play

    In2Play

    Joined:
    Apr 25, 2016
    Posts:
    66

    It works great so far. That may be a great tool!

    1. How "safe" is to implement this in a project in terms of something breaking up when new unity version releases?
    2. Also on what platforms was it tested and it works?
    3. Additionally - can be performance an issue here?

    Thanks!

    EDIT:

    I tested on:

    1. iOS
    2. Android
    3. tvOS
    4. macOS

    it works great.
     
    Last edited: May 10, 2023
    sandolkakos likes this.
  39. marcuslelus

    marcuslelus

    Joined:
    Jun 18, 2018
    Posts:
    67
    Hey! Glad to hear it works great for you!


    1. Pretty safe. On the runtime level, it's standard C#, so nothing special there. As for the Editor level, it uses the Legacy GUI, which might get deprecated one day, although not soon. I'm currently looking at changing the code to use UI Toolkit, but the whole thing changes too much to be the reliable solution for now.

    2. We've been using SerializableInterface in multiple projects since last year, mostly in VR (Android), with IL2CPP backend, without any issues. Since IL2CPP is the most restrictive backend, it should work very well in Mono. (we do use it in Mono when we test our application, cuz it's faster to build). We also tested it in WebGL. Again, there's nothing special about the code, it just wraps an interface, there's no reflection or weird code there, so it should work on all platforms.

    3. Didn't test yet. Would be nice to do so if anyone is a benchmark genius. But yes it does - of course - since it's not just a simple reference to an interface, but a wrapper class. The trade-off though is ABSOLUTLY worth it. We've been doing the best architecture design since we've introduced it in our projects. I mean, just about everything is reusable now. It's insane. But! Since you asked, these are the two considerations we've had in terms of performance:

    3.1 Serialization:
    It does have some serialization impacts, although, it's not a lot. Basically, SerializableInterface has 3 fields, which all of them needs to be serialized (in the scene, prefab or in a ScriptableObject..). So while you only deal with an interface, SerializableInterface needs to have a Mode (enum), a UnityReference (Object) and a RawObjectReference (object). This can slightly increase the size of your serialized Object, but it ensures a proper serialization of Unity Objects and raw C# objects (for the null check, for instance).

    3.2 Allocation:
    While an interface field can contain a struct and be allocated on the stack or directly in an object on the heap, SerializableInterface is a class, so it will be declared on the heap and subject to GC. This shouldn't be a huge deal in most cases though. In any case, changing SerializableInterface to a struct wasn't a practical choice for us since we inherit from it.

    Hope it answers your questions. :)
     
  40. Cylau

    Cylau

    Joined:
    Jun 5, 2019
    Posts:
    15
    If someone else is looking for the implementation of this function, I can share my version. I found the basis for it in one of the many threads in which the same topic was discussed and I finished it up to working condition.
    Code (CSharp):
    1. using UnityEngine;
    2.  
    3. public class SerializeInterfaceAttribute : PropertyAttribute
    4. {
    5.     public System.Type SerializedType { get; private set; }
    6.  
    7.     public SerializeInterfaceAttribute(System.Type type)
    8.     {
    9.         SerializedType = type;
    10.     }
    11. }
    Code (CSharp):
    1. using UnityEngine;
    2. using UnityEditor;
    3. using System;
    4. using Object = UnityEngine.Object;
    5.  
    6. [CustomPropertyDrawer(typeof(SerializeInterfaceAttribute))]
    7. public class SerializeInterfaceDrawer : PropertyDrawer
    8. {
    9.     public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
    10.     {
    11.         SerializeInterfaceAttribute serializedInterface = attribute as SerializeInterfaceAttribute;
    12.         Type serializedType = serializedInterface.SerializedType;
    13.  
    14.         if (IsValid(property, serializedType))
    15.         {
    16.             label.tooltip = "Serialize " + serializedInterface.SerializedType.Name + " interface";
    17.             CheckProperty(property, serializedType);
    18.  
    19.             if (position.Contains(Event.current.mousePosition) == true)
    20.             {
    21.                 if (DragAndDrop.objectReferences.Length > 0)
    22.                 {
    23.                     if (TryGetInterfaceFromObject(DragAndDrop.objectReferences[0], serializedType) == null)
    24.                         DragAndDrop.visualMode = DragAndDropVisualMode.Rejected;
    25.                 }
    26.             }
    27.         }
    28.  
    29.         label.text += $" ({serializedType.Name})";
    30.         EditorGUI.PropertyField(position, property, label);
    31.     }
    32.  
    33.     private Object TryGetInterfaceFromObject(Object targetObject, Type serializedType)
    34.     {
    35.         if (serializedType.IsInstanceOfType(targetObject) == false)
    36.         {
    37.             if (targetObject is Component)
    38.                 return (targetObject as Component).GetComponent(serializedType);
    39.             else if (targetObject is GameObject)
    40.                 return (targetObject as GameObject).GetComponent(serializedType);
    41.         }
    42.  
    43.         return targetObject;
    44.     }
    45.  
    46.     private bool IsValid(SerializedProperty property, Type targetType)
    47.     {
    48.         return targetType.IsInterface && property.propertyType == SerializedPropertyType.ObjectReference;
    49.     }
    50.  
    51.     private void CheckProperty(SerializedProperty property, Type targetType)
    52.     {
    53.         if (property.objectReferenceValue == null)
    54.             return;
    55.  
    56.         property.objectReferenceValue = TryGetInterfaceFromObject(property.objectReferenceValue, targetType);
    57.     }
    58. }
    The only condition for using this attribute is that the field must be of the UnityEngine.Object type, so that it is possible to use Drag and Drop to set components and GameObjects into it.
    Code (CSharp):
    1. [SerializeField, SerializeInterface(typeof(IMyInterface))] private UnityEngine.Object _interfaceField;
    But this problem can be easily smoothed out by creating a duplicate property for a field already with the desired interface type.
    Code (CSharp):
    1. private IMyInterface InterfaceProperty => _interfaceField as IMyInterface;
    If you drag a GameObject into this field, on which there are several components implementing the desired interface, then the very first one in the hierarchy will be selected.

    And also, bugs known to me: when several components are selected at the same time, which have the same fields with a serializable interface, they are all overwritten with the value of the first selected component. So it's better not to do that. I haven't figured out how to fix it, maybe someone here will tell me.
     
    sandolkakos likes this.
  41. KuanMi

    KuanMi

    Joined:
    Feb 25, 2020
    Posts:
    41
    This is definitely the feature I most desperately need right now. Hope Unity can realize it soon.
     
  42. Thomas-Mountainborn

    Thomas-Mountainborn

    Joined:
    Jun 11, 2015
    Posts:
    501
    Brilliant, thanks! A coffee has been donated. ♥
     
    In2Play likes this.
  43. In2Play

    In2Play

    Joined:
    Apr 25, 2016
    Posts:
    66
    @marcuslelus hey,

    In the above post I said it was working fine and it does in the empty unity project. However I've decided to add this to my project I'm working on but I'm getting this behavior. Screenshot 2023-06-20 at 17.49.26.png


    The interface is simple and this is MyProgress2 field:

    Code (CSharp):
    1.       public SerializableInterface<IMyProgress2> myprogress2;
    2.  
    Any idea on why this would be happening? Thank you very much

    Screenshot 2023-06-20 at 18.05.10.png
     

    Attached Files:

  44. In2Play

    In2Play

    Joined:
    Apr 25, 2016
    Posts:
    66
    I've found the issue: It's messing with ODIN serializer I have it in my project.
     
  45. Vectrex

    Vectrex

    Joined:
    Oct 31, 2009
    Posts:
    267
    Bump. It's my number one feature request.
     
  46. FoodFish_

    FoodFish_

    Joined:
    May 3, 2018
    Posts:
    58
    Bump. It's my number one feature request as well.
     
    sandolkakos likes this.
  47. Katerlad

    Katerlad

    Joined:
    Mar 26, 2015
    Posts:
    19
    Hey Unity, it would be very nice to hear from you guys on this.

    Also a top feature request for me, serializing interfaces in the inspector is so vital for good architecture.

    Please take us serious on this one and at least update us on the challenges or roadblock of building this into unity.
     
  48. SisusCo

    SisusCo

    Joined:
    Jan 29, 2019
    Posts:
    1,331
    The way I've approached resolving this major pain point is by using dependency injection.

    Clients specify what services they need just by defining an Init function - much like how a plain old C# class would with a constructor.
    Code (CSharp):
    1. public class Enemy : MonoBehaviour
    2. {
    3.     IPlayer player;
    4.     IEvent @event;
    5.     ILogger logger;
    6.  
    7.     public void Init(IPlayer player, IEvent @event, ILogger logger)
    8.     {
    9.         this.player = player;
    10.         this.@event = @event;
    11.         this.logger = logger;
    12.     }
    13. }
    Then the workarounds needed to serialize interface type service references can be offloaded from the client component into a separate class, with the sole responsibility of resolving all the services and passing them to the client.

    This way the code in the client can remain clean and focused, and the unimportant details of resolving all the dependencies are tucked away in a separate class that will rarely need to be read by anyone.

    A simplified example:
    Code (CSharp):
    1. [DefaultExecutionOrder(-1000)]
    2. class EnemyInitializer : MonoBehaviour
    3. {
    4.     [SerializeField] Enemy enemy;
    5.  
    6.     [SerializeField] Any<IPlayer> player;
    7.     [SerializeField] Any<IEvent> @event;
    8.     [SerializeField] Any<ILogger> logger;
    9.  
    10.     void Awake() => enemy.Init(player, @event, logger);
    11. }
    As it happens, this approach also unlocks other useful possibilities, such as the ability to only locate a service lazily at runtime (to resolve a cross-scene reference, as an example) without introducing any additional noise to the client.

    With a custom editor the initializer's editor can be embedded at the top of the client's editor, so the Inspector workflow can remain similar to how it is by default:
    select-init-argument-type.png

    You can see this approach in action in Init(args).
     
    trombonaut and CodeRonnie like this.
  49. NudgeRealityDB

    NudgeRealityDB

    Joined:
    Nov 24, 2020
    Posts:
    2
    Please.
     
    sandolkakos likes this.
  50. vakuor

    vakuor

    Joined:
    Jul 9, 2018
    Posts:
    17
    Hey guys,

    Since Unity is in no hurry to please us with useful updates.
    I can only suggest you to use the following lifehack method for [SerializeField] links to interfaces.

    Example is available on my github:
    https://github.com/vakuor/UnitySerialized

    You need to put this class to your scripts folder.
    Code (CSharp):
    1. [DefaultExecutionOrder(-1000)]
    2. public abstract class ComponentPicker<T> : MonoBehaviour
    3. {
    4.     public T Value { get; private set; }
    5.  
    6.     protected virtual void Awake()
    7.     {
    8.         Value = GetComponent<T>();
    9.     }
    10. }
    For each interface type you want to serialize you will need to create a separate MonoBehaviour class.

    For example you have:
    Code (CSharp):
    1. public interface IGroundDetector
    2. {
    3.     public bool IsGrounded { get; }
    4. }
    5.  
    6. public class DistanceGroundDetector : MonoBehaviour, IGroundDetector
    7. {
    8.     [field: SerializeField]
    9.     public bool IsGrounded { get; private set; }
    10.  
    11.     [field: SerializeField]
    12.     private float _detectionDistance = 0.01f;
    13.  
    14.     private void FixedUpdate()
    15.     {
    16.         IsGrounded = Physics.Raycast(transform.position, Vector3.down, _detectionDistance);
    17.     }
    18. }
    If you want to reference your IGroundDetector you can simply create a ComponentPicker for that and attach this component to GroundDetector GameObject.

    Like that:
    Code (CSharp):
    1. public class GroundDetectorPicker : ComponentPicker<IGroundDetector>
    2. {}
    Now you can put a reference to picker component and take a value from that!

    Usage example:
    Code (CSharp):
    1. public class Test : MonoBehaviour
    2. {
    3.     [SerializeField]
    4.     private GroundDetectorPicker _groundDetectorPicker;
    5.  
    6.     private void Update()
    7.     {
    8.         Debug.Log(_groundDetectorPicker.Value.IsGrounded);
    9.     }
    10. }
    upload_2023-11-14_5-39-4.png

    upload_2023-11-14_5-39-14.png

    upload_2023-11-14_5-39-21.png
     
    Last edited: Nov 14, 2023
    Vectrex likes this.