原カバンは鞄のお店ではありません。

Unityを使ったゲーム制作のあれこれを綴っていきます。

【Unity】公式サンプルプロジェクトから学ぶデザインパターン:State

階段

なぜか下り階段の一番最後の段が苦手な皆さんこんにちは。階段を降りる時、それまでは同じペースで歩を進めていたのに一番最後の段になると何故か急に距離感が掴めなくなって踏み外しそうになります。階段にトラウマとかないはずですが原因が分かりません。ちょと怖いです。

 

デザインパターン

さて、Unity公式から公開されているデザインパターン学習用のサンプルプロジェクトについての記事6回目

github.com

これまでは以下のデザインパターンについて記事を書いてます。

www.karvan1230.com

www.karvan1230.com

www.karvan1230.com

www.karvan1230.com

www.karvan1230.com

 

Stateパターン

Stateパターンはオブジェクトの振る舞いに関するデザインパターンの一つです。

オブジェクトの「振る舞い」が状況によらず一様に同じである場合は特に考慮する必要はありませんが、外部や内部の影響によりオブジェクトの「状態」が変わり、それによって「振る舞い」が変わる場合ではどうでしょう?

 

例えば、「Woman」というクラスは同じ言葉で同じように依頼をお願いしたとしても、その時の「状態」により応答が異るとした場合、その後の対応は「Woman」の応答に応じて処理を行う必要があります。

この場合、まずは「Woman」の現在の状態を条件付き演算子 (if または switch) で判定し、その条件分岐毎に応答するレスポンスを決定する形になりますが、そうすると、その後に新しい「状態」を追加したい場合は、全ての条件付き演算子の分岐を見直さなければならないような設計となってしまいます。

このようなケースでコードの冗長化を防ぎ、保守性を高める設計思想がStateパターンです。

Stateパターンでは「状態」をクラスとして表現し、この「状態」を入れ替え可能にしておくことで、条件付き演算子による分岐を無くし、処理を単純明快にして、新しい「状態」の追加にも対応することができます。

 

サンプルシーン

サンプルプロジェクトの「10 State」ディレクトリ配下にあるサンプルシーンは以下のよう動作をします。

 

シリンダー形状の自機は動かしていない場合は「Idle」状態、動き出すと「Walk」状態、スペースキーでジャンプすると「Jump」状態に遷移します。現在の状態は自機の上にテキスト表示されており確認することができます。

このサンプルシーンでは各状態を同じインターフェイス(IState)を継承したクラス(State)で実装しており、IStateインターフェイスでは以下のメソッドを定義しています。

  • Enter:その状態に遷移した時に呼ばれる
  • Update:その状態中に毎フレーム呼ばれる
  • Exit:その状態から他の状態に遷移する時に呼ばれる

 

自機の現在の状態(State)は「StateMachine」クラスにより管理されています。

StateMachine」クラスは「Idle」「Walk」「JumpそれぞれのStateクラスを入れ替えて現在のStateクラス」を把握しており、「現在のStateクラス」に対してIStateインターフェイスで定義されている各メソッドの呼び出しを行います

Stateの遷移は各状態クラスの中で行っており、現在の状態が他のStateへ遷移する条件と合致すると「StateMachine」クラスに対して他Stateへの遷移を依頼、「StateMachine」クラスは依頼されたStateを「現在のStateクラス」として入れ替えます。

 

各クラスの関係を図で表現するとこんな感じになります。

 

まとめ

Stateパターンはオブジェクトの各状態をそれに対応した各クラスで表現するパターンです。 
このパターンを適用すると、各状態を個々のクラスで表現するため、処理が単純明快となります。
「StateMachine」クラスのような管理クラスを作成して「状態」の入れ替えを行うのが一般的です。
「状態」の遷移判定は各状態クラスの内部で行っても、外部クラスで行っても構いませんが、遷移条件はきちんと整理する必要があります。

 

【Unity】公式サンプルプロジェクトから学ぶデザインパターン:Command

ブログ

不意に10年ぐらい前に書いていたブログを読み返してみると今と全く状況が変わってなくて泣きそうな皆さんこんにちは。

こんなAA描いてた

デザインパターン

と、いうわけでUnity公式から公開されているデザインパターン学習用のサンプルプロジェクトについての記事5回目です。

github.com

これまでの記事はこちら

www.karvan1230.com

www.karvan1230.com

www.karvan1230.com

www.karvan1230.com

Commandパターン

Commandパターンは、処理の呼び出し側と実装側を分離することを目的としたデザインパターンで、プロダクト内で行う処理を細かい命令(Command)に細分化し、それをそのままオブジェクトとして表現するパターンです。

命令を1つの「オブジェクト」として表現することにより命令の履歴管理を行うことが可能で、それによって命令の訂正、再実行などが容易になります。

 

