ターン制RPGの作り方#10|敵の行動と、受けるときの演出:痛がる・赤いふち・遅れて縮むゲージ

フェアリーがぼうぎょ、スライムちゃんとキョンシーがこぼれたインクを攻撃。すばやい紙ひこうきが前に出てフェアリーを攻撃し、フェアリーは痛がってのけぞり、画面のふちが赤く光る。本の虫はスライムちゃんを攻撃する

ここまでは、仲間が一方的に攻撃するだけでした。今回は、敵も攻撃してくるようにします。攻撃を受けた仲間は痛がってのけぞり、画面のふちが赤く光って、HPのゲージが減ります。HPが0になった仲間は戦闘不能になり、全員が倒れたら負けです。いよいよバトルらしくなってきますよ!

目次

今回作るもの

  • 敵が、使うコマンドと相手を選ぶEnemyBrain(敵も「たたかう」のコマンドを使う)
  • 受けたときの演出:痛がる絵(damage_62)・画面のふちが赤く光るDamageVignette・HPのゲージが減って、減った分が遅れて縮む
  • 戦闘不能:眠った絵(sleep_03)と灰色。コマンドを選ばず、ねらわれない
  • 仲間が全員倒れたら、負け
スクリプト役割付ける場所
EnemyData・BattleUnit・GameRandom敵のコマンド、敵か仲間か、整数の乱数を足す-
EnemyBrain敵が、コマンドと相手を選ぶ(付けない)
BattleContext・AttackCommand敵も仲間をねらえるようにする-
CharacterData・UnitView痛がる絵と、戦闘不能の絵を足す-
DamageVignette画面のふちを赤く光らせるDamageVignette(新しく作る)
Gauge減った分を赤く残して、遅れて縮めるStatusRow のプレハブ
BattleEffectsゲージを減らし、仲間が受けたら赤いふち-
BattlePhase・BattleManager敵の行動を足す、戦闘不能を飛ばす、負け-

敵も行動を選ぼう

敵にもコマンドを持たせる

#4 コマンドをデータにして、メニューに並べようで、コマンドを1つずつのクラス(BattleCommandを受け継ぐ)にしました。敵の攻撃も、仲間と同じ「たたかう」(AttackCommand)をそのまま使います。前に出る・当たる・光る・のけぞる、の演出も全部そのまま動きます。

