Skip to main content

Someone wanted to learn this too, so Grasp built them a personal learning path.

You can keep exploring this one or create your own

RPG System Design: Experience, Levels & Stat Curves

Hello! Welcome to the seventh module of our course, "Character Progression and Inventory." In the last module, we put the finishing touches on our combat system by adding impactful visual feedback like damage pop-ups and sprite flashes. Now that our battles are engaging, it's time to add the core reward that drives every JRPG: getting stronger.

This lesson directly addresses the learning outcome: Design and implement an experience and leveling system with stat growth curves. We will build the foundational mechanics of the RPG loop: win battles, gain Experience Points (XP), and "Level Up" to increase a character's power. This system is the engine of player progression and is crucial for creating a long-term sense of achievement.

1. The Purpose of a Leveling System

Before we dive into formulas and code, let's establish why leveling systems are so fundamental to the JRPG experience. They serve several key design purposes, from providing a sense of achievement to controlling the flow of the game.

How to Make an RPG: Levels

The article 'How to Make an RPG: Levels' from howtomakeanrpg.com provides an excellent overview of the role levels play in game design. It explains how they create a sense of achievement, support the 'hero's journey' narrative, and help manage game difficulty and complexity.

Please read the introduction and the sections 'Levels in JRPGs' and 'Why levels?'. As you read, think about how these concepts manifest in games like Final Fantasy or Breath of Fire. Notice how leveling isn't just about numbers, but about shaping the entire player experience.

2. The Experience System: The Path to the Next Level

The first half of our system is tracking experience. At its core, this involves three pieces of data for each character:

  • level: The character's current level.
  • currentXP: The amount of XP accumulated towards the next level.
  • xpToNextLevel: The XP threshold required to level up.

When a character gains XP (typically from winning a battle), we add it to currentXP. Then, we check if they have enough to level up. A character might even gain multiple levels from a single large XP reward, so this check should be a loop.

// Pseudocode for adding experience
function addXP(character, amount) {
    character.currentXP += amount;
    
    while (character.currentXP >= character.xpToNextLevel) {
        // Subtract the XP needed for this level
        character.currentXP -= character.xpToNextLevel;
        
        // Level up the character!
        levelUp(character); 
    }
}

The most interesting design decision here is: how do we calculate xpToNextLevel? This is determined by the XP Curve.

Designing the XP Curve

The XP curve dictates the pacing of your game. A steep curve means leveling takes longer and feels more momentous, while a shallow curve allows for rapid progression. Most JRPGs use a non-linear, exponential curve, making each subsequent level progressively harder to achieve.

The article "Level systems and character growth in RPG games" provides a fantastic overview of different mathematical functions you can use to create these curves. Given your background in Radiophysics, you'll likely appreciate the breakdown of how different functions (linear, polynomial, exponential, logarithmic) produce different player experiences.

Level systems and character growth in RPG games

Let's explore the mathematics behind progression. The article from pavcreations.com analyzes various growth rates used in game design.

Read section 3, 'Level systems growth rates'. You don't need to memorize every formula. Instead, focus on understanding the shape of the graphs for 'Slower in the beginning, faster later' (Exponential) and 'Faster in the beginning, slower later' (Logarithmic/nth-root). These are the most common patterns in JRPGs.

For our engine, let's adopt a simple but powerful exponential formula, similar to the one discussed in the howtomakeanrpg.com article.

The total XP needed to reach a certain level can be defined as:

Here:

  • baseXP controls the overall XP scale. A higher value means more XP is needed in general.
  • exponent controls the steepness of the curve. A value of 1.0 is linear. Values around 1.3 to 1.5 are common for a classic JRPG feel.

The amount of XP needed to get from Level L to L+1 is simply the difference:

However, many classic games pre-calculate these values and store them in a lookup table for efficiency, which is a perfectly valid approach for a fixed level cap (e.g., 99).

3. Stat Growth: The Reward for Leveling Up

When a character levels up, they don't just get a new number next to their name—they get stronger. This is handled by the stat growth system. This system is what defines a character's identity: a Fighter will see large gains in Strength, while a Mage's Intelligence will soar.

A naive approach would be to hardcode stat increases, e.g., strength += 2. A much more robust and data-driven approach is to define stat growth curves.

The following video, though it uses the Godot engine, provides an excellent conceptual model for a flexible, curve-based stat system. The principles of separating base stats from current stats and using curves to calculate growth are universal.

Advanced Stats System in Godot! (exp, leveling, buffs, etc.)

This video from Queble, 'Advanced Stats System in Godot!', demonstrates a brilliant architecture for managing stats that scale with level. We will adapt these concepts for our JavaScript engine.

