エンティティシステム
■ プレイヤーがミサイルを発射する例
ENTITY* FireProjectile ( char* className, ENTITY* entshooter, ENTITIY* parent )
{
ENTITY* entProj; // 発射物エンティティ
VECTOR3 pos; // 射手/発射物の位置
VECTOR3 dir; // 射手の方向ベクトル
VECTOR3 velocity; // 射手の速度ベクトル
// 『parent』のシーングラフの子として発射物を作成
entProj = EntCreate(className, parent, 0, 0);
if ( entProj)
{
// 射手から位置・方向・速度を取得
EntSendMessage( entShooter, EM_GETPOS, (int)&pos, 0 );
EntSendMessage( entShooter, EM_GETDIR, (int)&dir, 0 );
EntSendMessage( entShooter, EM_GETVELOCITY, (int)&vel, 0 );
// 新しい発射物の設定
EntSendMessage( entProj, EM_SETPOS, (int)&pos, 0 );
EntSendMessage( entProj, EM_SETDIR, (int)&dir, 0 );
EntSendMessage( entProj, EM_SETVELOCITY, (int)&vel, 0 );
EntSendMessage( entProj, EM_SETOWNER, (int)&entShooter, 0 );
EntSendMessage( entProj, EM_START, 0, 0);
return (entProj);
}
}
※ C++の実装では、(int)のキャストは正しい型付きパラメータを引数とする仮想関数により消える。
■ 全てがエンティティ
全てをメッセージで制御可能なエンティティとして考えること!
発射物も、地形も、ゲームスクリプト(アルゴリズム)といったもの全てを統一して考える。
弾丸、地形、スクリプトエンティティの様々なメッセージへの応答の仕方
弾丸 地形 スクリプト
更新 処理 無視 処理
描画 処理 処理 無視
衝突チェック 処理 無視 無視
衝突テストへの応答 無視 処理 無視
■ システムの要素
- エンティティメッセージ: エンティティの通信の仕方を定義する
- エンティティコード : エンティティクラスを実装するコードとデータ
- クラスリスト : 登録されたエンティティクラスのリストの保守
- エンティティマネージャ: エンティティの作成、エンティティツリーの管理、
1つあるいは複数のエンティティへのメッセージ送信のサポート
■ エンティティメッセージ
エンティティは必要でなかったり、理解できないメッセージを安全に無視できなければならない。
typedef int (ENT_PROC) ( ENTITY* ent, EM message, int var1, int var2 );
メッセージリストの例
この列挙型に1つ値を加えるだけで、新しいメッセージを追加できる。
enum EEntMessageTag
{
// クラス処理
EM_CLSINIT, // クラスの初期化 var1=char* データパス
EM_CLSFREE, // クラスの解放
EM_CLSNAME, // クラス名をvar1にコピー
// 作成と破棄
EM_CREAT, // エンティティの作成
EM_START, // エンティティを『ON』にする
EM_SHUTDOWN, // エンティティを上品に破棄
EM_DESTOROY, // エンティティを即座に破棄
// 標準アクション
EM_UPDATE, // var1 = int 経過秒数
EM_DRAW, // _normal_ レンダリング
// データアクセス
EM_SETPOS, // var1 = VECTOR3* 位置
EM_GETPOS, // VECTOR3 位置をvar1にコピー
...
};
■ エンティティコード(Cベース)
struct missileTag
{
char name[ENT_NAME_LEN]; // 名前
VECTOR3 velocity; // 速度
VECTOR3 position; // ワールド空間位置
VECTOR3 forceAccum; // 力の累算器
MATRIX4x4 matModel; // 姿勢行列
int engineRunning; // TRUE: エンジンON
// 他に必要なメンバがあれば以下に追加する
};
static MODLE mdlMissile;
static SOUND launchSount;
static float thrust = 10000.0f;
int MissileProc( ENTITY* entity, EM message, int var1, int var2)
{
// コンテナからエンティティのクラス データを取得
MISSILE* e = ( (MISSILE*)entity->data );
// ミサイルエンティティに関連する全てのメッセージを処理
switch(message)
{
// クラス操作
case EM_CLSNAME : strcpy((char*)var1, className); return(TRUE);
case EM_CLSINIT : return( ClsInit((char*)var1) );
case EM_CLSFREE : return( ClsFree() );
// 作成と破棄
case EM_CREATE : return( Create(entity) );
case EM_SHUTDOWN: return( Shutdown(THISENT) );
case EM_DESTROY : return( Destroy(entity, THISENT) );
case EM_START : return( Start(THISENT) );
// 標準アクション
case EM_UPDATE : return( Update(THISENT, INT2FLOAT(var1)) );
case EM_DRAW : return( Draw(THISENT) );
// データアクセス
case EM_SETNAME : strncpy(THISENT->name, (char*)var1, ENT_NAME_LEN); return(TRUE);
case EM_GETNAME : strcpy((char*)var1, THISENT->name); return(TRUE);
case EM_SETPOS : V3Copy(&THISENT->position, (VECTOR3*)var1); return(TRUE);
case EM_SETVEL : V3Copy(&THISENT->velocity, (VECTOR3*)var1); return(TRUE);
case EM_SETDIR : return( SetDirection(THISENT, (VECTOR3*)var1) );
// Win32のDefWindowProcと同様の粗野な形の継承をサポートする。
default : return( DefEntityProc(entity, message, var1, var2) );
}
return(TRUE);
}
■ クラスリスト
外側の世界に対してはWin32のウィンドウクラスと同様にエンティティクラスはメッセージ処理関数だけを公開する。
テキスト名でクラスを作成して参照できるように、クラスリストはこれらのメッセージハンドラのリンクリストや
ベクトルを保持する。
struct entityClass
{
char name[64]; // 一意なクラス名
ENT_PROC* clsProc // クラスのメッセージ ハンドラ
struct entityClass* next // リスト内の次のクラス
};
登録はEntCreateClass関数で行う。
1) エントリをクラスリストに追加し、EM_CLSNAMEメッセージをclsProcに送り、クラスに付随するテキスト名を取り出す。
2) EM_CLSINITメッセージをclsProcに送り、リソースのロードのような、1回限りの初期化を行う機会をクラスに与える。
ゲームで使う様々なクラスの初期化は、次のようなコードで起動時に行えば良い。
// par1: エンティティがデータを見つけるパス
void GameInitClasses( char* dataPath )
{
EntCreateClass( playerProc, dataPath, 0 ); // プレイヤーを登録
EntCreateClass( missileProc, dataPath, 0 ); // ミサイルを登録
// ... 以下初期化が続く
}
void EntDestoryAllClasses()
{
// EM_CLS_FREEメッセージを1つ又は全ての登録クラスに送って後始末を楽にする。
}
■ エンティティマネージャ
エンティティマネージャは個々のエンティティの作成と削除を行い、それらを含むリストやツリーを管理する。
エンティティのメッセージ処理関数とプライベートなデータへのポインタを、単純な汎用コンテナ構造で
エンティティを参照する。
struct entityTag
{
ENT_PRPC* Proc; // メッセージ処理関数
void* data; // エンティティのカスタムデータ
int guid; // ネット同期用の一意なID
struct entityTag* parent; // 親またはNULL
struct entityTag* prevSibling; // 前の兄弟またはNULL
struct entityTag* nextSibling; // 次の兄弟又はNULL
struct entityTag* child; // 最初の子
};
<エンティティマネージャの鍵となる操作>
ENTITY* EntCreateEntity( char* className, ENTITY* parent, int var1, int var2 );
1)この関数はclassNameをクラスリストで参照し、見つかったら新しいENTITY構造体を
作成し、parentの子としてエンティティツリーに付加する。
2)クラスのメッセージ処理関数Procへのポインタを保存し、直ちにvar1,var2パラメータ
でProcにEM_CREATEメッセージを送る。CREATEハンドラで、エンティティはクラスデータ
用の領域を確保し、dataでそれを指す。
int EntDestoryEntity( ENTITY* ent );
DESTOROYメッセージに応じて内部で呼び出され、ENTITYコンテナ構造体を開放して、
ツリーから取り出す。実際にエンティティを破棄するには、EM_DESTOYメッセージを
送るだけだ。
int ENtSendMessage( ENTITY* ent, EM message, int var1, int var2 );
与えられたパラメーターでメッセージを個々のエンティティに送る
int EntSendMessageGuid( int guid, EM message, int var1, int var2 );
メッセージをエンティティポインタではなく、guid経由で個々のエンティティに送る。
これはポインタが意味をなさない[[ネットワーク]]同期に役立つ。
void EntSendMessagePre( ENTITY* ent, EM message, int var1, int var2 );
メッセージをエンティティとその全ての子に前順序で送る。メッセージの処理後にFALSE
を返すエンティティがあれば、再帰を中断する。
エンティティツリーがシーングラフや衝突ツリーとしても使えるように、これらは階層
カリングをサポートする。これに似た、中断しない関数も役に立つかもしれない。
拡張案
・エンティティを名前で探したり、エンティティツリーを並び替えたり、節点ごとに
コールバックを発行するエンティティを反復する機能をサポート。
■ メッセージによるゲームループ
ほとんど全ての『ゲームコード』は、結局エンティティ自身の内部に存在するので、ループは
主としてメッセージ配送者になる。
static void GameProcessInput()
{
// map input events, then send appropriate events to the entity(ies)
// currently under human control
GetInputEvents(&inputEvent); // イベントデータの入力
MapInputEventsToGameEvents(&inputEvent, &gameEvent); // マップの衆力
EntSendMessage(entPlayer, EM_USERINPUT, (int)&gameEvent, 0); // プレイヤー
}
static void GameUpdateWorld()
{
EntSendMessagePre(entWorld, EM_UPDATE, GetElapsedMs(), 0);
EntSendMessagePre(entWorld, EM_POSTUPDATE, 0, 0);
EntCleanup(entWorld);
}
static void GameDraw()
{
// 全てのエンティティに自分をレンダリングさせ、オーバーレイを行う
EntSendMessagePre(entWorld, EM_DRAW, 0, 0);
EntSendMessagePre(entWorld, EM_DRAWSHADOW, 0, 0);
EntSendMessagePre(entWorld, EM_DRAWOVERLAY, 0, 0);
// デバッグモードなら、エンティティの追加情報を描画する
if (debugMode)
EntSendMessagePre(entWorld, EM_DRAWDEBUG, 0, 0);
// 現在選択中のエンティティの編集状態を表示
if (editMode)
EntSendMessage(entBeingEdited, EM_DRAWEDIT, 0, 0);
}
最終更新:2006年11月18日 17:01