例えばレストランでは注文を受けてシェフが料理を作りますが、料理には「肉料理」「魚料理」「野菜料理」等々種類が多様で、それぞれで作業工程、内容が異なります。
しかし、そういった作業内容はシェフ自身が把握していれば良いので、お客側から「鶏肉に小麦粉をまぶして油で揚げて調理して」といった指示を受ける必要はありません。
お客側は「唐揚げ」という料理名を注文するだけで自分が希望する料理が出されるわけです。

こうしたお客(Client)とシェフ(Receiver)間で決められた料理名(Command)で指示→実行を行うパターンがCommandパターンと言えますが、シェフが一人でお客が複数の場合、そのままではシェフがワンオペで複数の料理を同時にこなさなければならず混乱してしまいます。
この為、Commandパターンではお客(Client)とシェフ(Receiver)の間に給仕役(Invoker)を置き、お客からの注文に順序を付けてシェフに料理の依頼を行います。

上の図でいうと伝票がCommandの役割を担っています。伝票は図の通り順番に並べられているため、それが注文履歴となり以後の履歴管理に利用することができます。

 

サンプルシーン

サンプルプロジェクトの「9 Command」ディレクトリ配下にあるサンプルシーンは以下のよう動作をします。

画面左下にある各方向ボタンをクリックすると「P」のマークの自機がその方向へ移動します。Undo,Redoは実行した命令の訂正、再実行を行います。

Pマークの自機にはPlayerMoverというコンポーネントがアタッチされており、上位から移動方向を指定されるとその方向へ移動する処理が組み込まれています。

 

このサンプルシーンではボタンクリックのイベントをInputManagerが受け取るとボタンに応じたMoveCommandを生成し、CommandInvokerに対してMoveCommandの実行依頼します。

また、依頼されたMoveCommandはCommandInvoker内でスタック領域に保持されているため、Undoがクリックされた場合、スタック領域から直近のMoveCommandを取り出し(POP)て、そこから逆方向の移動処理を依頼しています。

これらのモジュールの関係を図で表現するとこんな感じになります。

 

まとめ

Commandパターンは命令を1つの「オブジェクト」として表現するデザインパターンです。

このパターンは処理の呼び出し側と実装側を分離し、命令の履歴管理、Undo/Redoを容易にするという利点があります。

処理のどこからどこまでをコマンド化するのか検討する必要があり、検討が足りないとコードが複雑化する可能性があります。

 

 

【Unity】公式サンプルプロジェクトで学ぶデザインパターン:Singleton

空気清浄機

新しく購入した空気清浄機の前でおならをしたらランプが真っ赤になって軽くショックを受けた皆さんこんにちは。自分の放屁がランプを真っ赤にするほど空気を汚すとは知りませんでした。以後気を付けます。

 

ソニックフロンティア

当初は購入予定ではなかったのですがネットの評判が良かったので購入しました。


従来のステージクリア型のソニックからオープンワールドに変わった本作ですが、私のような濃いソニックファンではないカジュアルなユーザには非常に楽しい内容になっていると思います。

これまでオープンワールドゲームと言えば自由なシナリオ進行と探索の深さを楽しむ物が主流となっていましたが、『Marvel's Spider-Man』の発売を契機として最近ではフィールド間の移動を楽しむ事に重点を置いたゲームがオープンワールドの一つの支流として確立されつつあり、『ソニックフロンティア』はこの支流に沿ったゲームであるといえます。

フィールド内に点在するチェックポイントのギミックを解除⇒従来型ソニックのステージをクリア、というゲームフローですが、ソニックらしい高速走行とレールライド、ピンボールジャンプ等のギミックによる空間移動は爽快感がありそれだけで楽しいため、ストーリーを追わずにただフィールドを彷徨うだけでも時間を忘れて遊ぶことが出来ます。

妙にフォトリアルよりなグラフィックで敵のデザイン(特にカラーリング)が地味だという事と、やけに中二病っぽい台詞回しは気になるところですが、それを上回るぐらいソニックの操作が楽しく爽快感が得られるゲームだと思います。

デザインパターン講座4回目

Unity公式から公開されているデザインパターン学習用のサンプルプロジェクト

github.com

各デザインパターンがディレクトリ別に格納されています。

 

今回はそのサンプルプロジェクトに関する解説4回目、「8 Singleton」になります。
前回まで解説は以下

www.karvan1230.com

www.karvan1230.com

www.karvan1230.com

 

Singletonパターン

このパターンを簡単に説明すると、生成するインスタンスの数を1つに制限した設計パターンと言えます。

