Unityに関連する記事です

 引き続き、障害物について設計と実装を行います。

 人型の障害物は、徘徊タイプ、追跡タイプ、それらを組み合わせたものを検討します。
いずれのタイプも視界の概念を持ち、視界内にプレイヤーが侵入した場合に反応します。

 今回の設計では障害物のベースとなるスクリプトについて学習します。
視界については、次回以降で実装を行います。


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



<新しく学習する内容>
 ・クラスの継承
 ・abstract(アブストラクト) 修飾子による抽象クラスと抽象メソッドの宣言
 ・virtual(バーチャル) キーワード
 ・override(オーバーライド) キーワード クラスの継承時に利用できる、仮想(抽象)メソッドのオーバーライド処理 ー多態性(ポリモーフィズム)ー
 ・NavMeshAgent に用意されている変数とメソッド



設計


 今回の手順では、クラスの継承多態性という、オブジェクト指向プログラミングにおける重要な概念(原則)を利用した設計の学習を行います。
障害物という種類のゲームオブジェクトであれば、すべて共通する処理によって動作するように設計し、実装を行います。

 このような場合、障害物として持たせたい役割を明確化し、それを1つのクラスにまとめます。
例えば、プレイヤーと障害物との接触の判定、障害物に接触した場合の処理、などです。

 これは、いずれの障害物のゲームオブジェクトのクラスでも同様の処理を記述することが想定されますので、
共通する処理をまとめて1つのクラス(親クラス)に記述し、その親クラスを継承する形で、処理を共通化して実装するようにしていきます。

 イメージしやすいように、これから作成するクラスの関係を図に示します。

<クラス図>



参考サイト
MicroSoft
継承
https://docs.microsoft.com/ja-jp/dotnet/csharp/fun...



 最も最上段にあるクラスは ObstacleBase クラスです。一番のスタート地点になるクラスを親クラス、あるいは基幹クラス(スーパークラス)と呼びます。
このクラスは MonoBehaviour クラスを継承しています。

 ObstacleBase クラスは abstract 修飾子を用いた抽象クラスとして作成をしています。
抽象クラスは、それ単体ではインスタンスが作成できないクラスです。つまり、いずれかのクラスに継承させる前提で設計を行うクラスになります。

 ObstacleBase クラスでは、すべての障害物に必要な変数やメソッドを作成し、障害物としての役割を共通化させます。
そのため、個々の障害物に別途備えたい機能(変数やメソッド)については、この ObstacleBase クラスには定義しません。

 共通する処理が記述された ObstacleBase クラスを親クラスとして継承することで、個々の障害物用のクラスを子クラスとして作成します。

 設計と実装にあたり、全体の設計が完了したら、まずは継承元(親)となる ObstacleBase クラスから順番に作成していくことになります。

 ObstacleBase クラスを作成したら、それを継承させた MovingObstacle クラスを子クラスとして作成します。
実際にはこの MovingObstacle クラスが、障害物のゲームオブジェクトにアタッチするためのクラスになります。


障害物用クラスの親クラスを設計して実装する


 今回親となるクラスは MonoBehaviour を継承したクラスになります。これがすべての障害物の親となるクラスになります。
親クラスは通常通り、新しいC#スクリプトを作成していく手順で作っていきます。

 子クラスでのみ実装が必要な情報と、親クラスに実装して子クラス全体で共通化して利用したい情報とがありますので
まずはその切り分けを行って、共通化できるもののみを親クラスに用意します

 ・障害物の初期設定
 ・プレイヤーと障害物とが接触した際の処理

 この辺りは、共通化できる処理になります。

 ですが、

 ・障害物の効果を適用する
 ・エフェクトを生成する

 この2つについては、それぞれの障害物によって内容が異なる可能性が高いため、
親クラスに共通化して処理を作った上で、子クラスでそれぞれの障害物の内容に沿う内容に修正する必要があります

 このように切り分けた処理の実装が可能になることが、クラスの継承による大きなメリットになります。



 親クラスに記述する内容は、通常のクラスと同じように変数の宣言とメソッドの作成になりますが、記述する書式がいくつか変わります。

 変数やメソッドの宣言において private 修飾子を利用する部分には、代わりに protected 修飾子を使用します。
これは継承したクラス間でのみ使用できることを許可する修飾子です。
外部で利用したい変数やメソッドの場合には、通常通り public 修飾子を使用します。

 またメソッドの場合には、宣言時に virtual キーワードを記述します。こうすることで子クラスが上書き可能な、親クラスのメソッドとして成立します。

 親クラスで設定されたアクセス修飾子の情報は子クラスでも引き継がれます
例えば親クラスで public float x を作成していれば、それは子クラスでも public float x として扱われます
メソッドも同様です。


