i-school - 3Dスライダーゲーム 手順18
 通過すると得点を獲得できるゲームオブジェクトを作成します。
この手順ではサークルに侵入した際の通過の判定と、その際に得点を加算する処理を実装します。





<実装動画>
https://gyazo.com/2ce61d2b5353e64f0b80630ec9cdcaec


手順18 −キャラとサークルの接触判定を実装−
31.スクリプトを使って、キャラがサークルを通過した際の判定を実装する
32.スクリプトを使って、サークルを通過した際に得点を加算する処理を実装する



新しく学習する内容


 ・TryGetComponent()メソッドとoutキーワード宣言
 ・メンバ変数に用意していない型への処理を実行していく方法


31.スクリプトを使って、キャラがサークルを通過した際の判定を実装する

1.設計


 どのように設計ロジックを考えていけばよいか考えてみましょう。

 サークルをくぐるとは?
   => サークルとキャラとのコライダー間で侵入判定が行えれば確認が取れる 
     サークルとキャラにはコライダーがあるので、サークルのコライダーを侵入可能なコライダー(IsTrigger)として設定する。
     このサークルのコライダーに対して、キャラのコライダーとが侵入・通過すれば、「くぐった」判定を行える 

 こちらの制御を実装するためには、サークルとキャラのゲームオブジェクトのコライダーのアタッチと、新しくサークル用のスクリプトの作成が必要になります。
どちらのゲームオブジェクトもすでにコライダーのアタッチは完了していますので、ここでは、サークル用のスクリプトの作成が必要になります。



 サークル用のスクリプトには次のような情報が必要になります。

 ・サークルのゲームオブジェクトの中央部(くぐれる空間)にコライダーを設置してあるので、キャラが侵入した場合に OnTriggerEnter メソッドを使って侵入判定を行えるようにする
 ・新しくサークル用のスクリプトを作成して、OnTriggerEnter メソッドを用意し、その内に Tag による分岐を作成し、Player の Tag を持つコライダーに侵入したら「侵入・通過」と判定できるように処理を記述する

 この2点です。

 なお今回はこのようにサークル側に侵入判定を用意する設計にしていますが、キャラ側(PlayerController)に侵入判定を用意しても構いません
設計方法は自由ですので、考えついた方法を実装していってください。


2.Circle スクリプトを作成する


 設計の情報を元に、スクリプトのロジックを考えて処理を書いてみましょう。
コメントとして日本語で処理を書いておいてから、プログラム化していくとロジックや処理の流れが見えやすくなります。


Circle.cs

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



3.Circle ゲームオブジェクトに Circle スクリプトをアタッチする


 作成した Circle スクリプトをヒエラルキーにある Circle ゲームオブジェクトにドラッグアンドドロップしてアタッチします。
アタッチしたらインスペクターを確認して、アタッチが完了しているかを確認します。

 インスペクターで設定する項目はありませんので、アタッチが出来ていれば問題ありません。


Circle ゲームオブジェクト インスペクター画像



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


 ゲームを実行して、キャラをサークルに侵入させてください。
Circle スクリプトの OnTriggerEnter メソッドが実行されて、Console ビューに Debug.Log メソッドで設定した "キャラ侵入" の文字列が表示されれば制御成功です。


Console ビュー画像



<実行動画>
https://gyazo.com/12968e827dc40661943a730f5ec8f487


 もしも表示されない場合には、Circle ゲームオブジェクトのコライダーの大きさを確認してください。
また、コライダー内にキャラが侵入できない場合には、Circle ゲームオブジェクトの BoxCollider の IsTrigger にチェックが入っているか確認してください。

 次の手順では、キャラの侵入時に、得点を加算する処理を実装します。

 ロジックを考えたら、一度にすべての処理を実装するのではなく、少しずつイメージしている処理に近づくように細かい実装を積み重ねていくことが大切です


32.スクリプトを使って、サークルを通過した際に得点を加算する処理を実装する

