i-school - 2Dトップビューアクション 手順13
 障害物の攻撃の処理について、処理を考えて追加します。



 以下の内容で順番に実装を進めていきます。

手順13 ー障害物の攻撃処理ー
20.障害物の攻撃処理



 新しい学習内容は、以下の通りです。

 ・ラムダ式によるプロパティの記述方法
 ・シングルトン・デザインパターンによるクラスの運用方法
 ・DontDestroyOnLoad メソッド



20.障害物の攻撃処理

1.設計


 前回の手順までで実装している、プレイヤーの現在の状態の制御処理と攻撃処理は以下の通りです。
すべて PlayerController スクリプト内で制御を行っています。


1.Update メソッド内に処理を追加し、PlayerState が Move 以外の状態ではメソッド内の処理(移動用のキー入力処理など)が実行されないように停止する制御処理を追加する
   ↓
2.FixedUpdate メソッド内に処理を追加し、PlayerStateが Move以外の状態ではメソッド内の処理(移動処理など)が実行されないように停止する制御処理を追加する
   ↓
3.AutoBattle メソッド内の処理を追加し、バトルの開始に合させて PlayerState を Battle_Before に切り替える。この状態の間、プレイヤーは攻撃を繰り返すように制御する
  障害物の Hp が 0 以下になったら PlayerState を Result に切り替える。この状態になることでプレイヤーは攻撃を終了し、バトル後の処理に移行する
  その後、再度 PlayerState を Move に切り替える
   ↓
4.PlayerState が Move になったので、停止していた Update メソッドと FixedUpdate メソッド内の処理が動き始める
   ↓
5.これを繰り返す

 このうちの、【3】の処理に、障害物側の攻撃の処理を追加します。
プレイヤーの攻撃 → 障害物の攻撃 → プレイヤーの攻撃というように、いずれかの Hp の値が 0 になるまで、【3】の処理を交互に繰り返すように処理を修正します。

 どのような処理を追加していけばいいのか、まずは考えてみてください。

 なお今回は、常にプレイヤーの攻撃から始まるように実装しますが、例えば、敏捷性のような値を双方に設定することで、
敏捷性の高い方から攻撃を始める、といった処理に変更していくことも可能です。


2.ObstacleBase スクリプトを修正する


 前回に引き続き、今度は障害物に攻撃力の概念を与えます。考え方は Hp 変数の際と同じです。

 今回は attackPower という名前の int 型の変数を作成し、この値を障害物の攻撃力として利用します。
変数の名前は任意ですが、その変数をプログラム内において、どういった役割として利用するかを設計した上で名称を考えると分かりやすくなります。

 今回も attackPower 変数は private 修飾子として外部のクラスからは直接アクセスできない状態にしておきます。
代わりにプロパティを作成し、プロパティを通じて、attackPower 変数へのアクセスを行うように設計します。

 ただし、インスペクターより、値を任意に入力・変更・確認できるようにしておくため、hp 変数と同じように SerializeField 属性を一時的に付与しておきます
こうすることにより、デバッグを行いやすくなります。
正式な運用が始まったら、SerializeField 属性は削除し、private 修飾子のみの変数とします。


ObstacleBase.cs

 <= クリックすると開きます。


 スクリプトを修正したら、セーブを行います。



 スクリプトを修正したので、このスクリプトがアタッチされている障害物のゲームオブジェクトをヒエラルキーで選択して確認します。

 インスペクターより ObstacleBase スクリプトを確認すると、AttackPower 変数が追加されています。
初期値は 0 ですので、まずは 1 に設定してください。これが、この障害物の攻撃力です。


<インスペクター画像>



 今回の実装では防御力の概念がないため、この障害物の攻撃力の値分を、プレイヤーの Hp の値から減少させます。


3.<ラムダ式によるプロパティの記述方法>


 ラムダ式による記述はプロパティについても利用することが出来ます。


