Unityに関連する記事です

 この手順も引き続き、画面の演出・エフェクト関連の実装を行っていきます。

 拠点のダメージエリアにエネミーが侵入した際にエネミーのぶつかった位置にヒット演出用のエフェクトを生成する処理を追加します。


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


<実行動画◆
動画ファイルへのリンク


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

手順23 −エネミーによるヒット・エフェクトの実装◆
47.Canvas 内にエフェクト等の一時オブジェクトの格納用ゲームオブジェクトである TemporaryObjectContainer ゲームオブジェクトを作成し、TransformHelper スクリプトを作成する。GameManager スクリプトを修正する
48.DefenseBase スクリプトを修正して、各エフェクトを生成後、 TemporaryObjectContainer ゲームオブジェクトと親子関係にする
49.TransformHelper スクリプトにプロパティを追加し、GameManager スクリプトと DefenseBase スクリプトを修正する



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

 ・static クラスと static 変数の宣言
 ・static メソッドの利用方法
 ・プロパティ



47.Canvas 内にエフェクト等の一時オブジェクトの格納用ゲームオブジェクトである TemporaryObjectContainer ゲームオブジェクトを作成する

1.設計


 拠点にエネミーが侵入した際にエフェクトを生成する処理については実装が出来ました。
現在はゲーム画面の中央位置にエフェクトが生成されていますので、次は拠点の、エネミーが侵入してきた位置に生成する処理を実装します。

 エネミーが侵入した位置については、DefenseBase スクリプトの OnTriggerEnter メソッドの Collider 変数にある情報を利用すれば取得できます。
こちらを利用してエフェクトの生成位置をエネミーが侵入した位置に変更します。



 設計に際して、大事な注意点があります。
そのままエネミーが侵入した位置の情報を利用すると、次のようにエフェクトが生成されない状態になりやすいです。


<エフェクトが生成されない例>
動画ファイルへのリンク

 このようになってしまう多くの理由は、エネミーの位置情報を利用する際に、エネミーとエフェクトが親子関係になってしまっているためです。
親子関係にあるゲームオブジェクトは、親オブジェクトが破壊されると、子オブジェクトも一緒に破壊されます

 拠点に侵入したエネミーは破壊される処理になっていますので、もしも生成されたエフェクトがエネミーと親子関係になっている場合、
エフェクトは生成されているものの、エネミーと一緒に破壊されてしまいます

 以前に修正した、バレットがエネミーに接触した際にエフェクトが一緒に消えてしまっていた問題と同じ種類の問題になります。



 こちらを改善するために、今回の手順では生成されたエフェクトを管理するためのゲームオブジェクトを Canvas ゲームオブジェクト内に作成しておき、
生成されたエフェクトをこのゲームオブジェクトの子オブジェクトとして親子関係にします。

 ポイントは、エフェクトが生成されてから親子関係にすることです。
エフェクトをこの管理用のゲームオブジェクトの子オブジェクトとして生成してしまうと、エフェクトの生成位置自体がこの管理用のゲームオブジェクトの位置になってしまいます。

 生成する位置はあくまでもエネミーが拠点に侵入した位置にしておき、生成されたあとに管理用のゲームオブジェクトの子オブジェクトにする、という処理の順番が大切になります

 最初に管理用のゲームオブジェクトを作成し、このゲームオブジェクトの情報を取得するための TransformHelper スクリプトを新しく作成します。
親子関係に必要な情報は Transform コンポーネントですので、この情報を TransformHelper スクリプトが管理するようにします。
その後、GameManager スクリプトを修正して、ゲーム全体で管理用のゲームオブジェクトの位置情報を利用できるように TransformHelper スクリプトへの処理を追加します。


2.Canvas ゲームオブジェクトの子オブジェクトとして TemporaryObjectContainer ゲームオブジェクトを作成する


 ヒエラルキーの空いている場所で右クリックをするか、ヒエラルキーの下にあるプラスボタンを押してメニューを開きます。
Create Empty を選択し、空のゲームオブジェクトを1つ作成します。名前を TemporaryObjectContainer に変更してください。
TemporaryObjectContainer ゲームオブジェクトは、エフェクトなどの、Canvas 内に一時的に生成されるゲームオブジェクトを管理するフォルダ役のゲームオブジェクトです。