ObstacleBase スクリプトを作成する


 それでは実際に親クラスである ObstacleBase クラスを作成します。
今回は abstract 修飾子を活用した、抽象クラスという形式で作成を行っています。


ObstacleBase.cs

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



<abstract(アブストラクト) 修飾子による抽象クラスと抽象メソッドの宣言>


 abstract 修飾子をクラスの宣言に追加することで、抽象クラスを作成することができます。

<抽象クラス>
 /// <summary>
 /// 障害物共通用の抽象クラス
 /// </summary>
 public abstract class ObstacleBase : MonoBehaviour

 抽象クラスとはインスタンスの作成できない(new できない)クラスです。継承を行う前提で作成されているクラスのことを言います。
抽象クラス内には抽象メソッドを1つ以上宣言しておく必要があります。通常のクラスと同じように、private 修飾子や public 修飾子を使って変数やメソッドを実装することも出来ます。



 メソッドの宣言に abstract 修飾子を追加することで抽象メソッドを作成することができます。
これは中身のない(処理の実体のない)メソッドであるため、継承した派生(子)クラス内においてオーバーライドして利用することを前提として作成されているメソッドです。

<抽象メソッド>
    /// <summary>
    /// 初期設定
    /// </summary>
    public abstract void SetUpObstacle();

 抽象メソッドは抽象クラス内でしか作成できません
また抽象クラスを継承している子クラスでは必ず、すべての抽象メソッドをオーバーライドして実装する必要があります
抽象メソッドにより、メソッドには多態性が生まれます

 この機能を用いることによって、実装の仕方(メソッドの強制)を統一することができます。
つまり、ObstacleBase クラスを継承したクラスでは、必ず、抽象メソッドである SetUpObstacle メソッドと TriggerObstacle メソッドの2つを実装する必要がありますので、
この処理を通じて障害物の初期設定を行い、プレイヤーと障害物とが接触した後の処理について、すべての障害物において同じ処理の流れで制御していくという設計で実装できます

 実装した抽象メソッドでは、子クラスがそれぞれの処理を実装(確定)することになりますので、プログラムの多態性(ポリモーフィズム)を実現出来るようになっています。


<参考サイト>
MicroSoft
abstract
https://docs.microsoft.com/ja-jp/dotnet/csharp/lan...
++C++; // 未確認飛行 C 様
抽象メソッド、抽象クラス
https://ufcpp.net/study/csharp/oo_abstract.html
.NET Column 様
C#のabstract classとは?インターフェイスについても併せて解説!
https://www.fenet.jp/dotnet/column/language/c-shar...
XR-HU3 様
[第14回] 抽象クラス(abstract)の使い方を学ぶ|Unityで学ぶC#入門
https://xr-hub.com/archives/19842


<virtual(バーチャル) キーワード>


 親クラスにおいて定義したメソッドは、virtual (仮想) キーワードを一緒に宣言することで、派生(子)クラスにおいてオーバーライド処理をして利用することが許可されます。
この機能を有しているメソッドを仮想メソッドといいます。抽象メソッドと同じように、仮想メソッドにより、メソッドには多態性が生まれます。


<親クラスにある仮想メソッド>
    /// <summary>
    /// プレイヤーと障害物とが接触した時の処理
    /// </summary>
    public virtual void TriggerObstacle() {
        // TODO 残り時間を減らしたり、スタート地点へ戻すといったペナルティとなる処理を障害物ごとに書く
    }


 virtual キーワードのないメソッド(通常のメソッド)は仮想メソッドではないため、処理をオーバーライドすることは出来ません


<各キーワードの関係性>
 virtual => override できる

  abstract => override できる


 今回の ObstacleBase クラスには、抽象メソッドと仮想メソッドの両方が混在しています。
どちらも子クラスにおいて処理を実装するという部分では同じですが、抽象メソッドの方は、子クラス内に必ず定義して実装を行う必要があります

 SetUpObstacle メソッドを抽象(abstract)メソッドにしているのは、障害物の初期設定については、必ず子クラスに記述してくださいと強制したいためです。
TriggerObstacle メソッドを仮想(virtual)メソッドにしているのは、プレイヤーと障害物との接触判定については不要な子クラスもある可能性があるため、強制して実装したくないためです。

 
 自分で処理を作る場合には、まずはすべてを virtual メソッドにしておいて、その後、すべての障害物に利用しているメソッドがあるのであれば、
そのメソッドを abstract メソッドに変えていくようにしてみましょう。プログラムにおいては、試してみて、自分で体験してみることがとても学習になります。

