Game Programming Patternsをよんで【Siv3D機能と対比しながら】

Game Programming Patterns 感想

 あくまで個人的な意見です。

総評

 ゲームプログラミング特にデザインパターンについて、著者の長年の経験から得た知識を元に使い時、使用方法、実装、ヒントなどについてとてもよくまとめられていたと思う。
 ゲームプログラミングによく使用するデザインパターンについて全体像を知りたいと思う人にはとてもお勧めだと思う。
 PC、専用ハード問わず使用できる知識についてかかれていたのが印象的だった。
 内部の実装については、C++で書かれていたがあくまで細かい部分にこだわることなく、クラス相互の関係などを分かりやすくまとめるためにコードを使用していたため、C++の知識がなくても読める内容になっていたと思う。
 4000円だして買う価値がある本。ただし、高いので図書館などにあるならそれでとりあえずはいい、積み本があるならまずそれをどうにかしたほうがいいかもしれません。(ちなみに私は、蟻本、チーター本、TLE(ヅ)本、応用情報技術者教本をつんでいます。)

注意

 ここに登場するコードは基本、C++&Siv3Dです。結構ポインタが出てきます。少し前の私のようにポインタは即危険という人間もいるはずですが、それならunique_ptr,optionalを使いましょう。しかし、この本をよんでからポインタは危険だけど便利だなとも思い始めました。

章ごとのまとめ(メモ)

 印象に残ったことをまとめます。
 かなり自分流に解釈しているため、実際の内容とは乖離していることが多々あります。

Part01

イントロダクション
良い設計とは、
1、部分的な変更が全体に影響を及ぼさないようになっている。(分離)
=>これにより同時に考えることをすくなくできる。
=>設計にかける労力は、必ず回収できる程度にしておく。(ライブラリ、エンジンにこだわりすぎて何を作りたかったのか忘れている開発者は多い)
2、コードの抽象化、実行速度、変更可能性
=>部分的に対立したこの3つのバランスが取れている。(抽象化しすぎると、実行速度が落ちて、実行速度を優先しすぎると変更しずらくなる)
3、完璧な正解はない=>数々の誤答があるだけ
4、迷ったら、吟味された単純なコードにしておけば良い。

Part02

コマンド。Command
入力に関するパターン。
if(Input::KeyA.clicked);
if(Input::KeyB.clicked);
だれでも書いたことがあるはず。
これを、
Command* command = handler.input();
if(command){
command->execute(actor);
}
とすれば全てhandlerに隠蔽できる。
input(){
if(Input::KeyA.clicked)return buttonA;
if(Input::KeyB.clicked)return buttonB;
return NULL;
}
class buttonA : public Command{
virtual void execute(Actor actor) { actor.jamp() };
}
冗長だよね。そう冗長、きちんと抽象化(隠蔽)する価値があるのか考えないといけない。
ちなみにこれはすぐexecuteしているけど。queueに積めば、まとめて実行するとか。
stackに積めば、
1、なにがいつ実行されたか分かる
2、わかるから打ち消しの動作(戻る)が実装できる。現在の実行状況を表すフラグを戻せば良い
3、その打ち消しを打ち消し(再実行)が実装出来る。同、進めれば良い
とかいろいろメリットを享受できる。

Part03

フライウェイト。Flyweight
まんま、Assetです。Ryoさんありがとうございます。
ゲーム内で、重くて(画像など)使いまわせるデータをクラスごとに持たせる必要はないよね。
ただ、Assetの問題は、いろいろなところからアクセスできるのでどこで書き換え(Register)られたかがわからないところ。
これは、どこからでも読めるようにした弊害。プログラマーが気をつけなければいけないから初心者使うなというのも納得。(特に並列プログラミングの際)
これは、FlyweightではなくSingletonの問題です。

Part04

オブザーバー。Observer
監視者。
業績の達成などに便利。コードのいろいろなところ、(肉を焼くところ)(魚をとるところ)(移動処理)などに業績達成チェックなんていちいち入れてられないとおもったら使いどころかも。
class Observer{
virtual void onNotify(Event event) = 0;
}

class Subject{
vector observers;
}

class ItemBox : Subject {
void enterItem(){
//アイテムを入れたときそれをobserverに送信。
}
}

class Achievements : public Observer {
virtual void onNotify(Event event){
//アイテムボックスに何が入ったかを入手しはじめてのものなら実績を解法。
}
}

これも、使いどころには気をつけよう。C#にはデフォルトで似たような機能があるらしい。Siv3Dでは、UIまわりに使っていそう。

Part05

プロトタイプ。Prototype
たくさんの種類の敵を各種類大量に用意するとき使う。
RPGで使いそう。
jsonで定義した敵データを読み込んで、クラス化とかもできるっぽい。

Part06

Singleton
まともにみえるグローバル変数
グローバル変数を多用しちゃいけない理由はみんなご存知。
どうしても使いたいときはSingletonより。
static変数を使うのもありかも。(定義が簡単なため)
利点、インスタンスの生成を遅延できる。

Part07

ステート。State
有限状態機械、プッシュダウンオートマトン
AIでつかう。上の二つでググレばOK.

Part08

ダブルバッファ。
みんな大好き。
MY豆知識。コンソールウインドウでやるとコンソールウインドウでテ○リス、イン○ーダーゲーム、パッ○マンぐらいは作れる。色も頑張ればつけれる。
丸ごと入れ替えるのと、ポインタ使う方法がある。

Part09