ヒエラルキー画像



 TemporaryObjectContainer ゲームオブジェクトを選択して、RectTransform コンポーネントがアタッチされているか確認します。
Canvas ゲームオブジェクトに含まれるオブジェクトは Transform コンポーネントではなく、RectTransform コンポーネントによって位置情報を管理しています。
もしも通常の Transform コンポーネントがアタッチされている場合には、再度作り直してください。
Canvas ゲームオブジェクト内にあっても Transform コンポーネントの場合には処理が上手くいきません。

 RectTransform コンポーネントの Position を確認し、0 以外になっていたら、Reset を実行して、すべての Position の値を 0 に戻してください


インスペクター画像



Sceneビュー画像



 以上でこのゲームオブジェクトは完成です。
今回の実装例では使用しませんが、必要に応じて Tag の設定などを行って利用してください。


3.TransformHelper スクリプトを作成する


 どのスクリプトからでも TemporaryObjectContainer ゲームオブジェクトの位置情報を参照できるようにするために、TransformHelper スクリプトを作成します。
親子関係に必要な情報は Transform コンポーネントですので、この情報を TransformHelper スクリプトが管理するようにします。



 このスクリプトは static(静的) クラスで作成を行います。
そのため通常作成するクラスとは異なり、MonoBehaviour クラスを継承することが出来ませんので、ゲームオブジェクトにアタッチすることが出来ないクラスになります。

 ではどうやって利用するのか? ということですが、static クラスのスクリプトは、ゲーム実行と同時にインスタンスが作られるようになっていますので、
ゲームオブジェクトに依存することなく、いつでも命令を出すことができるクラスになります。



 以上のような特性から、static クラスは、ゲームオブジェクトにアタッチすることなく、他のスクリプトからも参照することができるクラスになります。
そのため、本来であれば、ゲームオブジェクトを探して、そのゲームオブジェクトにアタッチされているコンポーネントやスクリプトを探して変数に代入して、
それから、その変数を利用してメソッドや変数の情報を利用する、という手順が必要なく、変数への代入不要で利用できるクラスになります。

 他のクラスと大きく異なる点は、static クラスは複数のインスタンスを作成することができません
また、自動的にインスタンスが生成されるという特性から、new キーワードを利用したクラスのインスタンスも作れません。

 逆に言うと、常にインスタンスが1つしかないことが保証されるため、この特性から、どのスクリプトからでも変数に代入せずに「これ」と指定することが出来ています。

 多くのスクリプトやコンポーネントは、複数のゲームオブジェクトにアタッチされている可能性があります。
例えば、EnemyController スクリプトであれば、ゲーム内に EnemySet ゲームオブジェクトが 5 つあれば、EnemyController スクリプトも 5 つ存在していることになります。
そのため、EnemyController スクリプトに対して命令を行うためには、どのゲームオブジェクトのどの EnemyController というように指定をする必要があります。

 static クラスは複数存在することはありません。そのため今回の場合であれば、 TransformHelper と static クラスを指定をすれば、すなわち常に1つしかないクラスを指し示すことができるのです。
そのため、変数に代入して利用をするという手順が不要で、どのスクリプトからでも命令を行えるようになっています。


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


 static クラスの TransformHelper スクリプトを作成します。
作成方法はいつもと同じです。エディターが開いたら、いつも継承している 「: MonoBehaviour」の部分を削除してください。

 クラス名に static を付けて宣言を行います。また static クラスの中に記述する変数名、メソッド名、プロパティの各部分にも static を記述します
他は通常のスクリプトと変わりません。

 Transform 型の temporaryObjectContainerTran 変数が、Canvas 内に作成した TemporaryObjectContainer ゲームオブジェクトの Transform コンポーネントの情報を代入する変数になります。
ただし、TransformHelper スクリプトはゲームオブジェクトにアタッチすることができません。そのため、インスペクターに変数を表示して事前にアサインしたりできません

 そのため今回は2つのメソッドを用意してあります。
