ターン制RPGの作り方#12|経験値とレベルアップ:経験値の表と、能力が伸びる演出

3体の敵を倒すと「11ポイントの けいけんちを かくとく!」。3人が順に黄色く光って「LEVEL UP!」の文字が飛び出し、ステータスのウインドのLvが2に、HPの最大が増える

前回で、バトルを最後まで遊べるようになりました。勝つと「けいけんち 11ポイント」と出ますが、まだ出るだけです。今回は、もらった経験値でレベルが上がって、能力が伸びるようにします。レベルが上がった瞬間は、RPGで一番うれしいところ。光って、文字が飛び出して、伸びた数字が並ぶ…という演出も作ってみたいと思います!

目次

今回作るもの

  • 経験値の合計から、レベルを決める表LevelTable
  • レベルごとの能力を、#2 キャラクターと敵のデータで入れておいた「Lv1の能力」と「1レベルごとの成長」から計算する
  • 勝ったら、立っている仲間が経験値をもらう。レベルが上がったら、黄色く光って「LEVEL UP!」が飛び出し、ステータスのウインドのLvとHPが増え、伸びた能力をメッセージで出す
スクリプト役割付ける場所
LevelTable経験値の表(付けない)
Statsレベルごとの能力を計算する(付けない)
BattleUnitレベル・経験値の合計・経験値をもらう-
DamagePopup・BattleEffects数字の代わりに文字も飛ばせるようにする-
UnitViewレベルアップで光る-
BattleManager勝ったら経験値を配って、レベルアップの演出を出す-

経験値の表を作ろう

3体の敵を倒したあと、ステータスのウインドで3人ともLv2になり、HPの数字が増えている

この節では、「経験値の合計が、いくつになったらレベルいくつか」を決める表を作ります。

なぜこの数字か

