摘要: 本文阐述了如何利用 C++ 的 Template 开发出方便组装,适合各种应用的 泛型粒子系统,开发过程中使用到了 Boost.Mpl 和 Boost.Random 。
关键字: 粒子系统,泛型编程, Boost
〇、引言
在各种 3D 应用中,粒子系统扮演着举足轻重的角色。雨雪的模拟、流水的模拟、火焰的模拟等等都离不开粒子系统。传统的粒子系统的开发很难同时满足各种各样的需要,只能针对特定的应用做特定的开发,有没有什么方法可以开发出适合各种应用的粒子系统呢?这让我们想到了求助 C++ 的 Template 。
一、 粒子结构
在传统的粒子系统中我们常看到这样的粒子结构的定义。
struct SParticle { tVector 3 m_vPos; tVector 3 m_vVel; tColor m_cColor; tReal m_rFade; tReal m_rLife; }; |
这样做的缺点就是整个粒子的结构被固定死了,无法适应不同的需要,针对不同类型的粒子就必须定义不同的结构。为了解决这个问题我们把粒子拆分成不同的部分,这样就可以按需要进行组装,并且针对粒子的特定部分制定的算法可以得到充分的复用。我们可以做如下拆分:
// 粒子位置 struct SParticlePos { tVector 3 m_vPos; };
// 粒子速度 struct SParticleVel { tVector 3 m_vVel; };
// 粒子颜色 struct SParticleColor { tColor m_cColor; };
// 粒子退色因子 struct SParticleFade { tReal m_rFade; };
// 粒子寿命 struct SParticleLife { tReal m_rLife; }; |
把粒子拆分以后我就需要一个组装机制。
// 粒子组装器 template < class _PartIter, class _PartsVector > classTParticlePartHolder : publicboost::mpl::deref< _PartIter >::type, , publicTParticlePartHolder< _PartIter::next > {};
template < class _PartsVector > classTParticlePartHolder< boost::mpl::end< _PartsVector >::type, _PartsVector > : publicboost::blank {}; |
这里用到了 Boost 的 mpl 库中的类型序列容器作为粒子组装器的 _PartsVector 模板参数,它可以是 boost::mpl::vector 或其它与之兼容的类型序列容器,用于容纳粒子个各个不同部分。 _PartsIter 模板参数是 _PartsVector 的一个跌代器,它指向了位于 _PartsVector 中的一个粒子部分。 TParticlePartHolder 是一个自递归继承的模板类,递归结束条件由一个偏特化的 TParticlePartHolder 确定。粒子组装器每一次递归继承一个 _PartIter 所指向的粒子部分(通过 boost::mpl::deref<> 获取),并把 _PartIter 向后移动一次传入下一次递归,直到 _PartIter 指向了 _PartsVector 容器的末端(继承一个空类 boost::blank )。
有了粒子组装器我们还需要一个名为 TParticlePolicy 的模板类把它包装起来。
// 粒子 template < class _PartsVector > structTParticlePolicy : publicTParticlePartHolder< boost::mpl::begin< _PartsVector >::type, _PartsVector > { typedef _PartsVector tPartsVector;
// 部分数 static const int s_ciNumOfParts = boost::mpl::size< _PartsVector >::type::value; } |
TPaticlePolicy 接受一个名为 _PartsVector 的类型序列容器,并以指向 _PartsVector 首的迭代器(通过 boost::mpl::begine<> 获取)和 _PartsVector 作为参数继承 TParticlePartHolder 。
现在就可以个把各个不同的粒子部分组装起来了。如组装一个具有位置、速度、颜色和寿命等属性的粒子我们可以这样写代码:
TParticlePolicy < boost::mpl::vector< SParticlePos, SParticleVel, SParticleColor, SParticleLife > > |
看到这里也许您还有一些不太清楚,让我们用一张继承结构图来说明:
现在我们已经可以很方便的组装我们所需要的粒子结构了,但任存在一个问题。每当我们需要加入一个新的粒子部分时必须自定义一个类似 SParticlePos 的结构,如果我们设计的是一个粒子的拆分部分非常多的系统这无疑是一件非常枯燥的差事。有没有什么方法可以解决呢?我们不妨定义一个泛型的粒子部分:
// 粒子部分 template < typename _Type > structTParticlePart { _Type m_Value; }; |
我们通过 _Type 模板参数来指定粒子部分的类型,比如原先的 SParticlePos 我们在使用 D3D 的平台上可以写成 TParticlePart< D3DXVECTOR3 > 。这样的设计看似很好,其实隐藏着一个问题。如我们在 D3D 平台上定义一个如下的粒子:
typedef TParticlePolicy < boost::mpl::vector< TParticlePart< D3DXVECTOR3 >, // 位置 TParticlePart< D3DXVECTOR3 >, // 速度 TParticlePart< D3DXCOLOR >, // 颜色 TParticlePart< FLOAT >, // 寿命 > > tMyParticle;
tMyParticle p; |
此时我们要访问粒子的位置部分应该写成 p.m_Value ,但如果你这样写编译器就会抱怨产生了歧义。原因是不论你需要访问的是位置、速度、颜色还是寿命都应该写成 p.m_Value ,如何告诉编译器你真正想要访问的部分呢?这就需要引入一个用于区分不同部分的 TPartDiff 模板类,同时对 TParticlePartHolder 和 TParticlePolicy 做一些修改:
// 区分粒子的不同部分 template < class _Part, class _Diff > class TPartDiff : public_Part {};
// 粒子组装器 template < class _PartIter, class _PartsVector > classTParticlePartHolder : publicTPartDiff< boost::mpl::deref< _PartIter >::type, boost::mpl::int_< _PartIter::pos::value > > , publicTParticlePartHolder< _PartIter::next, _PartsVector > {};
template < class _PartsVector > classTParticlePartHolder< boost::mpl::end< _PartsVector >::type, _PartsVector > : publicboost::blank {};
// 粒子 template < class _PartsVector > structTParticlePolicy : publicTParticlePartHolder< boost::mpl::begin< _PartsVector >::type, _PartsVector > { typedef _PartsVector tPartsVector;
// 部分数 static const int s_ciNumOfParts = boost::mpl::size< _PartsVector >::type::value;
// 获取一个粒子部分 template< size_tnIndex > TPartDiff< boost::mpl::at_c< _PartsVector, nIndex >::type, boost::mpl::int_< nIndex > >& Part( void ) { return *this; }
// 获取一个粒子部分 template< size_tnIndex > const TPartDiff< boost::mpl::at_c< _PartsVector, nIndex >::type, boost::mpl::int_< nIndex > >& Part( void ) const { return *this; }
|
TPartDiff 接受两个模板参数第一个是形如 TParticlePart< D3DXVECTOR3 > 的粒子部分,第二个是用此粒子部分在粒子定义中的索引位置转换的类型(通过 boost::mpl::int_<> 可以将常量转换为类型)用于区分。 TParticlePartHolder 不再直接继承粒子部分,而是通过 TPartDiff 继承带有区分的粒子部分。 TParticlePolicy 引入了一个公共模板成员函数 Part() 用于访问不同的粒子部分,其有一个模板参数用于指定所需要访问的粒子部分的索引,这个函数还有一个 Const 版本。如果我们要访问以上定义的 tMyParticle 类型的粒子 p 的不同部分我们可以写成:
p.Part< 0 >().m_Value; // 访问粒子的位置 p.Part< 1 >().m_Value; // 访问粒子的速度 p.Part< 2 >().m_Value; // 访问粒子的颜色 p.Part< 3 >().m_Value; // 访问粒子的寿命 |
二、 可制定行为的粒子系统
有了粒子结构,接下来的工作就是制定我们的粒子系统了。任何一个粒子系统都可以被分成 3 个部分:初始化器——用于初始化每一个刚刚产生的新粒子;更新器——用于在每一桢更新粒子的状态;死亡触发器——用于定义粒子死亡时所引发的事件。由于粒子结构的不同,而不同的粒子部分又需要做不同的初始化和更新,所以对于整个粒子的初始化器和更新器需要做类似粒子部分的组装。
有了以上的粒子结构,初始化器,更新器和死亡触发器我们就可以开始组装完整的粒子系统了:
// 粒子系统 template < typename _ParticleType, // 粒子结构 size_t nLifeIndex, // 粒子寿命所在粒子结构中的索引 size_t _Num, // 粒子总数 class _InitializePolicy, // 粒子初始化器 class _ActionPolicy, // 粒子更新器 class _DeadPolicy = TNilDeadTrigger< _ParticleType > // 死亡触发器 > classTParticleSystem : boost::noncopyable { public : // 粒子类型 typedef _ParticleType tParticle; // 初始化器类型 typedef _InitializePolicy tInitializer; // 更新器类型 typedef _ActionPolicy tActor; // 死亡触发器类型 typedef _DeadPolicy tDeadTrigger; protected : // 粒子总数 static const int s_ciNum = _Num; // 粒子数组 boost::array< tParticle, s_ciNum > m_aParticles; // 初始化器 tInitializer m_Initializer; // 更新器 tActor m_Actor; // 死亡触发器 tDeadTrigger m_DeadTrigger; // 当前活动粒子数 size_t m_nCurrentCount; public : // 构造函数 TParticleSystem( void ) : m_nCurrentCount( 0 ) { } // 析构函数 virtual ~TParticleSystem( void ) { } // 重置 inline void Reset( void ) { m_nCurrentCount = 0; } // 获取初始化器 inline tInitializer& Initializer( void ) { returnm_Initializer; } inline const tInitializer& Initializer( void ) const { m_Initializer; } // 获取更新器 inline tActor& Actor( void ) { returnm_Actor; } inline const tActor& Actor( void ) const { m_Actor; } // 获取死亡触发器 inline tDeadTrigger& DeadTrigger( void ) { returnm_DeadTrigger; } inline const tDeadTrigger& DeadTrigger( void ) const { m_DeadTrigger; } // 获取粒子数组的指针 inline const tParticle* GetParticles( void ) const { if( ParticlesCount() == 0 ) { return NULL; } return boost::addressof( m_aParticles[0] ); } // 获取最大粒子数 inline size_t MaxParticles( void ) const { return s_ciNum; } // 获取当前活动粒子数 inline size_t ParticlesCount( void ) const { return m_nCurrentCount; } // 发射指定数目的粒子 void Emit( size_tnAmount ) { // 是否已经达到最大粒子数? if( ( ParticlesCount() + nAmount ) > MaxParticles() ) { nAmount = MaxParticles() - ParticlesCount(); } if( nAmount > 0 ) { // 发射粒子 size_t nCnt = m_nCurrentCount; m_nCurrentCount += nAmount; for( ; nCnt < m_nCurrentCount; ++nCnt ) { Init< 0 >( m_aParticles[nCnt], m_Initializer ); } } } // 更新 void Update( doubledElapsedTime ) { for( size_tnCnt = 0; nCnt < m_nCurrentCount; ) { // 更新每一个活动的粒子 Update< 0 >( dElapsedTime, m_aParticles[nCnt], m_Actor ); // 杀掉所有寿命为0或负数的粒子 if( m_aParticles[nCnt].Part< nLifeIndex >().m_Value <= 0.0 ) { // 移除死亡的粒子,移动最后一个粒子到当前位置 m_DeadTrigger.On( m_aParticles[nCnt] ); m_aParticles[nCnt] = m_aParticles[m_nCurrentCount - 1]; // 当前活动粒子数减一 --m_nCurrentCount; } else { // 处理下一个粒子 ++nCnt; } } } private : // 执行初始化动作 template< size_tnIndex > void Init( tParticle& p, tInitializer& i ) { i.Part< nIndex >().Action< nIndex >( p ); Init< nIndex + 1 >( p, i ); } template<> void Init< tParticle::s_ciNumOfParts >( tParticle&, tInitializer& ) { }
// 执行更新动作 template< size_tnIndex > void Update( constdouble& dElapsedTime, tParticle& p, tActor& a ) { a.Part< nIndex >().Action< nIndex >( dElapsedTime, p ); Update< nIndex + 1 >( dElapsedTime, p, a ); } template<> void Update< tParticle::s_ciNumOfParts >( constdouble&, tParticle&, tActor& ) { } }; |
整个 TParticleSystem 模板类其实很简单,这里只对几个关键点进行说明。 TParticleSystem 一共接受 6 个模板参数,第一个 _ParticleType 为此粒子系统所需要处理的粒子结构;第二个 nLifeIndex 为粒子寿命部分所在粒子结构中的索引(每一个粒子都必须有一个寿命部分, TParticleSystem 将使用此处索引所指的粒子部分来判断粒子是否死亡);第三个 _Num 是整个粒子系统所能容纳的最大粒子数;第四个 _InitializePolicy 是用于初始化粒子的初始化器;第五个 _ActionPolicy 是用于更新粒子的更新器;第六个 _DeadPolicy 是用于处理粒子死亡的死亡触发器。
TParticleSystem 有两个主要的成员函数 Emit 和 Update 。 Emit 用于发射指定数目的粒子其中需要说明的是用于初始化粒子的一条语句:
Init< 0 >( m_aParticles[nCnt], m_Initializer )
它调用的是一个带有一个数值型模板参数的成员函数 Init , Init 有一个模板参数 nIndex ,用于指定当前所初始化的粒子部分。 Init 内部有一个递归调用,每次递归把索引指加一使用不同的粒子部分初始化器初始化不同的粒子部分:
i.Part< nIndex >().Action< nIndex >( p );
如上的调用表示从第 0 部分开始初始化粒子,直至所有部分初始化完毕。
Update 与 Emit 的运作机制类似,通过语句:
Update< 0 >( dElapsedTime, m_aParticles[nCnt], m_Actor );
从第 0 部分开始更新整个粒子。更新完粒子后还通过语句:
if( m_aParticles[nCnt].Part< nLifeIndex >().m_Value <= 0.0 )
检测当前粒子是否死亡,这里就用到了先前设置的一个模板参数 nLifeIndex 。如果死亡则使用死亡触发器做相应的处理:
m_DeadTrigger.On( m_aParticles[nCnt] );
此外 Update 还接受一个参数 dElapsedTime ,用于表示上次更新到此次更新的间隔。
三、 初始化器
初始化器是整个粒子系统的重要组成部分,每个初始化器都必须有个一公共的带有一个 size_t 型模板参数的 Action 成员函数, TParticleSystem 将调用此函数对粒子进行初始化,模板参数 nIndex 用于指定所需要初始化的粒子部分。系统中内建了几个常用的初始化器:
// 空初始化器 template < class _ParticleType > classTNilInitializer { protected : typedef _ParticleType tParticle; public : template< size_tnIndex > void Action( tParticle& ) { } }; |
TNilInitializer 是一个空初始化器,它有一个模板参数, _ParticleType 用于指定所需要初始化的粒子类型,如果指定其为某个粒子部分的初始化器系统将不对此粒子部分进行初始化动作。
// 常量初始化器 template < class _ParticleType, typename _Type > classTConstantInitializer { protected : typedef _ParticleType tParticle; typedef _Type tType; typedef boost::mpl::if_c< boost::is_pod< tType >::value, tType, boost::add_reference< boost::add_const< tType >::type >::type >::typetParaType;
// 初始化的值 tType m_Value; public : // 设定初始化值 void Set( tParaTypeValue ) { m_Value = Value; } // 执行初始化 template< size_tnIndex > void Action( tParticle& p ) { p.Part< nIndex >().m_Value = m_Value; } }; |
TConstantInitializer 是一个常量初始化器,它有两个模板参数, _ParticleType 用于指定所需要初始化的粒子类型, _Type 用于指点其将要初始化的粒子部分的类型。公共成员函数 Set 用于设置初始化的值。
// 矩阵变换初始化器 template < class _ParticleType, typename _MatrixType > classTTransformInitializer { protected : typedef _ParticleType tParticle; typedef _MatrixType tMatrix;
// 矩阵 tMatrix m_Matrix; public : // 构造函数 TTransformInitializer( void ) : m_Matrix( 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0 ) {} // 设置变换矩阵 void Set( consttMatrix& Matrix ) { m_Matrix = Matrix; } // 执行初始化 template< size_tnIndex > void Action( tParticle& p ) { p.Part< nIndex >().m_Value *= m_Matrix; } }; |
TTransformInitializer 是一个矩阵变换初始化器,它有两个模板参数, _ParticleType 用于指定所需要初始化的粒子类型, _MatrixType 用于指定变换矩阵的类型。公共成员函数 Set 用于设置变换矩阵。使用此初始化器还必须为 p.Part< nIndex >().m_Value 的类型定一个接受 tMatrix 类型的 *= 操作符,用于执行变换操作。
// 一维矢量随机初始化器 template < class _ParticleType, class _RandGenerator, class _RandDistribution > classTUniformRandomVec1Initializer { protected : typedef _ParticleType tParticle; typedef _RandGenerator tGenerator; typedef _RandDistribution tDistribution; typedef boost::variate_generator< _RandGenerator, _RandDistribution > tVarGen;
// 数值生成器 tVarGen m_VGen;
public : TUniformRandomVec1Initializer( void ) : m_VGen( tGenerator( 1 ), tDistribution() ) {} // 设置种子 void Seed( boost::uint32_tnSeed ) { m_VGen.engine().seed( nSeed ); } // 设置分布器 void SetDist( consttDistribution& Dist ) { m_VGen.distribution() = Dist; } // 执行初始化 template< size_tnIndex > void Action( tParticle& p ) { p.Part< nIndex >().m_Value = m_VGen(); } }; |
TUniformRandomVec1Initializer 是一个一维随机初始化器,它接受 3 个模板参数, _ParticleType 用于指定所需要初始化的粒子类型, _RandGenerator 用于指定随机数生成器(详细说明请参看 Boost.Random 的文档), _RandDistribution 用于指定随机数分布器(详细说明请参看 Boost.Random 的文档)。公共成员函数 Seed 用于设置随机数生成器的种子, SetDist 用于设置随机数分布器。
TUniformRandomVec2Initializer 二维随机初始化器,
TUniformRandomVec3Initializer 三维随机初始化器,
TUniformRandomVec4Initializer 四维随机初始化器,
TSphereRandomVec2XYInitializer 三维 XY 平面圆形随机初始化器,
TSphereRandomVec2XZInitializer 三维 XZ 平面圆形随机初始化器,
TSphereRandomVec2YZInitializer 三维 YZ 平面圆形随机初始化器,
的使用都与此类似,详细使用请参看本文所附例程,详细实现请参看本文所附源代码及 Boost.Random 文档。
如果以上提到的内建初始化器中并没有您需要的,那么动手自定义一个初始化器也是一件非常轻松愉快的事情。
// 根据寿命修正退色因子 template < class _ParticleType, size_t nFadeIndex > classTFixFadeByLifeInitializer { protected : typedef _ParticleType tParticle;
public : // 执行初始化 template< size_tnIndex > void Action( tParticle& p ) { p.Part< nFadeIndex >().m_Value = 1.0 / p.Part< nIndex >().m_Value; } }; |
这里我们自定义了一个根据初始寿命值修正退色因子的初始化器,它接受两个模板参数, _ParticleType 用于指定所需要初始化的粒子类型, nFadeIndex 用于指定退色因子在粒子结构中的索引。
四、更新器
更新器和初始化器一样也粒子系统重要的组成部分,每个更新器都必须有个一公共的带有一个 size_t 型模板参数的 Action 成员函数,此函数接受一个从上次更新到现在的时间间隔作为参数, TParticleSystem 将调用此函数对粒子进行更新,模板参数 nIndex 用于指定所需要更新的粒子部分。系统中内建了几个常用的更新器:
// 空更新器 template < class _ParticleType > classTNilActor { protected : typedef _ParticleType tParticle;
public : template< size_tnIndex > void Action( constdouble&, tParticle& ) { } }; |
TNilActor 是一个空更新器 , 它有一个模板参数, _ParticleType 用于指定所需要更新的粒子类型,如果指定其为某个粒子部分的更新器系统将不对此粒子部分进行更新动作。
// 移动位置更新器 template < class _ParticleType, size_t nVelIndex > classTMovePosActor { protected : typedef _ParticleType tParticle;
public : // 执行更新 template< size_tnIndex > void Action( constdouble& dTime, tParticle& p ) { p.Part< nIndex >().m_Value += p.Part< nVelIndex >().m_Value * dTime; } }; |
TMovePosActor 是一个移动位置更新器,它根据当前的速度值更新位置,只能应用于粒子的位置部分。它有两个模板参数, _ParticleType 用于指定所需要更新的粒子类型, nVelIndex 用于指定粒子的速度部分在粒子结构中的索引。
// 重力速度更新器 template < class _ParticleType, typename _Vector3Type > classTGravityVelActor { protected : typedef _ParticleType tParticle; typedef _Vector3Type tVector3;
// 重力加速度 tVector 3 m_vGravity; public : TGravityVelActor( void ) : m_vGravity( tVector3( 0.0,-9.8, 0.0 ) ) {}
// 设置重力加速度 void SetGravity( consttVector3& vec3 ) { m_vGravity = vec3; } // 执行更新 template< size_tnIndex > void Action( constdouble& dTime, tParticle& p ) { p.Part< nIndex >().m_Value += m_vGravity * dTime; } }; |
TGravityVelActor 是一个重力速度更新器,它根据指定的重力加速度更新速度,只能应用于粒子的速度部分。它有两个模板参数, _ParticleType 用于指定所需要更新的粒子类型, _Vector3Type 用于指定重力加速度的类型。公共成员函数 SetGravity 终于设置重力加速度。
// 寿命逝去更新器 template < class _ParticleType > classTElapseLifeActor { protected : typedef _ParticleType tParticle;
public : // 执行更新 template< size_tnIndex > void Action( constdouble& dTime, tParticle& p ) { p.Part< nIndex >().m_Value -= dTime; } }; |
TElapseLifeActor 是一个寿命逝去更新器,它根据逝去的时间减少寿命,只能应用于粒子的寿命部分。它有一个模板参数, _ParticleType 用于指定所需要更新的粒子类型。
// 死亡断言更新器 template < class _ParticleType > classTDeadLifeActor { protected : typedef _ParticleType tParticle; typedef boost::function< bool ( consttParticle& ) > tfnDead;
// 死亡断言函数对象 tfnDead m_DeadPred; public : // Set dead predicate void SetDeadPredicate( consttfnDead& DeadPred ) { m_DeadPred = DeadPred; } // 执行更新 template< size_tnIndex > void Action( constdouble&, tParticle& p ) { if( ! m_DeadPred.empty() ) { if( m_DeadPred( p ) ) p.Part< nIndex >().m_Value = -0.0f ; } } }; |
TDeadLifeActor 是一个死亡断言更新器,它调用死亡断言函数对象来确认粒子是否死亡,如果死亡则修改寿命值为 -0.0 ,只能应用于粒子的寿命部分。它有一个模板参数, _ParticleType 用于指定所需要更新的粒子类型。
如果以上提到的内建更新器中并没有您需要的,那么动手自定义一个更新器也是一件非常轻松愉快的事情。
// 退色更新器 template < class _ParticleType, typename _ColorType, size_t nFadeIndex > classTFadeColorActor { protected : typedef _ParticleType tParticle; typedef _ColorType tColor;
// 颜色一 tColor m_Value1; // 颜色二 tColor m_Value2; public : TFadeColorActor( void ) : m_Value1( tColor( 1.0, 1.0, 1.0, 1.0 ) ) , m_Value2( tColor( 0.0, 0.0, 0.0, 1.0 ) ) {}
// 设定颜色一 void SetColorOne( consttColor& col ) { m_Value1 = col; } // 设定颜色二 void SetColorTwo( consttColor& col ) { m_Value2 = col; } // 执行更新 template< size_tnIndex > void Action( constdouble& dTime, tParticle& p ) { p.Part< nIndex >().m_Value += ( m_Value2 - m_Value1 ) * p.Part< nFadeIndex >().m_Value * dTime; } }; |
这里我们自定义了一个根据寿命值使粒子颜色由颜色一退为颜色二的更新器,只能应用于粒子的颜色部分。它有三个模板参数, _ParticleType 用于指定所需要更新的粒子类型, _ColorType 用于指定颜色类型, nFadeIndex 用于指定退色因子在粒子结构中的索引。
五、死亡触发器
相对于初始化器和更新器,死亡触发器要简单一些,在有粒子死亡时 On 成员函数将被调用。系统内建了两个死亡触发器。
// 空死亡触发器 template < class _ParticleType > classTNilDeadTrigger { protected : typedef _ParticleType tParticle;
public : void On( consttParticle& ) const { } }; |
TNilDeadTrigger 是一个空死亡触发器,它不对粒子的死亡做任何的处理。它有一个模板参数, _ParticleType 用于指定所需要更新的粒子类型。
// 简单死亡触发器 template < class _ParticleType > classTSimpleDeadTrigger { protected : typedef _ParticleType tParticle; typedef boost::function< void ( consttParticle& ) > tfnDeadTrigger;
// 死亡触发函数对象 tfnDeadTrigger m_DeadTrigger; public : // 设置死亡触发函数对象 void SetDeadTrigger( consttfnDeadTrigger& DeadTrigger ) { m_DeadTrigger = DeadTrigger; }
// 处理死亡事件 void On( consttParticle& p ) const { if( ! m_DeadTrigger.empty() ) m_DeadTrigger( p ); } }; |
TSimpleDeadTrigger 是一个简单死亡触发器,当有粒子死亡时他调用死亡触发函数对象,公共成员函数 SetDeadTrigger 用于设置死亡触发函数对象。它有一个模板参数, _ParticleType 用于指定所需要更新的粒子类型。
六、其他
完成了以上工作后我几乎几经设计出了易于扩展,适应性强的泛型粒子系统了。为什么我要说几乎呢?不知道您有没有发现现在一个粒子部分只能对应一个初始化器,和一个更新器。如果我们需要用两个初始化器来初始化一个粒子部分,用两个更新器来更新一个粒子部分该怎么办呢?这就需要设计一个结合两个初始化器和结合两个更新器的工具。
// 整合两个初始化器 template < class _ParticleType, class _PolicyOne, class _PolicyTwo > classTBothInitializerPolicy : public_PolicyOne , public_PolicyTwo { protected : typedef _ParticleType tParticle; public : // 执行初始化 template< size_tnIndex > void Action( tParticle& p ) { _PolicyOne::Action< nIndex >( p ); _PolicyTwo::Action< nIndex >( p ); } }; |
TBothInitializerPolicy 整合了两个初始化器,使他们共同作用于一个粒子部分。它有三个模板参数, _ParticleType 用于指定所需要初始化的粒子类型, _PolicyOne 用于指定第一个初始化器, _PolicyTwo 用于指定第二个初始化器。
// 整合两个更新器 template < class _ParticleType, class _PolicyOne, class _PolicyTwo > classTBothActorPolicy : public_PolicyOne , public_PolicyTwo { protected : typedef _ParticleType tParticle;
public : // 执行更新 template< size_tnIndex > void Action( constdouble& dTime, tParticle& p ) { _PolicyOne::Action< nIndex >( dTime, p ); _PolicyTwo::Action< nIndex >( dTime, p ); } }; |
TBothActorPolicy 整合了两个更新器,使他们共同作用于一个粒子部分。它有三个模板参数, _ParticleType 用于指定所需要更新的粒子类型, _PolicyOne 用于指定第一个更新器, _PolicyTwo 用于指定第二个更新器。
七、附件
例子程序: TPS.rar
http://uj 86c 1.chinaw3.com/tmp/TPS.rar (源码已无法下载,谁能提供个源码?)
源程序: looParticleSystem.hpp
http://uj 86c 1.chinaw3.com/tmp/looParticleSystem.hpp
编译需求: Intel C++ Compiler v8.0 , vs2003.net , boost 1.31.0
八、参考资料
Designing an Extensible Particle System using C++ and Templates,
By Kent "_dot_" Lai