- Home /
Best structure for a perk system?
I got a game that makes use of a simple perk system. There is a list of "perks" and every time the player levels up the game should generate a list of a random 3 that the player can choose from. Here's a few examples of perks to give a basic idea:
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.
Shielded - You gain a shield which can absorb damage and recharges automatically.
Some perks require others to already have been picked before they can get randomized, and a lot of perks has values that I'll want to tweak for balance purposes.
With all of this in mind, I've been making a PerkManager class that handles all of this, but I'm not quite sure of the best way to structure it up. It makes sense, I think, to have a Perk class which would look like the following:
public class Perk
{
public string name;
public string description;
public bool picked = false;
public Perk requiredPerk;
public List<int> values;
}
Name/description are self-explanatory, picked determines whether the perk has been picked by the player, requiredPerk is, when needed, a reference to the instance of the other perk that would need "picked" set to true for it to appear, and values is a list of integers - set up here so that it's easy for me to balance the perks right in the PerkManager.
I then have a List that I would fill with Perk objects:
public List<Perk> perks = new List<Perk>();
This list would be read from to generate another list of eligible perks to be picked when the player levels up.
The main issue here is that I'm not sure of the best way to initialize the large number of Perk objects. I can do a simple initialization as such:
public Perk perkHealthy = new Perk();
public Perk perkVigorous = new Perk();
And then declare the perk info at start, which sets all the perk info and also adds each perk to the perks list.
void Start()
{
//Healthy
perkHealthy.name = "Healthy";
perkHealthy.description = "Increases your maximum life to {0}.";
perkHealthy.values.Add(50); //Extra health given
perks.Add(perkHealthy);
//Vigorous
perkVigorous.name = "Vigorous";
perkVigorous.description = "Increases your maximum life to {0}.";
perkVigorous.requiredPerk = perkHealthy;
perkVigorous.values.Add(100); //Extra health given
perks.Add(perkVigorous);
This works, but it seems a bit clumsy. I could of course also set Perk to serializable and enter all this information directly as members of 'perks' in the Unity editor, but I wanted to avoid this since I want these values to be inherent values of the class rather than tied to a specific instance of PerkManager.
Alternatively I can do it all at start, by first adding this to Perk:
public Perk(List<Perk> perkList = null)
{
if(perkList != null)
perkList.Add(this);
}
And then at start:
void Start()
{
Perk perkHealthy = new Perk(perks)
{
name = "Healthy",
description = "Increases your maximum life to {0}.",
values = { 50 }
};
Perk perkVigorous = new Perk(perks)
{
name = "Vigorous",
description = "Increases your maximum life to {0}.",
requiredPerk = perkHealthy,
values = { 100 }
};
This is a bit more elegant since there's only a single place I can declare all perks in order - but it has the huge disadvantage of the perks not being public and as such not being easily accessible - unless there's a simple way to refer to a specific instance in a List<> that I am not aware of? From what I understand I'd basically have to search the perks List for name or something similar every time I want to refer to a specific perk, which seems bad on performance.
I wish I could just do the above at the top of the class itself so that I can set the perk objects to public, but that wouldn't work since 1) I can't populate the list outside a function, and 2) I can't refer to these newly declared objects for requiredPerk at declaration - so then I'd still have to separately do these things in Start() in which case the very first solution is probably better.
Finally, a third solution might be to declare the perks List<> with all perks right away, and instead of making these instances just rely on a new "perkID" field which acts as its identifier and is used for things like finding it in the list or looking up a requiredPerk. But I don't really know if this is good practice or not.
Does anyone have thoughts of what would be the best way to structure up this?
Personally, I would probably either use ScritptableObjects and/or have a base class with virtual methods and derive from that. If the values for these pickups are constant, scriptableobjects are likely a good way to go.
Thanks for the reply!
I'm guessing I'd have to use a combination of both since the unique values per perk differ a lot (see https://answers.unity.com/questions/1608866/inheritance-best-solution-to-handle-many-instances.html for details on that, it's a bit of a separate question/issue).
Nonetheless, I will look into the use of ScriptableObjects, thanks for that suggestion!
Alternatively you could do something like this
public class Perk$$anonymous$$anager : ScriptableObject
{
#if UNITY_EDITOR
[UnityEditor.$$anonymous$$enuItem("Tools/New Perk manager")]
public static void CreateNewPerks()
{
UnityEditor.AssetDatabase.CreateAsset(ScriptableObject.CreateInstance<Perk$$anonymous$$anager>(), "Assets/Resources/" + PerkResourceHash + ".asset");
}
#endif
private static Perk$$anonymous$$anager instance;
private const string PerkResourceHash = "perks";
[System.Serializable]
public class Perk
{
public int id;
public string Name;
public string Desc;
public float baseValue;
public GameObject somePerkObject;
public Sprite Icon;
public Something someField;
}
public List<Perk> perks;
public static Perk GetPerk(int id)
{
if (instance == null)
instance = Resources.Load<Perk$$anonymous$$anager>(PerkResourceHash);
return instance.perks[id];
}
public static Perk GetPerk(string Name)
{
if (instance == null)
instance = Resources.Load<Perk$$anonymous$$anager>(PerkResourceHash);
foreach (Perk perk in instance.perks)
{
if (perk.Name == Name)
{
return perk;
}
}
return null;
}
}
Your answer
Follow this Question
Related Questions
C# List and GUI 3 Answers
How to sum a property of a class in a List 2 Answers
Can't call derived class function from list of base class. Ideas? 1 Answer
Need my function to work with different lists of different values (classes) 1 Answer
Requesting help utilizing resources.load to auto assign rigidbody variable in a class list 0 Answers