RPGなどのゲームを例にとると、プレイヤーのキャラクターやNPC、敵といったキャラクターはゲーム内で多数存在するため、その分それを制御するクラスは複数必要ですが、ゲーム全体の運行を管理する神視点のクラスは複数存在するとかえって処理が煩雑になり、後々のバグの原因となのでゲーム内に一つだけ存在する方が望ましいです。
この「ゲーム内に1つしか存在」を実現し、かつ保証する組みがSingletonパターンとなります。

 

サンプルシーン

「8 Singleton」配下には以下のようなファイルが格納されています。

 

そして、ここに格納されているサンプルシーン「Singleton」を実行してみるとこんな動作をします。

ゲーム実行前は複数あったスピーカーのオブジェクトが一つになり、クリックによりそのスピーカーから音が鳴ります
(動画では音がないため、スピーカーを揺らす処理を行っています)

各スピーカーにはそれぞれSingletonを継承したAudioManagerクラスがアタッチされており、起動時に各自インスタンスを生成しようとしますが、既に自身以外のSingletonインスタンスが存在する場合は生成を行わず、逆に自身を削除(Destory)しています。
これによりゲーム開始から最も先にインスタンスを生成したスピーカーのみが残り、「ゲーム内に1つしか存在」が見た目は実現します。

private void RemoveDuplicates()
{
    if (_instance == null)
    {
        _instance = this as T;
        DontDestroyOnLoad(gameObject);
    }
    else
    {
        Destroy(gameObject);
    }
}

 

唯一の存在を保証する

上記の処理によりゲーム開始時に「ゲーム内に1つしか存在」が見た目は実現できましたがこれだけでは不十分で、外部からの新規作成・コピーの禁止を保証して、ゲーム開始以後も「唯一の存在」が壊れない仕組みを実現する必要があります。
この為、Singleton(ゲーム内に1つ)となるクラスは以下のルールに準拠して製造する必要があります。

  1. 自身の型のインスタンスが privateなクラス変数として定義されている
  2. 外部から生成されないようにprivateにしている
  3. インスタンスを返すためのクラス関数が定義されている

 

サンプルシーンのSingletonクラスではこれらのルールが以下の形で実装されています。

1.自身の型のインスタンスが privateなクラス変数として定義されている

private static T _instance;

 

2.外部から生成されないようにprivateにしている

private static void SetupInstance()
{
    // lazy instantiation
    _instance = (T)FindObjectOfType(typeof(T));

    if (_instance == null)
    {
        GameObject gameObj = new GameObject();
        gameObj.name = typeof(T).Name;

        _instance = gameObj.AddComponent<T>();
        DontDestroyOnLoad(gameObj);
    }
}

 

3.インスタンスを返すためのクラス関数が定義されている

public static T Instance
{
	get{return _instance;}
}

 

上記の対応によりSingletonのクラスは自分自身のインスタンスをグローバルなstaticメンバとして持ちます。
この為、他クラスからSingletonのクラスの関数を呼び出す場合は、

クラス名.Instance.関数

の形で呼び出すことができるのでかなり便利になります。

 

MonoBehaviourを継承しよう

UnityでSingletonパターンを採用したクラスを作成する場合はMonoBehaviourを継承したクラスをSingleton化する方が色々便利です。
この為、サンプルシーンでは以下のような形でMonoBehaviourを利用しています。

 

・MonoBehaviourを継承したクラスをSingletonクラス

public class Singleton<T> : MonoBehaviour where T : Component
{
	private static T _instance;
	public static T Instance
	{
		if (_instance == null)
		{
			_instance = (T)FindObjectOfType(typeof(T));

			if (_instance == null)
			{
				SetupInstance();
			}
			else
			{
		        string typeName = typeof(T).Name;
			}
		}

		return _instance;
	}
}

 

・Singletonを継承したGameManagerクラス

public class GameManager : Singleton<GameManager>
{
}

 

まとめ

Singletonパターンとは生成するインスタンスの数を1つに制限した設計パターンです。
Singletonクラスは外部からの生成・コピーを禁止し、自分自身のインスタンスをグローバルなstaticメンバとして持ちます。
Untiyで利用する場合はMonoBehaviourを継承したクラスをSingleton化しましょう。

 

 

【Unity】公式サンプルプロジェクトで学ぶデザインパターン:Object Pool

感想を述べよ

赤ん坊の写真を見せられても「可愛いですね」の言葉以外思いつかない皆さんこんにちは。ほら、他にもっと何か言葉があるだろう的に何枚も写真を見せつけられても「可愛い」以外の感想は思い浮かばないので勘弁してください。また「その赤ちゃんの写真下さい」という言葉をきっかけに周りの人間すべてに写真を送りつけてくる方もいますが「あ、私も欲しいです」という言葉は長いものに巻かれた社交辞令なので真に受けないでください。

 

デザインパターン

