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?
