To use explicit typed classes or not to use explicit typed classes?

Background: When I started getting into programming and computer science, it was Unity that led me to it. I loved how easy it is to create variations of prefabs and new instances of ScriptableObjects.

However, while learning Java in college, I struggled because my mentors reinforced the idea of explicit typed classes, which we also do here, in Unity, when we use generic containers and such while also preserving inspector functionality. Such an example can be found here: Finally, a serializable dictionary for Unity!

Their reasoning was and is: “If it is a type, we know what it is”. This makes sense. It allows to add special behavior for certain derived classes, it makes it easy to differ and use specific types at certain locations. However, with it comes a huge workload.

Example: Here is a little example to help understand my problem. Right now, I tinker around in Unity, for fun, writing an ARPG framework. There, each character has character stats which have a base value. Each character stat can be modfied, altering its maximum value. If no modifier is present, the base value of the stat is the maximum value.

I solved this by introducing a ScriptableObject for the character stats. I then added a MonoBehaviour component to wrap a character stat and add it to characters. Each of these wrappers also know a collection that has all modifiers of that character stat.

So far so good. Imagine these non-generic so far. However, I now struggled to link stat data (ScriptableObject) with the characters themselves, into the wrapper (MonoBehaviour). How do I know which wrapper holds which stat data? How do I index stat data based on the wrapper, to allow modifiers to access them per character?

Eventually, I settled for generics. I made my character stat generic, I made the wrapper generic. Then I explicit typed both of them. Looks kinda like this:

public class CharacterStat : ScriptableObject {}
public class CharacterStat<T> : CharacterStat { /*...*/ }
public class VitalityStatData : CharacterStat<VitalityStatData> {}
public class CharacterStatWrapper<T> : MonoBehaviour where T : CharacterStat
{
    [SerializeField]
    private T instance;
}
public class VitalityCharacterComponent : CharacterStatWrapper<VitalityStatData> {}

The joke here is that I can use gameObject.GetComponent<CharacterStat<VitalityStatData>>() to get the vitality component of a unit without indexing the wrappers somewhere or checking which one is the one I need. This allows me to select which stat a modifier should reference. However, if you paid attention, a modifier would get an instance of a ScriptableObject. Thus, I have to make the modifier generic as well:

public class Modifier<T> : ScriptableObject where T : CharacterStatWrapper {} // there are no wildcards in C#, are there? So I wouldn't have to have CharacterStat as base class
public class VitalityModifier : Modifier<VitalityStatData> {}

Now in Modifier I can write myself some method:

public void Apply(GameObject target, T component) where T : CharacterStatWrapper
{
    target.GetComponent<CharacterStat<T>>().Add(this);
}

Nonetheless, this feels kinda odd, though it is hard to pinpoint why.

Conclusion: Now as you can imagine, this is a lot of work. A game like an ARPG easily has like, dunno, 30-50 stats. I would need to write a stat definition for each of them, a wrapper and a modifier, as well as who knows what else comes up. Then, when I create my unit instances, I would have to create a new stat data instance for every unit type, though assuming that unit variants share the same data.

So is this is whole “generic, but explicit typed” approach a good idea? It does appear to be working a little bit against what Unity is offering when it comes to workflow, however, besides that I see more advantages than drawbacks. In particular when it comes to indexing and knowing which object needs what, because the types already solve that and you already know what you have in front of you instead of having to guess by its name (ScriptableObject instances). What is your take on this subject?

This sounds like overkill. Why do you need these generic classes? What is the point of all this? Can you show us the implementation of some of these so we can get a better understanding of your design?

1 Like

I try to describe it with a more background now. It is quite a big package and it is hard to break it into little pieces so that others are able to comprehend my problem.

Currently, I try to build a character stat/attribute/value framework.

  • A character is an entity controlled either by the player or an AI (or someting in that direction). It interacts with other entities, by walking, spellcasting, attacking, talking and other actions.

  • A character stat is a value associated with such an entity, used to describe the state of said entity. A “Vitality” stat could provide information about how healthy an entity is, “Energy” how much power is left for the entity to use weapons or other gadgets and so on.

