任何一个rpg游戏都离不开战斗,那么就不得不用粒子系统,一个好的粒子系统可以让游戏的体验上升好多倍,我自己的Demo里粒子特效做的实在不怎么样,但是我还是班门弄斧下为初学者介绍下一个简单的粒子系统是如何创建的。
class CDXParticle
{
public:
class sParticle;
void Reset(CDXWindow *pWin,bool bSpriteEnable=true,bool bScaleEnable=true);
void AddParticle();
virtual void OnAdd(sParticle* pParticle){}
virtual void Update(){}
virtual void Render();
void SetParticleSize(float iLen);
protected:
struct sParticle
{
D3DXVECTOR3 pos;
D3DXVECTOR3 normal;
D3DCOLOR col;
float x,y;
static const DWORD FVF=D3DFVF_XYZ|D3DFVF_NORMAL|D3DFVF_DIFFUSE|D3DFVF_TEX1;
};
DWORD FToD(float f){
return *((DWORD*)&f);
}
vector<sParticle> m_ParVector;
LPDIRECT3DDEVICE9 m_pDevice;
};
在本文的粒子系统实现中,我用的是DX龙书介绍的点精灵,这个在DX9里面得到了很好的支持,而且编写起来也相对简单了很多,我在这里用一个vector来存储点精灵,那么渲染的时候我就用DrawPrimitiveUP函数来做渲染,在AddParticle这个函数里会调用OnAdd这个虚方法,我们可以在这里为粒子做初始化操作。
void CDXParticle::Reset( CDXWindow *pWin,bool bSpriteEnable/*=true*/,bool bScaleEnable/*=true*/ )
{
m_pDevice=pWin->GetD3dDevice();
m_ParVector.clear();
m_pDevice->SetRenderState(D3DRS_POINTSPRITEENABLE,bSpriteEnable);
m_pDevice->SetRenderState(D3DRS_POINTSCALEENABLE,bScaleEnable);
m_pDevice->SetRenderState(D3DRS_POINTSCALE_A,FToD(0));
m_pDevice->SetRenderState(D3DRS_POINTSCALE_B,FToD(0));
m_pDevice->SetRenderState(D3DRS_POINTSCALE_C,FToD(1));
}
这个便是粒子系统的初始化操作,因为SetRenderState不支持float类型,所以我们需要转化成DWORD类型才可以。有了这些基础,我们怎么去制作自己的粒子系统呢,比如说子弹的发射。
class CMyBullet:public CDXParticle
{
public:
void Create(CDXRPG* pObj);
void OnAdd(sParticle* pParticle);
void Update();
void Render();
private:
CDXRPG *m_RPG;
};
这个便是我们继承CDXParticle后的Bullet类,里面有一个我们游戏的主对象CDXRPG *m_RPG,那么我们如何去初始化一个子弹呢?
void CMyBullet::OnAdd( sParticle* pParticle )
{
pParticle->col=D3DCOLOR_XRGB(250,250,0);
pParticle->pos=m_RPG->m_Camera.GetAt();
D3DXVECTOR3 v(m_RPG->m_Camera.GetAt().x-m_RPG->m_Camera.GetEye().x,0,m_RPG->m_Camera.GetAt().z-m_RPG->m_Camera.GetEye().z);
D3DXVec3Normalize(&v,&v);
pParticle->x=30;
// pParticle->y=v.y;
pParticle->normal=v;
}
看上面的代码,初始化一个子弹,我们首先需要初始化他的起始位置,然后还有一个发射方向,最后有一个射程范围,这里有两个解决办法,第一种就是再定义一个结构体,里面存放这些数据,然后和新建立的粒子一一对应,相对有些繁琐但是扩展性很好,而我这里则使用了第二种,尽可能的在当前的粒子结构体里把数据往里面塞,大家可以看到,原本用作纹理坐标的x变成了射程范围,而
法向量则变成了发射的方向向量。然后我们再来看一下粒子的更新,粒子每过一段时间他的位置和状态都会发生改变,所以我们必须对粒子的数据更新。
void CMyBullet::Update()
{
for(int i=0;i<m_ParVector.size();i++)
{
sParticle *pBullet=&m_ParVector[i];
pBullet->pos+=2*pBullet->normal;
RECT r={1,1,1269,1270};
POINT p={pBullet->pos.x,pBullet->pos.z};
if(!PtInRect(&r,p))
{
m_ParVector.erase(m_ParVector.begin()+i);
}else
{
set<CDXEntity*> EntitySet;
float iHeight=m_RPG->m_Land.GetHeight(m_RPG->m_Camera.GetEye().x,m_RPG->m_Camera.GetEye().z);
if(pBullet->pos.y<=iHeight)
{
m_ParVector.erase(m_ParVector.begin()+i);
}else if((pBullet->x--)<=0)
{
m_ParVector.erase(m_ParVector.begin()+i);
}else if(m_RPG->m_OctNode.CheckCollideEntity(pBullet->pos,pBullet->pos,EntitySet))
{
m_ParVector.erase(m_ParVector.begin()+i);
CDXEntity *pEntity=(CDXEntity*)*EntitySet.begin();
CString StrID=pEntity->GetID();
map<CString,CDXRPG::sEnemy>::iterator it=m_RPG->m_EnemyMap.find(StrID);
if(it!=m_RPG->m_EnemyMap.end())
{
it->second.bAttack=true;
it->second.life-=10;
if(it->second.life<=0)
{
pEntity->Remove();
m_RPG->m_EnemyMap.erase(it);
}
}
}
}
}
}
上面便是对粒子位置的更新以及消失或者打中敌人的一些判断,最后,更新完以后,不要忘记渲染哦。
void CMyBullet::Render()
{
if(m_ParVector.size()>0)
{
D3DXMATRIX mat;
D3DXMatrixIdentity(&mat);
m_pDevice->SetTransform(D3DTS_WORLD,&mat);
m_pDevice->SetTexture(0,0);
m_pDevice->SetFVF(sParticle::FVF);
m_pDevice->DrawPrimitiveUP(D3DPT_POINTLIST,m_ParVector.size(),&m_ParVector[0],sizeof(sParticle));
}
}
这样,一个很简单的粒子系统便完成了,当然,如果要是要粒子的效果显示的更好的话,那不得不花不少功夫啊,不少大型的3d引擎里面都有粒子编辑器这个玩意,就是为了建立粒子特效而设立的东东。
本文有不足之处,还望各位多多指正。