スプライト構造体

クマレーザー発射!

最近、不真面目な更新が多いので、
今日は真面目路線で、私が使っているタスクシステムもどきの解説をします。
 
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で手抜きしています…。