ターン制RPGの作り方#14|全体のまほうと、覚えるまほう:みんなを光らせて、レベルで新しいまほうを

1ターン目は3人ともぼうぎょ。2ターン目にフェアリーの みんなでヒール で3人に緑の数字、メドゥーサが いなずま を選ぶと敵3体が全員光り、Enterで決めると3体に同時に数字と火花が出て、一度に倒す

前回で、まほうが使えるようになりました。でも、まだ1人にしか使えません。今回は、敵全体・味方全員に効くまほうを作ります。全体のまほうは、選んでいるときに対象の全員を光らせて、1人用のまほうと見まちがえないようにします。さらに、レベルが上がると新しいまほうを覚えて、本を読むポーズでお知らせするところまで作ってみたいと思います!

目次

今回作るもの

  • 攻撃のまほう・回復のまほうに「全体」を選べるようにする(いなずま・みんなでヒール)
  • 全体のまほうを選ぶと、対象の全員が光って、Enterで決める
  • 全員に同時に当たる・回復する
  • まほうごとに「覚えるレベル」を決めて、レベルが上がったら新しいまほうを覚える(本を読むポーズ)
まほう使う人MP相手効果覚える Lv
ぷるんアタックスライムちゃん4敵1体こうげきの1.5倍4
みんなでヒールフェアリー8味方全員回復 威力146
いなずまメドゥーサ9敵全体まほう 威力227
  • まもりの風・へびのどく など、状態を変えるまほうは、次の#15で足します
スクリプト役割付ける場所
TargetSelector全員を光らせて、決めるのを待つ-
SkillCommand・AttackSpellCommand・HealSpellCommand全体を選ぶ・全員に効かせる-
BattleEffects何人かに同時に当たる演出-
SkillLearning・CharacterData覚えるまほうと、覚えるレベル(付けない)
BattleUnit・BattleLineup覚えたまほう、最初の経験値(確認用)-
UnitView・BattleManager本を読むポーズで、覚えたことを知らせる-

全体をねらおう

メドゥーサが いなずま を選ぶと、仲間が暗くなって敵3体が全員光る。Enterで決めると、3体に同時に白い光・数字・火花が出て、画面が1回揺れる。3体がいっしょにしぼんで消えてから、ダメージと「たおした!」のメッセージが順に出る

この節では、敵全体・味方全員に効くまほうを作ります。

全員を光らせて、決めるのを待つ

1人を選ぶときは、#6 たたかう相手を選ぼうのTargetSelectorで、矢印キーで1人ずつ光らせました。全体のときは選ぶ必要がないので、対象の全員を光らせて、Enterで決める(Escでやめる)だけです。

全員が光っていると、ひと目で「これは全体に効くんだな」と分かります。1人だけ光っていると、全体のまほうなのに1人にしか効かないように見えてしまいます。

TargetSelectorに、変数を足します(isWaitingの下)。

    private bool isAll;         // 全員を選んでいるときは true(矢印キーで動かさない)

SelectTargetAsyncの下に、メソッドを足します。

    // candidates を全員光らせて、決めるのを待つ(決めたら true、キャンセルなら false)
    // others は、選んでいる間だけ暗くしておく人たち
    public async Awaitable<bool> SelectAllAsync(UnitView[] candidates, UnitView[] others)
    {
        // コマンドを決めた Enter が、まだこのフレームに残っていることがあるので、1フレーム待ってから始める
        await Awaitable.NextFrameAsync(destroyCancellationToken);

        foreach (UnitView view in others)
        {
            view.SetHighlight(false);
            view.SetDimmed(true);
        }
        foreach (UnitView view in candidates)
        {
            view.SetDimmed(false);
            view.SetHighlight(true); // 全員を光らせる
        }

        targets = candidates;
        index = 0;
        isAll = true;
        targetSource.Reset();
        isWaiting = true;
        EventSystem.current.SetSelectedGameObject(null);

        UnitView chosen = await targetSource.Awaitable;

        foreach (UnitView view in candidates)
        {
            view.SetHighlight(false);
        }
        foreach (UnitView view in others)
        {
            view.SetDimmed(false);
        }
        isWaiting = false;
        isAll = false;
        return chosen != null;
    }
  • 決めたかどうかは、今までと同じtargetSourceで待ちます。Enterならtargets[index](だれか1人)、Escならnullが入るので、nullでなければ「決めた」です
  • 全員を光らせるのは、#5 だれの番か、ひと目で分かるようにしようで作ったSetHighlightを、全員に呼ぶだけです