Please watch the following segments, focusing on the concepts rather than the specific Godot syntax: Base vs. Current Stats (04:39 - 05:50): Understand the critical distinction between a character's unchanging 'base' stats and their 'current' stats, which are calculated based on level. Experience and Leveling (07:24 - 09:20): Observe how he implements the XP and level variables. The level is a calculated property (a get function) based on experience, which is a clever way to ensure they are always in sync. Stat Curves (12:59 - 15:41): This is the most important part. See how he uses a Curve resource (conceptually, a function or a lookup table) to define how a stat grows. The x-axis is the level, and the y-axis is a multiplier. Recalculating Stats (15:41 - 17:49): Pay close attention to the recalculate_stats function. It takes the character's level, samples the appropriate growth curve, and multiplies the base stat by the sampled value to get the new current stat.

Adapting the Concepts to JavaScript

Let's translate this architecture into JavaScript.

  1. Character Definition: Your character data (e.g., in a JSON file) will now include base stats and growth information. Instead of a visual curve editor, we can use an array of multipliers.

    {
      "id": "hero",
      "name": "Alex",
      "classId": "warrior",
      "initialLevel": 1,
      "baseStats": {
        "maxHp": 35, "maxMp": 10, "attack": 12, "defense": 8
      },
      "growth": {
        "maxHp": [/* multipliers for levels 1-99 */],
        "attack": [/* multipliers for levels 1-99 */]
      }
    }
    

    This growth object holds our "curves." For example, growth.attack[20] would give the multiplier for the attack stat at level 21.

  2. Character Class: The in-game Character object will hold both base and current stats.

    class Character {
        constructor(definition) {
            this.definition = definition;
            this.level = definition.initialLevel;
            this.xp = 0;
            
            this.stats = { ...definition.baseStats }; // The current, calculated stats
            
            this.xpToNextLevel = this.calculateXpForLevel(this.level + 1);
        }
    
        levelUp() {
            this.level++;
            console.log(`${this.definition.name} is now Level ${this.level}!`);
    
            // Recalculate stats based on the new level
            this.recalculateStats();
    
            // Determine XP for the *next* level up
            this.xpToNextLevel = this.calculateXpForLevel(this.level + 1);
        }
    
        recalculateStats() {
            const growth = this.definition.growth;
            const baseStats = this.definition.baseStats;
    
            // Loop through each stat defined in growth curves
            for (const stat in growth) {
                const curve = growth[stat];
                const multiplier = curve[this.level - 1]; // -1 because arrays are 0-indexed
                
                // New stat = base stat * curve multiplier
                this.stats[stat] = Math.round(baseStats[stat] * multiplier);
            }
            // You might also want to fully heal the character on level up
            this.stats.hp = this.stats.maxHp;
            this.stats.mp = this.stats.maxMp;
        }
        
        // ... addXP and calculateXpForLevel methods here ...
    }
    
Test your understanding!

Imagine you are designing the growth curves for a "Black Mage" class. How would the multiplier arrays for attack and intelligence (a new stat you'd add) likely compare to those of a "Warrior" class?

Show answer
  • Warrior: The attack multiplier array would have values that increase significantly with each level (e.g., 1.0, 1.15, 1.3, 1.48...). The intelligence multiplier array would have very low growth, staying close to 1.0 for most levels.
  • Black Mage: The opposite would be true. The intelligence curve would show rapid growth, while the attack curve would be nearly flat. This is how data-driven growth curves define a class's role in combat.

An Alternative: Randomized Growth

While curves provide predictable progression, many classic JRPGs, drawing inspiration from tabletop games, use a randomized system. On level up, each stat has a chance to increase by a certain amount, often determined by a "dice roll."

The howtomakeanrpg.com article provides a fantastic deep dive into this technique, even including a parser for dice notation (e.g., "1d6+2" means "roll one 6-sided die and add 2"). This is a perfectly valid alternative for achieving a more old-school feel. For our purposes, the curve-based approach offers more fine-grained control over game balance, but it's valuable to know this method exists.

Conclusion

Today, we've designed the engine of character progression. By combining an experience formula with a stat growth system, we've created the core loop that rewards players for their efforts and allows their characters to evolve.

Key Takeaways:

  • A leveling system consists of two main parts: an XP system that determines when a character levels up, and a stat growth system that determines how they get stronger.
  • The XP Curve is a mathematical formula (often exponential) that controls game pacing by defining the XP required for each level.
  • Stat Growth Curves are a data-driven way to define a character's progression. By separating base and current stats, we can calculate a character's power at any level based on multiplier curves.
  • This architecture allows us to easily define different character classes by simply providing different growth curve data.

Preview of the next lesson:
We've built the mechanism for stat growth. In our next lesson, we'll build the system that leverages it: "Design a class/job system that dictates stat progression and available skills." We will formalize the concept of a "Class" or "Job" as a data structure that bundles stat growth curves, skill lists, and equipment permissions, allowing us to create truly distinct character archetypes like the classic Warrior, Mage, and Thief.

Can't find a good explanation? Sign up and we'll make it for you

Sign up