スプライト構造体
最近、不真面目な更新が多いので、
今日は真面目路線で、私が使っているタスクシステムもどきの解説をします。
C++でやるなら、本当ならクラスでやるべきなのですが、
モノグサな私は、構造体でスプライトを定義しています。
(…不真面目じゃん)
まずはユーティリティ関数の定義。
// オフセットを行列に変換する関数(D3DXMatrixTranslation)のラップ inline D3DXMATRIX* Vec3ToMatrix(D3DXMATRIX *mat, D3DXVECTOR3 *vec) { return D3DXMatrixTranslation(mat, vec->x, vec->y, vec->z); }; // スプライト更新のダミー関数 template<class T> inline BOOL UpdateDummy(T &p) { p.pos += p.mov; p.age--; if(p.age < 0) { p.alive = FALSE; return FALSE; } return TRUE; }; // スプライトコリジョン(境界球) template<class T> inline BOOL CollisionSphereSprite3(T &p1, T &p2) { FLOAT r = p1.rdi + p2.rdi; if(r*r > D3DXVec3LengthSq(&(p2.pos-p1.pos))) { return TRUE; } return FALSE; };
例えば、当たり判定を境界球でやるとした場合は、こんな感じです。
そしてそして、ここからがスプライト構造体です(やっとかい)
/** * スプライト3D構造体 */ struct TSprite3 { D3DXVECTOR3 pos; // 座標 D3DXVECTOR3 mov; // 移動量 FLOAT age; // 年齢 FLOAT dir; // 方向の角度 FLOAT rdi; // 半径 BOOL alive; // 生存フラグ char work[256]; // ワークエリア(※1) BOOL (*Update)(TSprite3 &sprite); // 更新関数ポインタ(※2) TSprite3( D3DXVECTOR3 p = D3DXVECTOR3(0, 0, 0), D3DXVECTOR3 m = D3DXVECTOR3(0, 0, 0), FLOAT a = 0, FLOAT d = 0, FLOAT r = 0, BOOL l = TRUE) { SetParam(p, m, a, d, r, l); }; void SetParam(D3DXVECTOR3 p, D3DXVECTOR3 m, FLOAT a, FLOAT d, FLOAT r, BOOL l) { pos = p; mov = m; age = a; dir = d; rdi = r; alive = l; Update = UpdateDummy; }; // 境界球を描画 void DrawBoundingSphere(LPDIRECT3DDEVICE9 pD3DDevice) { pD3DDevice->SetRenderState(D3DRS_LIGHTING, FALSE); LPD3DXMESH pMesh; if(FAILED(D3DXCreateSphere( pD3DDevice, rdi, 20, 20, &pMesh, NULL))) { return; } D3DXMATRIX mWorld; Vec3ToMatrix(&mWorld, &pos); pD3DDevice->SetTransform(D3DTS_WORLD, &mWorld); pMesh->DrawSubset(0); pMesh->Release(); pD3DDevice->SetRenderState(D3DRS_LIGHTING, TRUE); } };
※1のワークエリアとは、個別に拡張を行いたい場合に利用する領域です。
(オブジェクト指向の継承みたいなものです)
※2の更新関数ポインタが処理関数となり、自由に入れ替えを行うことができます。
このスプライト構造体を使う場合には、スプライトのワークエリア・更新関数と、
シーンクラスにスタティックなvectorをタスクリストとして用意しておきます。
(エフェクト用の単純なパーティクルなど、拡張が必要ない場合には、
このまま使うことができます)
まずは、スプライトのワークエリア・更新関数ポインタ。
/** * 敵ワークエリア */ struct WORK_ENEMY { FLOAT timer; // タイマー FLOAT scale; // 描画倍率 };
ワークエリアでは、拡張として「タイマ」と「描画倍率」を用意しました。
// 更新関数 BOOL UpdateEnemy(TSprite3 &sprite) { WORK_ENEMY *pWork = (WORK_ENEMY*)&sprite.work; // 波っぽい移動 pWork->timer++; if(1 <= pWork->timer && pWork->timer < 20) sprite.mov.x += 0.3; else if(20 <= pWork->timer && pWork->timer < 39) sprite.mov.x -= 0.3; sprite.pos += sprite.mov; if(!sprite.alive) { // 爆破されたので墜落(縮小)していく pWork->scale -= 0.05; if(pWork->scale < 0) return FALSE; } return TRUE; } // 初期化 BOOL InitEnemy(TSprite3 &sprite) { sprite.SetParam(D3DXVECTOR3(0, 0, 0), D3DXVECTOR3(0, 5, 0), 1000, 5); ((WORK_ENEMY*)&sprite.work)->timer = 0; ((WORK_ENEMY*)&sprite.work)->scale = 1; sprite.Update = UpdateEnemy; return TRUE; }
InitEnemyを更新関数を実行時にUpdateに入れてやります。
InitEnemyで、
sprite.Update = UpdateEnemy;
としているのがポイントですね。
これで、状態変数を持つ必要がなくなります。
(まあ、Jump処理っぽいですが…)
と、その前にシーンクラスの定義。
#include <vector> using namespace std; class CSceneXXX { private: static vector<TSprite3> m_enemyList; // 敵リスト static vector<TSprite3> m_tamaList; // 自弾リスト ・ ・ ・ public: static vector<TSprite3>* GetEnemyList() {return &m_enemyList;} static vector<TSprite3>* GetTamaList() {return &m_tamaList;} void Render(LPDIRECT3DDEVICE9 pD3DDevice); ・ ・ ・ }; ・ ・ ・
スタティックにしておいて、どこからでもタスクの追加・削除ができるようにします。
(…なんてモノグサな作りだ…(´Д`;
実行時はこんな感じです。
void CScene::Render(LPDIRECT3DDEVICE9 pD3DDevice) { ・ ・ ・ if(敵出現) { // 敵生成 TSprite3 enemy; enemy.Update = InitEnemy; enemy.Update(enemy); // 初期化だ! m_enemyList.push_back(enemy); } ・ ・ ・ // 敵移動・描画 for(UINT i = 0; i < m_enemyList.size(); i++) { if(!m_enemyList.at(i).Update(m_enemyList.at(i))) { // 敵消滅 // ここで爆発タスクを作るのもありどすえ m_enemyList.erase(m_enemyList.begin()+i); continue; } D3DXMATRIX mEnemy; Vec3ToMatrix(&mEnemy, &m_enemyList.at(i).pos); D3DXMATRIX mScale; WORK_ENEMY *work = (WORK_ENEMY*)&m_enemyList.at(i).work; D3DXMatrixScaling(&mScale, work->scale, work->scale, work->scale); // 描画 m_enemyList.at(i).DrawBoundingSphere(pD3DDevice); //DrawEnemy(pD3DDevice, 0, &(mScale*mEnemy)); for(UINT j = 0; j < m_tamaList.size(); j++) { // 自弾と敵機との当たり判定 if(CollisionSphereSprite3(m_tamaList.at(j), m_enemyList.at(i))) { // 自弾が敵機にあたった! m_tamaList.erase(m_tamaList.begin()+j); m_enemyList.at(i).alive = FALSE; break; } } } }
モノグサなので、Render()の中では「描画処理」、
更新関数の中では「データの更新」、
というように処理を分けています。
、、、vector::push_back()のようなメモリの動的確保は、パフォーマンスの低下や動作が不安定になるので、
市販ゲームではNGですね。
…でも、まあ、、、今のところは楽ちんなvectorで手抜きしています…。