Favourite Way To Serialize Interfaces

Hi Guys,

How do you Serialize your interfaces? Ignoring the ‘why would you serialize them’ debate, of course :). I have Odin Inspector and IUnified, but I was wondering what other people did? Oh and is Unity coming out with any new serializations soon? With UnityScript no longer holding things back I assume we’re going to see developments here???

Cheers!

1 Like

Well in the case of UnityEngine.Object’s (like Components and ScriptableObjects) I just type the field as Component, or ScriptableObject, or UnityEngine.Object, or whatever Unity base type I generically want to restrict within. And then I combine it with a PropertyAttribute of mine I created called ‘TypeRestriction’:

And drawer:
https://github.com/lordofduct/spacepuppy-unity-framework-3.0/blob/master/SpacepuppyUnityFrameworkEditor/Components/ComponentTypeRestrictionPropertyDrawer.cs

The drawer is a little complicated because it does quite a few extra features. But you could write a much more simplified version.

Basically I just make sure when dropping objects onto the field, that it conforms to the type we’ve restricted it to.

You can see this in action here:

Note though that the field is still typed as ‘Component’, so I just wrap a getter method around it to coerce it to the type I restricted it to:

For an example, here is a field restricted to the interface ‘IMovementAnimator’:

        [SerializeField()]
        [DefaultFromSelf(UseEntity = true)]
        [TypeRestriction(typeof(IMovementAnimator))]
        private Component _movementAnimator;

Without a reference:

Note it says ‘Component’, this is because the object field is still the Unity object field in my PropertyDrawer. And the type of the field is Component. It’s only after you drag the object onto the field that I then resolve if it’s the restricted type. Some day I might write my own object field that allows me to draw whatever text in it. But really… I can live with this.

With a reference:

Note that when I drop a GameObject on it, I get a drop down with all the components of that GameObject that implement the interface in question. This is one of the added functionalities of my ComponentTypeRestrictionPropertyDrawer. It doesn’t just check if the object matches the type, but also tries to resolve if more than one component exists on the source that satisfies the constraint. This also works for direct classes. Like if you have a ‘Collider’ field, or even ‘Component’ field and want to pick one of the many components.

9 Likes

Can’t really ignore the “why would you serialize them” debate when it’s the reason for the answer. Answer: I don’t, why would you need to? Just use custom object pickers instead.

As an aside here, Odin is one of those things that’s fantastic in theory and absolutely worthless in practice. I own it, and love using it, and can’t ever ever make any scripts with it. When you don’t know where your code might end up (like assets to sell on the UAS later), tearing out 50 different attributes per script and recreating custom editors because you can’t include the Odin DLLs is more painful than just never having it in the first place. It’s created more work for me to date than it’s saved, by a really staggering margin.

4 Likes

As for serializing objects of some interface type that aren’t UnityEngine.Object’s.

Well… Unity’s serialization engine just won’t support that at all. Since it resolves the type from the field declaration. This is also why if you have a field of some base type, and you reference some child type, it’ll serialize as the base type.

But .Net’s serialization engine bakes the type into the serialized data.

So if you use BinaryFormatter it works.

Or by creating a JsonFormatter:

I get something like this:

{
    "@type" : "com.mansion.SaveGameToken",
    "Scene" : "Threshold_A",
    "Data" : [
        "com.mansion.ScenarioPersistentData+ValuePair[]",
        {
            "@type" : "com.mansion.ScenarioPersistentData+ValuePair",
            "Id" : "*Ep1*GameState*",
            "Value" : {
                "@type" : "com.mansion.Scenarios.Episode1.Episode1Controller+Token",
                "ObjectivesToken" : [
                    "com.mansion.Objective[]"
                ],
                "MobsKilled" : 0,
                "MobsSpawned" : 0,
                "DocumentsScore" : 1,
                "Deaths" : 0,
                "RoundsFired" : 0,
                "TimesHealed" : 0,
                "TakeDamage" : 0,
                "Secrets" : 0,
                "CurrentTime" : {
                    "@type" : "System.TimeSpan",
                    "_ticks" : 268100000
                },
                "Difficulty" : 2
            }
        },
        {
            "@type" : "com.mansion.ScenarioPersistentData+ValuePair",
            "Id" : "08D52B5BC2171FCC",
            "Value" : {
                "@type" : "com.mansion.Scenarios.Episode1.Ep1_PlayerScenarioPersistentStateController+PlayerStateToken",
                "Health" : 100,
                "MaxHealth" : 100,
                "AmmoToken" : [
                    "com.mansion.Entities.Weapons.AmmoPouch+ValuePair[]",
                    {
                        "@type" : "com.mansion.Entities.Weapons.AmmoPouch+ValuePair",
                        "Type" : 0,
                        "Quantity" : 18
                    }
                ],
                "GunDamage" : 25,
                "GunCritPercent" : 0.0199999995529652,
                "GunAmmoInClip" : 9,
                "ImmuneToGrapple" : false,
                "Stat_DocumentsRead" : 1,
                "Stat_Secrets" : 0,
                "Stat_StepTracker" : false
            }
        }
    ]
}

So even if the field is an interface type, the serializer serializes the object it’s real type.

2 Likes

What do you mean by ‘custom object picker’? I’m interested.

