Getting player level from total XP without a loop

Hi,

For our game we want to store only experience for our level system, so if the formula changes for required XP in the future, we can scale it up/down appropriately without granting extra XP/removing XP.

My question now is, is there a formula that I can use to find out how to get the player’s level from their XP, instead of doing it iteratively (without a loop)?

Currently this is what we have for calculating XP from level and vice versa:

local module = {}

function module:GetXPForLevel(level: number)
	return math.floor(100 * (((level - 1) * 0.5) ^ 1.5))
end

function module:GetLevelFromXP(xp: number) -- is there a way to do what this function does, but without a loop?
	local level = 1

	while xp >= module:GetXPForLevel(level + 1) do
		level += 1
	end

	return level
end

return module

Thanks in advance for any help.

1 Like

Depending on the formula you are using, it is possible that you can convert lets say a summation for i=1,x of 2i into x(x+1). What is the formula you are using?

1 Like

I have no idea how your math works but can’t you just change all the multiplcation signs to division signs and - signs to + signs

math.floor(100 / (((level - 1) / 0.5) ^ 0.75))

It’s the one in the original post,

math.floor(100 * (((level - 1) * 0.5) ^ 1.5))

Note I’m subtracting 1 from the level as the base level is 1 instead of 0.

If this formula wouldn’t work with summation, I’m fine with switching it up as long as the required XP goes up over time. However, wouldn’t summation be the equivalent of a for loop in this case?

1 Like

Oh my bad I just skipped past that
Yes a summation would be a for loop
The easiest way would be to make a table with the cumulative exp required, given the index is the level.

2 Likes

The real question is do you really need to do this? Why is your loop an issue for you?

1 Like

That’s still going to end up requiring him to do a loop.

1 Like

It would be an O(n) startup time, but every subsequent call would be O(1)

1 Like

He would have to have an exact XP match for it to be a direct look up.

The level value should just be stored and updated as the player level’s up and only use the get level from xp method if there is a problem with the accuracy of the level data.

Sorry in advance for the long explanation of how the levels/experience is awarded. There’s a bunch of extraneous information but it gives insight as to why I didn’t consider certain routes.

The reason overall is because loops are relatively slow when compared to a simple formula.

We’re awarding XP based on the level the player is. The higher a player’s level, the more XP they’ll be awarded, so required XP doesn’t deviate from the XP awarded per level very quickly. It will eventually start deviating but not as quickly as some games. For this reason, high levels wouldn’t be unlikely.

XP is granted based on a weighted score based on time survived, and blocks broken. If a player broke 1000 blocks at a score of 0.3 per block, they’re awarded 300 XP which right away is already 6 levels before time survived, and that’s before the level multiplier and the time survived, thus we have to call GetLevelFromXP 6 times over, and so on.

I could save the XP required for each level to a lookup table and re-call the function as needed (ie the entry is missing), however we then run into the issue of it taking more and more memory as players reach higher and higher levels.

For those reasons, I believe the ideal solution here, if it’s even possible, would be to come up with a single formula that can calculate the level from the XP the player has, but if it isn’t something that is mathematically possible then the cache method would work to avoid recalculating levels from 1 to an arbitrarily large number.

The reason I didn’t design the level system like this is that the XP doesn’t reset per level. It just keeps accumulating over time (like GTA online’s leveling system if you’re familiar with that game).

The reason I do this is because we’re going to have a level leaderboard, and the players should be displayed based on their progress in the level.

So suppose that players at rank 1, 2, and 3 are all level 100

How would I determine in which order to rank them? The best option in my opinion, to not leave it up to luck, would be to display them based on their progress in the level, which is based on their total XP. Total XP would be saved to the OrderedDataStore, then just display the level equivalent to the total XP to the user.

Also what happens in the case of us switching up the formula? The best solution in order to ensure the player isn’t losing any XP/isn’t gaining any XP in the case of a formula change would be to calculate the required XP per-server.


Overall there are quite a few outlying factors that need to be taken into account with this whole system with respect to memory usage, predictability, etc which is why I didn’t go the typical route of level > reset XP to 0 > level, and so on.

This game is still not released to the public so I am fine with changing features around but to me it seemed like this structure of levelling and such was probably the way to go.

The simple solution is to store both total XP acquired and current XP. In the case of multiple players at the same max level, a simple comparison of total XP give you the ranking.

You aren’t going to be switching up the formula on a repetitive basis. So having to recalulate the level based off total xp would be a rare situation.

In this case you can just add currentXP + awardedXP and consume the XP required for the next level. Whatever is left over is carried toward the next level. You can simply use some recursion in this case to recall the method that applies the XP until there is not enough XP to level up anymore. Further if a player can gain 6 levels that quickly, then you need a new formula. They are progressing much to fast through levels, imo.

Doing this would bring up another issue, using OrderedDataStores we can only save a single number, which would be the XP in this case.

If I were to manually retrieve their data from the data store, that is another request to be made for each player that has the same level, this is an issue because the number of data store requests that can be made per minute is limited.

Honestly if there isn’t a way to do it what I initially wanted simply with a formula, I will just go with the cache method. It might take a bit to implement one that’s efficient enough but if it means not having to call the XP to level function over and over, it’s probably worth it.

Unfortunately I don’t believe you’ll find a formula that doesn’t require looping. Imo the cache/look up table is going to be a memory suck as the game progresses and will have to be rebuilt as well if you change the formula. Its a matter of memory vs processing. But we are here to help. So if that is your preferred solution then I am happy you are satisfied with one of our answers. I use a different method of storing and retrieving player data that allows me to store a great deal of player data without many calls to the datastores at all. So I don’t run into the data store request limitations.

1 Like

You also brought up a good point about levelling being too easy. I’ll just rework the formula as well as implement the cache. Players won’t level up so quickly which means there are less levels that need to be calculated, and that does double duty for memory usage.

One tip I could give you is don’t handle your data storage with direct get/set calls to the datastore. Cache the player data in the game and save it in intervals and when the player leaves. That way you can store much more data and not hit those request limitations.

1 Like

Oh no, we don’t do that. We make a copy of the data at runtime and then only call UpdateAsync when the player leaves or upon autosave.

By having to call :GetAsync upon leaderboard refresh, I just meant we’d have to get the player’s data from the data store if they aren’t in-game, and then have to use this to query who has the higher progress in the case of 2 leaderboard players having the same level.

1 Like

Why don’t you using methods rather than functions?

You did not just ask that question… Lock this topic someone lol.
Technically methods and functions are basically the same thing, it’s a matter of perspective and definition in most arguments. I assume you are reffering to him using “:” instead of “.” on this class functions. In his usage case I doubt its even worth talking about.

Knowing that the current formula is:

experience = 100*((level-1)*.5)^1.5

We can easily solve for exp by changing it to an equation that solves for level:

experience/100 = ((level-1)*.5)^1.5 -- step 1 move 100 over

(experience/100)^(2/3) = (level-1)*.5 -- step 2 move over our exponent (1.5 == 3/2 fraction form)

2*(experience/100)^(2/3) = level-1 -- step 3 move over our *.5 (1/2 fraction form)

2*(experience/100)^(2/3) + 1 = level -- step 4 move over our 1...

Our final equation is:

level = 2*(experience/100)^(2/3) + 1

Of course, you’ll need to floor it… and yes you’ll have to recalculate this whenever you change the level curve. This is the only way I can think of doing it without a loop… sorry if it’s not super straight forward I suck at explaining math. :derp:

2 Likes