Unity公式からゲーム開発で使用するデザインパターンを学習するためのサンプルプロジェクトが公開されています。

github.com

公式サンプルではディレクトリ別に各デザインパターンに対するサンプルプロジェクトが分けられています。

 

このブログではこのサンプルプロジェクトに対して、これまで2回解説記事を書いています。

www.karvan1230.com

www.karvan1230.com

今回取り上げるデザインパターンは数あるデザインパターンの中でもゲーム開発で最も利用されるであろう「7 Object Pool」です。

 

Object Poolパターン

Object Poolパターンは効率的なリソース運用を目指したデザインパターンで、プロジェクト内で生成するオブジェクトの数を制限し、再利用を繰り返すことで無駄なリソースの使用を防ぎパフォーマンスの向上に繋げようというパターンです。

例えばSTGの銃弾やモブの敵キャラなど、ゲーム内で大量に繰り返し登場するオブジェクトは必要な時に逐一、生成→破棄するよりも事前に大量に生成→保持しておいて必要なときにアクティブ/非アクティブを切り替えて利用した方がCPU負荷が掛からず、処理を最適化(高速化)することができます。もちろん保持しておく分のメモリは消費しますが。

 

Object Poolパターンの典型的な構成は、オブジェトの大量生成&保持を行うクラス(ObjectPool)と、オブジェクトが使用中か未使用なのか判断するための制御用クラス(Object)、そしてそれを利用するクライアントのクラス(Client)に分かれます。

ClientはObjectPoolに対して借用を依頼し、ObjectPoolはその依頼を受けてStack領域に保持しているObjectをClientに貸し出します。

ClientはObjectに「使用中」の状態を設定して使用し、使い終わったら「未使用」の状態に戻してObjectPoolに返却を行います。

ObjectPoolはObjectが返却されるとそれをStack領域に戻し、次の借用の依頼を待ちます。

 

サンプルシーンのObject Pool

今回のサンプルシーンを実行するとこんな感じの動作となります。

左クリックするとタレットから銃弾が発射されます。動画内のHierarchyに注意すると分かると思いますが、左クリックにより「=====ObjectPool=====」配下にあるオブジェクトの一部がアクティブ化され、また非アクティブに戻っています。

 

このシーンでは左クリックの検知がExampleGunクラスで行われ、ObjectPoolクラスに対してPoolObjectの借用を依頼、借用されたPoolObjectはAddForceにより前方に発射されます。

この借用されたPoolObjectは一定時間経過後に自身をObjectPoolへ返却するよう処理が組み込まれている為、発射後一定時間経過するとObjectPoolへ戻されます。

このサンプルシーンのObjectPoolクラスではClient側から借用依頼があった際にStack分を使い切っている場合、新しくObjectを生成してStack領域に設定する処理が行われています。

ここら辺は用途に合わせて、不足分を補うように生成するのか、使用中のオブジェクトが未使用状態になるまで待つのか、エラーとして処理するのか、使い分ける必要があると思います。

 

もう一つのサンプルシーン

このディレクトにはもう一つサンプルシーンが付属しています。

こちらはObjectPoolクラスをUnity標準のObjectPoolクラスを使用して実装した例となっています。
Unity標準のObjectPoolに関しては私もブログで記事を書いているのでそちらを参照してください。

www.karvan1230.com

 

まとめ

Object Poolパターンとはあらかじめオブジェクトを生成して格納しておき、それを繰り返し再利用するパターンです。

このパターンのメリットはオブジェクトの生成と破棄のコストを減らすことができ、処理速度の改善が狙える、という事で、特にACTやSTGでは有用な手法であるといえます。

デメリットはある程度のメモリ領域を必要とする、という点と、使用中/未使用の状態管理を自身で行う必要がある、という事で、特にこの状態管理がうまく実装できないと無駄にメモリ領域を消費してしまい、かえって処理が遅くなるので注意が必要です。

 

 

【Unity】公式サンプルプロジェクトで学ぶデザインパターン:Factory

個人的な見解

新作ゲームのジャンル紹介で「デッキ構築型ローグライトストラテジー」とか「ハクスラ型ダンジョン探索マルチRPG」とか目にするとそれだけでお腹一杯になる皆さんこんにちは。「ドラマチック謎解きアドベンチャー」という言葉も目にしましたが「ドラマチック」かどうかはこちらが判断するので勝手に名乗らないで下さい。

 

デザインパターン

前回の記事で紹介しましたがUnity公式からゲーム開発で使用するデザインパターンを学習するためのサンプルプロジェクトが公開されています。

github.com

 

デザインパターンは先人の経験に基づいたデザイン (設計) のパターンです。デザインパターンの要不要の議論はさておき、ゲームを設計する際の思考材料として覚えておいても損はありません。

www.karvan1230.com

 