After so many years dealing with Unity, I sometimes forget when something isn’t actually a built-in feature. In this case, DevDog made an attribute property drawer for selecting scene GOs / Components / Prefabs based on the types of components attached, which they called “custom object pickers”. I expanded on this later to make it possible to select objects based on the interfaces they implement as well, layers they’re on, tags they have, etc (using your MultiTag system, actually).

It’s really very straight-forward, looks like a normal reference in the inspector, acts like a button, and when clicked just uses Resources.LoadObjectsOfTypeAll, filters based on the specific attribute values for that field, and shoves the results into a little popup editor window to let you select between them.

2 Likes

Nice… I’ll have to check out DevDog’s thing. You got some source per chance?

There’s a bastardized version in my general-use editor assembly, but it was originally pulled from InventoryPro. I ended up ditching everything to do with inventories and items in that asset, ironically (I disagreed with just about every design decision they made with that), but some of their common assembly source was really quite good. I’m not really comfortable sharing it until I can go in and make sure all of the original code is gone though, since it was a paid asset and all of that- give me a few hours.

Sorry @lordofduct I completely forgot about this- been a busy day. Here’s the simplest version of this system I could make (had to tear out a ton of framework dependencies because they were clouding the issue). I just tested it and it still works for limiting by interface types. Dragging and dropping references are limited properly as well.

Of course, this doesn’t continuously enforce the restrictions onto the field or anything- if you assign a reference that works and then change the attribute, or change the interfaces implemented on the type being referenced, it won’t wipe the reference assignment. I do run validations of a similar kind over all objects in the scene/assets, but not for this (yet).

Also, the popup window for selecting objects looks like garbage- this is where most of the framework dependencies were so I just tossed something simple together from scratch to demonstrate.

Here’s the attribute.
Here’s the property drawer for the attribute.
Here’s the editor window for selecting objects.

3 Likes

In case someone’s looking for an easy and quick workaround and the case scenario is something like the following:

  • You have some classes (C1 and C2) that derive from the same class and ultimately from ScriptableObject.
  • The use of C1 and C2 is holding some data in the form of a variable.
  • C1 and C2 both implement a given interface (I1).
  • You have a script that you’d want to use as a component that would deal with a field (myField) of type C1 or C2 indistinctly.
  • The use of that field inside the component is given by a method declared in I1 (float MyMethod();).
  • You’ve tried to simply declare that field as of type I1 inside your component, but then you’ve realized that myField doesn’t show up in the Editor. (Yes, interfaces aren’t serialized.)

A very quick workaround would be to declare that field as ScriptableObject (or a fitter common ancestor of C1 and C2) and, wherever you used myField.MyMethod(), write ((I1)myField).MyMethod() instead.

Thanks to casting, you’re (pretty much) good to go, with caution though.

1 Like

Old thread but still relevant. I’ve found a very simple workaround for serialization of interface references.

These have to be made for each interface:

public class InteractInterface : InterfaceComponent<IInteract> { }

public interface IInteract
{
    void Interact();
}
public class InterfaceComponent<T> : MonoBehaviour where T : class
{
    [SerializeField] private MonoBehaviour component;

    public T _implmentation;

    public T implmentation
    {
        get
        {
            if (_implmentation != null)
            {
                return _implmentation;
            }
            else
            {
                if (component)
                {
                    if (component is T)
                    {
                        _implmentation = (T)(object)component;
                    }
                    else
                    {
                        Debug.LogError("The component does not implement the interface.");
                    }
                }
                else
                {
                    _implmentation = GetComponent<T>();
                }
                if (_implmentation != null)
                {
                    return _implmentation;
                }
                else
                {
                    Debug.LogError("Interface implementation not found on this object.");
                }
                return null;
            }
        }
    }
}

Usage:

    [SerializeField] private InteractInterface _interactiveObject;

    void Interact()
    {
        _interactiveObject.implmentation.Interact();
    }
1 Like

Looks like there is an alternative now ! Look for SerialiezReference :

In the 1st answer, the Unity guy says you were right about using ScriptableObject as a common ancestor, but now we can do otherwise ! (I mean at least in my case, as I am doing a graph editor)

3 Likes

Would SerializeReference help in any way in cases where references need to be exposed in the inspector?

From the few tests I have done, yes, the references (and their values) are shown in the inspector and the values are kept between sessions. One weird point though is that I actually didn’t marked my class as Serializable.

Shown… is not “exposed” the way I mean it. I mean drug and drop functionality. My tests shown that it doesn’t work. So is it because it wont serialize an interface reference to an Object at all even via script like you do?

Oh I see. In this case if your tests don’t work I assume this is because “The field type must not be of a type that specializes UnityEngine.Object”, as the doc says unfortunately.

1 Like

This entire thread isn’t people calling it pointless, but rather people showing off their work arounds.

If the current system doesn’t support it… what else are they supposed to do? If unicorns don’t exist, they don’t exist.

WOAH! WAT?

What kind of… wow… I can’t even.

I’m just going to stop right there… you clearly are a self-entitled little brat who wants to cry because the world doesn’t function with you in the center of it.

I have to assume that is going to get deleted or something… hate speech like that HAS to be against the rules on this forum.

Thanks for the heads up. Quickest perma of my life.

4 Likes