Create your own
Lesson illustration

XP and Loot Distribution System

Hello! In our last lesson, we established the crucial win/loss conditions for combat and built the logic to transition from the battle scene back to the map. We left a placeholder in our endBattle function, right at the moment of victory, for the all-important reward sequence.

Today, we'll fill that placeholder. This lesson directly addresses the learning outcome: Implement the system for distributing experience points and loot upon victory. We will design and code the mechanics that form the core of character progression in any JRPG. You'll determine how much experience enemies are worth, how characters level up, and what treasures they drop. This is the system that makes defeating monsters feel rewarding and drives the player's journey forward.

1. The Experience Point (EXP) System

The first part of any victory reward is Experience Points. Let's architect the data and logic for this system.

Data and Logic Flow

The process can be broken down into a clear sequence:

  1. Define EXP Value: Every enemy type must have a value for how much EXP it yields upon defeat. We'll add an expValue property to our enemy data structure.
  2. Calculate Total EXP: When the battle is won, the system sums the expValue of all defeated enemies.
  3. Distribute EXP: The total EXP is awarded to each surviving party member.
  4. Check for Level Up: For each character who receives EXP, the system checks if their currentExp has met or exceeded the amount needed for the next level (expToNextLevel).
  5. Process Level Up: If a character levels up, the game updates their level, calculates their new expToNextLevel, and carries over any "spillover" EXP.

Your background in front-end development involves managing state changes in response to events. This is a perfect parallel: a "victory" event triggers a series of state changes in your character objects.

Unreal Engine 4 Tutorial - JRPG Part 23: EXP Rewards

To see this logic in action, let's watch a video from Ryan Laley. Although it uses Unreal Engine, the core concepts of managing EXP variables and processing a level up are universal and demonstrated very clearly.

Watch the following segments to understand the fundamental building blocks: Setting up EXP Variables (00:27 - 01:25): Notice the essential variables: xp current, xp required, and unit level. Also, note the creation of distinct functions for reward xp and level up. This separation of concerns is a good design practice. Distributing EXP (01:25 - 03:40): Focus on how the total EXP from defeated enemies is calculated and then distributed to the party members. Implementing Level Up Logic (09:32 - 11:00): This is the most critical part. Pay close attention to how reward xp is implemented. It adds the new EXP and then checks if a level-up condition is met. The use of the modulo operator (%) to handle leftover EXP is a classic and efficient technique.

Implementing the Core EXP Logic

Let's translate those concepts into our JavaScript engine. First, we'll update our combatant data structure to include the necessary properties.

// In a character/actor data file
const actor = {
    // ... other stats
    level: 1,
    currentExp: 0,
    expToNextLevel: 100, // The EXP needed to reach level 2
    // We'll add methods to this object (or its class) later
};

// In an enemy data file
const enemy = {
    // ... other stats
    expValue: 50,
    goldValue: 25,
    lootTable: [
        { itemId: 'potion', dropChance: 0.5 }, // 50% chance
        { itemId: 'sword_01', dropChance: 0.05 } // 5% chance
    ]
};

Now, we can create the awardExp and levelUp methods for our actor class.

// Inside your Actor class or on your actor objects

/**
 * Awards experience points to the character and checks for level up.
 * @param {number} amount - The amount of EXP to award.
 */
awardExp(amount) {
    this.currentExp += amount;
    console.log(`${this.name} gains ${amount} EXP!`);

    // Keep checking for level ups in case of multiple levels gained at once
    while (this.currentExp >= this.expToNextLevel) {
        this.levelUp();
    }
}

/**
 * Handles the logic for a character leveling up.
 */
levelUp() {
    this.level++;
    this.currentExp -= this.expToNextLevel; // Subtract the threshold for the level just passed
    
    // In a future lesson, we would increase stats here
    // this.stats.strength += this.statGrowth.strength; 
    
    // Calculate the EXP required for the *next* level
    this.expToNextLevel = this.calculateExpForNextLevel();

    console.log(`%c${this.name} reached Level ${this.level}!`, 'color: yellow; font-weight: bold;');
    
    // Make sure leftover EXP is not negative
    if (this.currentExp < 0) {
        this.currentExp = 0;
    }
}
Test your understanding!

A character is Level 5 and needs 1000 EXP to reach Level 6. Their current EXP is 950. After a battle, they are awarded 250 EXP. Assuming the EXP needed for Level 7 is 1200, what will their level, currentExp, and expToNextLevel be after awardExp(250) is called?

Show answer
  1. Initial State: Level 5, currentExp = 950, expToNextLevel = 1000.
  2. awardExp(250) is called: currentExp becomes 950 + 250 = 1200.
  3. Level Up Check: 1200 is >= 1000, so levelUp() is called.
  4. Inside levelUp():
    • level becomes 6.
    • currentExp becomes 1200 - 1000 = 200.
    • expToNextLevel is recalculated for Level 7, becoming 1200.
  5. Final State: Level 6, currentExp = 200, expToNextLevel = 1200. The loop in awardExp checks again (200 < 1200) and stops.

2. Designing the EXP Curve

How do we implement calculateExpForNextLevel()? Simply adding a fixed amount each time makes the game feel flat. The most engaging JRPGs use mathematical formulas to create a specific progression curve. Since you have a background in radiophysics, the mathematical nature of these curves should be familiar territory.

There are many ways to design this curve:

  • Linear: Easy, but not very interesting.
  • Polynomial/Exponential: The most common choice. Starts slow and ramps up significantly at higher levels, making each new level feel like a greater achievement.
  • Logarithmic: The opposite. Levels come quickly at the start and then the required EXP flattens out, useful for games with very high level caps.

