i-school - 2DタイルマップRPG 手順24
 Debug.Log メソッドを活用して、探索イベントの実装の準備を行います。

 探索イベントは会話イベントの応用で実装出来ます。
会話イベント用の行動ボタンを押した際に、それが NPC であれば会話イベント用の会話ウインドウを開き、
探索イベント用のゲームオブジェクト(宝箱など)であれば、探索イベント用の会話ウインドウを開くようにします。

 この方法で実装を行うことで1つのボタンを会話・探索の両方に活用する制御処理が可能になります。


<実装動画>
動画ファイルへのリンク


手順24 ー探索イベントの実装準備ー
42.探索用の宝箱ゲームオブジェクトの作成と TreasureBox スクリプトの作成をし、設定を行う
43.プレイヤーの向いている方向に宝箱がある場合に、探索イベントが発生する処理を実装する



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

 ・プロパティ
 ・LayerMask による処理の自動分岐制御
 ・処理を読み解いてコメントを書く



42.探索用の宝箱ゲームオブジェクトの作成と TreasureBox スクリプトの作成をし、設定を行う

1.設計

 
 プレイヤーキャラが向いている方向に探索用のゲームオブジェクト(地点含む)が存在している場合には、
例えば、宝箱であれば、それを調べることによる探索用のイベントを発生させるようにします。

 処理の実装方法は、NPC との会話イベントと同じですので、対象物を調べると会話ウインドウが表示される必要があります。
そのため、NPC のゲームオブジェクトの子オブジェクトである DialogCanvas をそのまま利用して作成していくようにします。
 
 最初に DialogCanvas ゲームオブジェクトをプレファブにし、その後、NPC ゲームオブジェクトを複製して、
探索用のゲームオブジェクトを作成していきます。


2.NPC 用のゲームオブジェクトから探索用のゲームオブジェクトを作成する


 探索用のゲームオブジェクトの作成方法はいくつかあります。
今回はヒエラルキーにある NPC ゲームオブジェクトを活用して、宝箱のゲームオブジェクトの作成を行います。

 ヒエラルキーにある NPC 用のゲームオブジェクトの子オブジェクトである、DialogCanvas ゲームオブジェクトを選択して、
Prefabs フォルダへドラッグアンドドロップしてプレファブにしてください。


Prefabs フォルダ画像




 つづいて、ヒエラルキーにある NPC 用のゲームオブジェクトを選択して右クリックしてメニューを表示し、Duplicate を選択します。
NPC 用のゲームオブジェクトが複製されますので、名前を TreasureBox に変更します。

ヒエラルキー画像



 DialogCanvas がプレファブになっていますので、複製されたゲームオブジェクトの DialogCanvas もプレファブ状態のはずです。
このようにしておくことで、プレファブの DialogCanvas に変更を加えると、NPC 用の DialogCanvas と今回複製したゲームオブジェクトの DialogCanvas の両方が一緒に変更されるようになります。


3.TreasureBox ゲームオブジェクトの設定を行う


 TresureBox ゲームオブジェクトは複製したゲームオブジェクトと同じ位置にありますので、まずは位置を変更して見える状態にしてください。

 最初に、TreasureBox ゲームオブジェクトより、不要なコンポーネントのRemove(削除)を行います。

 Animator コンポーネントと NonPlayerCharacter コンポーネントを Remove(削除)してください。

 続いて、SpriteRenderer コンポーネントの Sprite に任意の画像を設定してください。
画像についてはダウンロードしたものをインポートして利用しましょう。
複数の画像が1枚の画像として登録されている場合には、Sprite Editor の機能を活用して分割してから利用してください。

 3つ目に、NPC と同じように、専用のレイヤーを作成して登録します。登録方法は、インスペクターの Layer の部分をクリックして Add Layer を選択します。
レイヤー名に TreasureBox と登録してから、再度 TreasureBox ゲームオブジェクトを選択して、レイヤーを設定してください。