公式サンプルではプロジェクト内のディレクトリ別に各デザインパターンに対してそれを使用したサンプルプロジェクトが内包していますが、今回はその中から「6 Factory」を紹介したいと思います。

 

Factoryパターン

Factoryパターンとは特定の機能を持つクラスに対して「インスタンスの生成」と「具体的な処理の実装」を分離して行うパターンのことを指します。

Factory=工場、だからといってこっちの工場じゃない

 

「車」を例にとると、「車」には「前輪駆動車」「後輪駆動車」「四輪駆動車」と駆動方式に違いがありそれに伴って内部の機構も異なっていますが、「車」を使用する側(ユーザ)にとってみれば内部機構の違いは認識せずに、「アクセル」を押せば「前進」し、「ハンドル」を回せば「左右に旋回」して欲しいと思います。

この為、「車」には「アクセル」と「ハンドル」というインターフェイスが「前輪駆動車」「後輪駆動車」「四輪駆動車」すべてに共通して存在しており、各駆動車はインターフェイスに応じた「前進」「旋回」の機能を実装します。

この時「車」というインスタンスを上位(ユーザ)に提供する際に

  • 各駆動車で共通のインターフェイスをもった「車」を作成するクラス:Creater
  • 各駆動車で「前進」「旋回」の機能を実装するクラス:Product

で分離しよう、という設計パターンがFactoryパターンとなります。

 

サンプルシーン

Factoryのサンプルシーンを実行してみるとこんな動作をします。

画面をクリックするとクリック位置に「パーティクルを放出する赤いボール」か「音を鳴らす黄色いボール」どちらかがランダムで生成されます。(上の動画はGIFのため音がありません)

 

このシーンのHierarchyを見ると「ClickToCreate」というオブジェトの配下に「FactoryA」「FactoryB」というオブジェトが配置されているのが分かります。

 

画面のクリックは「ClickToCreate」にアタッチされた「ClickToCreate」クラスで検知し、ランダムで「FactoryA」/「FactoryB」どちらかに対してボールを生成する依頼を行っています。

「FactoryA」では「赤いボール」が生成され、「FactoryB」から「黄色いボール」が生成されています。

 

FactoryA/FactoryBにはそれぞれCreaterとなる「ConcreteFactoryA」「ConcreteFactoryB」クラスがアタッチされており、どちらも抽象クラス「Factory」を継承しています。

「Factory」ではインスタンス生成を依頼するための「GetProduct」メソッドが用意されています。

 

 

一方、「パーティクルを放出する」「音を鳴らす」という機能はインターフェイス「IProduct」を継承した「ProductA」「ProductB」クラスの「Initialize」メソッドで実装され、「赤いボール」オブジェトに「ProductA」が、「黄色いボール」オブジェトに「ProductB」がアタッチされています。

 

 

この状態で「ClickToCreate」クラスから各Factoryに「GetProduct」を通じてインスタンス生成が依頼された場合

  • 「FactoryA」では「ProductA」がアタッチされた「赤いボール」を生成し、「Initialize」を実行
  • 「FactoryB」では「ProductB」がアタッチされた「黄色いボール」を生成し、「Initialize」を実行

を行うことでクリック位置に「パーティクルを放出する赤いボール」or「音を鳴らす黄色いボール」を生成させています。

各クラスの関わりを図で描くとこんな感じ、「インスタンス生成」の役割を「Factory」クラスが行い、「処理の実装」を「Product」クラスで行っている事が分ると思います。

 

今回のまとめ

Factoryパターンとは「インスタンスの生成」と「具体的な処理の実装」を分離して行うパターンです。

このパターンを利用すると、新しいオブジェクトが増えた場合でも新しいFactoryを作るだけとなり、利用する側は大きく処理を変更する必要がありません。

また、機能の追加/削除を行う場合もインターフェイスの変更とFactory内部を修正するだけなので影響範囲が最小限で収まります。

 

デメリットとしては作成するクラスが多くなる分コード量が多くなるという事と、上のような図を描かないと全体を把握しづらい、という事でしょうか。
ここら辺は使い所によって考慮する必要があると思います。

 

 

【Unity】Unity公式のサンプルプロジェクトから学ぶ設計思想:SOLID原則

Pixel Watch

勢いに乗って購入したものの使い道がいまいち思いつかない皆さんこんにちは。あまり使いこなせていないのですが、とりあえずオプションで購入したオレンジバンドが上下で色が微妙に違うのが気になります。

 

デザインパターン

システム開発の現場でオブジェクト指向のプログラミング言語が広く普及したことにより、それを利用した設計方針(デザインパターン)が提案されるようになりました。