1.設計


 Circle スクリプトの OnTriggerEnter メソッド内の処理が無事に動作しましたので、このメソッド内に処理を追加していきます。

 設計としては、次のようなロジックを考えています。

 ・PlayerController スクリプトに、得点の管理用の int 型の score 変数を用意する
 ・PlayerController スクリプトに、得点の加算用の AddScore メソッドを public 修飾子で用意する
 ・AddScore メソッドには int 型の引数を用意し、受け取った値を score 変数に加算処理を行う。引数を int 型にしているのは score 変数に加算できるようにするため
 ・Circle スクリプトには、得点の情報として int 型の point 変数を追加する。public 修飾子で宣言することでインスペクターから設定ができるようにしておく
 ・また OnTriggerEnter メソッドに処理を追加して、キャラにアタッチされている PlayerController の情報を取得して変数に代入して利用できる状態にする
 ・サークルのゲームオブジェクトにキャラが侵入したら、Circle スクリプトの OnTriggerEnter メソッド内の処理を実行し、PlayerController の情報を取得する
 ・取得した PlayerController の情報を使って、PlayerController に用意した AddScore メソッドを実行する。これを行うために、AddScore メソッドは public 修飾子にしてある
 ・AddScore メソッドを実行する際には int 型の引数情報が必要になるので、 Circle スクリプトに用意した int 型の point 変数の値を引数として渡す
 ・引数を利用することで、Circle スクリプトの得点の情報を PlayerController 側に届くようになる
 ・PlayerController では AddScore メソッドが実行され、引数として point 変数の値が届いている。score 変数に、Circle スクリプトから渡ってきた point 変数の値を加算していく

 一連の処理のイメージが沸きますでしょうか。

 この手順を参考にしながらコメントで書いてみて、ロジック化してプログラムを書いてみてください。


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


 int 型の score 変数を宣言します。private 修飾子で問題ありません。この変数を得点の管理用に利用します。

 新しく AddScore メソッドを作成し、引数を int 型で用意しておきます。こうすることで、このメソッドを実行する際には int 型の値を渡すことが必要になります。
AddScore メソッド内では引数で受け取った値を score 変数に加算していく処理を実装します。

 score 変数は private 修飾子ですので、処理が実行されても加算された値がわかりません。
そのため、加算後に Debug.Log メソッドを用意して、Console ビューに score 変数の値を表示するようにします。
これで、処理が実行されている確認と、現在の score 変数の値の、両方の確認が行えるようになります。

 Debug.Log メソッドを有効に活用しましょう。

 なお、今回は1つのスクリプト内に、点数を管理するための変数と、加算する処理を行うメソッドを用意していますが、
新しいスクリプトを作成して、そちらで点数の管理と加算の処理を行うようにしても構いません。
各スクリプトの役割を明確化しておくことで、スクリプトの処理の肥大化を防ぐことにもつながります。チャレンジしてもよいでしょう。



PlayerController.cs

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




 スクリプトを修正したらセーブを行い、PlayerController スクリプトがアタッチされている Penguin ゲームオブジェクトのインスペクターを確認します。
新しく宣言した変数は private 修飾子であるので表示されませんので、いままで同じ情報のみ表示されていれば問題ありません。


Penguin ゲームオブジェクト インスペクター画像



3.Circle スクリプトを修正する(タグによる判定をやめる)


 int 型の point 変数を宣言し、得点という情報をこのスクリプトに持たせます。
public 修飾子で宣言しておくことで、インスペクターから任意の得点の設定を可能にしておくと便利です。
int 型を利用しているのは、得点を管理している PlayerController スクリプトにある score 変数の型が int 型であるためです。

 新しい情報を用意する場合には、その変数がどの部分で利用されるのかを考えて、必要な形で設計をします



 重要な変更点として、OnTriggerEnter メソッド内に処理を修正し、現在のキャラが侵入したら、そのゲームオブジェクトのタグによって判定を行う処理から、
侵入したゲームオブジェクトにアタッチされている指定されたスクリプト(ここでは PlayerController)を取得できるかを判定する処理に変更します。

 得点の情報を管理しているのは、Penguin ゲームオブジェクトではなく、そこにアタッチされている PlayerController が管理を行っています。
そのため、例えばタグによってゲームオブジェクトの判別が出来たとしても、その後に再度、そのタグによって判別されたゲームオブジェクトに対して、
指定されたスクリプト(PlayerController)を取得することができるかを判定する必要があります。

 つまり、タグによる処理の場合、そのあとの処理につなげていく際に、再度、判定の処理を行うことになるため、重複判定をすることになります。
それは無駄な処理になってしまうため、今回はタグの処理を止め、代わりに、スクリプトを取得できるかを判定し、
スクリプトを取得できた場合にのみ、次の処理へとつながるようなロジックを作って処理を組み上げています。
これには新しい TryGetComponent メソッドを活用しています。

 この処理に変更することにより、現在用意しているタグによる判定処理の代わりにもなりますので、タグの判定部分を削除することが出来ます。
 
 PlayerController が取得出来た場合には変数に代入して、PlayerController スクリプトを参照したり、命令を出せるようにします。
そして、public 修飾子で宣言している PlayerController スクリプトの AddScore メソッドを実行(呼び出)します。

 AddScore メソッドを実行するには int 型の引数を用意する必要がありますので、今回宣言を追加した int 型の point 変数を渡します。
この処理によって、スコアの加算処理とともに、どの値を加算するのか、という情報も一緒に PlayerController 側に届くようになります。



Circle.cs

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


 スクリプトの修正が終了したら、セーブして、Circle ゲームオブジェクトのインスペクターを確認します。
新しく public 修飾子で宣言した変数が表示されていれば問題ありません。

 point 変数の初期値は 0 ですので、任意の値を設定してください。この値がサークルを通過した際に獲得できる得点になります。


Circle ゲームオブジェクト インスペクター画像(初期値時)