全員を選んでいる間は、矢印キーやマウスで1人に動かないようにします。Updateの、矢印キーを読むところのifに条件を足します。

        // 右か下で次の相手、左か上で前の相手(全員を選んでいるときは動かさない)
        if (!isAll && inputActions.UI.Navigate.WasPressedThisFrame())

PointAtの最初のifにも足します。

        if (!isWaiting || isAll)
  • クリックは、だれをクリックしても「決めた」になります(Chooseはそのまま)

全体を選ぶまほう

SkillCommandに、全体かどうかの欄を足します(effectPrefabの下)。

    [SerializeField] private bool targetsAll;             // チェックすると、敵全体・味方全員に効く

    public bool TargetsAll => targetsAll;

SelectAllyAsyncの下に、選ぶメソッドを2つ足します。

    // 敵の全員を光らせて、決めるのを待つ(相手は実行するときに決めるので、Target は null)
    protected async Awaitable<BattleAction> SelectAllEnemiesAsync(BattleContext context)
    {
        context.MessageText.text = "てき ぜんたいに つかう?";
        bool isDecided = await context.TargetSelector.SelectAllAsync(context.Enemies, context.Party);
        if (!isDecided)
        {
            return null;
        }
        return new BattleAction(this, null);
    }

    // 味方の全員を光らせて、決めるのを待つ
    protected async Awaitable<BattleAction> SelectAllAlliesAsync(BattleContext context)
    {
        context.MessageText.text = "みかた ぜんいんに つかう?";
        bool isDecided = await context.TargetSelector.SelectAllAsync(context.LivingParty(), context.Enemies);
        if (!isDecided)
        {
            return null;
        }
        return new BattleAction(this, null);
    }
  • 全体のまほうは、相手を1人に決めません。実行するときに「その時点で立っている全員」にするので、BattleActionのTargetはnullです(選んだあとに倒れた敵には当たらないように)

何人かに同時に当たる演出

#8 攻撃の手応えを作ろうのBattleEffectsのPlayHitAsyncは、1人に当たる演出でした。全体のまほうで、これを1人ずつ同時に呼ぶと、ヒットストップと画面の揺れが何回も重なってしまいます。特に揺れは、揺れている途中の位置を「元の位置」として覚えてしまい、画面がずれたまま止まることがあります。

そこで、何人に当たっても、ヒットストップと揺れは1回だけにします。PlayHitAsyncを、次の2つのメソッドに書き換えます。

    // 1人に当たった瞬間の演出
    public async Awaitable PlayHitAsync(UnitView target, int damage)
    {
        await PlayHitAllAsync(new UnitView[] { target }, new int[] { damage });
    }

    // 何人かに同時に当たった瞬間の演出。ヒットストップと揺れは1回だけ(いちばん大きいダメージで揺らす)
    public async Awaitable PlayHitAllAsync(UnitView[] targets, int[] damages)
    {
        List<Awaitable> hits = new List<Awaitable>();
        int maxDamage = 0;
        for (int i = 0; i < targets.Length; i++)
        {
            UnitView target = targets[i];
            int damage = damages[i];
            BattleUnit unit = target.Unit;
            if (unit.Row != null)
            {
                unit.Row.SetHp(unit.Hp, unit.Stats.maxHp); // ステータスのゲージを減らす(仲間だけ)
            }
            if (!unit.IsEnemy)
            {
                damageVignette.Play(); // 仲間が受けたら、画面のふちを赤く
            }

            DamagePopup popup = Instantiate(damagePopupPrefab, popupLayer);
            popup.transform.position = target.transform.position;
            popup.Setup(damage);
            Instantiate(sparksPrefab, target.transform.position, Quaternion.identity);

            hits.Add(target.HitAsync()); // 光る・のけぞるは、全員いっしょに始める
            maxDamage = Mathf.Max(maxDamage, damage);
        }

        Awaitable shake = ShakeAsync(maxDamage);
        await HitStopAsync();
        foreach (Awaitable hit in hits)
        {
            await hit;
        }
        await shake;
    }
  • PlayHitAsyncは、1人だけの配列を作ってPlayHitAllAsyncを呼ぶようにしました。たたかう などは、今までどおりPlayHitAsyncを呼べばOKです
  • new UnitView[] { target }は、「target が1つだけ入った配列」を作る書き方です
  • 1行目にusing System.Collections.Generic;を足す(Listを使うため)