MicroSoft
virtual (C# リファレンス)
https://docs.microsoft.com/ja-jp/dotnet/csharp/lan...


親クラスを継承した障害物用の子クラスを設計して実装する


 実際に移動する障害物用のクラスとして、MovingObstacle クラスの設計を行います。
このクラスは MonoBehaviour クラスの代わりに ObstacleBase クラスを継承させます
これによって親クラス ObstacleBase 、子クラス MovingObstacle という関連性が生まれます。

 クラスの継承は、クラス名の右側に : (コロン)を書き、その右側に継承させるクラス名を書きます

  public class MovingObstacle : ObstacleBase

 なお、ゲームオブジェクトにアタッチするのは子クラス(ここでは MovingObstacle )のみで大丈夫です。
継承元である親クラスを別途アタッチする必要はありません。親クラスを継承しているので、その継承元の機能も子クラスは有しているためです。

 Unity において利用する機会の多い MonoBehaviour クラスを継承しているスクリプト(新しくスクリプトを作成すると自動的に継承しているクラス)も、
MonoBehaviour というクラス自体はアタッチしていませんが、MonoBehaviour クラスの持つ Start メソッドなどが有効に動くのは、このクラスの継承という機能によるものです。
 


 クラスを継承した場合、継承している親クラス(今回であれば ObstacleBase クラス)の継承しているクラスも引き続きます
そのため、これから作成する MovingObstacle クラスは MonoBehaivour クラスと ObstacleBase クラスの2つのクラスを継承しているクラスになります。

 MonoBehaivour クラスが継承されていますので、Start メソッドや OnTrigger 〜 メソッドも利用できますし、
ゲームオブジェクトにアタッチして利用することも出来ます。実際にアタッチするクラスが MovingObstacle クラスのみでよい理由になります。


MovingObstacle スクリプトを作成する


 実装例を2つ提示します。

 1つは NavMeshAgent コンポーネントを利用して、障害物の移動を行わせるクラスです。
前回の手順で移動経路用の情報を Bake して NavMeshAgent を利用できる状態にしていますので、基本的にはこちらでの実装を考えてください。
 
 もう1つは NavMeshAgent コンポーネントではなくて、DOTween により、障害物の移動を行わせるクラスです。


<実装例 NavMeshAgent 利用する>


 using の宣言や、クラスの継承部分などに注意しながら記述してください。
どのような処理を実装しているのかを理解しながら学習することで、自分でスクリプトを作成する際に役立ちます。


MovingObstacle.cs

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



<override(オーバーライド) キーワード クラスの継承時に利用できる、仮想(抽象)メソッドのオーバーライド処理 ー多態性(ポリモーフィズム)ー>


 abstract キーワードによる抽象実装や、virtual キーワードによる仮想実装に対して実行できる機能です。
上記の2つのキーワードを持つメソッドに対して、上記部分を override に変更することで、処理を拡張したり、書き換えたりすることが出来ます。

 このとき、メソッドのアクセス修飾子を変更することは出来ません
例えば、protected virtual メソッドであれば、protected override メソッドのように、あくまでも同じアクセス修飾子での実装になります

<親クラスにある抽象メソッド>
    /// <summary>
    /// 初期設定
    /// </summary>
    public abstract void SetUpObstacle();

 ↓ 

<派生(子)クラスでのオーバーライド実装>
    /// <summary>
    /// 初期設定
    /// </summary>
    public override void SetUpObstacle() {
        if (TryGetComponent(out agent)) {
            StartCoroutine(MoveObstacleByAgent());
        }
    }

 オーバーライドという単語の持つ意味の通りで、この処理を行った親クラス側に記述されていた抽象/仮想メソッド内の処理は上書きます
特に親クラスに実装されている仮想メソッド内の処理も利用した上でオーバーライドしたい場合、base キーワードを利用します。こちらは次に説明します。

 親クラスにある virtual キーワード、あるいは abstract キーワードと override キーワードとは、1対1でつながっているイメージです。

 ここまでに実装している仮想(抽象)メソッドと、それを上書きするオーバーライド処理機能により、
同じメソッドであっても、処理の内容を自動的に変更することができます。よって、同じメソッドにもかかわらず、処理の振る舞い(内容)が自動的に変わることになります。

 このように、同じメソッドを実行した際に、異なるオブジェクトが異なる動作を行い、振る舞いを自動的に変化させる特性を多態性(ポリモーフィズム)といいます。

 クラスの継承と合わせて、オブジェクト指向プログラミングの重要な概念になります。
多くの記事がネット上にありますので、自分でも調べて、理解を深めていきましょう。


<参考サイト>
MicroSoft
override (C# リファレンス)
https://docs.microsoft.com/ja-jp/dotnet/csharp/lan...
MicroSoft
ポリモーフィズム
https://docs.microsoft.com/ja-jp/dotnet/csharp/fun...


<NavMeshAgent に用意されている変数とメソッド 


 NavMeshAgent クラスには多くの変数とメソッドが用意されています。
この機能を上手く活用することにより、AI による NavMeshAgent のルートの自動移動を実装することができます。

 NavMeshAgent 型をスクリプト内で宣言し、各変数やメソッドを利用するためには using UnityEngine.AI; の宣言が必要です。

1.NavMeshAgent.destination 変数

 NavMeshAgent 目標地点を設定することができる Vector3 型の変数です。
この情報をセットすることで、NavMeshAgent に目的地へ移動することを伝え、移動を開始させることができます。

<参考サイト>
Unity 公式スクリプトリファレンス
NavMeshAgent.destination
https://docs.unity3d.com/jp/current/ScriptReferenc...
Unity 公式マニュアル
NavMeshAgent に目的地へ移動することを伝える
https://docs.unity3d.com/ja/current/Manual/nav-Mov...


2.NavMeshAgent.speed 変数

 NavMeshAgent によるルート移動時の最大速度の設定値です。0 にすることで移動は停止します。


<参考サイト>
Unity 公式スクリプトリファレンス
NavMeshAgent.speed
https://docs.unity3d.com/jp/current/ScriptReferenc...


3.NavMeshAgent.velocity 変数

 NavMeshAgent による現在の移動速度を示します。この値は Vector3 型であるため、Vector3 型にふくまれている sqrMagnitude 変数も扱うことが出来ます。
目的地点に向けた移動時の衝突回避情報や加速度の制御などを加味した上での移動速度を自動的にシミュレートして計算されて設定されます。

 移動を停止しているときは、 velocity の各値は 0 になります。

<参考サイト>
Unity 公式スクリプトリファレンス
NavMeshAgent.velocity
https://docs.unity3d.com/ja/current/ScriptReferenc...
Unity 公式スクリプトリファレンス
Vector3
https://docs.unity3d.com/ja/current/ScriptReferenc...


<実装例◆NavMeshAgent 利用しない>


 移動処理に NavMeshAgent を利用せず、DOTween の DOPath メソッドを利用したケースです。

 抽象メソッドの処理の内容が NavMeshAgent を利用している場合と違うことに注目しておいてください。


MovingObstacle.cs

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



移動する障害物の目標地点を作成する


 障害物用のゲームオブジェクトが NavMeshAgent の機能によって移動させることになりますので、
その目標地点となる地点を、ゲームオブジェクトとして事前に登録しておきます。

 Sceneビュー の NavMeshAgent が移動可能な範囲上に、CreateEmpty を行い、ゲームオブジェクトを配置してください。
これが目標地点の座標情報として扱うことになりますので、好きな数のゲームオブジェクトを配置してください。
また、現在ゲームオブジェクトがいる地点にも配置してください。そうすることで、スタート地点にも戻れるようになります。

 最低でも2つ以上の地点を作成してください。


移動する障害物のゲームオブジェクトに MovingObstacle スクリプトをアタッチする


 移動する障害物としての役割を持たせたいゲームオブジェクトに、MovingObstacle スクリプトをアタッチします。
RequireComponent 属性が付与されているため、自動的に NavMeshAgent コンポーネントがアタッチされます。

 MovingObstacle スクリプトの設定については、移動用の設定のみを行い、他の部分は後程追加します。

 TargetTrans 変数には、NavMeshAgent の目的地を順番に設定します。
 変数は配列になっていますので、好きな数の Size を設定し、その分の目標地点を登録してください。Element 0 が最初の目的地として利用されます。
最低でも2つの配列は用意し、最後の配列の要素にはゲームオブジェクトのスタート地点を登録しておくことで、
ぐるぐると同じルートを周回する障害物を作成することが出来ます。

 WaitTimes 変数も配列になっていますので、TargetTrans 変数の Size と同じ数の Size で設定します。
この値が目標地点に到達後の待機時間になります。長くとどまらせたい場合には数字を大きくし、
目標地点をスルーしてすぐに次の目標地点に向かわせたい場合には 0 にしておきます。
すべての地点をスルーするようにしても問題ありませんが、その場合にも配列を用意して、各 Element に 0 を登録してください。


参考用 インスペクター画像



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


 ゲームを実行し、障害物用の敵が経路上を自動で移動するかを確認してください。
自動的にループし、同じルート上を徘徊するようになっていますので、そちらの確認もおこなってください。


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


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


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

コメントをかく


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

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

Menu



プログラムの基礎学習

コード練習

技術/知識(実装例)

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

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

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

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

レースゲーム(抜粋)

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

3D脱出ゲーム(抜粋)

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

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

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

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

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

VideoPlayer イベント連動の実装例

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

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

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

private



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

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