4.<TryGetComponent()メソッドとoutキーワード宣言>


 Unity2019.2以降に追加されたメソッドです。処理結果として bool 型で戻り値を返してくれます
このときの処理結果というのは、指定したコンポーネントの型の取得を行い、それが取得できれば true、取得できなければ false が戻ります

 またoutキーワードによる宣言がありますので、trueの場合には必ず、このoutの後に宣言した変数内に型が代入されます。
 outキーワード宣言を行うと、outを付けた引数で指定した変数はメソッド内で必ず結果が入ることが保証されるものです

 この処理を活用することにより、タグを利用した if 文による評価処理の代替処理が作成可能になります。

  // col.gameObject(つまり、Penguin ゲームオブジェクト)に対して、TryGetComponent メソッドを実行し、PlayerController クラスの情報を取得できるか判定する
 if (col.gameObject.TryGetComponent(out PlayerController player)) {

      // この中の処理は、PlayerController クラスが取得できた場合のみ実行される。
      // その場合、player 変数を利用することで PlayerController クラスを参照できる
  }

 今回はこのような処理として利用しています。
Collider 情報から gameObject(つまりキャラです)へとアクセスし、その gameObject に対して TryGetComponent メソッドを実行しています

 out キーワードの後には PlayerController 型と player という変数を用意しておきます。

 もしもこの TryGetComponent の結果が true であるならば、out として用意した player 変数に PlayerController コンポーネントの情報が代入された上で、if文内に処理が入ります
また if 文内の間はこの player 変数が使用できることになります。(player 変数のスコープが if 文ブロック内であるためです)

 TryGetComponent メソッドの結果が false の場合には PlayerController コンポーネントの取得ができなかったため false が結果として戻り、この if 文は処理されないままで終了します

 なおTryGetComponentメソッドには複数の書式があります。こちらは下記のリファレンスを参照してください。


参考サイト
Unity公式スクリプトリファレンス
Component.TryGetComponent
https://docs.unity3d.com/ScriptReference/Component...


5.<メンバ変数に用意していない型への処理を実行していく方法>


この方法は大変重要な処理の考え方です。

 スクリプトでは、外部のスクリプトの情報を取得し、変数に代入することで、そのスクリプトへの参照や、メソッドを実行することが出来るようになります。
外部のスクリプトの情報は、メンバ変数で用意しておく以外にも、スクリプト内で動的(ゲームが始まってから、特定のタイミングで)取得し、利用していくことも可能になっています。

 外部のスクリプトの情報を取得した場合、そのスクリプトの扱っている public 修飾子の情報が扱えるので、
public 修飾子の変数の情報を参照して利用したり、public 修飾子のメソッドを呼び出すことが可能になります


 if (col.gameObject.TryGetComponent(out PlayerController player)) {

   // PlayerController クラスが取得出来ている場合、player 変数を通じて PlayerController クラスに記述されている public 修飾子の AddScore メソッドを呼び出す命令をする
    // 引数には point 変数の値を渡す
    player.AddScore(point);
  }

 if 文において col.gameObeject 変数(Cirlce ゲームオブジェクトのコライダーに侵入してきた、外部のコライダーを持つゲームオブジェクト。つまり、Penguin ゲームオブジェクト)に対して
TryGetComponent メソッドを実行し、引数において指定した型(PlayerController 型)の取得を行います。

 TryGetComponent メソッドの結果、PlayerController 型が取得できている場合には true が戻り値として戻り、if 文の評価が成立します。
かつ、out キーワードがありますので、player 変数に、今回取得した PlayerController 型の情報が代入されます。
そして、if 文内においてのみ、player 変数を利用して、PlayerController に対して命令できるようになります。(player 変数のスコープは、if 文内のみです)

 このように、GameObject.Find メソッドを利用することなく、命令を出したいゲームオブジェクトの情報を OnTriggerEnter メソッドの引数から取得し、
加えて TryGetComponent メソッドを利用して、命令したい PlayerController スクリプトの情報を取得するように処理をつなげることで
PlayerController スクリプトに対して命令を出し、PlayerController スクリプト内の public 修飾子で定義されている AddScoreメソッドを実行することが出来ます。

 Unity では色々なゲームオブジェクトや、それにアタッチされているコンポーネントに対して命令を出すことで、ゲーム内に動きをつけています。
それを実現する方法が色々と用意されていますので、一からすべての処理を自作するのではなく、Unity の機能を覚えて、それを活用していくことで処理のロジックを構築することが出来ます。


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


 修正が完了しましたので、ゲームを実行してサークルにキャラを侵入させてみます。
Debug.Log メソッドが実行されて、Console ビューに Score 変数の値が更新されて表示されれば、スコアの加算処理の制御成功です。


Console ビュー画像



<実行動画>
https://gyazo.com/c21796b8e6e21199d1f1d8747c389f88


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

 次は 手順19 −スコアの表示制御− です。