As for implementation, a character is a GameObject with components, that’s in stone, but character stats are open to design. While tinkering around, one of the central questions that kept appearing is the ability to modify such stats while preserving the original value. Such an object is a modifier. Examples for such a modifier could be:

  • “The Vitality [stat] points of this character is increased by 100 bonus points”.
  • “The total Vitality [stat] points of this character are increased 10%”.

This creates the need for a wrapper component. We already know that a character has an associated value with this stat Vitality, but now this value is altered, while we want to preserve the original value. As a result, a character has a bunch of components, with each component wrapping a particuar stat.

So far, so good. However, now one of my main problems, which leads to my overkill solution with explicit typed objects, is how do I reference a stat? Let’s say a stat is a ScriptableObject, the wrapper knows exactly one instance of such a stat, let’s call it “DefaultPlayerVitality”. My player character has therefore a component with this stat instance referenced.

Now my next goal is creating a modifier for this stat. I would like to be able to add a modifier to this wrapper, this stat component. How do I tell the modifier, a ScriptableObject, that it is supposed to reference the Vitality component of my character? I can’t use “DefaultPlayerVitality” because this is an instance and only my player character uses it. Another character might use “DefaultFoeVitality”. You might see where I am getting here. I basically need to tell my modifier “Go modify Vitality!”, but such a reference cannot be serialized in vanilla editor, can it? It is important to mention that the modifier does not know which target GameObject and thus which stat instance it is dealing with. I can’t just reference them in the Inspector ( think of buffs from games, a character is slowed, modifiying its MovementSpeed stat value. The buff gets added to the target and later on, at some point, removed again).

An easy solution would be introducing an enumeration that includes every stat and then a dispatcher uses the enumeration value from my modifier to grab the right stat component from the target character. However, this is something that I don’t want to do because I don’t want this structure to rely on the enumeration for various reasons, including but not limited to making changes to it and breaking serialization in the process.

However, now I am at loss if I don’t use the overkill solution. Somehow, I need to tell my modifier that it should target a specific stat component type. It does boil down to something like a TypeSpecifier property allowing me to choose a type deriving from the stat component base class, but that is too loose for my taste. Therefore, I am kinda stuck now.

Question…

What are you doing with these ScriptableObject’s object identity?

Like… if you drag a SO onto the wrapper behaviour. What makes the SO unique to that entity? Because SO instances are shared unless duplicated. If you have a single SO in your asset and you drop it onto multiple entities, they all have the same SO/stat.

SO instances act as data references.

Think of an enemy type “Arachnid”. For some reason, there are dozens of arachnid variants (different prefabs) in the game which all share the same stat values, they all have the same Vitality base value, for instance.

Using a shared SO instance here, it allows me not only to edit the stat value for all of these unit types, it also saves a lot of memory because not every prefab instance has its own clone of the data, as it is with prefabs, but referencing the SO instance with the value. I do know that Prefab Variants are able to offer the edit functionality, however, I expect these stats to become somewhat large in memory consumption for various reasons, so I do not want every instantiated prefab to have its own clone of the data.

Inspired by 3 cool ways to architect your game with Scriptable Objects.

If I were making an RPG, I wouldn’t bother wrapping any of the stats in any sort of explicit type. I would just have one generic stat type and use a dictionary-like collection to store them. To be honest, I don’t really understand your example as to why having explicit types would be an advantage.

I think I understand what you’re going for… and if so, what about this? :

public abstract class EntityStat : ScriptableObject
{

 

}

public abstract class EntityStatBehaviour<TStat, TBehaviour> : MonoBehaviour where TStat : EntityStat where TBehaviour : EntityStatBehaviour<TStat, TBehaviour>
{

    [SerializeField]
    private TStat _statData;

    [System.NonSerialized]
    private HashSet<StatModifier<TStat, TBehaviour>> _modifiers = new HashSet<StatModifier<TStat, TBehaviour>>();

    //translator property to resolve the generic type
    protected abstract TBehaviour StatBehaviour { get; }