デザインパターンとは一言で言うと、設計のノウハウ集で過去のエンジニアが使用してきた設計方法を分類別に纏めたものです。
最も有名なのはGoF(Gnag of Four)のデザインパターンで「生成」「構造」「振る舞い」の分類別に23のパターンが紹介されています。

一昔前まではこのデザインパターンはオブジェクト指向による設計の基礎知識となっていたので職場の研修や学校の授業等で履修した方も多いと思います。
しかし、最近の開発現場ではフレームワークでの開発が主流となっており、デザインパターンの知識はそこまで必須というわけではないようです。

Unityではオブジェクト指向言語のC#を使用するので、一般のシステム開発と同様にこのデザインパターンを利用してゲーム開発を行うことが出来ます。
まぁ、こちらも最近ではCorgi EngineやAdventure Creator、宴のようなフレームワーク系のツールを使って開発をする事ができる為、わざわざデザインパターンを考慮してゲーム内の処理を設計する必要はありませんが、基礎知識として、または品質や開発効率を高める為にデザインパターンを学んでおいても損はありません。

 

先日、Kan Kikuchiさんのブログで取り上げられていたのですが、このデザインパターンを学習するためにUnity公式でサンプルプロジェクトが公開されているようです。

kan-kikuchi.hatenablog.com

 

GitHubで公開されているので下のリンクからDownload ZIPからzipファイルをダウンロードして、UnityHubで解凍したプロジェクトを開いてください。

github.com

 

公開されているもの

このプロジェクトは1~12にナンバリングされたディレクトリと、プロジェクト内で汎用的に使用されるオブジェクト、フォント等が格納されたディレクトリ(General)とテンプレートスクリプトが格納されたディレクトリ(ScriptTempletes)で構成されています。

「1 SingleResponsibility」から「5 DependencyInversion」まではデザインパターンを学習する前に必要となる「SOLID原則」について、それぞれの原則に沿ってコーディングされたサンプルソースが格納されています。

 

一般的なデザインパターンとしては「6 Factory」から「12 MVP」のディレクトに各デザインパターンの実用例としてのデモシーンが格納されています。

■Factory

 

■Object Pool

 

■Singleton

 

■Command

 

■State

 

■Observer

 

■MVP

 

SOLID原則

先程も紹介したように、このプロジェクトの「1 SingleResponsibility」から「5 DependencyInversion」までのディレクトリには「SOLID原則」の各原則に沿ったサンプルソースがそれぞれ格納されています。

SOLID原則とは、オブジェクト指向プログラミング言語を使ってソフトウェアを構築する際に従うべき以下の5つの原則(ガイドライン)のことを指します
この原則を守ることで、ソフトウェアの拡張性や保守性を高めることを目的とします。

  • Single Responsibility Principle:単一責任の原則 
  • Open/closed principle:開放閉鎖の原則
  • Liskov substitution principle:リスコフの置換原則 
  • Interface segregation principle:インターフェース分離の原則 
  • Dependency inversion principle:依存性逆転の原則

上にあげた5つの原則のそれぞれの頭文字をとって「SOLID原則」と呼びます。サンプルプロジェクトではこの5原則のそれぞれの名前でディレクトリが作られています。

これらディレクトリ配下にはサンプルソースが格納されていますが、ソース名だけ見てもどれが何を示しているのか分からないと思うので、各原則と各サンプルソースについて軽く説明したいと思います。

 

1 SingleResponsibility:単一責任の原則 

1つのクラスが受け持つ責任は1つだけ」という原則です。
このディレクトリを開くと以下の5つのソースが格納されていることがわかります。

ここでは「Player」というクラスに「入力を受け取る」「移動する」「音声を鳴らす」という機能を持たせるケースが実装されており、
それぞれの機能が

  • 入力を受け取る → PlayerInputクラス
  • 移動する → PlayerMovementクラス
  • 音声を鳴らす → PlayerAudioクラス

それぞれのクラスに分けて分担するように実装されています。
「UnrefactoredPlayer」というクラスは前述の三機能をすべて受け持ったクラスで、単一責任の原則に反する例として作成されています。

 

2 OpenClosed:開放閉鎖の原則

拡張にはオープンで、変更にはクローズドであるべき」という原則です。

ここでは「AreaCalculator」クラスで円(Circle)と四角(Rectangle)それぞれの面積を計算する機能を実現しようとするケースについての実装例となっています。

AreaCalculatorクラス内で円の面積計算の関数と四角の面積計算の関数をそれぞれ実装するのは開放閉鎖の原則に反する実装、として紹介されており、開放閉鎖の原則に即した実装として以下のクラスが作成されています。

  • 面積を計算する関数(CalculateArea)を持った抽象クラス:Shape
  • Shapeを継承した円の面積計算クラス:Circle
  • Shapeを継承した四角の面積計算クラス:Rectangle