インスペクター画像



Sceneビュー画像



 設定した画像のサイズと画面のタイルのサイズが合わない場合には、画像ファイルを選択してインスペクターを表示し、
Pixels Per Unit の値を調整してください。タイルの画像ファイルの Pixels Per Unit は 32 に設定されていますので、
32 よりも小さい値を設定すると、画像がタイルの画像よりも大きくなりますし、32よりも大きい値に設定すれば、画像は小さくなります。


参考
タイル用の画像と Pixels Per Unit



タイルよりも宝箱の画像が大きかった場合の Pixels Per Unit の設定



 最後にコライダーのサイズを確認します。
Sceneビューにて確認しながら、ゲームオブジェクトの形状と同じ位になるようにサイズを変更してください。


インスペクター画像(参考値です。画像に合わせましょう)



Sceneビュー画像 

 

 以上で設定は完了です。
子オブジェクトである DialogCanvas ゲームオブジェクトの設定は不要ですが、念のため、インスペクターからアサインが外れていないか確認しておきましょう。


インスペクター画像



 次にスクリプトを作成して、その後、TresureBox ゲームオブジェクトにアタッチします。


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


 コメントはカットしてありますが、処理の大筋は NonPlayerCharacter クラスと同じです。
見直しをしつつ、こちらの処理を書いてみてください。また、処理を書く前に、NonPlayerCharacter クラスのコメントを元にして処理の実装に挑戦してみてもいいでしょう。


TreasureBox.cs

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


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



 ヒエラルキーにある TreasureBox ゲームオブジェクトに TreasureBox スクリプトをドラッグアンドドロップしてアタッチしてください。
スクリプトをアタッチしたらインスペクターからアタッチされているかを確認します。

 事前にインスペクターより設定しておく情報として、dialogController 変数が表示されていますので、
TreasureBox ゲームオブジェクトの子オブジェクトの DialogCanvas ゲームオブジェクトをドラッグアンドドロップしてアサインしてください。
これで子オブジェクトとのつながりが出来ました。

 EventData の内容は NPC ゲームオブジェクトと同じようにゲーム実行時に自動的にデータを読み込むようにしますので、初期値のままで構いません。


インスペクター画像

 

43.PlayerController スクリプトを修正して、プレイヤーの向いている方向に宝箱がある場合に、探索イベントが発生する処理を実装する

1.設計


 プレイヤーキャラの行動用のボタンは1つにしておきたいので、ボタンを押して Ray を発射したさいに、Ray と接触したゲームオブジェクトの Layer の値によって
処理を自動的に分岐するような制御を考えていきます。

 現在は Ray が NPC というレイヤーを持つゲームオブジェクトに接触した場合に、その確認を行った上で、会話イベントを発生させています。
こちらの処理を応用していきましょう。

 Ray が判定する対象となるレイヤーは複数指定ができますので、NPC レイヤーだけではなく、TreasureBox レイヤーにも反応すうようにし、
さらにそれを「どのレイヤーのゲームオブジェクトであるのか」を特定することが出来れば、1つのボタンで異なる制御処理を自動的に判別出来るようになります。

 今回は「どのレイヤーのゲームオブジェクトであるのか」を特定する方法として、そのゲームオブジェクトにアタッチされているクラス(コンポーネント)から判別を行っています。
これはゲームオブジェクトの種類の特定に合わせて、必要な情報(クラス)を取得するようにしていますので、判別+取得の処理を1回で行うようにしている実装方法です。

 これは一例であるので、直接レイヤー自体の判別をするようにしても構いません。ただしその場合は、「レイヤーの判定後」に、対象となるコンポーネントの取得処理が別途必要になります。


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


 前回の応用課題であった、インベントリウインドウ表示中のキャラの制御処理も一緒に記述してあります。

 最後に復習を行うため、新しく追加している処理についてはコメントをカットしてあります。