    public TStat StatData
    {
        get { return _statData; }
        set { _statData = value; }
    }

    public void AddModifier(StatModifier<TStat, TBehaviour> modifier)
    {
        if (_modifiers.Contains(modifier)) return;

        _modifiers.Add(modifier);
        modifier.Apply(this.StatBehaviour);
    }

    public void RemoveModifier(StatModifier<TStat, TBehaviour> modifier)
    {
        if(_modifiers.Remove(modifier))
        {
            modifier.Remove(this.StatBehaviour);
        }
    }

    public IEnumerable<StatModifier<TStat, TBehaviour>> GetModifiers()
    {
        return _modifiers;
    }

}

public abstract class StatModifier<TStat, TBehaviour> : ScriptableObject where TStat : EntityStat where TBehaviour : EntityStatBehaviour<TStat, TBehaviour>
{

    public abstract void Apply(TBehaviour behaviour);

    public abstract void Remove(TBehaviour behaviour);

}

public static class StatExtensionMethods
{

    public static void AddModifier<TStat, TBehaviour>(this GameObject go, StatModifier<TStat, TBehaviour> modifier) where TStat : EntityStat where TBehaviour : EntityStatBehaviour<TStat, TBehaviour>
    {
        var stat = go.GetComponent<EntityStatBehaviour<TStat, TBehaviour>>();
        if (stat != null)
        {
            stat.AddModifier(modifier);
        }
    }

    public static void RemoveModifier<TStat, TBehaviour>(this GameObject go, StatModifier<TStat, TBehaviour> modifier) where TStat : EntityStat where TBehaviour : EntityStatBehaviour<TStat, TBehaviour>
    {
        var stat = go.GetComponent<EntityStatBehaviour<TStat, TBehaviour>>();
        if (stat != null)
        {
            stat.RemoveModifier(modifier);
        }
    }

}

And an implementation:

[CreateAssetMenu(fileName = "VitalityStat", menuName = "Stat Helper/VitalityStat")]
public class VitalityStat : EntityStat
{

    public float SomeData; //this is some random data that is unique to VitalityStat


}

public class VitalityStatBehaviour : EntityStatBehaviour<VitalityStat, VitalityStatBehaviour>
{

    public float CurrentStat; //state that can be modified by modifier

    protected override VitalityStatBehaviour StatBehaviour { get { return this; } }

}

[CreateAssetMenu(fileName = "VitalityModifier", menuName = "Stat Helper/VitalityModifier")]
public class VitalityModifier : StatModifier<VitalityStat, VitalityStatBehaviour>
{

    public override void Apply(VitalityStatBehaviour behaviour)
    {
        //modify the behaviour
        behaviour.CurrentStat = behaviour.StatData.SomeData + 10f;
    }

    public override void Remove(VitalityStatBehaviour behaviour)
    {
        //remove the modification from the behaviour
        behaviour.CurrentStat = behaviour.StatData.SomeData;
    }


}

And here’s me adding a modifier to a gameobject and looping them:

public class zTest01 : MonoBehaviour
{

    public VitalityModifier modifier;

    void Start()
    {
        this.gameObject.AddModifier(modifier);
        foreach(var modifier in this.GetComponent<VitalityStatBehaviour>().GetModifiers())
        {
            Debug.Log(modifier.name);
        }
    }

}
1 Like

What I’m wondering, though, is why there needs to be VitalityStat type, rather then Vitality simply being an instance of EntityStat.

1 Like

I’m wondering a lot of things too. I really do wish they’d just show us their current implementation to better wrap my head around what it is they’re attempting to accomplish and what freedoms they want.

Like for instance… maybe VitalityStat might have members that aren’t in say AttackStat. For instance Vitality might just have a ‘health’ value, but Attack might have Strength and Speed?

At first, this confused me a lot, too. Why explicit typing when generic types are so much easier to create and maintain? The answer is, there are a lot of If-Then and Maybe-Or-Not involved. I am trying to find an example to give some insight.