Level systems and character growth in RPG games

The article 'Level systems and character growth in RPG games' by Pav Creations is an excellent resource that dives into the mathematics of these systems. It's a fantastic primer on the design philosophy behind character progression.

Please read the following sections: 'Common leveling systems formulas in games': This gives you concrete examples from classic games like Pokemon and D&D, showing how different formulas create different feelings. 'Level systems growth rates': Focus on the subsections for Constant, Polynomials, and Exponential function. You don't need to memorize the C# code, but pay attention to the formulas and the graphs. This will give you the tools to design your own curve.

For our engine, we will implement a simple but effective power function, a type of polynomial. This gives us a base value and an exponent to tweak the curve's steepness.

// Inside your Actor class or on your actor objects

calculateExpForNextLevel() {
    const baseExp = 100;
    const exponent = 1.2;
    // Formula: base * (level ^ exponent)
    const requiredExp = Math.floor(baseExp * Math.pow(this.level, exponent));
    return requiredExp;
}

With this, our EXP system is complete.

3. The Loot Drop System

Next up: treasure! A good loot system adds excitement and replayability. Enemies shouldn't drop the same thing every time. We'll use probabilities to determine what, if anything, the player receives.

The logic is straightforward:

  1. Define a Loot Table: Each enemy has a list of potential items to drop, each with an associated dropChance (a value between 0 and 1). They also have a value for goldValue.
  2. Roll for Drops: After defeating an enemy, the system iterates through its loot table. For each item, it generates a random number between 0 and 1. If this number is less than the item's dropChance, the item is added to the party's inventory.
  3. Award Gold: The enemy's goldValue is added to the party's total gold.

This concept is a staple of RPG Maker, which uses a visual "eventing" system to achieve the same result.

Guide :: Creating a Random Loot Drop System

This Steam Community guide for RPG Maker explains how to build a random loot system using the engine's event commands. The implementation is visual, but the underlying logic of using random numbers and conditional checks is identical to what we will do in code.

Read through these sections to grasp the architecture of a loot system: 'First RNG Sequence': This shows how a random number is used to decide the type of reward (e.g., mimic, weapon, gold). We can adapt this to decide if an enemy drops an item or just gold. 'Second RNG Sequence Weapons, Armor, & Items': This details the core loop: generate a random number and use conditional branches (if statements) to award a specific item. 'Second RNG Sequence Gold': Note how gold can be a random range, adding more variety.

4. Putting It All Together in Code

Now we'll create our master reward function and integrate it into the endBattle method from the previous lesson.

// In BattleScene.js

// This replaces the placeholder in your endBattle method
endBattle() {
    // ... existing code to disable UI, etc.

    if (this.currentState === BattleState.WIN) {
        console.log("VICTORY!");
        const rewards = this.calculateAndAwardRewards(); // Calculate rewards

        // We'll display this in the UI later
        console.log(`Gained ${rewards.totalExp} EXP and ${rewards.totalGold} Gold.`);
        
        setTimeout(() => {
            console.log("Returning to the map...");
            this.sceneManager.pop();
        }, 3000); // 3-second delay to read results

    } else if (this.currentState === BattleState.LOST) {
        // ... existing defeat logic
    }
}

/**
 * Calculates and distributes all rewards (EXP, Gold, Items) after a victory.
 */
calculateAndAwardRewards() {
    let totalExp = 0;
    let totalGold = 0;

    // 1. Calculate totals from all defeated enemies
    this.enemies.forEach(enemy => {
        if (enemy.hp <= 0) {
            totalExp += enemy.expValue;
            totalGold += enemy.goldValue;
            
            // 2. Process loot drops for each enemy
            enemy.lootTable.forEach(drop => {
                if (Math.random() < drop.dropChance) {
                    console.log(`Enemy dropped ${drop.itemId}!`);
                    // This assumes you have a global party/inventory object
                    // In Module 7, we'll build a proper inventory API.
                    party.inventory.addItem(drop.itemId, 1);
                }
            });
        }
    });

    // 3. Award Gold to the party
    party.gold += totalGold;

    // 4. Distribute EXP to surviving party members
    const survivingActors = this.party.filter(actor => actor.hp > 0);
    survivingActors.forEach(actor => {
        actor.awardExp(totalExp);
    });

    return { totalExp, totalGold }; // Return calculated rewards for display
}

This implementation brings together everything we've discussed. It's a robust, data-driven system that cleanly separates the calculation of rewards from the victory condition itself.

Conclusion

Congratulations! You have now implemented the heart of JRPG progression. Victory in battle is no longer just a state change; it's a meaningful event that strengthens the player's party and rewards them with tangible items.

Key Takeaways:

  • Data-Driven Rewards: Enemy data should define its worth (expValue, goldValue) and potential treasures (lootTable). This makes balancing and content creation a matter of editing data, not code.
  • Decoupled EXP Logic: The process of awarding EXP should be separate from leveling up. A character's awardExp method can call levelUp one or more times, making the system flexible.
  • EXP Curves Define Pacing: The mathematical formula used to determine expToNextLevel is a powerful tool for controlling the game's pacing and difficulty curve.
  • Probabilistic Loot: Using Math.random() and dropChance values in a loot table is the standard, effective way to create varied and exciting treasure drops.

Preview of the next lesson:
With the core combat loop—attack, damage, victory, and rewards—now in place, we can start adding layers of strategic depth. The next lesson begins Module 6, where we will design a data structure for skills and magic, including cost, target type, power, and effects. This will be the first step in moving beyond simple "Attack" and "Defend" commands into the rich tactical systems that define the genre.

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

Sign up