<通常のプロパティの書式の一例>
  /// <summary>
    /// hp のプロパティ
    /// </summary>
    public int Hp
    {
        get {
            return hp;
        }
        set {
            hp = value;
        } 
    }


<ラムダ式によるプロパティの記述方法>
 // attackPower 変数のプロパティ
  public int AttackPower { get => attackPower; set => attackPower = value; }

 get 部分については return を省略することが出来ます。

 注意点としては、プロパティでラムダ式にできるのは1行分だけです。
そのため、例えば set 内に2行以上の処理がある場合にはラムダ式での記述はできません。


4.UserDataManager スクリプトを作成する


 障害物からの攻撃処理を作成するにあたり、今度はプレイヤーの Hp の値も管理が必要になります。
この情報は PlayerController スクリプトに直接持たせずに、専用のクラスをスクリプトで作成して、そちらで管理を行います。

 今後、Hp 以外にもプレイヤーの情報が増えていくことを念頭に置き、
このクラスはシングルトンというデザインパターンで作成をしておきます。


UserDataManager.cs

<= クリックすると開きます


 スクリプトを作成したらセーブします。


5.<シングルトンデザインパターンによるクラスの作成>


 シングルトンとは、数多くあるデザインパターンの1つです。
そのクラスのインスタンスが必ず1つであることを保証するデザインパターンのことを言います。

 UserDataManager クラスでは、このシングルトンを採用しています。
つまり、ゲーム中を通じて、この UserDataManager クラスが1つしか存在できないようになります。
実装例は複数ありますが、一番読みやすい方式で記述しています。



<シングルトンデザインパターンのクラスの作成方法>
    public static UserDataManager instance;  // クラス名と同名の型を static で宣言する

    private void Awake() {
        if (instance == null) {
            instance = this;
            DontDestroyOnLoad(gameObject);
        } else {
            Destroy(gameObject);
        }
    }



 ポイントは、自分自身の UserDataManager 型を static 修飾子付きの instance 変数として宣言していることです。
この instance 変数が UserDataManager クラス自身が代入された情報として利用することになります。

 Awake メソッドを利用して、instance 変数が null (空っぽ) である場合には、UserDataManager クラス(this)を代入します。
次の DontDestroyOnLoad メソッドは Unity が用意しているメソッドで、引数に指定されたゲームオブジェクトはシーン遷移をしても破壊されてないゲームオブジェクトになります。
この DontDestroyOnLoad メソッドはシングルトンデザインパターンにする際に一緒に用いられることが多いです。

 そして instance 変数が null ではない場合、つまり、2つ目以降の複数の UserDataManager クラスが存在する場合には、その UserDataManager クラスのゲームオブジェクトを Destroy します。
この手順により、UserDataManager クラスがアタッチされているゲームオブジェクトが常にヒエラルキー上に1つしか存在しない状態を作り出しています

 このシングルトンによってインスタンスが1つか生成されないことが保証されますので、
逆説的に考えると、この UserDataManager クラスへの参照は、いずれのクラスからであっても変数を介さずに参照を行えるようになります。



 例えば、NonPlayerCharacter というクラスがあり、その NonPlayerCharacter クラスを持つゲームオブジェクトが5つあった場合、
どの」NonPlayerCharacter クラスであるかを確定できないと、対象となる NonPlayerCharacter クラスへは参照できません。
そのため、NonPlayerCharacter 型の変数を用意して、その変数へ参照したい NonPlayerCharacter クラスを代入することによって、
はじめて NonPlayerCharacter クラスの情報を扱うことができるようになります。これが情報を扱う際の基本的な処理になります。

 ですがシングルトンである UserDataManager クラスの場合には、このインスタンスは常に1つしかないことが保証されていますので、
どの」という指定の部分が不要になります。よってクラスの特定ができているため、変数への代入が不要になります。
UserDataManager という指定はすなわち、自動的にただ1つの UserDataManager クラスの参照が行われることになるためです。

 この機能を利用して UserDataManager クラスを作成しておくことで、どのクラスからでも参照しやすい設計にしておきます。