Your solution works fine, but requires hashing. Hashing is expensive. With explicit types, you can reference and don’t have to hash to get access to the stat itself. keep in mind that stats are going to be accessed a lot, possibly multiple times per frame. Having to hash every time because completely different objects try to access it is very slow.

Trying to find an example, explicit typing allows you to freely extend any type, for any reason. If you used generic definitions, you have to have objects that look exactly the same. Well, you could introduce generic members that enable more modularity, but this would quickly get out of hands. One of your stats need a particular additional member of a type that no other one needs? Now all your other generic stats are stuck with references to null, except the one that actually needs it. If I come across an actual example, I will make sure to post it here.

This is my idea, yes. It allows to freely build your individual stats and their structure, if needed.

This looks very good, thank you! Some parts of the structure are almost identical to my own code, others differ. I like a lot how you solved handling adding and removing modifiers to behaviors.

One question regarding the property StatBehaviour. You commented that it is needed to handle the generic nature of the component, but is it? I don’t see you using it anywhere in the code, at first I thought one need it to get the connection between stat and modifier, however, the extension method already does that, with var stat = go.GetComponent<EntityStatBehaviour<TStat, TBehaviour>>();, not requiring the translator. Would you mind going into detail with the translator, please, and its benefits?

There is only one little detail missing in my implementation, then I can share a little example project in Unity that works and everything. It should be ready before the end of the week. I appreciate a lot that you guys take the time to comment here and help me with my problem!

Note that it’s protected, so it’s really only used in the EntityStatBehaviour itself. Specifically here:

    public void AddModifier(StatModifier<TStat, TBehaviour> modifier)
    {
        if (_modifiers.Contains(modifier)) return;

        _modifiers.Add(modifier);
        modifier.Apply(this.StatBehaviour);
    }

    public void RemoveModifier(StatModifier<TStat, TBehaviour> modifier)
    {
        if(_modifiers.Remove(modifier))
        {
            modifier.Remove(this.StatBehaviour);
        }
    }

It allows us to have ‘this’ typed appropriately.

Since StatModifier<TStat, TBehaviour> expects ‘this’ typed as TBehaviour, but in the scope of EntityStateBehaviour we don’t know that this IS a TBehaviour (technically speaking our constraints to enforce that ‘this’ is TBehaviour, only that TBehaviour inherits from EntityStateBehaviour). So this is our cast for it.

We could just say “this as TBehaviour” when call Apply or Remove. BUT technically speaking since the constraint doesn’t enforce that this is TBehaviour, we’re not actually certain. Say someone inherits from EntityStatBehaviour and composites another TBehaviour. Sure, you wouldn’t do this as it’s kind of dumb… but the compiler doesn’t know that.

Also. I modified it further so that you can have a non-generic base type. Which is useful with the Unity serializer (especially in older version) where it doesn’t like dealing with generic types.

public abstract class EntityStat : ScriptableObject
{

 

}

public abstract class EntityStatBehaviour : MonoBehaviour
{

    public bool AddModifier(StatModifier modifier)
    {
        return AddModifierGenerically(modifier);
    }

    public bool RemoveModifier(StatModifier modifier)
    {
        return RemoveModifierGenerically(modifier);
    }

    public IEnumerable<StatModifier> GetModifiers()
    {
        return GetModifiersGenerically();
    }

    protected abstract bool AddModifierGenerically(StatModifier modifier);
    protected abstract bool RemoveModifierGenerically(StatModifier modifier);
    protected abstract IEnumerable<StatModifier> GetModifiersGenerically();

}

public abstract class EntityStatBehaviour<TStat, TBehaviour> : EntityStatBehaviour where TStat : EntityStat where TBehaviour : EntityStatBehaviour<TStat, TBehaviour>
{

    [SerializeField]
    private TStat _statData;

    [System.NonSerialized]
    private HashSet<StatModifier<TStat, TBehaviour>> _modifiers = new HashSet<StatModifier<TStat, TBehaviour>>();

    //translator property to resolve the generic type
    protected abstract TBehaviour StatBehaviour { get; }