倒れるのは、全員いっしょに

全体のまほうで何体も倒したときに、「1体消えてメッセージ、次の1体が消えてメッセージ…」と1体ずつ進むと、機械的な処理に見えて、まとめて倒した爽快感がありません。そこで、当たる・倒れる演出までは全員いっしょに起こして、そのあとでメッセージを1人ずつ出すようにします。

#13でBattleCommandに作ったShowFallAsyncは、「消える演出」と「メッセージ」を続けて行っていました。これを分けて、演出だけ・メッセージだけを使えるようにします。ShowFallAsyncを、次の3つのメソッドに書き換えます。

    // HPが0になった相手の演出:敵は消えて、仲間は戦闘不能の絵に(メッセージは出さない)
    protected async Awaitable PlayFallAsync(BattleUnit target)
    {
        if (target.IsEnemy)
        {
            await target.View.DisappearAsync();
        }
        else
        {
            target.View.ShowDown();
        }
    }

    // 倒れたときのメッセージ
    protected string FallMessage(BattleUnit target)
    {
        return target.IsEnemy ? $"{target.Name}を たおした!" : $"{target.Name}は たおれてしまった!";
    }

    // HPが0になった相手の演出をして、メッセージを出す(1人を相手にするコマンド用)
    protected async Awaitable ShowFallAsync(BattleUnit target, BattleContext context)
    {
        await PlayFallAsync(target);
        await context.Message.ShowAsync(FallMessage(target));
    }
  • たたかう など1人を相手にするコマンドは、今までどおりShowFallAsyncを呼べばOKです
  • 全体のまほうでは、PlayFallAsyncを全員分いっしょに始めて、全部終わってからFallMessageでメッセージを出します

攻撃のまほう・回復のまほうを、全体にも

#13のAttackSpellCommandのSelectAsyncとExecuteAsyncを、次のように書き換えます。

    public override async Awaitable<BattleAction> SelectAsync(BattleUnit user, BattleContext context)
    {
        if (TargetsAll)
        {
            return await SelectAllEnemiesAsync(context);
        }
        return await SelectEnemyAsync(context);
    }

    public override async Awaitable ExecuteAsync(BattleUnit user, BattleAction action, BattleContext context)
    {
        // 全体なら、まだ立っている敵の全員。1人なら、ねらった相手(倒れていたら別の敵)
        List<BattleUnit> targets = new List<BattleUnit>();
        if (TargetsAll)
        {
            foreach (UnitView view in context.Enemies)
            {
                if (!view.Unit.IsDead)
                {
                    targets.Add(view.Unit);
                }
            }
        }
        else
        {
            targets.Add(FindTarget(user, action, context));
        }

        SpendMp(user);
        await context.Message.ShowAsync($"{user.Name}は {CommandName}を となえた!");
        await user.View.PlayOrbAsync(); // 杖をふる

        UnitView[] views = new UnitView[targets.Count];
        int[] damages = new int[targets.Count];
        for (int i = 0; i < targets.Count; i++)
        {
            BattleUnit target = targets[i];
            views[i] = target.View;
            damages[i] = DamageCalculator.Magic(power, user, target);
            target.TakeDamage(damages[i]);
            PlayEffect(views[i]);
        }
        await context.Effects.PlayHitAllAsync(views, damages);
        user.View.ResumeIdle();

        // 倒れた相手は、全員いっしょに消える(1体ずつ順番に消すと、機械的に見えるので)
        List<Awaitable> falls = new List<Awaitable>();
        foreach (BattleUnit target in targets)
        {
            if (target.IsDead)
            {
                falls.Add(PlayFallAsync(target));
            }
        }
        foreach (Awaitable fall in falls)
        {
            await fall;
        }

        // 演出が終わってから、1人ずつメッセージを出す
        for (int i = 0; i < targets.Count; i++)
        {
            await context.Message.ShowAsync($"{targets[i].Name}に {damages[i]}の ダメージ!");
        }
        foreach (BattleUnit target in targets)
        {
            if (target.IsDead)
            {
                await context.Message.ShowAsync(FallMessage(target));
            }
        }
    }
  • 1人のときも、相手が1人だけ入ったリストにすれば、同じ流れで書けます
  • 倒した敵は、全員いっしょにしぼんで消えてから、「〇〇に 50の ダメージ!」「〇〇を たおした!」のメッセージが1人ずつ出ます
  • 1行目にusing System.Collections.Generic;を足す