6.<DontDestroyOnLoad メソッド>


 新しいシーンをロードして遷移をするときに、引数で指定している対象のゲームオブジェクトについては破棄されないようにするメソッドです。
そのため次のシーンに遷移しても、このスクリプトがアタッチされているゲームオブジェクトは前のシーンから破棄されずに残り続けます。

 多くの場合、今回のようにシングルトンクラスで一緒に利用され、スクリプトがアタッチされているゲームオブジェクト自身を対象に取ります。
(アタッチされているゲームオブジェクトが破棄されなくなる)

  DontDestroyOnLoad(gameObject);

 なお、この DontDestroyOnLoad メソッドの引数で対象となっているゲームオブジェクトは、ゲーム実行時、DontDestroyOnLoad シーンが作成されます。
ヒエラルキーにも別のシーンの情報として表示され、通常の破棄されるゲームオブジェクトとは、管理が別になっていることを見分けられるようになっています。


ヒエラルキー画像



参考サイト 
Unity公式スクリプトリファレンス
Object.DontDestroyOnLoad
https://docs.unity3d.com/ja/current/ScriptReferenc...


7.UserDataManager ゲームオブジェクトを作成し、UserDataManager スクリプトをアタッチし、設定を行う


 ヒエラルキーの空いている場所で右クリックをしてメニューを開き、CreateEmpty を選択し、新しいゲームオブジェクトを作成します。
名前を UserDataManager に変更し、先ほど作成した、同名の UserDataManager スクリプトをアタッチしてください。

 アタッチが正常に行われているかどうか、インスペクターを確認した上で、Hp 変数に値を設定してください。


<インスペクター画像>



 以上で設定は完了です。


8.PlayerController スクリプトを修正する


 AutoBattle メソッド内に障害物による攻撃の処理を追加し、プレイヤーの攻撃と障害物の攻撃が交互に発生するように制御を行います。

 この制御により、いずれかの Hp の値が 0 以下になるまで攻撃を繰り返す処理を完成させます。


PlayerController.cs

 <= クリックすると開きます。


 スクリプトを修正したらセーブをします。


9.ゲームを実行して動作を確認する


 新しい機能をたくさん実装しているので、それらの復習を行いながら、処理の理解を深めていきましょう。
処理が理解できたら、ゲームを実行してデバッグを行います。

 双方の攻撃処理を実装しましたので、今回の Console ビューには、
【障害物の残り HP : 】、【プレイヤーの残り HP : 】という形で、交互に攻撃の処理が表示されます。

 また今回は、プレイヤーの Hp が 0 になったら Console ビューにゲームオーバーと表示されるように Debug.Log メソッドを用意しています。
いままでと同じにように障害物が破壊されて、再度、プレイヤーの移動が可能になることを確認するとともに、
プレイヤーの Hp の値についてもデバッグを行い、ゲームオーバー表示が Console ビューに表示されるかも確認もしてください。

 このように、実際にデバッグを開始する前に、どういった処理の結果が考えられるかを想定した上で
考えられるすべての結果について順番に確認を行っていく必要があります

 デバッグを行いやすくするため、双方の Hp の値はどちらもインスペクターより変更ができますので、
この機能を活用して調整し、デバッグを行ってください。


<プレイヤーの勝利(プレイヤーの Hp を障害物の Hp よりも高くする)>



<障害物の勝利(プレイヤーの Hp を障害物の Hp よりも低くする)>


 
 処理を理解しておくことで、さらにデバッグをするべき点が見つかります。
プレイヤーの Hp が 0 になった場合には、プレイヤーの状態(currentPlayerState 変数)が Move に切り替わらない(代わりに GameUp になる)ため、
そのまま移動が出来ない状態が継続されます

 こちらも合わせて確認しておいてください。



 以上でこの手順は終了です。

 次は 手順14 −−? です。