    public TStat StatData
    {
        get { return _statData; }
        set { _statData = value; }
    }

    public bool AddModifier(StatModifier<TStat, TBehaviour> modifier)
    {
        if (_modifiers.Contains(modifier)) return false;

        _modifiers.Add(modifier);
        modifier.Apply(this.StatBehaviour);
        return true;
    }
 
    public bool RemoveModifier(StatModifier<TStat, TBehaviour> modifier)
    {
        if(_modifiers.Remove(modifier))
        {
            modifier.Remove(this.StatBehaviour);
            return true;
        }
        else
        {
            return false;
        }
    }

    public new IEnumerable<StatModifier<TStat, TBehaviour>> GetModifiers()
    {
        return _modifiers;
    }

    protected override bool AddModifierGenerically(StatModifier modifier)
    {
        var concrete = modifier as StatModifier<TStat, TBehaviour>;
        if (concrete != null) return this.AddModifier(concrete);
        else return false;
    }

    protected override bool RemoveModifierGenerically(StatModifier modifier)
    {
        var concrete = modifier as StatModifier<TStat, TBehaviour>;
        if (concrete != null) return this.RemoveModifier(concrete);
        else return false;
    }

    protected override IEnumerable<StatModifier> GetModifiersGenerically()
    {
        return _modifiers;
    }


}

public abstract class StatModifier : ScriptableObject
{

    public abstract System.Type GetBehaviourType();

}

public abstract class StatModifier<TStat, TBehaviour> : StatModifier where TStat : EntityStat where TBehaviour : EntityStatBehaviour<TStat, TBehaviour>
{

    public override Type GetBehaviourType()
    {
        return typeof(TBehaviour);
    }

    public abstract void Apply(TBehaviour behaviour);

    public abstract void Remove(TBehaviour behaviour);

}

public static class StatExtensionMethods
{

    public static bool AddModifier<TStat, TBehaviour>(this GameObject go, StatModifier<TStat, TBehaviour> modifier) where TStat : EntityStat where TBehaviour : EntityStatBehaviour<TStat, TBehaviour>
    {
        var stat = go.GetComponent<EntityStatBehaviour<TStat, TBehaviour>>();
        if (stat != null)
        {
            return stat.AddModifier(modifier);
        }
        else
        {
            return false;
        }
    }

    public static bool AddModifier(this GameObject go, StatModifier modifier)
    {
        var stat = go.GetComponent(modifier.GetBehaviourType()) as EntityStatBehaviour;
        if (stat != null)
        {
            return stat.AddModifier(modifier);
        }
        else
        {
            return false;
        }
    }

    public static bool RemoveModifier<TStat, TBehaviour>(this GameObject go, StatModifier<TStat, TBehaviour> modifier) where TStat : EntityStat where TBehaviour : EntityStatBehaviour<TStat, TBehaviour>
    {
        var stat = go.GetComponent<EntityStatBehaviour<TStat, TBehaviour>>();
        if (stat != null)
        {
            return stat.RemoveModifier(modifier);
        }
        else
        {
            return false;
        }
    }

    public static bool RemoveModifier(this GameObject go, StatModifier modifier)
    {
        var stat = go.GetComponent(modifier.GetBehaviourType()) as EntityStatBehaviour;
        if (stat != null)
        {
            return stat.RemoveModifier(modifier);
        }
        else
        {
            return false;
        }
    }

    public static IEnumerable<StatModifier<TStat, TBehaviour>> GetStatModifiers<TStat, TBehaviour>(this GameObject go) where TStat : EntityStat where TBehaviour : EntityStatBehaviour<TStat, TBehaviour>
    {
        var stat = go.GetComponent<EntityStatBehaviour<TStat, TBehaviour>>();
        return stat != null ? stat.GetModifiers() : System.Linq.Enumerable.Empty<StatModifier<TStat, TBehaviour>>();
    }

    public static IEnumerable<StatModifier> GetAllStatModifiers(this GameObject go)
    {
        foreach(var c in go.GetComponents<EntityStatBehaviour>())
        {
            foreach(var m in c.GetModifiers())
            {
                yield return m;
            }
        }
    }

}

