- Home /
Inheritance best solution to handle *many* instances of a class with unique values?
This is related to https://answers.unity.com/questions/1608857/best-structure-for-a-perk-system.html - but since fundamentally it's a separate question I'm making a separate post.
I have a game which makes use of "perks", which are unique player bonuses that can be picked upon leveling up. Here's a few examples:
Healthy - Increases your maximum life to 150.
Vigorous - Increases your maximum life to 200. (Requires "Healthy")
Health Regeneration - While detected by an enemy, you regenerate 1 health per second.
Heavy Armor - You no longer take damage from enemy death explosions.
Health Specialist - Health pickups now give an additional 25% health, but armor pickups now give 50% less armor.
Thus I have a large amount of instances of a "Perk" class which currently looks like this:
public class Perk
{
public string name;
public string description;
public bool picked = false;
public Perk requiredPerk;
public List<int> values;
}
A Perk always has a name, description, and picked status. Optionally it might also have a requiredPerk. Finally, each perk may or may not have a few unique values. As seen in the examples above, "Healthy" would have a unique int set to 50 (bonus health). "Health Specialist" would have two unique floats - 1.25 and 0.5 - which are the pickup multipliers, and so on. Currently my setup assumes it's all ints, so I'd have to either store those values as percentages and convert, or have a separate List variable - both of which seems like clumsy solutions.
Now, at a first glance the obvious fix for this is to use inheritance. E.g.
public class Perk
{
public string name;
public string description;
public bool active = false;
public Perk requiredPerk;
}
public class PerkHealthSpecialist : Perk
{
public float armorMultiplier;
public float healthMultiplier;
}
But I'm not quite sure if this is the most logical approach. Most perks have some kind of value attached to them, so they would all pretty much be their own class with a single instance.
Is there a more recommended way to do this, or is inheritance the way to go?
Realistically no perk interacts differently then another, you simply call upon the data from the perk when necessary. So if it adds health, you call upon the health perk or whatever and check the base value * current level. So you could simply do something like this to more easily define such ideas.
public enum Perk$$anonymous$$odifier
{
Health,
Armor,
Damage,
Speed
}
[System.Serializable]
public class $$anonymous$$odifierValuePair
{
public Perk$$anonymous$$odifier modifier;
public float value;
}
[System.Serializable[
public class Perk
{
public int id;
public string Name;
public List<$$anonymous$$odifierValuePair> perks;
}
Thanks for the response!
$$anonymous$$any of the perks do a lot more than change basic data - so far I've implemented them by calling on their 'picked' state wherever they manipulate existing game systems. An example, the "Last Stand" perk which makes the player invulnerable for 10 seconds and gives them max ammo if they would otherwise have died, is called in a function elsewhere that handles player death:
if (playerHealth <= 0)
{
playerHealth = 0;
if (Perk$$anonymous$$anager.Instance.perkLastStand.picked)
{
//Gives max ammo, waits for 10 seconds and then calls "Die"
StartCoroutine("LastStand");
}
else
StartCoroutine("Die");
//...
The number of seconds (10) is currently defined as a value within that perk's class instance.
So far the only reason I really need the different values defined within the perk class is to be able to quickly make balance changes in a single place ins$$anonymous$$d of having to hunt down the places where the perks are called.
In theory I could even have it all as a completely separate set of variables, e.g.
public class Perk$$anonymous$$anager: $$anonymous$$onoBehaviour {
public int perkHealthy_amount = 50;
public int perkVigorous_amount = 100;
public float perkHealthSpecialist_armor$$anonymous$$ultiplier = 0.75f;
public float perkHealthSpecialist_health$$anonymous$$ultiplier = 1.5f;
public int perkLastStand_duration = 10;
Which technically fits closer to the purpose of it all, namely a central place to tweak balance, but it feels quite inelegant.
Why wouldn't you be able to also just make Duration a perk modifier and have a value of 10? Also your perks should be data objects, that are represented in a visual object. Never the same object. For example, you could create a perk module class and load it dynamically from the resources and have the module name as a part of the perk data object. This way you can dynamically load your perk modules that handle various perk activities through your base module class. Something like this.
public partial class Perk$$anonymous$$odule : ScriptableObject
{
private Perk _perk;
public virtual void Initialize(Perk perk)
{
_perk = perk;
}
public virtual void Activate$$anonymous$$odule()
{
}
public virtual void Update$$anonymous$$odule(float time)
{
}
public virtual void Deactivate$$anonymous$$odule()
{
}
}
public class Duration$$anonymous$$odule
{
public float duration;
public override void Initialize(Perk perk)
{
base.Initialize(perk);
foreach ($$anonymous$$odifierValuePair pair in perk.modifiers)
{
if (pair.modifier == Perk$$anonymous$$odifier.Duration)
{
duration = pair.value;
break;
}
}
}
}
Answer by mchts · Mar 05, 2019 at 05:24 PM
A simple solution is just to create a generic class for perks with additional values which derives from Perk. Then instantiate it with desired type (or just use List<object>
to store multiple types of values).
public class PerkWithValues<T> : Perk{
public List<T> values;
}
But i must state that this question may not be completely suitible when it comes to unity. Because unity's approach is more like component based. So it's most likely to face some problems at some point which may look like violating OOP rules.
I could make further recommendations but it depends on whether you want to create predefined perks with predetermined values or just create random ones in runtime.
That is a nice approach, at least for the time being - thanks!