これよりAreaCalculatorの面積計算関数(GetArea)では抽象クラス(Shape)の面積計算関数(CalculateArea)をコールするだけとなります。
以後、例えば三角の面積計算の機能を追加したい場合は、Shapeを継承した三角の面積計算クラスを追加するだけで機能を拡張することができます。

 

3  LiskovSubstitution:リスコフの置換原則 

派生型(サブクラス)は、その基底型(スーパークラス)と置換可能でなければならない」という原則
この原則は、サブクラスとスーパークラスがエラーなしで同じ方法で使用できるように、一貫性を保つことを目的としています。

ここでは以下の二つのスーパークラスがあるケースについて

◇RoadVehicle:前進後退の機能インターフェース(IMovable)と左右旋回機能のインターフェース(ITurnable)を持つスーパークラス

◇RailVehicle:前進後退の機能インターフェース(IMovable)だけを持つスーパークラス

RailVehicleを継承したサブクラスのTrainは前進後退の機能のみを作り込みべきで、左右旋回を機能を持つとスーパークラスRailVehicleの提供機能を超える(=置き換え不可)ので原則に反します。

 

4 InterfaceSegregation:インターフェース分離の原則

「クライアントが使用しないメソッドへの依存を強制すべきではない」という原則
簡単に言うと無駄に不要なインターフェース作るな、という原則で、汎用的なインターフェースを作っていろんなクラスで使用するよりも、各クラスの要件に応じたインターフェースをそれぞれに特化して作るほうがよい、という考えです。

このディレクトリでは敵ユニットクラス(EnemyUnit)と爆発物クラス(ExplodingBarrel)について、EnemyUnitには、ダメージ処理用(IDamageable)、移動処理用(IMovable)、ステータス処理用(IUnitStats)のインターフェースを継承させいます。
一方、ExplodingBarrelにはダメージ処理用(IDamageable)と爆発処理用(IExplodable)のインターフェースを継承させて、それぞの要件に応じたインターフェースを使っています。

 

5 DependencyInversion:依存性逆転の原則

上位のモジュールは下位のモジュールに依存せず、どちらのモジュールも「抽象」に依存すべき」という原則
importやuseしてモジュールを使う側が、依存する側です。
依存する側がモジュールを直接呼び出すのではなく、抽象クラスやインターフェースを使い「抽象」に依存しましょうというのが、この原則です。

このディレクトリではドアクラス(Door)とトラップクラス(Trap)はどちらもISwitchableインターフェースを継承しており、Switchクラスではアクティブ化(ドアオープン/トラップ作動)と非アクティブ化(ドアクローズ/トラップ作動)の処理をISwitchableインターフェースを通して行います。
インターフェイスを導入することにより、上位モジュール(Switch)は下位モジュール(Door/Trap)を直接呼び出さず(依存せず)処理を実装することができます。

 

おわりに

ここまで5つの原則(SOLID原則)とそれを実装したサンプルソースについて概要を説明してきました。

SOLID原則はデザインパターンを学ぶための基礎となります。
この原則を頭の片隅に置いて、「6 Factory」以後のデモシーンを触ると理解度も変わってくるのではないでしょうか?

今回の記事ではSOLID原則までの紹介でしたが、次回以降は各デザインパターンとデモシーンでどのように実装されているのかについて説明していきたいと思います。

 

【雑記】秋に購入したゲームと進捗報告

遊べないゲーム

以前から発売を楽しみに待っていたタイトルがいざ購入してみるとフリーズの嵐でまともに遊べない皆さんこんにちは。
PS4版の「TUNIC」という作品なんですが、ゲームを開始すると早々にフリーズ、暫く待つと解除されますが、2,3分遊んでいるとまたフリーズ、以後ずっとその繰り返しでゲームの楽しさよりイライラが先立ってコントローラーを投げそうになります。

まともにテストしたのか開発者を問い詰めたい所ですが、「PS4 TUNIC フリーズ」で検索しても何もヒットしないし、Twitterでもそれらしき事を呟いている人は見当たらないので私だけの現象かもしれない。
(まぁ「TUNIC」自体の記事が少ないけど)

 

いい加減しつこいゲーム

「TUNIC」と同時に「信長の野望」の最新版「新生」も購入しました。

シリーズ全てを遊んだわけではないのですが無印(PKでない)バージョンとしては出来が良いと思います。
このシリーズは「天道」辺りからRTS(リアルタイムストラテジー)色が強くなっていきましたが、本作もその流れのまま、内政よりも行軍の方を優先させるゲームデザインで、自国の規模が大きくなると内政面は殆どCOMに委任してどこの城をどの順番で攻めるか、どこに兵力を集中させて運用するか、がメインになっていきます。
昔からこのシリーズは小勢力の自国を中程度の規模に拡大させるまでが面白さのピークで、その後は周りの勢力を潰していくだけの作業と化すのですが、今回は作品はCOM側の勢力拡大が早いので終盤まで緊張感をもって楽しむことができます。

