ここ最近遊んだデモと言えば、PS3の『KILLZONE2』とXbox360の『Halo Wars』。
KZ2はコンシューマFPSとしては最高峰のグラフィックで、特にモーションはずば抜けていると言っていいでしょう。
GoW1みたいに色が薄い、と言うかモノクロ調なので派手さは感じませんが、さすがに待たせただけあるものにはなってます。
ただ、もっとカバーオブジェクトとか破壊できるものと思ってたんですが、全然破壊できず。
体験版だからなのか、そういうステージなのかはわかりませんが、ちょっとがっかり。
あと、操作性が悪すぎる。半分はPS3パッドの所為とは言え、半分は間違いなくゲームの所為。
カバーアクションとかもただしゃがんでるだけとしか感じられない。ボタン押しっぱなしなのも個人的に×。
リアリティを追求した結果かも知れませんが、私は遊びやすいゲームの方が好きです。
対照的に遊びやすかったのがHWの方。まさか、コンシューマRTSがこんなに遊びやすくなるとは思ってませんでした。
確かに所々「マウスとキーボードの方が」と思う部分はありますが、ほぼ不満がない出来映えです。
ゲーム自体もうまい具合に簡略化されていて、Halo世界の表現としても秀逸。
Haloは好きだけどRTSはちょっと…という人でも体験してみるといいと思います。
問題は、初心者には難しすぎて、上級者には簡単すぎるかもしれないと言うこと。
RTS初心者をうまく取り込めればいいですが、そうでなければ上級者に底が浅いと言われる可能性も否定できません。
しかし、体験版としては成功したのではないかと思います。
今日は思うところがあってGameProgrammingGemsの1章を中心に読んでいたんですが、Gems5の1.3 コンポーネントベースのオブジェクト管理がちょっと面白かったので簡単に紹介。
ゲーム中のオブジェクトを管理する伝統的な方法としては、たとえばCObjectみたいな基底クラスを用意し、すべてのオブジェクトはそこから派生させる、と言うものだと思います。
つまり、シーンはCObject*をvectorとかで管理するわけです。
問題は、あるオブジェクトに今までなかった機能を追加しようとする場合に発生します。
記事にも書いてありますが、たとえばアニメーションしないはずのオブジェクトがアニメーションすることになったとすると、そのオブジェクトは継承ツリーの別の場所に移動させる必要が出てきます。
また、その機能がそもそも存在しない場合、どこかのクラスにその機能を追加させ、そのクラスから派生しているすべてのオブジェクトにその機能の実装を求める必要が出てきます。
これは結構面倒ですし、ゲーム開発末期にそんな話が出てきたりするとかなり危険です。
そこで、機能ごとにコンポーネントを作成し、オブジェクトとコンポーネントを動的に関連づけることで継承による問題をなくしてしまおうというもの。
オブジェクト、コンポーネント、インターフェースの関係は以下のような1つのデータベースで管理されます。
struct SObjectManagerDB
{ SComponentTypeInfo mComponentTypeInfo[NUM_COMPONENT_TYPE_IDS]; std::set<EComponentTypeId> mInterfaceTypeToComponentTypes[NUM_INTERFACE_IDS];
std::map<CObjectId, IComponent*> mComponentTypeToComponentMap[NUM_COMPONENT_TYPE_IDS];
std::set<EComponentTypeId> mMessageTypeToComponentTypes[NUM_MESSAGE_TYPES]; }; |
オブジェクトとコンポーネントの関係は mComponentTypeToComponentMap で管理されます。
たとえば、キャラクタの体力を示すHPコンポーネントを考えます。
EnemyオブジェクトはPlayerオブジェクトのHPをチェックし、その行動を変化させるものと考えてください。
まず、Enemyオブジェクトは mComponentTypeToComponentMap からHPコンポーネントのIDに対応するmapを取得します。mComponentTypeToComponentMap[ CID_HP ] って感じで。
その中からPlayerオブジェクトIDに対応するコンポーネントインターフェースを取得します。
NULL以外が返ってくればコンポーネントは見つかったので、そこからHPの値を取得することができます。
また、メッセージディスパッチは mMessageTypeToComponentTypes から指定メッセージを受け取るべきコンポーネントIDのsetを取得し、やっぱり mComponentTypeToComponentMap からmapを受け取ります。
特定のオブジェクトならそのmapからコンポーネントを取得し、不特定の相手ならそのmapすべてのコンポーネントにメッセージを送るというわけです。
オブジェクトはコンポーネントという形の能力を持っていて、その能力に従ってメッセージを受けたり処理を行ったりすると言うわけです。
しかし、個人的に気になったのはsetやmapをかなり使っていると言うこと。
コンポーネントの数が増えれば mComponentTypeToComponentMap の数が増えます。
メッセージが増えれば mMessageTypeToComponentTypes の数が増えます。
どちらも10個くらい、とかならともかく、どんどん大きくなってきたらメモリ管理が大変そうだと思ったり思わなかったり。
っていうか、そもそもゲーム本体の中でsetやmapってそんなに使うかなぁ?
もちろん、ピンポイントで使用するならそれほど問題ないですが、この技術のように配列で大量に使用するっていうのはいまいち納得できないというか…。
技術的には面白いだけに、別の解決策は模索できないのかなぁと思ったりするわけで。
皆さんはどう思います?