How to maintain a "learning by using" skill system

I am in the process of writing my own skill system for a game, but I am having a hard time figuring out a good structure.
The skill system is fairly simple, the skills have a minimum level of 1 and a maximum of 100, and are leveled simply by using them. I’m using a simple formula that makes the skill levels increase slower as the skill goes higher : x = (y/b)^(1/a) - Where x is the skill level, and y is the required xp, and a and b are constants.

My question is: Should I keep a variable storing the skill level, and then have a separate variable storing the xp, or would it be okay simply to calculate the skill at runtime based on the xp, every time it’s needed? (Which could be pretty often). By calculating it I mean simply getting the skill level by calling a property that returns (y/b)^(1/a).

Thanks for reading!

This comes down to programmer preference as much as anything… a fairly simple calculation like this doesn’t really need to be cached but it doesn’t hurt if as you say this could be accessed very frequently (i.e. more than a few times per frame). If the performance impact is minimal either way go with whatever keeps the code cleanest!

One option to consider, if accessing the Skill value is erratic and calculation is costly, is deferred calculation:

private int _skill = 0;
public int Skill {
  get {
    if (_skill <= 0) {
      _skill = // Your XP->skill calculation here!
    }
    return _skill;
  }
  private set {_skill = value;}    
}

You would set Skill to 0 whenever XP changes so that Skill is only re-calculated the first time it is accessed after an XP update.

I’ll do a quick little example of how I handle this kind of thing since I already have in my project and might be useful reference for people starting up on these kinds of systems.

//these aren't really the #'s I use just example
//I think it's probably better to just have the XP totals set on awake()
//that way you don't have to do a bunch of formula calculations every time you gain XP
//should probably just use an array for this to more easily handle it
//could probably just load all 100 XP levels (or a reasonable # of levels based on current level)
//in the array once per scene

float level1XP = 50; 
float level2XP = 100;

//variables to store relevant data

public static int playerCurrentLevelInt;
float playerCurrentXPRequired;
float playerLastXPRequired;

//create a public static Instance to easily access
//local XP functions from any other script
public static PlayerXPScript XPInstance;

//use start or Awake
void Start() 
{
//some other stuff you need

//get player prefs for XP and player level
//in my game these are actually stored in another script called GlobalDataScript
playerXPTotal = PlayerPrefs.GetFloat ("XP Total", 0);
playerLevel = PlayerPrefs.GetInt ("Player Level", 1);


//get instance ready
XPInstance = GetComponent<PlayerXPScript>();
}

//use our instance to easily access the local XPUpdateFunction
//from any script in our project
public static void XPUpdateCall()
{
XPInstance.XPUpdateFunction();
}

//function where all the xp calculations and updates take place locally
void XPUpdateFunction()
{
//this part could probably be improved
//but it gets the job done and is fairly simple anyways

//checks what level the player is each time function is called
if (GlobalDataScript.playerXPTotal < level1XP)
{
playerCurrentLevelInt = 1;

playerCurrentXPRequired = level1XP;
playerLastXPRequired= 0;
}

//checks what level the player is each time function is called			
if (GlobalDataScript.playerXPTotal >= level1XP)
{
playerCurrentLevelInt = 2;

playerCurrentXPRequired = level2XP;
playerLastXPRequired = level1XP; 
}

//repeat the above process or use an improved system
//until you know what level you are after the current XP update

//then get what level you currently are according to PlayerPref
//excessive since you should already know but whatever I'll eventually
//rewrite my system
int playerLevelInt = PlayerPrefs.GetInt ("Player Level", 1);

//if you have more xp than level 1 MAX
if (GlobalDataScript.playerXPTotal >= level1XP)
{

//if you still are level 1 then update everything so you are level 2
if (playerLevelInt==1)
{
PlayerPrefs.SetInt ("Player Level", 2);
GlobalDataScript.playerLevel = 2;
GlobalDataScript.playerStatPointsAvailable +=  2;
PlayerPrefs.SetInt ("Player Stat Points", GlobalDataScript.playerStatPointsAvailable);
GlobalDataScript.playerSkillPointsAvailable += 2;
PlayerPrefs.SetInt ("Player Skill Points", GlobalDataScript.playerSkillPointsAvailable);
				
levelUpNotificationFunction();
}
}

//if you have more xp than level 2 MAX
if (GlobalDataScript.playerXPTotal >= level2XP)
{

//if you still are level 2 then update everything so you are level 3
if (playerLevelInt==2)
{
PlayerPrefs.SetInt ("Player Level", 3);
GlobalDataScript.playerLevel = 3;
GlobalDataScript.playerStatPointsAvailable +=  2;
PlayerPrefs.SetInt ("Player Stat Points", GlobalDataScript.playerStatPointsAvailable);
GlobalDataScript.playerSkillPointsAvailable += 2;
PlayerPrefs.SetInt ("Player Skill Points", GlobalDataScript.playerSkillPointsAvailable);
				
levelUpNotificationFunction();
}
}

}

So by using that instance you can easily call this function from any other script with 1 line of code. For example this could be on an enemy script:

if (XPUpdateBool == false)
{
XPUpdateBool = true;
PlayerXPScript.XPUpdateCall();
}

Once you call it from enemy script in the PlayerXPScript it does a check to see what level you are currently based on XP total. And then once it has that it checks to see if you have enough XP for next level but haven’t yet reached it and if so updates all your stats and saves PlayerPrefs.

It’s a bit of messy system the way I did this so I’ll probably make some improvements once the player can start hitting high levels. But that should hopefully be a pretty nice and simple outline of logic for people learning this for the first time.