HealSpellCommandも、同じように書き換えます。

    public override async Awaitable<BattleAction> SelectAsync(BattleUnit user, BattleContext context)
    {
        if (TargetsAll)
        {
            return await SelectAllAlliesAsync(context);
        }
        return await SelectAllyAsync(context);
    }

    public override async Awaitable ExecuteAsync(BattleUnit user, BattleAction action, BattleContext context)
    {
        // 全体なら、まだ立っている仲間の全員。1人なら、選んだ人(倒れていたら自分)
        List<BattleUnit> targets = new List<BattleUnit>();
        if (TargetsAll)
        {
            foreach (UnitView view in context.Party)
            {
                if (!view.Unit.IsDead)
                {
                    targets.Add(view.Unit);
                }
            }
        }
        else if (action.Target.Unit.IsDead)
        {
            targets.Add(user); // 選んだ人が先に倒れていたら、自分を回復する
        }
        else
        {
            targets.Add(action.Target.Unit);
        }

        SpendMp(user);
        await context.Message.ShowAsync($"{user.Name}は {CommandName}を となえた!");
        await user.View.PlayCastAsync(); // 力をためる

        // 全員いっしょに回復の演出を始めて、全部終わるまで待つ
        List<Awaitable> heals = new List<Awaitable>();
        int[] amounts = new int[targets.Count];
        for (int i = 0; i < targets.Count; i++)
        {
            BattleUnit target = targets[i];
            PlayEffect(target.View);
            amounts[i] = target.Heal(DamageCalculator.Heal(power, user));
            heals.Add(context.Effects.PlayHealAsync(target.View, amounts[i]));
        }
        foreach (Awaitable heal in heals)
        {
            await heal;
        }
        user.View.ResumeIdle();

        for (int i = 0; i < targets.Count; i++)
        {
            await context.Message.ShowAsync($"{targets[i].Name}の HPが {amounts[i]} かいふくした!");
        }
    }
  • 回復は、#8と同じ「始めておいて、あとで待つ」で、全員の緑の数字をいっしょに出しています
  • 1行目にusing System.Collections.Generic;を足す
Data/Skills/ThunderのInspector。Command Nameにいなずま、Mp Costに9、Effect PrefabにMagicBurst、Targets Allにチェック、Powerに22

レベルでまほうを覚えよう

敵を全員倒すと、フェアリーがレベル6に上がり、正面を向いて本を読むポーズで「フェアリーは みんなでヒールを おぼえた!」

この節では、レベルが上がったら、新しいまほうを覚えるようにします。

覚えるまほうと、覚えるレベル

#13では、CharacterDataのSkillsに、使えるまほうをそのまま並べていました。これを「まほう」と「覚えるレベル」の組にします。

  • 「SkillLearning」スクリプトを作る
// 覚えるまほう1つ分:何レベルで、どのまほうを覚えるか
[System.Serializable]
public class SkillLearning
{
    public int level;         // 覚えるレベル
    public SkillCommand skill;
}

CharacterDataのskillsとSkillsを、次のように書き換えます。

    [SerializeField] private SkillLearning[] skills; // 覚えるまほうと、覚えるレベル
    public SkillLearning[] Skills => skills;

