Unityに関連する記事です

 ローグライト系のゲーム(Sly The Spire、ファントムローズなど)に見られる、プレイヤーが1マスずつ移動してゴール地点へ向かいながら、
各マスごとに分岐(選択肢)があり、任意のイベントを実行しながら進行するタイプのゲームシステムの実装例です。

 ここでは各マス目に複数の分岐処理がある形で1マスずつイベントを進行させていく方法を実装します。
最初のマスは分岐なし、次のマスは2つの分岐がある、といったようなローグライト系のゲームでよくみられるイベントシステムです。



1.設計

 
 前回につづき、合計4回の手順に分けて実装を行います。

〇1.UI 制作
◇2.イベント用のクラス制作
 3.データベース制作
 4.管理クラス制作

 今回は【2.イベント用のクラス制作】を行います。

 イベント用のインターフェース、親クラスを作成し、それを元に実際にイベントの処理を実装する子クラスを作成します。


2.サンプルコード   インターフェースー


 イベント実行のインターフェースを作成します。
ここでは UniTask を利用して非同期処理が実行できるようにメソッドを準備しています。

using Cysharp.Threading.Tasks;

public interface IEvent
{
    UniTask ExecuteEvent();
}

 インターフェースは具体的な実装を提供しないため、クラスがインターフェースを実装する際に、どのように実装するかは具体的なクラスに委ねられます。
これにより、インターフェースを定義するときには、そのインターフェースを実装するクラスの詳細な実装に依存しない形で設計できます。

 また、インターフェースを使用することで、新しいクラスを追加し、既存のクラスが新しいインターフェースを実装するだけで、コードの拡張が容易に行えます。
これにより、アプリケーションの拡張や変更が簡単に行えるようになります。


3.サンプルコード◆ 蔀蠑櫂ラスー

 
 イベント用の抽象(abstract)クラスを作成し、これをイベントの親クラスとします。
抽象クラスはサブクラス(子クラス)に継承させる前提で作成するクラスです。
そのため、このクラスを単体で利用することは出来ません。
また MonoBehaviour クラスを継承している場合にも、このクラス自体はゲームオブジェクトへのアタッチは行えません。
抽象クラスを継承しているサブクラスであれば、アタッチすることが出来ます。

 抽象クラスはインターフェースを実装することができます。
そのため、インターフェースを実装する複数のクラスが、共通の抽象クラスのメソッドやプロパティを持つことができます。

 これは、クラス階層での一貫性を保ち、コードの保守性を向上させるのに役立ちます。

using System;
using UnityEngine;
using Cysharp.Threading.Tasks;
using UniRx;
using UnityEngine.UI;

public abstract class EventBase : MonoBehaviour, IEvent // IEvent インターフェースを実装
{
    public abstract UniTask ExecuteEvent(); // IEvent インターフェースの ExecuteEvent メソッドを抽象メソッドとして実装

    [SerializeField] private Button btnEvent; // Button 登録用

    public IObservable<Unit> OnClickEventButtonObserbable => btnEvent.OnClickAsObservable(); // 外部クラスで Button 機能を利用できるようにする
}

 インターフェースを抽象クラスに実装する際には、通常のメソッドとして実装する以外にも、抽象メソッドとして実装することができます。
これにより、抽象クラス内での具体的な実装は行われないため、抽象クラスを継承した子クラスでの振る舞いの実装を強制することができます。

 これが重要な理由は、以下の通りです:


1.一貫性を維持

 インターフェースを抽象クラスに組み込むことで、すべての子クラスが同じメソッド(抽象メソッド)を持つことが保証されます。
これにより、クラス階層内で一貫性が維持され、特定のメソッドの存在が確実に保証されます。


2.実装の強制

 子クラスは抽象クラスを継承し、抽象メソッドを実装しなければなりません
これにより、子クラスが特定のメソッドを実装しない限り、コンパイルエラーが発生します。

 つまり、望まない振る舞いの抜け漏れを防ぎ、プログラマが正しい実装を強制できます。


3.柔軟性と拡張性

 抽象クラスを使用して、インターフェースの一部のデフォルト実装を提供することもできます。
この場合、子クラスは必要に応じてデフォルトの実装を再定義することができ、より柔軟な設計を実現できます。



 このようにインターフェースと抽象クラスの組み合わせは、クラス階層を設計し、コードの柔軟性、拡張性、保守性を向上させるのに役立つ強力な手法です。
具体的な要件や設計上の要因に応じて、どちらを選択するかはプロジェクトによって異なることがあります。


4.サンプルコード・ぁ 璽汽屮ラスー


 イベント用の子クラスです。

 発生するイベントの種類に応じて、EventBase クラスを継承して作成してください。
ここではサンプルとして2つ提示しておきます。ExecuteEvent メソッド内に、自分のプロジェクトに応じた処理を作成してください。