I see, thanks a lot for explaining!

I got set back on my solution, because I tried to implement something that I need later and it messed up my implementation, trying to implement my modifier container logic, which you use a hashset for. It appears that I ran into this issue: Serializing nested generic types in concrete classes

using System;
using System.Collections.Generic;
using UnityEngine;

public interface IValue
{
    float Value { get; }
}

public abstract class StatComponent<TStat, TModifier, TLayers> : MonoBehaviour
    where TStat : CharacterStat<TStat>
    where TModifier : CharacterStatModifier<TStat>, IValue
    where TLayers : Enum
{
    [SerializeField]
    private TStat stat;

    [SerializeField]
    private LayerContainer modifiers;

    [Serializable]
    public class LayerContainer : IValue
    {
        [SerializeField]
        private CustomCollection[] collections;

        public LayerContainer()
        {
            int size = Enum.GetValues(typeof(TLayers)).Length;
            this.collections = new CustomCollection[size];

            for (int index = 0; index < size; index++)
            {
                collections[index] = new CustomCollection();
            }
        }

        public void Add(TModifier item, TLayers layer)
        {
            int index = Convert.ToInt32(layer);
            collections[index].items.Add(item);
            collections[index].isDirty = true;
        }

        public bool Remove(TModifier item, TLayers layer)
        {
            bool removed = false;

            int index = Convert.ToInt32(layer);
            removed = collections[index].items.Remove(item);
            if (removed) collections[index].isDirty = true;

            return removed;
        }

        public float GetValue(TLayers layer)
        {
            return GetValue(Convert.ToInt32(layer));
        }

        private float GetValue(int layerIndex)
        {
            float sum = 0.0f;

            if (collections[layerIndex].isDirty)
            {
                for (int index = 0; index < collections[layerIndex].items.Count; index++)
                {
                    sum += collections[index].items[index].Value;
                }

                collections[layerIndex].isDirty = false;
            }

            return sum;
        }

        public float Value
        {
            get
            {
                float sum = 0.0f;

                for (int layerIndex = 0; layerIndex < collections.Length; layerIndex++)
                {
                    sum += GetValue(layerIndex);
                }

                return sum;
            }
        }
    }

    [Serializable]
    public class CustomCollection
    {
        [SerializeField]
        public List<TModifier> items;

        [SerializeField]
        public float value;

        [SerializeField]
        internal bool isDirty;

        public CustomCollection()
        {
            this.items = new List<TModifier>();
            this.value = 0.0f;
            this.isDirty = true;
        }
    }
}

My property modifiers refuses to be serialized. The thoughts behind this container is that modifiers are added to a specific layer of modification. Let’s say there are layers Base, Bonus and Total, then there are modifiers which are altering the Base value in the Bonus layer, others might alter the value in the Total layer, reflecting:

  • “Increases the base vitality of your character by 10 points.”

  • “Your character receives 50 bonus vitality.”

  • “Increases your total vitality by 10%.”

However, I could not quite nail down why Unity refuses to serialize this collection containing the layers of modifiers, not showing it in the inspector. It does not make sense from my point of knowledge. At first I thought it might be because of using TModifier in CustomCollection, but even using non-generic types there did not enable serialization.

As it appears to be always the case with such problems, I did not commit my code to source control before applying thse changes, so there are a lot of scorched files now. I might need to do a hard reset and use your solution as a base. After all, our code lines only differ in how we add and remove modifiers from our stats.

Looks great, thank you! I have had a fair share of problems with that issue, too.

I think part of your idea is a respectable and old one. “Hungarian Notation” might be the best explanation. The real version says that if you have pounds and ounces, both floats, you should have some way to distinguish them: pnd_n1+ozc_n3 would look wrong.

Then, for example, C++ (I think) can easily make it an error. You can define pound_t and ounce_t as ints, declare pound_t n; and ounce_t k;, and have n+k give you a perfect “can’t add pounds and ounces error”, even though you’re adding a pair of ints. I believe other old approaches involve classes and operator overloading, which seems like your approach. I feel like if you tried this in Python, say, it would be easier. Not every language is good at everything.