1つは、外部のスクリプトから呼び出してもらって、Transform コンポーネントの情報を受け取るための SetTemporaryObjectContainerTran メソッドです。
引数に Transform 型を宣言しているので、呼び出し命令の時、Transform 型の情報をメソッドに送ってもらうことが出来ます。
これを GameManager スクリプトにおいて実行し、GameManager スクリプトに取得しておいた TemporaryObjectContainer ゲームオブジェクトの Transform の情報を取得するようにします。

 もう1つは、他のスクリプトから temporaryObjectContainerTran 変数の情報が欲しい場合に呼び出す GetTemporaryObjectContainerTran メソッドです。
このメソッドを実行することで、他のスクリプトから自由に temporaryObjectContainerTran 変数の情報を利用できます。

 今回エネミーが拠点に侵入した位置にエフェクトが生成されたら、この temporaryObjectContainerTran 変数の情報を取得する GetTemporaryObjectContainerTran メソッドを外部のスクリプトから実行して
管理用のゲームオブジェクトである TemporaryObjectContainer ゲームオブジェクトの Transform の情報を取得して、親子関係を構築する際に利用します


TansformHelper.cs

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


 スクリプトを作成したらセーブします。
このスクリプトは、ゲームを実行すると自動的に実体化(インスタンス)がされて、どのスクリプトからでも利用できる状態になります。


4.GameManager スクリプトを修正する


 TransformHelper スクリプトに作成した temporaryObjectContainerTran 変数が、管理用のゲームオブジェクトの Transform 情報を管理する変数になります。
そのため、この情報を参照すれば、どのスクリプトからでも Canvas 内にある TemporaryObjectContainer ゲームオブジェクトの Transform の情報を利用できます。

 ですがこの temporaryObjectContainer 変数にはまだ代入処理が行われていないため、Transform の情報は空っぽの状態です。
また、前述の通り、static クラスである TransformHelper スクリプトはゲームオブジェクトにアタッチすることができないため、
SerializeField 属性などを利用して、TemporaryObjectContainer ゲームオブジェクトの情報をインスペクターから事前にアサインすることもできません。

 代わりに、GameManager スクリプトに SerializeField 属性の Transform 型の変数を用意しておいて、
GameManager スクリプトが TemporaryObjectContainer ゲームオブジェクトの情報をインスペクターから事前にアサインしておきます

 そして、GameManager スクリプトの Start メソッドで、TransformHelper スクリプトに用意した SetTemporaryObjectContainerTran メソッドを実行し、
TransformHelper スクリプトに用意した temporaryObjectContainerTran 変数に、GameManager スクリプトに用意した Transform の情報を渡すようにロジックを組みます。

<処理の流れ>
 1.GameManager スクリプトに SerializeField属性の Transform 型の temporaryObjectContainerTran 変数を作成する
 2.GameManager ゲームオブジェクトのインスペクターより、事前に TemporaryObjectContainer ゲームオブジェクトの Transform をアサインして登録しておく
 3.GameManager スクリプトの Start メソッドで、TransformHelper スクリプトの SetTemporaryObjectContainerTran メソッドを実行する
   引数として、Transform 型の temporaryObjectContainerTran 変数を渡す
 4.TransformHelper スクリプトの SetTemporaryObjectContainerTran メソッドに GameManager スクリプトから Transform 型の temporaryObjectContainerTran 変数が届くので
   用意しておいた Transform 型の temporaryObjectContainerTran 変数に代入する
 5.以上の手順で TransformHelper スクリプトの temporaryObjectContainerTran 変数には  TemporaryObjectContainer ゲームオブジェクトの Transform の情報が代入されたので
   TransformHelper スクリプトの GetTemporaryObjectContainerTran メソッドを実行すれば、どのスクリプトからでも TemporaryObjectContainer ゲームオブジェクトの Transform の情報を利用できる

 どの処理と、どの処理がつながっているのかを、しっかりと追いかけながら処理を記述してください


GameManager.cs

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


 スクリプトを修正したらセーブします。
GameManager ゲームオブジェクトのインスペクターを確認し、新しく SerializeField 属性で宣言した変数が表示されているか確認しておきます。


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




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


 GameManager ゲームオブジェクトを選択し、インスペクターより GameManager スクリプトの変数に必要な情報をアサインして登録します。