using Cysharp.Threading.Tasks;
using UnityEngine;

public class BattleEvent : EventBase
{
    public async override UniTask ExecuteEvent() {

    // TODO 実際には戦闘用の処理を記述する
    // 今回はサンプルとして、1秒間の待機とログ出力を行います
        await UniTask.Delay(System.TimeSpan.FromSeconds(1.0f));

        Debug.Log($"{this} : 終了");
    }
}



using Cysharp.Threading.Tasks;
using UnityEngine;

public class SearchEvent : EventBase
{
    public async override UniTask ExecuteEvent() {

    // TODO 実際には探索用の処理を記述する
    // 今回はサンプルとして、1秒間の待機とログ出力を行います
        await UniTask.Delay(System.TimeSpan.FromSeconds(1.0f));

        Debug.Log($"{this} : 終了");
    }
}

 どちらのサブクラスも親クラスとして EventBase クラスを継承しています。
EventBase クラスは抽象クラスであるため、継承を前提としたクラスになっています。
またインターフェースのメソッドについても抽象メソッドとして実装されているため、
サブクラス内には必ず、ExecuteEvent メソッドを記述する必要があります。

 ExecuteEvent メソッドがすべてのサブクラスに実装されることを強制することにより、
イベントの実行処理は、各サブクラスの ExecuteEvent メソッドをすることで実行されます。
この際、イベントの実行側は、ExecuteEvent メソッドの中身は知りません。(抽象化)

 サブクラスで共通している ExecuteEvent メソッドを実行している状態です。



 上記の BattleEvent クラスで考えてみます。
このクラスは EventBase クラスを継承し、その中にある抽象メソッドである ExecuteEvent メソッドを実装します。
このメソッド内に、バトルイベント独自の処理を記述します。

 例えば、バトルイベントではプレイヤーが敵と戦う場面を想像できます。
ここでは、バトルのシミュレーションを目的として、1秒間のウェイト(待機)を入れ、バトルイベントが実行されたことをログに記録しています。

 この抽象化の重要なポイントは、ゲームのメインエンジン(処理を実行するクラス)が ExecuteEvent メソッドを呼び出す際に、
どの具体的なイベントクラスが実行されるかを知らないことです。
ゲームのロジックは、イベントが「何をするか」を気にする必要がなく、各イベントクラスの ExecuteEvent メソッドがどのように振る舞うかを気にする必要がありません

 このアーキテクチャ(構造)により、新しいイベントタイプを追加する際には、
新しいサブクラスを作成し、ExecuteEvent メソッドをそのタイプのイベントに合わせて実装するだけで、ゲームエンジン(処理を実行するクラス)側のコードに変更が必要ありません
SearchEvent クラスであれば、クラス内の ExecuteEvent メソッドには、探索用の処理を記述することになります。
同名のメソッドですが、それぞれの ExecuteEvent メソッド内には、異なる処理を記述できます。

 その結果、新しいイベントを容易に追加できるだけでなく、既存のコードも保守しやすくなります。
具体的には、switch 文や if 文による分岐処理が不要となり、処理が自動的に振る舞いを変えるようになります。


5.サブクラスをアタッチするための Button ゲームオブジェクトを作成する


 EventBase クラスは MonoBehaviour クラスを継承しています。
そのため、先ほど作成したサブクラスもゲームオブジェクトにアタッチして利用することが出来ます。

 イベントの可視化のため、これらのサブクラスを Button ゲームオブジェクトにアタッチして利用します。
このようにすることで、Button を押した際に、アタッチされているサブクラスのイベントを実行できるように連動させます。

 MonoBehaviour クラスを継承していない場合には、別のアプローチがあります。



 ヒエラルキーにある EventButtonSet ゲームオブジェクトの子オブジェクトとして、Button ゲームオブジェクトを作成します。
いずれかのサブクラスをアタッチし、そのクラス名と同じゲームオブジェクト名に変更します。

 この例であれば、SearchEvent クラスをアタッチし、ゲームオブジェクトの名前も SearchEvent に変更しています。

 Button ゲームオブジェクトには子オブジェクトとして Text ゲームオブジェクトが自動的についてきます。
Text を Search に書き換えて、ボタンの持つイベントの役割を明確化しましょう。

 これらの部分は後で画像に差し替えたりして任意に加工してください。


ヒエラルキー画像















 完成したらプレハブにしてください。

 同じ手順で、BattleEvent も作成し、プレハブにしておいてください。
こちらには BattleEvent クラスをアタッチしましょう。








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

 => 次は 【2D】マス目ごとにイベントが発生するシステムの実装例 です。

コメントをかく


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

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

Menu



技術/知識(実装例)

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

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

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

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

レースゲーム(抜粋)

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

3Dレールガンシューティング(応用編)

3D脱出ゲーム(抜粋)

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

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

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

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

VideoPlayer イベント連動の実装例

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

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

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

private



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

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