My other thought is that everyone else is right. The people doing this were researchers. For a bank, a big framework with lots of safety might pay off. But in a game, anything simple that works is fine. I mean, think of the most complicated RPG you ever played – even the 5th update wouldn’t have all that many different stats and mechanics. And plenty of popular games handled, say, poison “wrong” for 6 months (then players complained when it was fixed).

I finally got around importing your code and playing around with it. I learned a lot from it, thank you, @lordofduct ! One essential thing I learned is about using abstract base classes. I already use them a lot, but I usually end up implementing things in the base class. Your code showed me how useful it can be to leave methods abstract and leave the implementation to derived classes. I already know one or another place where I can put this information to good use, previously struggling.

I got to say, I miss having a coding mentor. When I started coding, I had a friend that would teach me things occasionally and that one could bug now and then, with questions and what-ifs. He moved away eventually and I didn’t find such a relation again, with something being there to ask. I heard Discord has a Unity place as well, I might try that one out.

I agree. It is easy to lose oneself in generalization and design optimization when in the end, it is merely about glueing few components together, not needing the scalability that another design might offer, making trying to get there pointless. Nonetheless I do enjoy doing this now and then, even if it is only for learning new things.

That’s funny since it’s usually the other way. It’s often nice to implement the default in a base class, and it’s never a hardship doing it. You never need abstract virtual functions – they’re more of an error-checking thing. For example:

class MapItem { // base class for anything that can be on the map

  virtual public bool isDestroyed() { return false; } // most common answer
  // most sub-classes can't be destroyed, and will use this

  virtual public void takeDamage(int d) {} // a do-nothing body is a cheap, fake-abstract
  // most items don't take damage, most will skip implementing it

