Brainstorming: Developing a Roleplaying Skills and Subskills System

Good day, everyone!

I’m currently working on a Role-Playing combat system, and am trying to puzzle out the structure for said system. For example, one would have an Aim skill, and then subskills under that for things such as Pistol, Rifle and so on.

This thread is for my mucking about in code, but also for any suggestions folks might have as to how to implement or improve this system. My hope is to eventually create a framework that can be easily expanded upon to support additional sub/skills as it goes along, but I honestly have no idea how to get there. Yet :slight_smile:

I don’t really understand inheritance that well, to be honest, so I am kind of operating without a rudder atm. IE, should I create a Subskill class that inherits from the Skill class, or perhaps just create each subskill as another instance of skill? Should I create and maintain a list of skills, and how would I go through that list to find a specific skill by name? IE, the third entry in a list is far less intuitive than being able to look up a specific skill or subskill by name. How might one link skills and subskills so that the system could find each of them in a reasonably easy-to-understand fashion?

Below is the current Skill class. It has been made serializable so that it will appear in the inspector.

[System.Serializable]
public class Skill {
    public int skillLevel = 0;
    public int usePoints = 0;
    public int usePointsToAdvance = 1;  // 1 point for 0-9 pts.  2 points for 10-19, and so on.

    public void IncreaseUsePoints()
    {
        //Upon the successful use of a skill (hitting a target, for example), increase the use points,
        //then evaluate the number of use points.  If enough use points have been earned to advance the skill, do so.
        usePoints++;
        if (usePoints == usePointsToAdvance)
        {
            IncreaseSkill();
        }
    }

    public void EvaluateUsePointsToAdvance()
    {
        //this allows each 10 skill level points to round down to the nearest integer (0-9 = 0, 10-19 = 1 and so on) and then adds 1.
        //Used to determine how many use points must be earned to increase the skill value.
        usePointsToAdvance = Mathf.FloorToInt((float)skillLevel * 0.1f) + 1; 
    }

    public void IncreaseSkill()
    {
        //Increase the skill level, reset use points and determine how many
        //use points will be required to advance the skill again.
        skillLevel++;
        usePoints = 0;
        EvaluateUsePointsToAdvance();
    }
}

So far, this works just fine. Here is where things get fun, interesting, scary, inefficient… whatever :slight_smile: I am trying to determine the best way to organize this data on a character summary sheet. Perhaps adding a string to the skill that determines the skill name? Or simply creating new fields for each one, as in this Test class. Perhaps, as above, one could implement a list of skills and subskills, but I have no idea how I would assign each subskill, as an example, to a parent skill. The test script inherits from MonoBehaviour for now so that I can drag it onto a game object:

public class Test : MonoBehaviour {
    [Header("Character Attributes")]
    //Strength, dexterity and so on will go here.  Figure out skills first.
    [Header("Character Skills")]
    public Skill aim;  //this is a parent skill and will influence all ranged attacks.  Represents character's general aiming capability.
    public Skill pistol;  //this is a subskill, and is influenced by Aim skill when evaluating attacks using a pistol.  May not exceed 1/2 of parent skill.
  
    void Update()
    {
        ///
        /// This is testing code.  Pressing N will generate random numbers for the aim skill.
        /// Pressing spacebar increments the use points as though the skill was successfully performed.
        ///
        if (Input.GetKeyDown(KeyCode.N))
        {
            aim.skillLevel = Generator.GenerateRandomNumber(0, 100);
            aim.EvaluateUsePointsToAdvance();
        }
        if (Input.GetKeyDown(KeyCode.Space))
        {
            aim.IncreaseUsePoints();
        }
    }
}

I would welcome any thoughts folks might have in this regard. Right now, I’m kinda playing with a tool belt, but have only a basic idea of what a lot of things do :slight_smile: So my plan is to challenge the heck outta myself to learn different ways to organize, implement and go through this data. I simply have no idea what my options are, much less the drawbacks or benefits to each.

Well if I would implement such system then I would make a template class that store all info about the skills. Like so:

[System.Serializable]
public class AllSkills(){

public int Pistol;
public int Rifle;
public int EnergyWeapons;

public AllSkills(int pistol, int rifle, int energyWeapons){
Pistol = pistol;
Rifle = rifle;
EnergyWeapons = energyWeapons;
}

}

Then each object would reference this class template to store all required information about their skills.

AllSkills Skills = new AllSkills(10,10,10);

Okay, so I’ve been working on this some more. Bantaru, your idea of using a list of ints could work, however, I’ve been developing a fairly extensive list of skills and subskills. At present, I am just shy of 50 skills, and that would be a lot of data to keep track of by hand. Not to mention that some things may shift around. Due to this, I actually overhauled the system a fair bit.

Originally, I had changed the Skills code to make use of the following fields:

[Header("Physical Skills")]
    public Skill coreAim = new Skill("Aim2");
    public Skill coreMelee = new Skill("Melee");
    public Skill coreUnarmed = new Skill("Unarmed");
    public Skill coreEvasion = new Skill("Evasion");

    [Header("Mental Skills")]
    public Skill coreAnalysis = new Skill("Analysis");
    public Skill coreLeadership = new Skill("Leadership");

    [Header("Support Skills")]
    public Skill coreRepair = new Skill("Repair");
    public Skill coreMedical = new Skill("Medical");
    public Skill coreCompOps = new Skill("Computer Operation");

    [Header("Sub Skills: Aim")]
    public Skill aimPistol = new Skill("Pistol", Generator.ParentSkillList.Aim);  //use of small semi-automatic firearms.  Handguns and revolvers.
    public Skill aimRifle = new Skill("Rifle", Generator.ParentSkillList.Aim);  //use of longarm weapons that require two hands when fired in semi-automatic mode.  Rifles, shotguns, PDW's, SMG's and carbines.
    public Skill aimAutomatic = new Skill("Automatic", Generator.ParentSkillList.Aim);  //use of any weapons that fire in fully automatic or burst-fire modes.
    public Skill aimHeavy = new Skill("Heavy", Generator.ParentSkillList.Aim);  //Use of any heavy weapons such as rocket launchers and rotary cannons.
    public Skill aimThrown = new Skill("Thrown", Generator.ParentSkillList.Aim);  //Use of any thrown weapons such as throwing knives and grenades.

    [Header("Sub Skills: Melee")]
    public Skill meleePiercing = new Skill("Piercing", Generator.ParentSkillList.Melee); //Use of thrusting and piercing attacks with weapons such as spears and swords.
    public Skill meleeSlashing = new Skill("Slashing", Generator.ParentSkillList.Melee); //Use of slashing attacks.  Usually attempted with swords, axes, halberds and so on.
    public Skill meleeBlunt = new Skill("Blunt", Generator.ParentSkillList.Melee);  //Use of blunt weapons such as staves, clubs and such.
    public Skill meleeDeflect = new Skill("Deflection", Generator.ParentSkillList.Melee);  //Use of weapons in deflecting melee or unarmed attacks.  No effect on ranged attacks.

Following this, I went through and manually linked each child skill. IE, aimPistol.myParentSkill = coreAim; and so on. Needless to say, this is a royal pain to maintain :slight_smile: As such, I have moved over to two static lists (for primary and subskills) of strings that define each Skill by name. The constructor for a skill will accept that name while iterating through a list, and where appropriate, will assign a child skill’s parent skill.

I then set up a function that will return each Skill by searching for the name. This seems to be working fairly well, but I am wondering if a dictionary would be better than digging through a list of skills everytime you need to find one. I’ve never used them before, so I’m not exactly sure what the advantage would be.

Here is a snippet of the current code:

public Skill GetSkillByName(List<Skill> listToSearch, string nameToSearchFor)
    {
        int index = listToSearch.FindIndex(f => f.GetName() == nameToSearchFor);
        if (index >= 0)
        {
            Debug.Log("Found " + nameToSearchFor + " skill!");
            if (listToSearch[index].myParentSkill != null)
            {
                Debug.Log(nameToSearchFor + " skill has a parent skill: " + listToSearch[index].myParentSkill.GetName());
            }
            return listToSearch[index];
        }
        else
        {
            Debug.LogError("Error: No skill found: " + nameToSearchFor + ".  Did you spell the skill name correctly?");
            return null;
        }
    }
void GenerateStatList()
    {
        //Generate primary skills
        for (int i = 0; i < Generator.PrimaryPhysicalSkillsList.Count; i++)
        {
            Skill tmpSkill = new Skill(Generator.PrimaryPhysicalSkillsList[i], Generator.SkillCategoryList.Physical);
            parentSkillList.Add(tmpSkill);
        }
        for (int i = 0; i < Generator.PrimaryMentalSkillsList.Count; i++)
        {
            Skill tmpSkill = new Skill(Generator.PrimaryMentalSkillsList[i], Generator.SkillCategoryList.Mental);
            parentSkillList.Add(tmpSkill);
        }
        for (int i = 0; i < Generator.PrimarySupportSkillsList.Count; i++)
        {
            Skill tmpSkill = new Skill(Generator.PrimarySupportSkillsList[i], Generator.SkillCategoryList.Support);
            parentSkillList.Add(tmpSkill);
        }

        //Generate secondary skills
        for (int i = 0; i < Generator.SecondaryAimSkillsList.Count; i++)
        {
            int index = parentSkillList.FindIndex(f => f.GetName() == "Aim");
            if (index >= 0)
            {
                Skill tmpSkill = new Skill(Generator.SecondaryAimSkillsList[i], Generator.SkillCategoryList.Physical, parentSkillList[index]);
                childSkillList.Add(tmpSkill);
                Debug.Log("Child skill: " + tmpSkill.GetName() + " has parent skill: " + tmpSkill.myParentSkill.GetName());
            }
        }
        for (int i = 0; i < Generator.SecondaryMeleeSkillsList.Count; i++)
        {
            int index = parentSkillList.FindIndex(f => f.GetName() == "Melee");
            if (index >= 0)
            {
                Skill tmpSkill = new Skill(Generator.SecondaryMeleeSkillsList[i], Generator.SkillCategoryList.Physical, parentSkillList[index]);
                childSkillList.Add(tmpSkill);
                Debug.Log("Child skill: " + tmpSkill.GetName() + " has parent skill: " + tmpSkill.myParentSkill.GetName());
            }
        }
        for (int i = 0; i < Generator.SecondaryUnarmedSkillsList.Count; i++)
        {
            int index = parentSkillList.FindIndex(f => f.GetName() == "Unarmed");
            if (index >= 0)
            {
                Skill tmpSkill = new Skill(Generator.SecondaryUnarmedSkillsList[i], Generator.SkillCategoryList.Physical, parentSkillList[index]);
                childSkillList.Add(tmpSkill);
                Debug.Log("Child skill: " + tmpSkill.GetName() + " has parent skill: " + tmpSkill.myParentSkill.GetName());
            }
        }
}

Does anyone have any thoughts on using a dictionary? What might be the benefits and drawbacks of it?

This is something that would highly benefit from using interfaces, abstracts and polymorphism in general.