まずは自分で処理を書きながら、どのような処理が行われているのかを理解しながら書き進めてみてください。

 その後、コメントをつけながら、復習を行ってみてください。コメントの参考例は最後にあります。


PlayerController.cs

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


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


3.<プロパティ>


 プロパティとは、クラス外部から見るとメンバー変数のように振る舞いクラス内部から見るとメソッドのように振る舞う機能です。
そのため、実装状態(private修飾子のまま)を変更することなく、外部クラスへの参照・変更を行える機能であるため、扱いを覚えておくと非常に便利です。

 この特性より、プロパティを作成する場合、多くは private 修飾子の変数の情報を扱うために作成します
今回の場合は、PlayerController クラスの isTalking 変数に対してのプロパティを作成します

<プロパティの作成>
    private bool isTalking;


    /// <summary>
    /// isTalking 変数のプロパティ
    /// </summary>
    public bool IsTalking
    {
        set {
            isTalking = value;
        }

        get {
            return isTalking;
        }
    }

 上記のようなプロパティを作成することで、private 修飾子で宣言している isTalking 変数を
プロパティを利用することによって、外部クラスから参照出来るようにしています。

 private 修飾子にて宣言フィールドで宣言した変数については、外部のクラスからは参照・変更を行うことが出来ません
このとき参照を行いたい場合には、変数の修飾子を public 修飾子に変更して対応するのではなく
プロパティの持つ get キーワードを利用して、戻り値を利用して private 修飾子の変数を外部クラスに参照させることで対応することが出来ます。

 こういった機能を知っているかどうかで設計部分が大きく変わります
色々な機能を知っていれば、例えば、今回はプロパティを使おうか、どうしようか、という選択肢が増えますが、知らなければ選択肢自体が生まれません。

 設計の引き出しを広げる上でも、新しい技術を覚えること、常に学習する意欲と向上心を持つことがプログラム学習のポイントです



 プロパティを利用した設計により、public ではない変数を外部クラスで利用できるようにします。
外部からプロパティを呼び出して戻り値を参照する処理のことをゲッター(getter)と呼びます。

  get {
      return isTalking;
  }



 同様に、private 修飾子の値を外部クラスより変更したい場合には、プロパティを仲介する手法を使って書き換える処理を実装出来ます。
こちらの処理は set キーワードを利用して処理を記述します。そのため、この処理をセッター(setter)と呼びます。

 set キーワード内には呼び出し元から value の値で情報が届いていますので、value の値を利用することで private 修飾子の変数に代入処理を行うことが出来ます。

  set {
      isTalking = value;
  }

 この処理を外部のクラスから実行する場合には、参照する場合と同じで「クラスの変数.プロパティ名」と記述することで値の書き換えが可能です。

今回のケースであれば次のような形で参照して利用できます。

<TreasureBox.cs>
  // プロパティの利用
    public void CloseTreasureBox() {

        dialogController.HideDialog();

        // クラスの変数.プロパティで利用している
        playerController.IsTalking = false;
    }



 get、set キーワードはどちらか片方だけでも記述できます
またプロパティ内部に条件式を用意して、その結果に合わせて処理を変更する記述も出来ます


参考サイト
未確認飛行 C 様
プロパティ
https://ufcpp.net/study/csharp/oo_property.html

FEnetインフラ様 テックブログ
C#のプロパティを使いこなそう!さまざまな実装方法を紹介
https://www.fenet.jp/infla/column/technology/c%E3%...



 実際の使用例です。外部クラスからは、「プロパティのあるクラスの変数.プロパティ名」と記述することで参照が可能です。
参照する場合は通常の変数と同じように、左辺に変数を用意して代入したり、引数の部分に利用したり出来ます。

<利用例>
  PlayerController player = GetComponent<PlayerController>();

  player.IsTalking = false;

 今回のケースではメソッドの引数に PlayerController クラスの情報が届いていますので、それを活用してクラスを取得しています。