レベル必要な経験値の合計前のレベルからの差
Lv21010
Lv32515
Lv44520
Lv57025
Lv610030
Lv714040
Lv819050
Lv925060
Lv1032070
  • 地下1階の3体(こぼれたインク3・紙ひこうき3・本の虫5)を倒すと、11ポイント。最初のバトルに勝つと、すぐLv2になるようにしています。最初のレベルアップが早いと、「強くなった!」がすぐ味わえます
  • 差は5ずつ、Lv6からは10ずつ大きくしていきます。地下2階の敵は1体8〜11ポイントなので、下の階へ進むほど、同じペースでレベルが上がっていきます
  • 最大はLv10。ボスの「禁じられた本」は200ポイントなので、ボスの手前でLv8〜9になっていれば、倒したあとにLv10に届きます(ボスは#23、バランスの見直しは#24)

LevelTable

  • 「LevelTable」スクリプトを作る
// 経験値の表:そのレベルになるのに必要な、経験値の合計(最大 Lv10)
public static class LevelTable
{
    // [0] が Lv1(0)、[1] が Lv2(10)、…、[9] が Lv10(320)
    private static readonly int[] totalExp = { 0, 10, 25, 45, 70, 100, 140, 190, 250, 320 };

    public static int MaxLevel => totalExp.Length;

    // 経験値の合計から、レベルを出す
    public static int LevelFor(int exp)
    {
        int level = 1;
        while (level < MaxLevel && exp >= totalExp[level])
        {
            level++;
        }
        return level;
    }
}
  • LevelFor:Lv1から始めて、「次のレベルに必要な合計」に届いている間、レベルを1つずつ上げる。合計11なら、Lv2(10)には届いて、Lv3(25)には届かないのでLv2
    • totalExp[level]は「今のレベル+1」に必要な合計です。配列は0から数えるので、[1]がLv2になります
  • 全員が同じ表を使い、ゲームの途中で変わることもないので、ScriptableObjectにせず、スクリプトに直接書いています

能力を伸ばそう

スライムちゃんがLv2に上がると、ステータスのウインドのLvが1から2に、HPが33/38から38/43に、MPが8/8から9/9に増える

この節では、レベルに合わせて、能力(HP・こうげき など)を計算できるようにします。

Stats

#2のCharacterDataには、「Lv1の能力(Base Stats)」と「1レベル上がるごとに増える分(Growth)」を入れておきました。レベルnの能力は、Lv1の能力 + 成長 ×(n − 1)で計算できます。

スライムちゃんLv1成長Lv2Lv5
HP3854358
こうげき1221420
ぼうぎょ1121319
すばやさ911013

Statsに、次のメソッドを足します(magicの下)。

    // レベル level の能力:Lv1 の能力(baseStats)に、成長(growth)を(level − 1)回足したもの
    public static Stats AtLevel(Stats baseStats, Stats growth, int level)
    {
        int times = level - 1;
        Stats stats = new Stats();
        stats.maxHp = baseStats.maxHp + growth.maxHp * times;
        stats.maxMp = baseStats.maxMp + growth.maxMp * times;
        stats.attack = baseStats.attack + growth.attack * times;
        stats.defense = baseStats.defense + growth.defense * times;
        stats.speed = baseStats.speed + growth.speed * times;
        stats.magic = baseStats.magic + growth.magic * times;
        return stats;
    }
  • データ(Base Stats・Growth)は書き換えず、新しいStatsを作って返します。ScriptableObjectのデータを書き換えると、再生をやめても元に戻らないからです

BattleUnitに、レベルと経験値を足す

BattleUnitのStatsの行を書き換えて、3つ足します。

    public Stats Stats { get; private set; } // 今のレベルの能力(レベルが上がると変わる)
    public int Level { get; private set; } = 1;
    public int TotalExp { get; private set; } // もらった経験値の合計(仲間だけ)
  • 敵のExp(倒されたときにくれる経験値)と区別するため、仲間の経験値の合計はTotalExpという名前にしています

Rowの下に、仲間のデータを覚えておく変数を足します。

    private readonly CharacterData characterData; // 仲間のデータ(レベルが上がったときに、能力を計算し直す。敵は null)

仲間のコンストラクタのStats = data.BaseStats;を、次の2行に書き換えます。

        characterData = data;
        Stats = Stats.AtLevel(data.BaseStats, data.Growth, Level);

TakeDamageの下に、経験値をもらうメソッドを足します。

    // 経験値をもらう。レベルが上がったら、能力を計算し直して true を返す
    public bool GainExp(int amount)
    {
        TotalExp += amount;
        int newLevel = LevelTable.LevelFor(TotalExp);
        if (newLevel == Level)
        {
            return false;
        }

        int oldMaxHp = Stats.maxHp;
        Level = newLevel;
        Stats = Stats.AtLevel(characterData.BaseStats, characterData.Growth, Level);
        Hp += Stats.maxHp - oldMaxHp; // 最大HPが増えた分だけ、今のHPも増やす
        return true;
    }
  • 1回で2つ以上レベルが上がっても、LevelForが合計から決めるので、そのまま正しいレベルになります
  • Hp += …:最大HPが5増えたら、今のHPも5増やす。減っていたHPが全回復するわけではありません
  • Stats.AtLevelのStatsは、プロパティではなくクラスの名前です。C#では、プロパティとクラスが同じ名前でも、Stats.AtLevelのように静的メソッドを呼べます

レベルアップの演出

スライムちゃんが黄色く3回光って「LEVEL UP!」の文字が飛び出し、「スライムは レベル2に あがった!」「HP+5 MP+1 こうげき+2 ぼうぎょ+2 すばやさ+1」とメッセージが出る(GIF)

この節では、レベルが上がった瞬間の演出を作ります。

  • 黄色く3回光る
  • 「LEVEL UP!」の文字が飛び出す
  • ステータスのウインドのLvとHPが増える
  • 伸びた能力を、数字でメッセージに出す

文字も飛ばせるようにする

#8 攻撃の手応えを作ろうで作ったDamagePopupは、ダメージの数字しか出せませんでした。Setupを、次の2つのメソッドに書き換えます。

    // ダメージの数字を出す。大きいダメージほど大きな文字に
    public void Setup(int damage)
    {
        SetupText(damage.ToString(), Mathf.Min(70f + damage * 3f, 140f), Color.white);
    }

    // 数字の代わりに、文字を出す(「LEVEL UP!」など)
    public void SetupText(string message, float fontSize, Color color)
    {
        text = GetComponent<TextMeshProUGUI>();
        rectTransform = (RectTransform)transform;
        text.text = message;
        text.fontSize = fontSize;
        text.color = color;
        velocity = new Vector2(Random.Range(-150f, 150f), jumpSpeed);
    }
  • 飛び出して消える動き(Update)はそのまま。出す文字と大きさと色だけを、外から決められるようにしました

BattleEffectsに、文字を出すメソッドを足します(PlayHitAsyncの下)。

    // 文字を飛び出させる(「LEVEL UP!」など)
    public void ShowText(UnitView target, string message, Color color)
    {
        DamagePopup popup = Instantiate(damagePopupPrefab, popupLayer);
        popup.transform.position = target.transform.position;
        // 光っている体と重ならないように、頭の上から出す
        ((RectTransform)popup.transform).anchoredPosition += new Vector2(0f, 160f);
        popup.SetupText(message, 64f, color);
    }
  • 文字の色は「LEVEL UP!」も光る色も黄色なので、体の真ん中から出すと、光っている体に重なって読めません。anchoredPositionを160だけ上にずらして、頭の上から出しています

黄色く光る

UnitViewの一番下に、レベルアップで光るメソッドを足します。

    // レベルアップ:黄色く3回光る
    public async Awaitable PlayLevelUpAsync()
    {
        // 表示する前に、形と色を入れておく
        flashImage.sprite = image.sprite;
        Color color = flashColor;
        color.a = 0.8f;
        flashImage.color = color;
        for (int i = 0; i < 3; i++)
        {
            flashImage.enabled = true;
            await Awaitable.WaitForSecondsAsync(0.1f, destroyCancellationToken);
            flashImage.enabled = false;
            await Awaitable.WaitForSecondsAsync(0.1f, destroyCancellationToken);
        }
    }

勝ったら経験値を配る

BattleManagerに、「LEVEL UP!」の色を足します(randomSeedの上)。

    [SerializeField] private Color levelUpColor = new Color(1f, 0.88f, 0.4f); // 「LEVEL UP!」の色

ShowVictoryAsyncを、次のように書き換えます。

    // 勝った:立っている仲間が喜んで、経験値をもらう。レベルが上がったら演出を出す
    private async Awaitable ShowVictoryAsync()
    {
        foreach (BattleUnit unit in lineup.PartyUnits)
        {
            if (!unit.IsDead)
            {
                _ = unit.View.PlayWinAsync(); // 喜ぶのは待たずに、メッセージと一緒に進める
            }
        }
        await messageWindow.ShowAsync("てきを ぜんぶ たおした!");

        int exp = 0;
        int gold = 0;
        foreach (BattleUnit enemy in lineup.EnemyUnits)
        {
            exp += enemy.Exp;
            gold += enemy.Gold;
        }
        await messageWindow.ShowAsync($"{exp}ポイントの けいけんちを かくとく!");

        // 立っている仲間だけが、経験値をもらう
        foreach (BattleUnit unit in lineup.PartyUnits)
        {
            if (unit.IsDead)
            {
                continue;
            }
            Stats before = unit.Stats;
            if (unit.GainExp(exp))
            {
                await ShowLevelUpAsync(unit, before);
            }
        }

        await resultWindow.ShowAsync("しょうり!", $"けいけんち {exp}ポイント\n{gold}ゴールドを てにいれた");
    }

    // レベルアップ:光って「LEVEL UP!」、ステータスの行を書きかえて、伸びた能力を出す
    private async Awaitable ShowLevelUpAsync(BattleUnit unit, Stats before)
    {
        Stats after = unit.Stats;
        unit.Row.SetLevel(unit.Level);
        unit.Row.SetHp(unit.Hp, after.maxHp);
        unit.Row.SetMp(after.maxMp, after.maxMp); // MP は #13 で使うようになるまで、満タンのまま

        battleEffects.ShowText(unit.View, "LEVEL UP!", levelUpColor);
        // ここで、レベルアップの効果音を鳴らす(#26 で足す)
        Awaitable glow = unit.View.PlayLevelUpAsync();
        await messageWindow.ShowAsync($"{unit.Name}は レベル{unit.Level}に あがった!");
        await glow;

        await messageWindow.ShowAsync(
            $"HP+{after.maxHp - before.maxHp}  MP+{after.maxMp - before.maxMp}  " +
            $"こうげき+{after.attack - before.attack}  ぼうぎょ+{after.defense - before.defense}  " +
            $"すばやさ+{after.speed - before.speed}");
    }
  • Stats before = unit.Stats;:GainExpはレベルが上がると新しいStatsに入れ替えるので、入れ替える前のStatsを取っておけば、あとで「いくつ伸びたか」を引き算で出せます
  • _ = と Awaitable glow:喜ぶポーズは待たずに進め、光るのはメッセージと同時に始めて、メッセージのあとで待っています(#8と同じ「始めておいて、あとで待つ」)
  • 効果音は、#26でまとめて鳴らします。鳴らす場所だけ、コメントで印を付けておきます
  • 倒れている仲間は経験値をもらいません。バトルに出ていない控えの1人に半分を配るのは、#20で作ります

「もういちど」を押すと、レベルは1に戻ります。今はシーンを読み込み直すと、全部が最初の状態になるからです。上がったレベルやお金を次のバトルに持ち越すのは、#17でGameSessionを作ってからです。

動かして確認!

3体の敵を倒すと経験値をもらい、3人が順に光って「LEVEL UP!」。ステータスのウインドがLv2になり、伸びた能力のメッセージが出たあと、「しょうり!」のウインド(GIF)

再生して、3体とも倒してみましょう(Random Seed 42で、3人とも「たたかう」で左の敵から倒していきます)。

#12の動作確認

  • 勝つと「11ポイントの けいけんちを かくとく!」
  • 立っている仲間がLv2に上がり、黄色く光って「LEVEL UP!」が飛び出す
  • ステータスのウインドのLvが2になり、HPの最大が増える(スライムちゃん 38→43、フェアリー 26→29、キョンシー 32→36)
  • 伸びた能力が、メッセージで出る

あとがき

今回は、経験値の表を作って、勝つとレベルが上がるようにしました。数字が伸びるのを見ると、もっと戦いたくなりますね。次回は、MPを使う「まほう」を作りたいと思います!

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

コメント

コメント一覧 (1件)

コメントする

目次