Create your own
Lesson illustration

Classic JRPG Damage Formulas

Hello! Welcome back to our journey into creating a classic JRPG.

In our last lesson, we built the foundational combat actions: Attack, Defend, and Escape. We implemented a placeholder damage formula, Damage = Attacker's ATK - Defender's DEF, which was a great starting point. Now, it's time to replace that placeholder with something much more dynamic and interesting.

Today's lesson directly addresses the learning outcome: Design and implement damage calculation formulas inspired by classic JRPGs. This is one of the most crucial elements in defining how your game feels. A good damage formula makes stat progression satisfying, battles strategic, and the overall difficulty curve manageable. We'll move beyond simple subtraction and explore the architectural patterns that make classic RPG combat maths so compelling.

1. The Limits of Simple Subtraction

Our current formula, ATK - DEF, works when numbers are small. However, in a JRPG where characters level up from 1 to 99 and stats can grow from 10 to 255 (or more), this simple model breaks down. This is a problem of scaling.

Imagine two scenarios:

  • Early Game: An attacker with 20 ATK hits an enemy with 10 DEF. Damage is 10.
  • Late Game: The attacker now has 200 ATK, the enemy has 180 DEF. Damage is 20.

Even though the attacker's power has increased tenfold, the damage dealt has only doubled. The impact of stat growth feels diminished. Furthermore, if DEF ever becomes greater than ATK, you deal 0 or 1 damage, creating a "damage wall" that feels frustrating. To solve this, JRPG designers use more sophisticated formulas.

2. Architectures of Damage Calculation

There are several common architectural patterns for damage formulas. To get a high-level overview of these, let's watch a segment from a video by a fellow indie developer.

Making Our Own RPG Combat Formula | Battle Gem Ponies: Breakdown

The video 'Making Our Own RPG Combat Formula' by YotesMark gives an excellent breakdown of the three most common methods for calculating damage in RPGs. This will provide the conceptual foundation for our design choices.

Watch the sections from 01:19 to 02:55 that cover the 'Subtraction-Based', 'Ratio-Based', and 'Division-Based' methods. Pay attention to which types of games use each method and why.

As the video explains, the main types are:

  1. Subtraction-Based: Damage = (Attack + Power) - Defense. Used in games like Fire Emblem with smaller, more predictable numbers.
  2. Ratio-Based: Damage = BaseDamage * (Constant / (Constant + Defense)). Used in MOBAs where consistent scaling of defense is important.
  3. Division-Based: Damage = (Attack / Defense) * Power. Common in games like Pokémon and Final Fantasy that feature large numbers and wide stat ranges.

Given your goal of creating a game inspired by Final Fantasy 1-5, the division-based approach and the classic subtraction model with added complexity are our most relevant starting points.

3. Deconstructing a Classic: The Final Fantasy I Formula

Let's dive into a specific example from a game that is a key inspiration for your project. We'll analyze the mechanics of the original Final Fantasy.

Final Fantasy - Game Mechanics Guide - NES - By AstralEsper

This GameFAQs 'Final Fantasy - Game Mechanics Guide' by AstralEsper is a fantastic deep-dive into the game's inner workings. We'll use it to extract the exact formula for physical damage.

Please read the section 'IV. PHYSICAL ATTACKS', focusing on subsection 'B. DAMAGE FORMULAS'. You only need to read the two formulas under '1. The formula for Physical Damage is:' and '2. For Critical Hits, the formula is:'.

As you've read, the formula for a standard physical attack in FF1 is:

Damage = (A...2A) - D

Where:

  • A is the attacker's Attack stat.
  • A...2A means a random number between A and 2*A.
  • D is the defender's Defense stat.

This is still a subtraction-based model, but with a crucial addition: variance. The random range (A...2A) means that attacks don't always do the same damage. This small touch makes battles feel less deterministic and more dynamic. A lucky high roll can finish an enemy, while a low roll might leave them with a sliver of health.