本を読むポーズの絵も足します(変数はcastFramesの下、プロパティはCastFramesの下)。

    [SerializeField] private Sprite[] bookFrames; // 本を読む(book_01・02・03)

    public Sprite[] BookFrames => bookFrames;

BattleUnitで、覚えたまほうを持つ

BattleUnitのSkillsの行を、次のように書き換えます。

    public List<SkillCommand> Skills { get; } = new List<SkillCommand>(); // 覚えたまほう・わざ(仲間だけ)
  • 1行目にusing System.Collections.Generic;を足す

TakeDamageの上に、まほうを覚えるメソッドを足します。

    // 今のレベルで覚えられるまほうのうち、まだ覚えていないものを覚える。新しく覚えたものを返す
    public List<SkillCommand> LearnSkills()
    {
        List<SkillCommand> learned = new List<SkillCommand>();
        foreach (SkillLearning learning in characterData.Skills)
        {
            if (learning.level <= Level && !Skills.Contains(learning.skill))
            {
                Skills.Add(learning.skill);
                learned.Add(learning.skill);
            }
        }
        return learned;
    }
  • Skills.Contains:リストにもう入っているかを調べる。同じまほうを2回覚えないように
  • データの並び順のまま覚えるので、まほうの一覧も、覚える順(レベルの低い順)に並びます

仲間のコンストラクタを、次のように書き換えます。

    // 仲間のデータから作る。totalExp は、最初から持っている経験値の合計(今は確認用。#17 でロビーから持ち越す)
    public BattleUnit(CharacterData data, UnitView view, int totalExp)
    {
        Name = data.CharacterName;
        characterData = data;
        TotalExp = totalExp;
        Level = LevelTable.LevelFor(TotalExp);
        Stats = Stats.AtLevel(data.BaseStats, data.Growth, Level);
        Hp = Stats.maxHp;
        Mp = Stats.maxMp;
        LearnSkills();
        View = view;
        view.Unit = this;
        Commands = data.Commands;
    }
  • レベルを経験値から決めて、そのレベルまでに覚えるまほうを、最初に全部覚えます
  • Levelの行の= 1は、そのままでOKです(敵は1のまま)

確かめるために、最初の経験値を決める

みんなでヒール(Lv6)や いなずま(Lv7)を確かめるには、そこまでレベルを上げる必要があります。何十回も戦うのは大変なので、BattleLineupに確認用の欄を作って、最初から経験値を持たせます。

BattleLineupに、欄を足します(statusWindowの下)。

    [SerializeField] private int startExp = 0; // 確認用:仲間が最初から持っている経験値の合計(0ならLv1)

Setupの、仲間を作るところを次のように書き換えます。

            BattleUnit unit = new BattleUnit(character, view, startExp);
            unit.Row = row;
            StatusRows[i] = row;
            PartyUnits[i] = unit;
            // レベルと能力を、ステータスの行に出す
            row.SetLevel(unit.Level);
            row.SetHp(unit.Hp, unit.Stats.maxHp);
            row.SetMp(unit.Mp, unit.Stats.maxMp);
Start Expレベル覚えているまほう(Lv1のほかに)
0Lv1-
45Lv4スライムちゃん:ぷるんアタック
100Lv6+ フェアリー:みんなでヒール
140Lv7+ メドゥーサ:いなずま

本を読むポーズで知らせる

UnitViewに、変数を足して(castFramesの下)、

    private Sprite[] bookFrames; // 本を読む(仲間だけ)

SetupCharacterの最後に1行足します。

        bookFrames = data.BookFrames;

PlayCastAsyncの下に、メソッドを足します。

    // まほうを覚えた:正面を向いて、本を読む
    public async Awaitable PlayBookAsync()
    {
        await PlayFramesAsync(bookFrames);
    }

#12 経験値とレベルアップのBattleManagerのShowLevelUpAsyncの最後に、覚えたまほうを知らせるところを足します。

        // 新しいレベルで覚えたまほうを、1つずつ知らせる
        foreach (SkillCommand skill in unit.LearnSkills())
        {
            Awaitable book = unit.View.PlayBookAsync();
            await messageWindow.ShowAsync($"{unit.Name}は {skill.CommandName}を おぼえた!");
            await book;
        }