ただ、このCOMがいい加減しつこい。

蟻の行列みたいに軍勢が列を成してこちらの城に攻め込んできます。そしてそれを逐一撃破しても何度も出陣を繰り返してくるので、ついには根負けしてしまいます。本当にしつこい。
こちらが相手方の城を攻める時も同じです。城を包囲していると次から次へと他の城から兵を送り出すので、多方面に同時に攻めないと城を奪うことはできません。
ここら辺はアプデで改善してほしいところです。

 

何が変わったのか分からないゲーム

元々サッカーゲームが好きで毎年のように「FIFA」シリーズを購入しているのですが、今年も例にもれず最新版の「FIFA23」を購入しました。

この手のゲームは新規ユーザー向けというよりは私のように惰性で購入するコアユーザー向けなので最新版だからといって大きく仕様が変わることはないのですが、今年の最新版はいつも以上に前年版と何が変わったのか分からない。
氷の上を滑っているようなプレイヤーの挙動も、無駄にドリブルをこねくり回す敵COMの動きもほとんど同じ、攻略法も同じで足の速い選手を裏抜けさせるだけで勝てる、あまりにも変わってないのでデータをただ入れ替えただけじゃないの?と疑ってしまうほど。
むしろJリーグのライセンスを更新しなかった所為でJリーグ未搭載となり劣化したまである。日本代表も相変わらず搭載されないし。
フルプライスのサッカーゲーム自体がもうそろそろ潮時なのかもしれないですね。

 

進捗報告(ここから本編)

前回(7月)の進捗報告では左右に白黒のシャッターで閉じられた部屋があるステージができたことを紹介したのですが、ここに来てようやく白シャッター側の部屋に設置するパズル群が完成しました。

 

これまでは下のようにパズルのピースを回転させるだけ、あるいは並べ替えるだけの静的なパズルが主だったので、このステージでは動きのあるパズルを幾つか取り入れてみました。

 

最初に色々なギミックと連動するパズル

下のギア部分の回転と上の天体模型、右側のランプの点灯には相関関係があるので、プレイヤーにそれらを見てどんな関係があるのか推測してもらう、というパズル。

 

こちらも色々なギミックが連動するパズル

 

こちらも同様ですが、こちらはギミックの相関関係を一見で理解するのは難しいかもしれません。

 

他にも落ちゲーっぽいパズル

 

昔unityroomに投稿した作品も登場します。

 

これで白シャッター部屋のパズルは一通り完成したのですが、黒シャッター部屋の方は未着手のままです。
ゲーム制作って本当に時間が掛かりますね・・・

◇プライバシーポリシー

●個人情報の利用目的

当ブログでは、メールでのお問い合わせ、メールマガジンへの登録などの際に、名前(ハンドルネーム)、メールアドレス等の個人情報をご登録いただく場合がございます。

これらの個人情報は質問に対する回答や必要な情報を電子メールなどをでご連絡する場合に利用させていただくものであり、個人情報をご提供いただく際の目的以外では利用いたしません。

●個人情報の第三者への開示

当サイトでは、個人情報は適切に管理し、以下に該当する場合を除いて第三者に開示することはありません。

・本人のご了解がある場合
・法令等への協力のため、開示が必要となる場合

個人情報の開示、訂正、追加、削除、利用停止
ご本人からの個人データの開示、訂正、追加、削除、利用停止のご希望の場合には、ご本人であることを確認させていただいた上、速やかに対応させていただきます。

アクセス解析ツールについて

当サイトでは、Googleによるアクセス解析ツール「Googleアナリティクス」を利用しています。

このGoogleアナリティクスはトラフィックデータの収集のためにCookieを使用しています。このトラフィックデータは匿名で収集されており、個人を特定するものではありません。
この機能はCookieを無効にすることで収集を拒否することが出来ますので、お使いのブラウザの設定をご確認ください。

●免責事項

当サイトからリンクやバナーなどによって他のサイトに移動された場合、移動先サイトで提供される情報、サービス等について一切の責任を負いません。

当サイトのコンテンツ・情報につきまして、可能な限り正確な情報を掲載するよう努めておりますが、誤情報が入り込んだり、情報が古くなっていることもございます。

当サイトに掲載された内容によって生じた損害等の一切の責任を負いかねますのでご了承ください。

●プライバシーポリシーの変更について

当サイトは、個人情報に関して適用される日本の法令を遵守するとともに、本ポリシーの内容を適宜見直しその改善に努めます。

修正された最新のプライバシーポリシーは常に本ページにて開示されます。