4. Building Our Damage Calculation Pipeline

Let's design and implement a more robust damage calculation function. Instead of putting all the logic in executeAttack, we'll create a dedicated calculateDamage function. This is good practice, creating a "pure" function that takes inputs and returns an output, which you can later reuse for skills, enemy attacks, and more. This is analogous to creating a utility service or helper function in a front-end application to handle a complex transformation.

Our damage pipeline will have several steps:

  1. Calculate base damage with variance.
  2. Check for a critical hit and apply a multiplier.
  3. Apply modifiers from status effects (e.g., Defend).
  4. Ensure damage is at least a minimum value (usually 1).

Step 1: Base Damage with Variance

Let's implement the FF1 formula.

// This will be a new method in your BattleScene class
calculateDamage(attacker, defender) {
    // 1. Calculate base damage with variance
    const attackWithVariance = attacker.stats.atk + Math.random() * attacker.stats.atk;
    let damage = attackWithVariance - defender.stats.def;

    // ... more steps to come ...
    
    return Math.max(1, Math.floor(damage));
}

Step 2: Critical Hits

The FF1 guide describes a peculiar critical hit formula. A more modern and straightforward approach, as seen in many RPGs and tools like RPG Maker, is to apply a simple multiplier.

RPG Maker MV Tutorial Custom Damage Formulas

This short clip from Driftwood Gaming's tutorial on RPG Maker damage formulas mentions how critical hits are typically handled.

Watch from 02:29 to 02:45, where the video explains that a critical hit will triple the output. This confirms the 'damage multiplier' approach.

Let's add this to our pipeline. We'll use a 5% critical hit chance and a 1.5x damage multiplier as a starting point.

// Inside calculateDamage function...

let isCritical = false;
const critChance = 0.05; // 5% chance
if (Math.random() < critChance) {
    isCritical = true;
}

// ...after calculating base damage...
if (isCritical) {
    damage *= 1.5;
    console.log("A critical hit!");
}

Step 3: Status and State Modifiers

We already have the isDefending state from our last lesson. This is the perfect place to check for it. Classic games like Final Fantasy Tactics have many such modifiers.

Guide :: Mechanics

This guide for Final Fantasy Tactics provides excellent examples of how status effects can modify damage. We won't implement all of these now, but they illustrate the design pattern.

Scan the tables under 'Status Effects - Positive/Neutral' and 'Status Effects - Negative'. Note how 'Protect' and 'Shell' reduce damage by 1/3, while 'Berserk' increases Physical Attack by 50%. This shows how buffs and debuffs hook directly into the damage formula.

We'll integrate our isDefending check. This modifier should apply after the base damage is calculated but before the final flooring/clamping.

// Inside calculateDamage function...

// Check if the defender is defending
if (defender.isDefending) {
    damage /= 2;
    console.log(`${defender.name} defends against the attack!`);
}

Putting it all together

Here is our new, complete calculateDamage function and the updated executeAttack.

// --- New method in your BattleScene class ---
calculateDamage(attacker, defender) {
    // Determine if it's a critical hit first
    const critChance = 0.05; // 5% chance
    const isCritical = Math.random() < critChance;

    // 1. Calculate base damage with FF1-style variance
    const attackWithVariance = attacker.stats.atk + (Math.random() * attacker.stats.atk);
    let damage = attackWithVariance - defender.stats.def;

    // 2. Apply critical hit multiplier
    if (isCritical) {
        damage *= 1.5;
    }

    // 3. Apply 'Defend' state multiplier
    if (defender.isDefending) {
        damage /= 2;
    }

    // You could add other modifiers here in the future (e.g., elemental weakness)

    // 4. Ensure damage is at least 1, and an integer
    const finalDamage = Math.max(1, Math.floor(damage));
    
    // Return both the damage and whether it was a crit for display purposes
    return { damage: finalDamage, isCritical };
}


