I’m writing have written a book called How To Make an RPG. I wrote this article while mulling over the combat section; it discusses stats in JRPG games. I suggest a basic architecture to track stats as the starting point to launch into a fully fleshed out combat system.
What Is a Stat?
A statistic, or stat, is a number describing an aspect of a game entity. A game entity might be a monster, character, weapon or spell. Stats define game entities in the world. In RPGs, the player character’s stats can be increased to benefit the player.
In JRPGs stats are used exclusively to simulate combat. If you attack a monster the computer needs a way to decide if you hit it and if you do, how much damage you inflict and finally it needs to check if the blow killed the creature. To make these decisions the computer runs simulation code using the stats.
Let’s take a look at example stats for a JRPG character:
- Health Points or HP - represents the amount of damage a character can take before dying or being knocked out.
- Magic Points or MP - represents the amount of magical power a character has. Higher the power, the more spells can be cast.
- Strength - represents the character’s physical strength. Determines amount of damage attacks inflict.
- Speed - represents how fast the character moves. Determines frequency of attacks and chance to dodge incoming attacks.
- Intelligence - represents how clever the character is. Determines power of spells and ability to resist magic attacks.
If you play JRPGs you’ll already be familiar with these stats, they’re quite common, but the exact effect of each stat varies according to the game.
Stats like charisma (representing how a charming a person is) rarely feature in JRPGs because they don’t affect combat. Characterization in a JRPG isn’t done via stats, it’s done via dialog, appearance and actions characters take in the game world.
Stats are a description the computer can understand
Stats describe aspects of game entities which are important for a combat simulation. We’ve listed the stats a character might have but characters aren’t the only game entities involved in combat - what about weapons, armor, spells and enchantments? These are all important in combat and are described using stats.
- Attack type - is it a physical attack? magical? elemental? or a combination!
- Attack power - how much damage can this weapon inflict?
- Defense power - how much damage can this weapon block?
- Attack rate - how quickly can the weapon be used to attack? Is it a small knife or giant two-handed hammer?
These stats describe a sword but it’s worth thinking about other entities as well. What stats might a spell have, a piece of armor, a magic ring? Try coming up with a few examples and think what information that computer is going to need.
Notice how similar the sword stats are to the character stats. Attack power is similar to character strength; they both determine the amount of damage inflicted. Attack rate is similar to speed they both determine how often an attack can take place. Stats that seem to have a similar effect combine in the combat simulation, according to a formula, to get a single, final number, we’ll called a derived stat.
Derived Stats
All the stats we’ve looked at so far are called base stats. Base stats represent one pure aspect of an entity, they can’t be broken down into smaller parts. Derived stats, on the other hand, are built up from multiple smaller parts. Derived stats are calculated from a number of base stats to form a new number that represents them both. This calculation might be as simple as adding them, or it maybe more complicated.
Base stats are rarely used in combat simulation directly, instead they’re often transformed into derived stats that make the calculations easier. When simulating combat we might want to know the total attack power of a character, this would be a derived stat. The calculation might look like this
total_attack_power = char.strength + weapon.power
The final attack power is derived from the weapon’s power and the character’s strength. In this case the calculation is simple and intuitive but in a finished game it’s likely to be more complicated.
Why Have Stats at All?
Reality is made from nothing but atoms and space, there are no stats, a cheetah does not have some intrinsic speed stat to compare with a human, both are just billions of atoms combined in different ways. Games simulate alternate realities modeled on our own. In JRPGs we want to be able to simulate a fight between a cheetah and a man blow by blow without modeling all their individual atoms.
Computer games need a higher level of abstraction to make things simpler. Stats are part of that abstraction. How do we decide who wins in a battle between a dragon and a giant squid? We plug the stats into a simulation and we find out!
Stats without the simulation aren’t very useful. The stats and simulation together help us answer questions important for an RPG, such as:
- How well does the magic potion heal my character?
- How much damage does my axe strike do to this troll?
- Will this Dragon’s fireball kill my character?
- Do I have enough magical energy to teleport my party?
The combat simulation and stats aren’t modeled closely on reality because that tends to be quite limiting; Dragons aren’t real, rabbits rarely carry gold coins and combatants rarely trade blows in an orderly turn-based manner but games are often better with these features. Gameplay has to be the designers most important consideration when coming up with a simulation.
In JRPGs the most important simulation is the combat, stats exist to make combat interesting and enjoyable.
What Stats Should a Game Have?
Now we know why we’re using stats, it’s a good time to consider exactly which stats we should have in our game. Here are three rules of thumb to help decide when to add a stat.
1. The simulation requires it
The best reason to create a stat is that you need it to simulate some action in the game world. We might need to decide who attacks first in combat, then we need to know who is fastest and therefore we need a speed stat. We need to determine how much damage is done and therefore we need a strength stat and an attack stat for the weapon, and so on.
2. Thematic
If the game is based on the Cthulhu mythos, and deals with mind-melting gods from other dimensions; a stat to represent sanity may be a good addition. If the game’s about vampires, a stat representing the player’s need to feed, would be a good addition. A post-apocalypse game could have stats to represent hunger or radiation level.
Where possible make stats complement the game themes.
3. Define by difference
A stone giant and a dragon are fighting, imagine how the battle would play out. The giant is strong but the dragon is fast. Those comparisons indicate an excellent place to introduce two stats to describe that difference. In this case we could introduce speed and strength stats, and assign them appropriate values. If you want to differentiate between two entities according to some aspect, then that’s a good indicator that it’s worth adding a new stat.
What Stats Should a Game Have?
Games differ wildly on how many stats they have.
If you take an RPG like Zelda, does Link have a strength stat or an intelligence stat? No! An RPG doesn’t need a lot of stats to be good but nearly all RPGs will have some stats. The screenshot below shows that Link’s HP stat; the line of hearts, near the bottom of the screen represents the HP. Each heart represents 2 HP points. If Link takes 1HP of damage, half a heart will disappear, if you take 2HP damage an entire heart will dissapear! The green bar next to the hearts represents Link’s current MP. Zelda does have a small number of stats but presents them graphically instead of using numbers.
PC games like Wizardry use large amounts stats as can be seen in the screenshot below. Wizardry is a western style RPG and they use more stats. A game like Wizardry uses stats in many more places, outside of combat, than a typical JRPG. Western RPG stats are involved in general game progression, like conversations, lock-picking etc.
When deciding on the number of stats the most important question to ask is
“Is each stat important in the game?”
If it is, that’s fine, add the stat. But don’t have a stat for swimming if your game doesn’t have any water!
The greater the number of stats, the greater the number of inputs into the combat simulation. Too few stats and the combat might be dull and predictable, too many and it may be difficult for a player to understand how the stats influence it. As a rough guide, for base stats I wouldn’t go higher than 10. For base weapons and armor I’d try to keep it no more than 5. The combat game presented in the How To Make an RPG book keeps the number of stats minimal, so the combat simulation is clear and easy to understand.
Secret Stats
Playing Zelda you discover increasingly powerful weapons, you start with a basic sword which might require 2 strikes to kill an enemy. The next sword is a little better and slays enemies in a single strike. The game doesn’t give you any stats about these two weapons, but an attack power stat exists, it’s just hidden.
Hiding stats from players is a good idea if showing them doesn’t add much to the game. In Zelda there’s no real choice about weapons and armor, it’s a linear progression, you find sword 1 then sword 2 and then sword 3, each is better than the last. You don’t need to know the weapon stats. In more traditional JRPGs there are multiple weapons to choose from and they have different stats, with different trade-offs, making for an interesting choice.
Luck stats that effect loot, dodging and critical hits are a favorite to keep as a hidden stat.
Implementing Stats
Now we have a definition of what a stat is, we can start on an implementation. At the most simple level a stat are a name associated with a number. A dictionary is a good data structure to describe an entity using a number of stats. Here’s a sketch of an implementation in Lua.
-- Stat: [id:string] -> [value:int]
base_stats =
{
["hp_now"] = 300,
["hp_max"] = 300,
["mp_now"] = 300,
["mp_max"] = 300,
["str"] = 10,
["spd"] = 10,
["int"] = 10,
}
This code snippet defines a table of some basic stats that might describe a player or creature in the game. The HP and MP stats have been broken into two, hp_now
and hp_max
. This is because these stats are commonly depleted during combat. The hp_max
and mp_max
represent the maximum value the HP and MP can be, the hp_now
and mp_now
represent the value of the HP and MP at the current point in time. In combat a player might receive a lot of damage, this would reduce his hp_now
stat, if he drank a potion is would restore his hp_now
stat, but never above the hp_max
value. The hp_max
stat will rarely change and when the player is fully healed the hp_now
stat is the same value as the hp_max
stat.
A General Stat Class
A table of names and values is a great starting point but for use in a combat simulation we need to add more functionality. To begin with we’ll make a stat class that will make it easy to add extra functions to.
Stats = {}
Stats.__index = Stats
function Stats:Create(stats)
local this =
{
mBase = {},
mModifiers = {}
}
-- Shallow copy
for k, v in pairs(stats) do
this.mBase[k] = v
end
setmetatable(this, self)
return this
end
function Stats:GetBase(id)
return self.mBase[id]
end
This class takes in one argument, a table of base stats, it copies the values of this table into mBase
. The values are copied so each instance of stat will have it’s own copy of the stats. If you didn’t copy the stats and created a few classes with same stat table, when you reduced the hp in one object, you’d reduce the hp in all the objects! Probably not what you want.
There’s an additional empty table called mModifiers
that handles how stats change during combat. We’ll talk about this in more detail in the next section. The class currently has one function, GetBase
, if you pass in a stat’s id, it will return the current value. This next snippet shows it being used.
local stats =
Stats:Create
{
["hp_now"] = 300,
["hp_max"] = 300,
["mp_now"] = 300,
["mp_max"] = 300,
["str"] = 10,
["spd"] = 10,
["int"] = 10,
}
print(stats:GetBase("int")) -- 10
print(stats:GetBase("hp_now")) -- 300
At the moment, there’s not much advantage to using the stats class but it’s a good structure for us to add more detail to.
Modifiers
RPGs are complicated and base stats are commonly modified. Here are some examples of modifiers:
- A magic sword that +5 to the player’s strength
- A ring that adds 5% to the maximum hp
- A curse that reduces str, spd and int by 5
- The character becomes enraged doubling his strength and halving his intelligence
Without careful planning these types of modification can quickly give rise to a to a heap of special cases and spaghetti code. We need a general way to deal with modifying player stats.
There are two types of modifier, modifiers of addition and modifiers of multiplication. Let’s reformulate the examples using addition and multiplication.
magic_sword = { add = { ["str"] = 5 } }
magic_ring = { mult = { ["max_hp"] = 0.05 } }
curse = { add = { ["str"] = -5, ["spd"] = -5, ["int"] = -5 } }
rage = { mult = { ["str"] = 1, ["int"] = -0.5} }
The above code has translated the English language descriptions into data, which is pretty cool! Halving a value is as simple as multiplying it by -0.5 and then subtracting that from the original value. Why not just multiply by 0.5 instead? We’re coming to that, but basically it helps us stack multiple modifiers.
We’re going to add support for modifiers to the stat class but not just yet. First we have another decision to make.
Let’s imagine the player has the following items equipped:
magic_hat = { add = { ["str"] = 10} }
magic_sword = { add = { ["str"] = 5, ["spd"] = 5} }
The player has two items and they both affect the str stat, one adds 10 and one adds 5, so what should happen when the player is wearing both? How should the modifiers stack? Well, it seems obvious; the player should get a +15 bonus.
Let’s consider the case with multiple mult modifiers.
curse = { mult = { ["str"] = -0.5, ["spd"] = -0.5, ["int"] = -0.5 } }
bravery = { mult = { ["str"] = 0.1, ["spd"] = 0.1, ["int"] = 0.1 } }
Here the player has a curse spell that halves the players basic stats but also a bravery stat that increases all stats by 10%. How do we deal with applying both of these modifiers? Again we add them and get -0.4, which means stats are reduced by 40% rather than 50%.
Then the final decision we have to take is when each of these modifiers types should be applied. Do we multiply the base stats, then add all the stat bonuses? Or do we add the stat bonuses first, then do the multiplication? This is down to preference, the only key rule is that they must be applied as sets, otherwise the order each modifier is evaluated changes the bonuses which would be very unclear to the player.
Doing the multiplier last makes multipliers more powerful and that’s what I’ve opted for here. This works both ways, bonuses are stronger but so are curses.
Let’s extend the code so we can apply modifiers.
--
-- id = used to uniquely identify the modifier
-- modifier =
-- {
-- add = { table of stat increments }
-- mult = { table of stat multipliers }
-- }
function Stats:AddModifier(id, modifier)
self.mModifiers[id] =
{
add = modifier.add or {},
mult = modifier.mult or {}
}
end
function Stats:RemoveModifier(id)
self.mModifiers[id] = nil
end
function Stats:Get(id)
local total = self.mBase[id] or 0
local multiplier = 0
for k, v in pairs(self.mModifiers) do
total = total + (v.add[id] or 0)
multiplier = multiplier + (v.mult[id] or 0)
end
return total + (total * multiplier)
end
In order to apply and remove modifiers, we added two new functions AddModifier
and RemoveModifier
.
The AddModifier
function takes in two parameters an id, to uniquely identify the modifier being added and the modifier table itself that describes the types of modification to be applied.
AddModifier
is called when the player equips a weapon, is affected by a spell or uses a special skill. The modifier table may have add
and mult
tables but these are both optional and if they don’t exist an empty table is used.
To understand why it’s important that id is unique, let’s the consider an example. If the player has two rings of +1 strength, the ring’s ids need to differ. If each id was “ring_str_plus_one” then the modifiers would overwrite each other. Instead of giving plus +2 strength, the rings would only give +1.
If ids are totally unique such as “000001” and “000045” then the modifiers don’t clash and everything works as expected. (There may be certain cases where a clashing id is desirable. For instance the curse spell might only be applied once to a creature, if the id’s of all curse spells are the same, only one can ever be applied.)
RemoveModifier
simply looks up the modifier by it’s id and deletes it from the table. This is used when unequiping a weapon or when a spell expires.
The Get
function gets a base stat with all modifications applied. In a combat simulation, we’ll rarely need the base str stat, we’ll want to use the fully modified one. The fully modified stat is calculated by first getting the base number and then applying all add modifiers in the table that affect that stat.
Modifiers are combined together using a simple loop. The add modifiers are added to the base stat directly. The multiplier modifiers have their values summed and are applied after the add modifiers.
Let’s run through some quick examples to show it working:
local stats =
Stats:Create
{
["hp_now"] = 300,
["hp_max"] = 300,
["mp_now"] = 300,
["mp_max"] = 300,
["str"] = 10,
["spd"] = 10,
["int"] = 10,
}
function PrintStat(id)
local base = stats:GetBase(id)
local full = stats:Get(id)
local str = string.format("%s>%d:%d", id, base, full)
print(str)
end
PrintStat("str") -- str>10:10
Here we’ve created a new stats object with some default values, then we’ve added a function that prints out a stat with it’s id, base value and total modified value. The default strength value is 10 and because we have no modifiers both the base and total str value are printed out as 10.
Let’s add some modifiers!
magic_hat =
{
id = 1,
modifier =
{
add =
{
["str"] = 5
},
}
}
stats:AddModifier(magic_hat.id, magic_hat.modifier)
PrintStat("str") -- str>10:15
When our character wears the hat of +5 str, the based value remains at 10 but the total goes to 15, as expected. Equipping an additional item in the form of the magic sword increases the str even more.
magic_sword =
{
id = 2,
modifier =
{
add =
{
["str"] = 5,
}
}
}
stats:AddModifier(magic_sword.id, magic_sword.modifier)
PrintStat("str") -- str>10:20
There are now two add modifiers each adding +5 which combined with the base 10 gives a total of 20 strength. The stacking works as expected! Now to add some percentage increases.
spell_bravery =
{
id = "bravery",
modifier =
{
mult =
{
["str"] = 0.1,
-- other stats omitted
}
}
}
stats:AddModifier(spell_bravery.id, spell_bravery.modifier)
PrintStat("str") -- str>10:22
In our fictitious example game, I like to think the bravery spell adds 10% to all stats but here we only care about strength. Our total strength, after the +10 from equipment, is 20. 10% of 20 is 2, add them together and we get the final value of 22 strength. Our character is getting pretty powerful! Time to bring him down a notch, with a final modifier!
spell_curse =
{
id = "curse",
modifier =
{
mult =
{
["str"] = -0.5,
-- other stats omitted
}
}
}
stats:AddModifier(spell_curse.id, spell_curse.modifier)
PrintStat("str") -- str>10:12
The curse spell halves all stats, again we’re only concerned about strength. As before all the add modifiers are applied first giving 20. Then the multiplication modifiers are summed, in this case 0.1 + -0.5 = -0.4. So instead of reducing our strength by 50%, the +10% of the bravery spell means the strength will only be reduced 40%. 40% of 20 is 8, subtracted from 20 gives the final strength total of 12. Which is great! Everything is working as expected.
The stat class allows multiple modifiers such as spells and equipment to be tracked easily and applied in a general scalable way. The class is a great base to build up the equipment and item code; ready for use in the combat simulation.
Derived Stats Implementation
Derived stats are some function that combines multiple stats together in some manner to be used by the simulation. A derived stats might use stats from all over; weapon stats, player stats and even stats from the environment! Therefore they exist at a higher level of abstraction than the stat class we’ve made (we don’t have weapons, or a notion of equipping them etc). The implementation for derived stat, relies on code and systems that we haven’t written yet and so the implementation must be delayed until later.
What’s Next?
The stat code is just one component of a full combat system. At this stage, some of the other components are still unclear - how does regeneration of HP or MP work? What about effects like elemental attacks like fire? How do levels fit into this system? The answers to these questions all require more of combat framework to be built and the next piece to build is the leveling and experience component.
Ready to Go Beyond Stats? Build Your Entire RPG
You've mastered the basics of stats systems, but that's just one piece of the RPG puzzle. Ready to tackle the rest?
The games industry rarely shares its secrets. "How To Make An RPG" is your comprehensive guide to turning your RPG dream into reality. This isn't just theory – it's practical, hands-on knowledge.
Stop dreaming about making your RPG. Start building it today.
Level Up Your RPG Dev Skills – Get the Full GuideEnjoyed this article? Share or link to it, to support the site and help fellow developers.