  abstract public void setTarget();
  // everyone will want to implement this. An error not to is what we want

Everyone is free to override isDestroyed and takeDamage. Most won’t – the bodies save us lots of time – but those bodies don’t force us to do or not do anything. Down in setTarget we don’t think anyone, or hardly anyone, would want an empty body. It’s a tiny bit safer to make it abstract and get a reminder if we forget.

The most common complaint is that Java/C# Interfaces don’t allow the convenience of a default.

1 Like

Does overriding virtual methods work in Unity? I remember using them in the context of custom editors and context menues and they would always call the virtual function in the non-abstract base class, never the overriden function in the derived class. I have to admit, this is still on my list of testing and trying to see what works and what does not, so thanks for bringing it up, I am going to do some testing with the code that I tinkered out with @lordofduct .

In C#, default implementations for interfaces are a thing in C# 8 I think. It is funny, I recall reading a huge discussion on a Microsoft Github repository about whether this should be a thing, with hundreds of replies. The argument against it basically was that in C#, interfaces are merely promises that an object behaves in a certain way, which is different to C++ headers. Seems whoever is developing C# decided for it now, though, adding default implementations in the newer C# versions. Multiple inheritance, inheritance from multiple base classes, is also going to be a thing. We are not going to see that in Unity for the next decade, though, probably.

I think everyone who knew inheritance first tries public void override Update(), since how else would a class inheriting from Monohavior work. That leaves a funny feeling that Unity mucks up virtual functions. But they work fine if you’re not dealing with one of the magic words. I just double-checked one of my working editor scripts –
public override void OnInspectorGUI() and it’s working fine.

Yes, those people on github are correct; an interface is checked by the compiler and then it’s nothing. 0 byes of executable. “Only a promise” is a common way to express that. But unless someone can write “and that’s important because…”. I just looked – Java 8 lets you write bodies in interfaces now. The microsoft C# page also mentions Swift doing it (which is Apple’s language, microsoft’s greatest frenemy). So it seems the actual reason is “Gah! Now we have to do it since everyone else is”.

Yep, that is a classic one! In my particular case, it was something like this:

[ContextMenu("Generate Level")]
protected virtual void GenerateInEditor()
{
    /*...*/
}

When I created a derived class and override this method, it would still call the original function, which, on a second thought, makes sense, I guess, because it is that function having the attribute. Nonetheless, I recall adding the attribute to the overriden function doing nothing, not adding a context menu, so I was stuck with the base class implementation. However, last time I tried this was in Unity V2018, so who knows what is the current state of art.

Can’t say I like it, though it is certainly going to be an exciting feature. Should ease up one or another thing. One is allowed to be a bit salty about Microsoft following the “popular path” even though it does not quite fit into C-Sharp, as you pointed out with what interfaces are.

This weekend, I tinkered around a little bit, allowing me to sharpen my problem. The explicit typed approach as posted by @lordofduct works, however, I could not befriend it because it blows up the workload on so many levels and in some cases, it is not even sure that an object always is this simple. I could imagine when it comes to something like DamageEffect, requiring FireDamageEffect and such, this approach could become less and less usable.

As a result, I tried a hardcore generic approach as displayed in the attachments, excluding modifiers for now. Here, my underlying problem becomes clear, which is dispatching. Before I get into detail, I would like to mention that I previously mixed up, seeing CharacterValue and CharacterStat as equal. The difference between these two is, a CharacterValue is a value associated with a character, such as maximum hitpoints, whereas a CharacterStat alters such a CharacterValue or multiple CharacterValue. In other words, a CharacterStat grants/influences CharacterValue. An example for such stats can be found here: Attributes - Official Pillars of Eternity Wiki

That being said, I limited my example to CharacterValue for now, as it already exposes my problem. Dispatching I said earlier in this post, telling an object “You should reference/target that!”. If you look on the attached screenshot, you notice that there is a component called CharacterValue which references CharacterValueData. In this case, it is the maximum life of a character. So far, so fine, however, now look on CharacterValues, which is a component that should eventually provide references to all other CharacterValues of this GameObject. Immediatly, you see the problem. It is just a CharacterValue and it does not know that it is Life. If you observe the object from the outside, you have no idea what it is and remember that you cannot just reference the component itself because the objects wanting to get the reference aren’t instanced in the editor, but at runtime, such as a modifier or other objects accessing it. They somehow need to know what they should reference.

This is the root of my problems. How do I tell a modifier that it should alter a CharacterValue, for instance the maximum life of a character? There is no way to tell where the life of this character is. How do I solve this? My first thought are enumerations, using its values as indices for the array in the container CharacterValues. Sounds good, right? However, think again. Each instance of CharacterValue (instance!!!) can be referenced. If I create a new instance, a new character value, I also have to edit my enumeration for it. It also does not solve the problem of not knowing what something is, only enabling referencing character values using the container.

I am at loss how to solve this, frankly. It is kind of embarassing, because I study computer science in college and thus feel as if I should know how to solve it, but with any experience I have I can’t find the solution for this that really “fits”, if you know what I mean, finding the balance between workload and usability. My only idea right now is to write a new ScriptableObject definition or a custom class definition and using the [SerializeReferece] for it, something like CharacterValueTarget, explicit typing CharacterValue, dumping the generic approach, explicit typing the components themselves and removing the container for now (or introducing an enumeration that can be used with the container, but is independent from CharacterValueTarget). More or less, I would end up with the same code as @lordofduct already posted, only “externalizing” the retrieval of components that hold the value.

What I like about the new approach is its “double effect”, meaning that on the one hand, I have explicit typed CharacterValue objects, allowing customization, on the other hand I have a generalized retrieval of these components, because I don’t have to care about which instance of CharacterValueData I have. Let’s say, a modifier would only need to know the CharacterValueTarget, which resolves retrieving the actual component without caring which underlying data instance there is. This would be a CharacterValueModifier, which would know the CharacterValueTarget, eliminating the need for something like LifeModifier.

For now, I think this is what I am going for and I would greatly appreciate input and opinions on the matter. I kow that I am overthinking it a little bit, but this very problem has stuck with me since 2014 when I started with Unity: The inability to reference specific objects without losing the generic design of easy workflow.