どのゲームオブジェクトにアタッチされている情報が必要か考えて、ドラッグアンドドロップしてアサインしてください。


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



 以上で設定は完了です。


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


 ゲームを実行して、Console ビューに TransformHelper スクリプトの SetTemporaryObjectContainerTran メソッド内に記述した
Debug.Log メソッドの内容が表示されれば制御成功です。


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


Consoleビュー



 TransformHelper スクリプトの temporaryObjectContainerTran 変数の情報は、どのスクリプトからでも変数の参照を行うことなく自由に利用できます。
この処理を活用して、エネミーが拠点に侵入したとき、エネミーの位置でエフェクトを生成する処理を実装します。
 


48.DefenseBase スクリプトを修正して、エフェクトを生成後、 TemporaryObjectContainer ゲームオブジェクトと親子関係にする

1.設計


 管理用のゲームオブジェクトと、それを参照するための処理が完成しました。
この管理用のゲームオブジェクトを利用することで、エネミーが拠点に侵入した位置にエフェクトを生成するように制御処理を実装します。
 
 エネミーの侵入した位置にエフェクトを生成後、エフェクトをエネミーの子オブジェクトから管理用のゲームオブジェクトである
TemporaryObjectContainer ゲームオブジェクトと親子関係する(エフェクトが子オブジェクト)ことで、生成されたエフェクトがエネミーと一緒に破壊されるのを回避します

 エフェクトの生成処理は DefenseBase スクリプトの GenerateEnemyAttackEffect メソッド内において実装されていますので、
こちらの生成位置の指定をエネミーの侵入した位置に変更することで、エネミーが拠点に侵入した位置にエフェクトを生成することが出来ます。
ただし、このエネミーの侵入した位置の情報は、 OnTriggerEnter メソッド内でしか利用できない情報になっていますので、
GenerateEnemyAttackEffect メソッドに Transform 型の引数を追加して、OnTriggerEnter メソッド内に取得されているエネミーの Transform の情報を送ってもらう必要があります。

 エフェクトが生成されたら、そのあとに、先ほど作成した管理用のゲームオブジェクトの Transform を利用して
エフェクトと管理用のゲームオブジェクトを親子関係にします。

 管理用のゲームオブジェクトの Transform の情報は TransformHelper スクリプトの GetTemporaryObjectContainerTran メソッドを実行することで
戻り値として取得できますので、こちらを実行して親子関係の情報として利用します。


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


 設計を参考に処理を記述してみましょう。
管理用のゲームオブジェクトの Transform の情報は、スクリプトに

  TransformHelper.GetTemporaryObjectContainerTran() 

と記述することで実行できます。実行にあたり、変数はいりません。

 この戻り値の使い方がポイントになります。
戻り値を持つメソッドは、戻り値の型が取得できる情報になります。
つまり、TransformHelper.GetTemporaryObjectContainerTran() を実行すると、処理の実行結果は Transform 型の情報になります。
処理の見た目に惑わされないように、この戻り値を活用しましょう。


DefenseBase.cs

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


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


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


 スクリプトを修正が済んだらゲームを実行して、エフェクトの生成される位置を確認してください。
いままで画面の中央位置に生成されていたエフェクトが、エネミーが侵入した位置になっていれば制御成功です。


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


 次は検証を行います。


4.親子関係を構築せずに実行して動作を確認する


 実際に、エネミーが破壊されてしまうとエフェクトも一緒に破壊されてしまうのか、検証を行いましょう。

 先ほど修正した DefenseBase スクリプトの GenerateEnemyAttackEffect メソッド内の下記の処理をコメントアウトしてください。
この部分が実行されない場合、生成されたエフェクトのゲームオブジェクトは、enemyTran 変数のゲームオブジェクトの子オブジェクト、
つまり、EnemySet ゲームオブジェクトの子オブジェクトとして生成されたままになります。

DefenseBase.GenerateEnemyAttackEffect メソッド
  // 生成されたエフェクトを TemporaryObjectContainerTran の子オブジェクトにする(引数に GetTemporaryObjectContainerTran メソッドの戻り値を利用)
 enemyAttackEffect.transform.SetParent(TransformHelper.GetTemporaryObjectContainerTran());

 ゲームを実行して、エフェクトの生成を確認してください。