ゲームループ。GameLoop
System::Update()。Ryoさん本当にありがとうございました。
何が凄いって、60FPSに自動でなること。(FPSは各自ググル)
ならないなら重い。(フレームごとに移動量を設定すると環境によってゲームが動作を変える地獄へ)


自作でゲームループを作るときに60FPSならないときは、対処法が3パターンある。
1、あきらめる。(そういうもんだ仕様)
2、60FPSにするために、遅延時間を調節する。
3、フレーム依存のコードから秒数依存にする。(windowsはタイマーが正確に取れないため、5msほど誤差がでる。オススメしない)

Part10

更新メソッド。UpdateMethod
HamFrameWorkのSceneManager。
updateとdrawがある。以上。

Part11

バイトコード。ByteCode
キャラクターや魔法、ゲーム要素の振る舞いをコードではなく外部に書き込みそれを読み込む。
ミニインタプリタ言語を自作して、それでゲーム要素の振る舞いを書く。
利点、(敵キャラクターを例にとって)
攻撃、魔法攻撃、回復。などと振る舞いを別ファイルに書いただけでゲーム内のキャラクターの振る舞いが変わるとコードをいちいちコンパイルしたりデバッグする手間がない。
欠点。
~~以下なら、以上ならとか複雑な振る舞いを表現しずらい。
かといって、表現しやすくするとセキュリティーホールになりかねない。(例えばメモリをいじれるようにしてしまったら。悪意のある誰かに振る舞いファイルを置き換えられたら恐怖)
凝ると一気に複雑化、よく考えよう。

Part12

サブクラスサンドボックス。Subclass Sandbox
データ書き換え、描画、ユーティリティ関数を全て用意したクラスを準備してprivateにして、そのクラスを継承して使えば外部のクラスからいろいろ機能を変な風に使われなくなる。(グローバール関数よりまし)
問題、全てそろえたそのクラスのとり回しが悪すぎる。分離もクソもない。

Part13

型オブジェクト。Type Object
継承機能の代替。
継承の変わりに、クラスをメンバとして持つ。
メンバのクラスは、関数の参照などを入れておけるクラス。
独自に継承などの機能に変わる機能を作ったりとかもできる。
継承を使わずに、関数の変更などを”動的”に出来る。動的に重要。
利点
継承は、コードの依存関係を強くするため。この方法の方がとり回しがいい事もある。つよい柔軟性がある。
欠点
使い道が分からない。(自身の力量不足)

Part14

コンポーネント。Component
これ重要。今ナウい。いろんなゲームエンジンで使ってるし、波がきてる。Unityね。
ぜひ、コンポーネント指向で調べてみて。
class GameObject{
private:
InputComponent* input;
PhysicsComponent* physics;
GraphicsComponent* graphics;
}
class Hero : public GameObject{
}
class Enemy : public GameObject{
}
ゲームのオブジェクトはただの箱。
そこに接続されたコンポーネントで無限の可能性と取り回し。
コンポーネント共有もOK。
端的に言って凄い。

Part15

イベントキュー。EventQueue
うっ、、WinAPI。悪夢が
ゲーム内のイベントを(敵が死んだ、何を入手など)queueにいれていって。
それを、だれが受け取って処理するかは関係ない。
敵が死んだのは、グラフィッククラスが受け取るかもしれないし。
入手は、アイテムボックスクラスが受け取るかもしれない。
どこに送るかは知らなくて入れていけばいいだけ。

Part16

サービスロケーター。ServiceLocator
リクエストを送ると(サウンドクラス頂戴など)、サービスを行うクラスのポインタを返してくれる。
利点、
実装の分離。(面倒なだけではと思うあなた)
デバッグ時などには、NULLを渡しておけば簡単にそのサービスが動かなかったときのテストが出来たり。
デバッグ時サウンドクラスを呼ばれたときにNULLを返すようにしておけば単調な音楽を聴かされなくて済む。(音量0、イヤホンipodでいいのでは)

Part17

データ局所化。
いままではなんだったんだ、裏切られた。
ポインタは実行効率を悪くする。
ポインタの先をたどらなければいけないから。
0->1->2->3->4->5>6->7
とすると、メモリ中(主記憶領域はレジスタより100倍遅い)
を飛び回るのでよくない。
更にいうなら、継承や仮想関数もよくない。
更に言うなら、クラスも、クラス関数のポインタだからよくない。
、、、
本もいってる、”必要がなければ気にするな。時間の無駄”
配列は順々に並んでいるため、実行効率が高いらしい

Part18

ダーティフラグ。Dirty Flag
Dirty Byteは響きがよくないらしい?
計算結果のキャッシュ。
!!!!!みんな大好きDP(動的計画法)!!!!!!
を、フラグを持たせて管理しようという話らしい。
親フラグが立ってたら相対的に存在する場合子供も再計算される。
child |= parent。ハイ。
相対位置座標などに便利。

Part19

オブジェクトプール。ObjectPool
ガベージコレクタがない環境で。
自作、メモリ管理の方法。
メモリのフラグメンテーションを防げる。
データ局所化と似たような効果。

Part20

空間分割。Spatial Partition
格子=>1次元ではバケットソート
四分木(八分木)=>1次元ではトライ木
以下、1次元では2分探索木らしい。
BSP(バイナリ空間分割)
k-d木
バウンディング・ボリューム・ハイアラーキー
でググレ。正直読み飛ばした。(分からん)

総合的には、理解度30%ですかね。
まあ、メモとして残しておきます。