GetComponent メソッド以外でもクラスの情報を取得できる方法があることを覚えておきましょう。


<TreasureBox.cs にて利用>
    // 宣言フィールドにて変数を宣言
    private PlayerController playerController;
 
----------------------------------------------------------------------------------------------

  // クラスの情報を取得
    public void OpenTresureBox(Vector3 playerPos, PlayerController playerController) {

    // メソッドに届いた PlayerController クラスの情報を変数に代入
        if (this.playerController == null) {
            this.playerController = playerController;
        }

        // 省略
  }

----------------------------------------------------------------------------------------------

  // プロパティの利用
    public void CloseTreasureBox() {

        dialogController.HideDialog();

        // クラスの変数.プロパティで利用している
        playerController.IsTalking = false;
    }

 処理を見直して、流れを把握してみてください。


4.<LayerMask による処理の自動分岐制御>


 プレイヤーキャラが Action のボタンを押すと、いままではその方向にあった NPC という Layer が設定されているゲームオブジェクトのコライダーを取得していました。
その際には、Physics2D クラスの持つ Raycast メソッドを実行して取得していましたが、この Raycast メソッドの対象 Layer は1種類ではなく、複数の指定が可能です。

 今回はこの取得する Layer の範囲を配列変数に登録しておくことで、複数の Layer を取得対象にして、NPC と 宝箱の両方のゲームオブジェクトの情報を取得するように制御を変更しています。


  // 宣言フィールド
    private string[] actionlayerMasks = new string[2] { "NPC", "TresureBox" };   // 複数の LayerMask の設定

  // actionlayerMasks 変数に代入されている複数の文字列の Layer を判定対象とする <= NPC レイヤーのゲームオブジェクトだけではなく、TreasureBox レイヤーのゲームオブジェクトにも反応する
    RaycastHit2D hit = Physics2D.Raycast(rb.position, lookDirection, 1.0f, LayerMask.GetMask(actionlayerMasks));



5.<処理を読み解いてコメントを書く>


 上記の情報を元に、PlayerController スクリプトの Action メソッドを読み解いて、コメントを記述してみてください。

<Action メソッド内の処理のコメント例>

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


 コメントはこの通りである必要はありません。分岐の状態や、呼び出されるメソッドがどのように処理されているのかを理解しておいてください。

 取得した情報は RaycastHt2D 型の hit 変数に代入されます。この時点では、NPC レイヤーと TrasureBox レイヤーのうち、
どちらの情報を持つコライダーが hit 変数に代入されているかわかりませんので、新しく分岐を追加して、コライダーを持つゲームオブジェクトにアタッチされている
コンポーネントの種類を見て、情報を分岐するようにしています。

 NonPlayerCharacter クラスがコンポーネントとしてアタッチされている場合には、NPC との会話イベントを、
TreasureBox クラスがコンポーネントとしてアタッチされている場合には、探索用の会話イベントというように、1つのボタンの操作を自動的に分岐して
処理の内容を変更する制御を行っています。


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


 準備が整いましたので、ゲームオブジェクトを実行して、宝箱のゲームオブジェクトに向かって行動ボタンを押してみてください。
また、宝箱ゲームオブジェクトにはコライダーがありますので、この中に侵入してしまわないかも確認しておいてください。

 Debug.Log に "探索イベント用の会話ウインドウを開く" と表示されれば、レイヤーによる自動分岐による制御成功です。
もう一度ボタンを押して "探索イベント用の会話ウインドウを閉じる" と表示されれば制御成功です。
この間はキャラの移動ができないことも確認しておいてください。

 まだ会話ウインドウは開く処理は実行していませんので、ウインドウは開きません。
処理の流れを把握して、現在までにどのような実装が済んでおり、どこが残っているのかを正確に把握しておきましょう。


<実装動画>
動画ファイルへのリンク


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

 次は 手順25 ー探索イベントの実装ー です。