EnemyDataに、使うコマンドを足します。

    [SerializeField] private BattleCommand[] commands; // 使うコマンド(この中から選ぶ)

    public BattleCommand[] Commands => commands;
  • 変数はgoldの下に、プロパティはGoldの下に足す
  • Data/Enemiesの3体(SpilledInk・PaperPlane・Bookworm)のCommandsに、Data/CommandsのAttackを入れる(「かみつく」などの敵だけの行動は、#16・#22・#23で足します)
SpilledInkのCommandsのElement 0にAttack (Attack Command)

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

using UnityEngine;

// バトル中の1人(仲間も敵も)。データは読むだけにして、今のHPはここで持つ
public class BattleUnit
{
    public string Name { get; }
    public Stats Stats { get; }  // 能力(データのものを読むだけ)
    public int Hp { get; private set; }
    public UnitView View { get; } // 画面の見た目
    public BattleCommand[] Commands { get; } // 選べるコマンド
    public bool IsGuarding { get; set; }     // ぼうぎょ しているか(ターンの始めに戻す)
    public bool IsEnemy { get; }             // 敵なら true
    public StatusRow Row { get; set; }       // ステータスの行(仲間だけ。敵は null)
    public bool IsDead => Hp <= 0;

    // 仲間のデータから作る(今はLv1の能力)
    public BattleUnit(CharacterData data, UnitView view)
    {
        Name = data.CharacterName;
        Stats = data.BaseStats;
        Hp = Stats.maxHp;
        View = view;
        view.Unit = this;
        Commands = data.Commands;
    }

    // 敵のデータから作る
    public BattleUnit(EnemyData data, UnitView view)
    {
        Name = data.EnemyName;
        Stats = data.Stats;
        Hp = Stats.maxHp;
        View = view;
        view.Unit = this;
        Commands = data.Commands;
        IsEnemy = true;
    }

    // ダメージを受ける(0より下にはしない)
    public void TakeDamage(int damage)
    {
        Hp = Mathf.Max(0, Hp - damage);
    }
}
  • IsEnemy:敵のコンストラクタだけでtrueにする。仲間は何もしないのでfalseのまま
  • Row:仲間のHPが減ったら、ステータスのゲージも減らしたいので、その人の行を覚えておく

BattleLineupのSetupで、行を入れます。仲間を並べるループの中を、次のように書き換えます。作った行とBattleUnitは何度か使うので、いったん変数に入れてから配列にしまいます。

            CharacterData character = party[i];
            UnitView view = partyViews[i];
            view.SetupCharacter(character);
            StatusRow row = statusWindow.AddRow(character);
            BattleUnit unit = new BattleUnit(character, view);
            unit.Row = row;
            StatusRows[i] = row;
            PartyUnits[i] = unit;

EnemyBrain

GameRandomに、整数の乱数を足します(Rangeの下)。

    // 0 以上 count 未満の整数(配列やリストから1つ選ぶとき)
    public static int Index(int count)
    {
        return random.Next(count);
    }
  • 「EnemyBrain」スクリプトを作る
using System.Collections.Generic;

// 敵が、使うコマンドと相手を選ぶ(今は、どちらもランダム)
public static class EnemyBrain
{
    public static BattleAction Decide(BattleUnit enemy, BattleUnit[] party)
    {
        BattleCommand command = enemy.Commands[GameRandom.Index(enemy.Commands.Length)];

        // 相手は、まだ倒れていない仲間から選ぶ
        List<BattleUnit> living = new List<BattleUnit>();
        foreach (BattleUnit member in party)
        {
            if (!member.IsDead)
            {
                living.Add(member);
            }
        }
        BattleUnit target = living[GameRandom.Index(living.Count)];

        BattleAction action = new BattleAction(command, target.View);
        action.User = enemy;
        return action;
    }
}
  • 仲間はSelectAsyncで、メニューと矢印を使って選びます。敵は画面で選ばないので、SelectAsyncは使わず、ここでBattleActionを直接作る
  • コマンドも相手もランダム。敵ごとの行動の選び方やボスの決まった行動などは、#22・#23でここに足していきます
  • 相手を選ぶ乱数は結果に関わるので、GameRandomを使う(シードを決めると、毎回同じ相手をねらう)

敵が使っても動くように

AttackCommandは、ねらった相手が先に倒れていたら「まだ立っている敵」にねらいを変えていました。敵が使うときは「まだ立っている仲間」に変える必要があります。BattleContextのFindLivingEnemyを、使う人の相手から探すFindLivingOpponentに書き換えます。

    // user の相手(仲間なら敵、敵なら仲間)から、まだ倒れていない1人を返す(いなければ null)
    public UnitView FindLivingOpponent(BattleUnit user)
    {
        UnitView[] opponents = user.IsEnemy ? Party : Enemies;
        foreach (UnitView view in opponents)
        {
            if (!view.Unit.IsDead)
            {
                return view;
            }
        }
        return null;
    }

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

    // 前に出て、振って、当てて、戻る。HPが0になったら、敵は消えて、仲間は戦闘不能に
    public override async Awaitable ExecuteAsync(BattleUnit user, BattleAction action, BattleContext context)
    {
        UnitView targetView = action.Target;
        if (targetView.Unit.IsDead)
        {
            // ねらっていた相手が、先に倒れていた。まだ立っている相手に、ねらいを変える
            targetView = context.FindLivingOpponent(user);
        }
        BattleUnit target = targetView.Unit;

        await context.Message.ShowAsync($"{user.Name}の こうげき!");
        await user.View.StepAsync(true);       // 前に出る
        await user.View.PlayAttackPoseAsync(); // 振りかぶって、振る(ポーズの絵が無い敵は、何もしない)

        int damage = DamageCalculator.Physical(user, target);
        target.TakeDamage(damage);
        await context.Effects.PlayHitAsync(target.View, damage); // 当たった!

        // もとの位置に戻りながら、メッセージを出す
        Awaitable stepBack = user.View.StepAsync(false);
        await context.Message.ShowAsync($"{target.Name}に {damage}の ダメージ!");
        await stepBack;

        if (target.IsDead)
        {
            if (target.IsEnemy)
            {
                await target.View.DisappearAsync();
                await context.Message.ShowAsync($"{target.Name}を たおした!");
            }
            else
            {
                target.View.ShowDown();
                await context.Message.ShowAsync($"{target.Name}は たおれてしまった!");
            }
        }
    }
  • FindLivingOpponent(user)に変えたところと、最後のif (target.IsEnemy)で分けたところが変わっています。ShowDownは、このあと戦闘不能のところで作ります
  • 前に出るStepAsyncは、#8 攻撃の手応えを作ろうでforward(敵は-1)を掛けておいたので、敵は左へ前に出る。のけぞりも、仲間は左へのけぞります
スライム

同じAttackCommandを、仲間にも敵にも使えるんですね

フェアリー

そう!#4でコマンドを1つずつのクラスにしておいたから、敵に持たせるだけで済んだんだよ

受けたときの演出

紙ひこうきの攻撃を受けたフェアリーが、痛がる絵で白く光って左へのけぞり、画面のふちが赤く光る。HPのゲージの減った分が赤く残って、少しあとに縮む(ゆっくり再生)

仲間が攻撃を受けたときは、敵が受けたときよりも大げさに知らせます。自分たちが受けたダメージは、ひと目で分かってほしいからです。デッキ構築カードゲームの#11 守る・受ける・回復するときの演出と同じ考え方です。

痛がる絵と、戦闘不能の絵

CharacterDataに、2枚の絵を足します(変数はattackFramesの下、プロパティはAttackFramesの下)。

    [SerializeField] private Sprite damageSprite; // 痛がる絵(damage_62)
    [SerializeField] private Sprite downSprite;   // 戦闘不能の絵(sleep_03)

    public Sprite DamageSprite => damageSprite;
    public Sprite DownSprite => downSprite;
  • 4人のデータのDamage Spriteに<名前>_damage_62、Down Spriteに<名前>_sleep_03をドラッグする
SlimeのDamage Spriteにslime_damage_62、Down Spriteにslime_sleep_03

UnitViewに足します。まず変数です(attackFramesの下)。

    private Sprite damageSprite; // 痛がる絵(仲間だけ)
    private Sprite downSprite;   // 戦闘不能の絵(仲間だけ)
    private bool isDown;         // 戦闘不能になったか

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

        damageSprite = data.DamageSprite;
        downSprite = data.DownSprite;

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

    // 当たった:一瞬だけ真っ白に光って、後ろへのけぞり、元に戻る(仲間は痛がる絵にする)
    public async Awaitable HitAsync()
    {
        if (damageSprite != null)
        {
            idleBounce.enabled = false; // 待機の弾みを止めて、痛がる絵にする
            image.sprite = damageSprite;
        }

        flashImage.sprite = image.sprite;
        flashImage.color = Color.white;
        flashImage.enabled = true;
        Vector2 pushed = homePosition - new Vector2(knockbackDistance * forward, 0f);
        rectTransform.anchoredPosition = pushed;

        await Awaitable.WaitForSecondsAsync(hitFlashSeconds, destroyCancellationToken);
        flashImage.enabled = isHighlighted;

        float time = 0f;
        while (time < recoverSeconds)
        {
            time += Time.deltaTime;
            rectTransform.anchoredPosition = Vector2.Lerp(pushed, homePosition, time / recoverSeconds);
            await Awaitable.NextFrameAsync(destroyCancellationToken);
        }
        rectTransform.anchoredPosition = homePosition;

        if (damageSprite != null && !Unit.IsDead)
        {
            idleBounce.enabled = true; // 倒れていなければ、待機に戻る
        }
    }
  • 最初と最後のifを足しただけです。痛がる絵は、のけぞって戻るまで(0.3秒ぐらい)見えます
  • HPが0になったときは、待機に戻さない(このあとすぐ戦闘不能の絵にするので)

画面のふちを赤く(DamageVignette)

  • 素材のTurnBasedRpgAssets.zipに、画面のふちの絵Sprites/Effects/fx_vignette.png(真ん中が透明で、ふちほど白い)を足しました。ダウンロードし直して、プロジェクトに入れる
  • Canvasの子にDamageVignette(UI (Canvas)>Image)
    • PopupLayerのすぐ上(PopupLayerより1つ前)に置く。数字は赤いふちより手前に出ます
    • アンカー:stretch-stretch、Left・Top・Right・Bottom:0
    • Source Image:fx_vignette、Color:#E8433A
    • Raycast Targetのチェックを外す
  • 「DamageVignette」スクリプトを作って、DamageVignetteに付ける
using UnityEngine;
using UnityEngine.UI;

// 画面のふちを赤く光らせて、だんだん消す(仲間がダメージを受けたとき)
[RequireComponent(typeof(Image))]
public class DamageVignette : MonoBehaviour
{
    [SerializeField] private float fadeSeconds = 0.5f; // 消えるまでの時間
    [SerializeField] private float maxAlpha = 0.6f;    // 一番濃いときの濃さ

    private Image image;
    private float timeLeft;

    private void Awake()
    {
        image = GetComponent<Image>();
        SetAlpha(0f);
    }

    public void Play()
    {
        timeLeft = fadeSeconds;
    }

    private void Update()
    {
        if (timeLeft <= 0f)
        {
            return;
        }
        // ヒットストップ中も消えていくように、unscaledDeltaTime で減らす
        timeLeft = Mathf.Max(0f, timeLeft - Time.unscaledDeltaTime);
        SetAlpha(maxAlpha * timeLeft / fadeSeconds);
    }

    private void SetAlpha(float alpha)
    {
        Color color = image.color;
        color.a = alpha;
        image.color = color;
    }
}
  • Playで残り時間を0.5秒にして、Updateで濃さを0.6から0に下げていく
  • 光らせたら待つ必要がない(ほかの演出と一緒に消えていけばいい)ので、asyncにせずUpdateで消しています
  • エディタでは赤いふちが見えたままでOK。再生するとAwakeで透明になります
Canvas>DamageVignette(PopupLayerのすぐ上)。Source Imageはfx_vignette、Colorは赤、Raycast Targetのチェックなし、Damage Vignette (Script)のFade Seconds 0.5、Max Alpha 0.6。エディタでは画面が赤いままでOK

遅れて縮むHPのゲージ

HPのゲージがパッと短くなるだけだと、どれだけ減ったかが見えにくいので、減った分をいったん赤く残して、少しあとから縮めるようにします。

  • Prefabs/StatusRowを開いて、HpGauge>Fillを選び、Ctrl+Dで複製して名前をLagにする
    • LagをBackとFillの間(Fillより上=Fillより後ろに描かれる)に置く。HpGaugeの子は、上からBack・Lag・Fill・Label・Valueの順になる
    • Colorを#F06A5E(明るい赤)にする。白にすると、右はしの「24/26」の白い数字が見えなくなるので、白い文字が読める色にしています
StatusRowのプレハブで、HpGaugeの子のBackとFillの間にLag。LagのColorは明るい赤、Image TypeはFilled

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

using TMPro;
using UnityEngine;
using UnityEngine.UI;

// ゲージの長さと「38/38」の数字を、今の値と最大値から決める
public class Gauge : MonoBehaviour
{
    [SerializeField] private Image fill;         // Image Type:Filled
    [SerializeField] private TMP_Text valueText; // 「38/38」
    [SerializeField] private Image lag;          // fill の後ろの赤いゲージ(無ければ空のまま)
    [SerializeField] private float lagDelay = 0.4f; // 減ってから、赤いゲージが縮み始めるまでの時間
    [SerializeField] private float lagSpeed = 1.2f; // 赤いゲージが縮む速さ(1秒でゲージ何本分か)

    private int current;
    private int max;
    private float lagTimer; // 縮み始めるまでの残り時間

    public void SetValue(int newCurrent, int newMax)
    {
        current = newCurrent;
        max = newMax;
        float newFill = max > 0 ? (float)current / max : 0f;

        if (lag != null)
        {
            if (newFill < fill.fillAmount)
            {
                lagTimer = lagDelay; // 減ったら、赤いゲージは少し待ってから縮む
            }
            else
            {
                lag.fillAmount = newFill; // 増えたときは、すぐにそろえる
            }
        }

        fill.fillAmount = newFill;
        valueText.text = $"{current}/{max}";
    }

    private void Update()
    {
        if (lag == null)
        {
            return;
        }
        if (lagTimer > 0f)
        {
            lagTimer -= Time.deltaTime;
            return;
        }
        // 赤いゲージを、今の長さまで一定の速さで縮める
        lag.fillAmount = Mathf.MoveTowards(lag.fillAmount, fill.fillAmount, lagSpeed * Time.deltaTime);
    }

    // 確認用:Inspector の右上の︙から、ゲージを半分にしてみる
    [ContextMenu("半分にしてみる")]
    private void TestHalf()
    {
        SetValue(current / 2, max);
    }
}
  • SetValueで減ったときは、0.4秒のタイマーを始める。待ち終わったらUpdateで、Mathf.MoveTowardsを使って今の長さまで一定の速さで縮める
    • MoveTowards(今, 目標, 1回に進める量)は、目標を追いこさずに近づく計算です
  • 増えたとき(回復)は、赤いゲージもすぐにそろえる
  • lagが空なら何もしないので、MPのゲージは今までどおり
  • StatusRowのプレハブで、HpGaugeのGaugeのLagに、Lagをドラッグ

BattleEffectsで、ゲージと赤いふちを出す

BattleEffectsに、赤いふちを足します(stageの下)。

    [SerializeField] private DamageVignette damageVignette; // 仲間が受けたときに、画面のふちを赤く

PlayHitAsyncの最初に、ゲージを減らして、仲間なら赤いふちを光らせる行を足します。

    // 当たった瞬間の演出を、まとめて出す
    public async Awaitable PlayHitAsync(UnitView target, int damage)
    {
        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);

戦闘不能

HPが0になった仲間は、戦闘不能にします。

  • 眠った絵(sleep_03)にして、灰色にする
  • コマンドを選ばない(順番が来ても飛ばす)
  • 敵にねらわれない(EnemyBrainは、倒れていない仲間から選ぶようにしました)

眠った絵にする

UnitViewに、ShowDownを足して、SetDimmedを書き換えます。

    // 暗くする(false で、もとの色に戻す。戦闘不能の人は暗いまま)
    public void SetDimmed(bool isDimmed)
    {
        image.color = (isDimmed || isDown) ? dimColor : Color.white;
    }

    // 戦闘不能:待機の弾みを止めて、眠った絵にして、暗くする
    public void ShowDown()
    {
        isDown = true;
        idleBounce.enabled = false;
        image.sprite = downSprite;
        image.color = dimColor;
    }
  • SetDimmedは、コマンドを選ぶたびに「ほかの仲間を暗く → 全員もとの明るさに」と呼ばれます。戦闘不能の人が明るく戻らないように、isDownなら暗いままにしています

BattleManagerで、戦闘不能の人を飛ばす

BattlePhaseに、負けを足します(Victoryの下)。

    Defeat,  // 負けた(仲間が全員倒れた)

BattleManagerを直します。まず、RunAsyncのCommand・Actionのところと、Defeatを書き換えます。

                case BattlePhase.Command:
                    BattleAction[] partyActions = await SelectActionsAsync();
                    actions = AddEnemyActions(partyActions);
                    phase = BattlePhase.Action;
                    break;

                case BattlePhase.Action:
                    await ExecuteActionsAsync();
                    if (LivingEnemyViews().Length == 0)
                    {
                        phase = BattlePhase.Victory;
                    }
                    else if (IsPartyDown())
                    {
                        phase = BattlePhase.Defeat;
                    }
                    else
                    {
                        phase = BattlePhase.Command; // 次のターン
                    }
                    break;

                case BattlePhase.Victory:
                    await messageWindow.ShowAsync("てきを ぜんぶ たおした!");
                    phase = BattlePhase.End;
                    break;

                case BattlePhase.Defeat:
                    await messageWindow.ShowAsync("みんな たおれてしまった…");
                    phase = BattlePhase.End;
                    break;
  • コマンドを選んだら、AddEnemyActionsで敵の行動を足してから、行動する場面へ。敵の行動も仲間の行動と一緒に、すばやさの順に並びます
  • 行動のあとで、敵が全員倒れていたら勝ち、仲間が全員倒れていたら負け
  • 勝ったとき・負けたときの画面は、次の#11で作ります

SelectActionsAsyncのwhileの最初と、コマンドでキャンセルしたところを書き換えます。

        while (index < party.Length)
        {
            BattleUnit unit = party[index];
            if (unit.IsDead)
            {
                // 倒れている人は、コマンドを選ばない
                selected[index] = null;
                index++;
                continue;
            }

            StatusRow row = lineup.StatusRows[index];
            if (command == null)
            {
                // コマンドでキャンセルしたら、前の人に戻る(倒れている人は飛ばす)
                index = PreviousLivingIndex(index);
                continue;
            }

ExecuteActionsAsyncを書き換えて、メソッドを3つ足します。

    // 選んだ行動を、すばやさの順に実行する
    private async Awaitable ExecuteActionsAsync()
    {
        ActionOrder.Sort(actions);
        foreach (BattleAction action in actions)
        {
            if (LivingEnemyViews().Length == 0 || IsPartyDown())
            {
                // 途中で勝ち負けが決まったら、残りの行動はしない
                break;
            }
            if (action.User.IsDead)
            {
                // 自分の番が来る前に、倒されていた
                continue;
            }
            await action.Command.ExecuteAsync(action.User, action, context);
        }
    }

    // 仲間の行動(倒れていて選ばなかった人の分は入れない)に、敵の行動を足す
    private BattleAction[] AddEnemyActions(BattleAction[] partyActions)
    {
        List<BattleAction> all = new List<BattleAction>();
        foreach (BattleAction action in partyActions)
        {
            if (action != null)
            {
                all.Add(action);
            }
        }
        foreach (BattleUnit enemy in lineup.EnemyUnits)
        {
            if (!enemy.IsDead)
            {
                all.Add(EnemyBrain.Decide(enemy, lineup.PartyUnits));
            }
        }
        return all.ToArray();
    }

    // 仲間が全員倒れたか
    private bool IsPartyDown()
    {
        foreach (BattleUnit unit in lineup.PartyUnits)
        {
            if (!unit.IsDead)
            {
                return false;
            }
        }
        return true;
    }

    // index より前で、倒れていない一番近い人の番号(いなければ index のまま)
    private int PreviousLivingIndex(int index)
    {
        for (int i = index - 1; i >= 0; i--)
        {
            if (!lineup.PartyUnits[i].IsDead)
            {
                return i;
            }
        }
        return index;
    }
  • action.User.IsDead:すばやい敵に先に倒された仲間は、選んでいた行動をしない。倒された敵も同じ
  • PreviousLivingIndex:2人目が倒れているときに3人目でキャンセルしたら、1人目に戻る

動かして確認!

再生して、何ターンか戦ってみましょう。

仲間の攻撃のあと、敵が前に出て攻撃してくる。受けた仲間は痛がってのけぞり、画面のふちが赤く光って、ゲージの赤い部分があとから縮む

戦闘不能と負けを確かめるときは、仲間がすぐ倒れるように、条件を決めて試しましょう。

  • Data/CharactersのFairy・Slime・JiangshiのBase StatsのMax Hpを、一時的に5にする
  • 再生して、3人とも「ぼうぎょ」以外を選んで、何ターンか進める
  • 確かめたら、Max Hpを元に戻す(フェアリー26・スライムちゃん38・キョンシー32)
Max Hpを5にして戦うと、攻撃を受けた仲間が眠った絵になって暗くなり、次のターンはその人のコマンドを飛ばす。全員が倒れると「みんな たおれてしまった…」

#10の動作確認

  • 仲間の行動のあと(すばやい敵は先に)、敵が前に出て攻撃してくる
  • 受けた仲間は痛がる絵になって白く光り、左へのけぞる。画面のふちが赤く光る
  • HPのゲージが減り、減った分が赤く残ってから縮む
  • HPが0になった仲間は、眠った絵になって暗くなり、コマンドを選ばない。敵にもねらわれない
  • 全員が倒れると「みんな たおれてしまった…」で止まる

敵が攻撃してこないときは、Data/Enemiesの3体のCommandsにAttackが入っているか確かめましょう。空だと、EnemyBrainでエラーになります。

あとがき

今回は、敵が攻撃してくるようにして、受けたときの演出と戦闘不能を作りました。同じAttackCommandを敵にも使えたのは、コマンドを1つずつのクラスにしておいたおかげですね。次回は、勝ったとき・負けたときの画面と、「にげる」を作りたいと思います!

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

コメント

コメント一覧 (1件)

コメントする

目次