最後に、MagicCommandの一覧を開くところで、リストを配列にして渡します(CommandMenuは配列を受け取るので)。

            BattleCommand skill = await context.SkillMenu.WaitForCommandAsync(user.Skills.ToArray(), user);
  • ListのToArray()は、中身を同じ順に入れた配列を作ります

データを作る

  • Data/Skillsに、まほうを3つ作る
ファイル作り方Command NameMp CostEffect PrefabTargets Allほか
PurunAttackMulti Hitぷるんアタック4なし-Damage Rate 1.5、Hit Count 1
AllHealHeal Spellみんなでヒール8HealSparklesチェックPower 14
ThunderAttack Spellいなずま9MagicBurstチェックPower 22

ぷるんアタックは、#13のれんげき(MultiHitSkillCommand)を、1.5倍で1回にしたものです。1回だけのときに「あわせて」と出ないように、MultiHitSkillCommandのメッセージの行を書き換えます。

        // 2回以上なら「あわせて」を付ける
        string together = hitCount > 1 ? "あわせて " : "";
        await context.Message.ShowAsync($"{target.Name}に {together}{total}の ダメージ!");
  • 4人のデータのSkillsを、Level と Skill の組に入れ直す
キャラSkills
スライムちゃんLv1 SmallHeal、Lv4 PurunAttack
フェアリーLv1 Heal、Lv6 AllHeal
キョンシーLv1 DoubleStrike
メドゥーサLv1 MagicArrow、Lv7 Thunder
  • 4人のデータのBook Framesに、<名前>_book_01・02・03をドラッグ
Data/Characters/FairyのInspector。Book Framesにfairy_book_01〜03、SkillsのElement 0がLevel 1・Heal、Element 1がLevel 6・AllHeal

SkillsをSkillLearningの組に変えると、前に入れていたまほう(#13のSmallHealなど)は外れます。4人とも、Level 1のまほうから入れ直しましょう。

動かして確認!

Start Exp 140で、スライムちゃん・フェアリー・メドゥーサ。1ターン目は全員ぼうぎょで敵の攻撃を受け、2ターン目にフェアリーの みんなでヒール で3人に緑の数字、メドゥーサの いなずま で敵3体を一度に倒す

全体のまほう

  • BattleのBattle LineupのStart Expを140、Partyの3人目をメドゥーサにする
  • 1ターン目:3人とも「ぼうぎょ」(敵の攻撃でHPを減らしておく)
  • 2ターン目:スライムちゃんは ぼうぎょ、フェアリーは まほう > みんなでヒール、メドゥーサは まほう > いなずま

覚えるまほう

Start Exp 95で戦って勝つと、3人ともLv6に上がり、フェアリーが本を読むポーズで「フェアリーは みんなでヒールを おぼえた!」
  • Start Expを95(Lv5、あと5でLv6)、Partyの3人目をキョンシーに戻す
  • 3人とも「たたかう」で敵を全員倒す(11ポイントもらって106になり、Lv6に上がる)

#14の動作確認

  • 全体のまほうを選ぶと、対象の全員が光る。Enterで決まり、Escで一覧に戻る
  • いなずま は、立っている敵の全員に同時に当たる。揺れは1回だけ。倒した敵は全員いっしょに消えて、そのあとメッセージが出る
  • みんなでヒール は、立っている仲間の全員に緑の数字が出る(HPが満タンの人にも「+0」が出るので、効いたことが分かる)
  • レベルが上がって覚えるまほうがあると、本を読むポーズで「〇〇を おぼえた!」
  • 確かめたら、Start Expを0、Partyの3人目をキョンシーに戻す

あとがき

今回は、全体のまほうと、レベルで覚えるまほうを作りました。全員を光らせるだけで、全体に効くことがひと目で分かりますね。次回は、どく・石化などの「状態」を変えるまほうを作りたいと思います!

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

コメント

コメント一覧 (1件)

コメントする

目次