親子関係を構築していないと、エネミーが拠点に侵入してもエフェクトが生成されなくなります


<検証動画>
動画ファイルへのリンク


 実際には、エネミーが拠点に侵入した際にエフェクトは生成されるものの、それがエネミーの子オブジェクトとして生成されるので、
そのあとにエネミーが破壊されると一緒に、エフェクトも破壊されている状態になっています。

 つまり今回実装している、エフェクトのゲームオブジェクトを別のオブジェクトと親子関係にしているのは
エネミーが破壊されたときに一緒にエフェクトが破壊されてしまうことを防ぐ処理になっています。


49.TransformHelper スクリプトにプロパティを追加し、GameManager スクリプトと DefenseBase スクリプトを修正する

1.<プロパティを学習する>


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

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

<プロパティ>
    private static Transform temporaryObjectContainerTran;


    /// <summary>
    /// temporaryObjectContainerTran 変数のプロパティ
    /// </summary>
    public static Transform TemporaryObjectContainerTran
    {
        set {
            temporaryObjectContainerTran = value;
        }

        get {
            return temporaryObjectContainerTran;
        }
    }

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

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

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

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



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

  get {
      return temporaryObjectContainerTran;
  }

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

  TransformHelper.TemporaryObjectContainerTran



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

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

  set {
      temporaryObjectContainerTran = value;
  }

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

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

  TransformHelper.TemporaryObjectContainerTran = "代入して書き換えたい値";



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


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

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


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


 TransformHelper スクリプトに、プロパティを追加します。
今回プロパティを追加する TransformHelper クラスは static クラスであるため、プロパティも static として宣言する必要があります。
理由は先ほども説明したように、static クラスの中で宣言する変数やメソッド、そしてプロパティなどの情報は、すべて static で定義するというルールがあるためです。


TransformHelper.cs

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



3.GameManager スクリプトを修正する


 ◇,良分をコメントアウトし、◇△良分を追加します。

GameManager.cs

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



4.DefenseBase スクリプトを修正する


 ◇,良分をコメントアウトし、◇△良分を追加します。

DefenseBase.cs

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


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


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


 ゲームを実行して、以前と同じようにエフェクトがエネミーが侵入した位置に生成されるか、動作をするか確認します。
プロパティによる実装も、メソッドの戻り値による実装も処理の動作的には同じものになります。
どちらで実装していただいても構いません。

 static クラス、プロパティ機能やメソッドの戻り値の利用方法などについて、しっかりと復習をしておいてください。


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

 次は 手順24 −エネミーの生成完了状態とボスの討伐状態の追加とゲーム終了判定の実装− です。

このページへのコメント

ご報告ありがとうございます。
リンク先修正させていただきました。

0
Posted by  orika_ex_miyako orika_ex_miyako 2021年07月06日(火) 12:20:46 返信

リンク切れ報告

FEnetインフラ様 テックブログ
C#のプロパティを使いこなそう!さまざまな実装方法を紹介

1
Posted by L 2021年02月23日(火) 23:33:24 返信

コメントをかく


「http://」を含む投稿は禁止されています。

利用規約をご確認のうえご記入下さい

Menu



プログラムの基礎学習

コード練習

技術/知識(実装例)

2Dおはじきゲーム(発展編)

2D強制横スクロールアクション(発展編)

3Dダイビングアクション(発展編)

2Dタップシューティング(拡張編)

レースゲーム(抜粋)

2D放置ゲーム(発展編)

3D脱出ゲーム(抜粋)

2Dリアルタイムストラテジー

2Dトップビューアドベンチャー(宴アセット使用)

3Dタップアクション(NavMeshAgent 使用)

2Dトップビューアクション(カエルの為に〜、ボコスカウォーズ風)

3Dトップビューアクション(白猫風)

VideoPlayer イベント連動の実装例

VideoPlayer リスト内からムービー再生の実装例(発展)

AR 画像付きオブジェクト生成の実装例

AR リスト内から生成の実装例(発展)

private



このサイト内の作品はユニティちゃんライセンス条項の元に提供されています。

管理人/副管理人のみ編集できます