// --- Updated executeAttack method in BattleScene ---
executeAttack(attacker, defender) {
    // 1. Check if the attack hits (from previous lesson)
    if (!this.checkHit(attacker, defender)) {
        console.log(`${attacker.name} attacks ${defender.name} but misses!`);
        // We'll show "Miss" on screen later
        return;
    }

    // 2. Calculate damage using our new pipeline
    const attackResult = this.calculateDamage(attacker, defender);
    const damage = attackResult.damage;

    // Optional: Log if it was a critical hit
    if (attackResult.isCritical) {
        console.log("A critical hit!");
    }
    
    // 3. Apply damage
    defender.hp -= damage;
    console.log(`${attacker.name} hits ${defender.name} for ${damage} damage!`);
    
    // ... (rest of the function for checking HP and death is the same)
}
Test your understanding!

Looking at the calculateDamage pipeline, where would you add a check for elemental properties? For example, if an attacker uses a 'Fire Sword' against an 'Ice Slime' (weak to fire), you want to double the damage.

Show answer

The best place to add the elemental modifier would be right after applying the critical hit multiplier and before applying the defensive state multiplier. The pipeline would look like this:

  1. Calculate base damage ((A...2A) - D).
  2. Apply critical hit multiplier (damage *= 1.5).
  3. Apply elemental modifiers (damage *= 2 for weakness, damage /= 2 for resistance).
  4. Apply 'Defend' state multiplier (damage /= 2).
  5. Return final damage.

This order ensures that elemental properties correctly modify the core damage of the attack, while defensive states reduce the total incoming damage.

5. The Art of Balancing: "Turns to KO"

We now have a formula with several "magic numbers" (crit chance, crit multiplier, defend multiplier, etc.). How do we choose these numbers? This is the art of game balance. A powerful way to think about this is not in terms of raw damage numbers, but in the average "Turns to KO" a standard enemy.

Making Our Own RPG Combat Formula | Battle Gem Ponies: Breakdown

Let's return to the 'Making Our Own RPG Combat Formula' video for a crucial design insight on how to approach balancing.

Watch from 04:13 to 06:11. This section explains the concept of 'Turns until K.O.' and using a 'control scenario' (a baseline character) to test and tune your formulas. This is a vital professional game design technique.

The core idea is to define a desired game feel. For example: "A standard party member should be able to defeat a standard enemy of the same level in about 3-4 hits."

With this goal, you can create a test case:

  • An Actor at Level 5 with typical stats for that level.
  • An Enemy at Level 5 with typical stats.
  • Run calculateDamage 100 times and find the average damage.
  • Check if EnemyHP / AverageDamage is close to your target of 3-4.
  • If not, you can tweak the formula's constants or the character's base stats until it feels right.

This "unit testing" approach to game balance is a systematic way to manage the complexity of your combat system.

Conclusion

Today, you've taken a massive step from a simple placeholder to a professionally designed damage formula. You've seen how classic JRPGs construct their math to create a satisfying and scalable combat experience.

Key Takeaways:

  • Formula Architecture: Simple ATK - DEF has scaling issues. Division-based or more complex subtraction-based formulas are better for classic JRPGs.
  • Variance is Key: Adding a random element (A...2A) makes combat less predictable and more exciting.
  • The Damage Pipeline: A robust calculateDamage function should be a sequence of steps: base calculation, critical hits, elemental/status modifiers, and final clamping. This modular design is clean and extensible.
  • Balance with a Goal: Don't just pick numbers randomly. Design your formulas around a target "Turns to KO" to achieve your desired game feel and difficulty.

Preview of the next lesson:
Now that our characters can deal damage and potentially reduce an opponent's HP to zero, we need to handle the consequences. In the next lesson, we will implement victory and defeat condition checks, and the transition from the battle scene back to the map. This will involve checking the HP of all combatants after each action to see if the battle has been won or lost.

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

Sign up