Hello,
for my game I need units to have attributes, stats, commonly known ones like Health, Health Regeneration, …
Therefore, I created the following interfaces to describe stats and their behavior:
IStat<T>
- This MonoBehavior is considered a Stat, with a value, a name and a descriptionIDynamicStat<T>
- This MonoBehavior is anIStat<T>
with an additional dynamic value ( as in X of MAX Health)IRegenerationStat<T>
- This MonoBehavior is anIStat<T>
which periodically modifies anotherIStat<T>
Additionally, I created the following interfaces to allow modification:
-
IGenericBaseModifying<T>
- This object modifies objects of Type T -
IGenericBaseModifiable<T>
- This object allowsIGenericBaseModifying<T>
to subscribe to it. -
IGenericDynamicModifying<T>
- This object modifies objects of Type T -
IGenericDynamicModifiable<T>
- This object allowsIGenericDynamicModifying<T>
to subscribe to it.
Here is an example for an implementation ( in reality there is a base class to reduce redundancy):
public class Health : MonoBehavior, IStat<Health>, IDynamicStat<Health>, IGenericBaseModifiable<Health>, IGenericDynamicModifiable<Health>
{
#region IStat<Health>
public float BaseValue
{
get
{
throw new NotImplementedException();
}
}
public float Value()
{
throw new NotImplementedException();
}
#endregion
#region IGenericBaseModifiable<Health>
public void Add(IGenericBaseModifying<Health> modifier)
{
throw new NotImplementedException();
}
public bool Remove(IGenericBaseModifying<Health> modifier)
{
throw new NotImplementedException();
}
#endregion
#region IGenericDynamicModifiable<Health>
public void Add(IGenericDynamicModifying<Health> modifier)
{
throw new NotImplementedException();
}
public bool Remove(IGenericDynamicModifying<Health> modifier)
{
throw new NotImplementedException();
}
#endregion
#region IDynamicStat<Health>
public float DynamicValue
{
get
{
throw new NotImplementedException();
}
}
#endregion
}
I hope this is not too confusing. Health is a Stat with a base value and a max value, it is also a DynamicStat with a [runtime] current value and as well its base value and its dynamic value can be modified from the outside by subscribers who implement IGenericDynamicModifying<Health>
and/or IGenericBaseModifying<Health>
.
As you can see, this is quite clean in the inspector:
So far so good.
However, I now wanted to implement Items into my game. An Item
has several ItemModifier
which should be able subscribe to the corresponding stats.
As the interfaces for Modifying and Modifiable are generic, ItemModifier
has to be generic as well. This means, I have to create a HealthItemModifier, an EnergyItemModifier, basically for every Stat I have in my game. Additionally, one also has to consider how an ItemModifier
works, multiplicatively, additive, …, which means I would easily have to create four ItemModifier
per Stat.
If you now think about it, this will be the same story for BuffModifier, DebuffModifier, TalentModifier, SkillModifier and whatever I will implement into my game. An obvious solution would be to make one Modifier
class, however, Java teached me that you then do not know what kind of modifier you have, which is absolutly required when trying to interact with a specific kind, like all BuffModifiers.
Another solution I came up with, I could get rid of ItemModifier entirely and implement the interfaces into the objects directly, exactly like the Health example above. An item would then imlpement all required interfaces. As you can imagine, this would be awful to do as well, since one then has to write one class for every possible combination of items ( one item which modifies only damage, one item which only modifies energy and damage, …).
Last solution I can think of, making the IModifiable
and IModifying
not generic, but how would I then know which type I should aim for? How would an item know it modifies Health and not Energy? I would then have to serialize types, which I kinda hesitate to do.
I could need some input and thoughts with my situation, I greatly appreciate your comments, feedback and suggestions.