[Cocos2d-x相关教程来源于红孩儿的游戏编程之路CSDN博客地址:http://blog.csdn.net/honghaier]
红孩儿Cocos2d-X学习园地QQ2群:44208467加群写:Cocos2d-x
红孩儿Cocos2d-X学习园地QQ群:249941957加群写:Cocos2d-x
Cocos2d-x2.0 粒子系统深入分析三部曲(一)
另:本章所用Cocos2d-x版本为:
cocos2d-2.0-x-2.0.2@ Aug 30 2012
http://cn.cocos2d-x.org/download
大家好,今天我们来学习一下Cocos2d-x 2.0 的粒子系统,所谓粒子系统,即:“具有相同运动物理特性的一定数量级的有生命周期的个体,通过对其的控制来表现一些特定的现象,如火、爆炸、烟、水流、火花、落叶、云、雾、雪、尘、流星尾迹或者象发光轨迹这样的抽象视觉效果等等”。
在几乎所有的游戏引擎中,都有专属的类来进行生成粒子系统,其中主要有两个功能类:
(1)。粒子:具有大量属性(如生命,方向,速度,加速度等)的结构体或类。表现为一个粒子个体。
(2)。粒子发射器:用来进行生成,控制,回收粒子的管理器类。
在进行粒子系统的创建时,首先把粒子发射器放到某个位置,然后设定要生成的粒子的数量及粒子的起始属性(往往设定生命值为随机),然后在开始创建出粒子并不断的更新粒子,粒子在更新运动状态的同时生命值会被不断的消耗直至死亡。死亡后的粒子被发射器记录回收,为了保证同一时间内有固定数量的粒子在存活中,发射器会在合适的时间重新初始化并运行回收的粒子。
回到Cocos2d-x,打开ParticleTest.h:
//演示粒子系统的场景
class ParticleTestScene : public TestScene
{
public:
virtual void runThisTest();
};
//由纯色层派生的显示粒子系统的层,做为后面多种形式粒子系统演示所在层的基类。
class ParticleDemo : public CCLayerColor
{
protected:
//粒子发射器
CCParticleSystem* m_emitter;
//背景图精灵
CCSprite* m_background;
public:
//析构
~ParticleDemo(void);
//当前层加载时调用的函数。
virtual void onEnter(void);
//取得当前层的标题。
virtual std::string title();
//取得当前层的副标题。
virtual std::string subtitle();
//响应菜单按钮的回调函数。
//重新启动当前演示。
void restartCallback(CCObject* pSender);
//启动下一个演示。
void nextCallback(CCObject* pSender);
//启动上一个演示。
void backCallback(CCObject* pSender);
//点击文字选项时切换粒子运动模式的响应函数。
void toggleCallback(CCObject* pSender);
//注册相应的触屏消息处理。
virtual void registerWithTouchDispatcher();
//触屏消息处理
//触屏(按下时调用)
virtual bool ccTouchBegan(CCTouch* touch, CCEvent* event);
//触屏(按下并移动时调用)
virtual void ccTouchMoved(CCTouch* touch, CCEvent* event);
//触屏(松开时调用)
virtual void ccTouchEnded(CCTouch* touch, CCEvent* event);
//更新
virtual void update(float dt);
//设置粒子发射器的位置。
void setEmitterPosition();
};
//模拟火的粒子系统演示。
class DemoFirework : public ParticleDemo
{
public:
virtual void onEnter();
virtual std::string title();
};
后面是更多的粒子系统演示,不再一一重复列出。
上面定义了一些比较常见的粒子系统的演示,我们来看一下它们是怎么实现的,打开CPP文件:
//加载当前演示Layer时调用的函数。
void DemoFirework::onEnter()
{
//先调用基类的相应函数。
ParticleDemo::onEnter();
//创建一个Fireworks类型的粒子发射器。
m_emitter = CCParticleFireworks::create();
//对其引用计数器加一。
m_emitter->retain();
//将发射器放入背景图精灵。
m_background->addChild(m_emitter, 10);
//设置发射器的纹理。 m_emitter->setTexture( CCTextureCache::sharedTextureCache()->addImage(s_stars1) );
//设置发射器的位置。
setEmitterPosition();
}
//取得当前演示的标题。
std::string DemoFirework::title()
{
return "ParticleFireworks";
}
后面也是一大堆相似的代码,也不一一列出了。
重点是:所有的演示都需要用到一个粒子系统对象实例,所有的粒子系统都是由CCParticleSystem派生出来的。我们只需要好好的掌握它和学会派生出更多需要的粒子系统类就可以了。
在学习粒子系统基类之前,我们要先了解一下CCParticleBatchNode。这个类名称中有“Batch”这个词,说明它与粒子的批次优化有一定关系。在“Cocos2d-x中图字原理之深入分析”一文中,我有讲解过CCSpriteBatchNode这个类。如果您是跟随本博一起学习Cocos2d-x的话,那么会很容易看懂CCParticleBatchNode这个类。
CCParticleBatchNode是为了将使用相同纹理的粒子并合到一个渲染批次中,可以大大的提升渲染的效率。
CCParticleBatchNode.h:
#ifndef __CCPARTICLEBATCHNODE_H__
#define __CCPARTICLEBATCHNODE_H__
#include "base_nodes/CCNode.h"
#include "CCProtocols.h"
//使用Cocos2d命名空间
NS_CC_BEGIN
//使用相应的类。
class CCTexture2D;
class CCTextureAtlas;
class CCParticleSystem;
//默认粒子系统中粒子的容器的容量。
#define kCCParticleDefaultCapacity 500
//粒子的批次结点。
class CC_DLL CCParticleBatchNode : public CCNode, public CCTextureProtocol
{
public:
//构造函数。
CCParticleBatchNode();
//析构函数。
virtual ~CCParticleBatchNode();
//创建粒子系统的批次结点,参数一为对应的纹理,参数二为创建批次结点所能容纳的最大粒子数量。内部调用create实现。
CC_DEPRECATED_ATTRIBUTE static CCParticleBatchNode* batchNodeWithTexture(CCTexture2D *tex, unsigned int capacity = kCCParticleDefaultCapacity);
//创建粒子系统的批次结点,参数一为图片文件名称,参数二为创建批次结点所能容纳的最大粒子数量。内部调用create实现。
CC_DEPRECATED_ATTRIBUTE static CCParticleBatchNode* batchNodeWithFile(const char* fileImage, unsigned int capacity = kCCParticleDefaultCapacity);
//第一个创建函数的create实现。
static CCParticleBatchNode* createWithTexture(CCTexture2D *tex, unsigned int capacity = kCCParticleDefaultCapacity);
//第二个创建函数的create实现。
static CCParticleBatchNode* create(const char* fileImage, unsigned int capacity = kCCParticleDefaultCapacity);
//初始化粒子系统。
bool initWithTexture(CCTexture2D *tex, unsigned int capacity);
bool initWithFile(const char* fileImage, unsigned int capacity);
//将一个粒子系统做为子结点加入批次管理结点。
virtual void addChild(CCNode * child);
virtual void addChild(CCNode * child, int zOrder);
virtual void addChild(CCNode * child, int zOrder, int tag);
//将一个粒子系统做为子结点插入批次管理结点的相应位置。
void insertChild(CCParticleSystem* pSystem, unsigned int index);
//将一个粒子系统子结点从批次管理结点中移除。
virtual void removeChild(CCNode* child, bool cleanup);
//重新排序粒子系统子结点.
virtual void reorderChild(CCNode * child, int zOrder);
//将一个指定索引位置的粒子系统子结点从批次管理结点中移除。
void removeChildAtIndex(unsigned int index, bool doCleanup);
//将所有粒子系统子结点从批次管理结点中移除。
void removeAllChildrenWithCleanup(bool doCleanup);
//设置某个粒子失效不显示。
void disableParticle(unsigned int particleIndex);
//绘制当前管理的所有粒子系统。
virtual void draw(void);
// 返回所用的纹理。
virtual CCTexture2D* getTexture(void);
// 设置所用的纹理。
virtual void setTexture(CCTexture2D *texture);
//设置ALPHA混合方案。
virtual void setBlendFunc(ccBlendFunc blendFunc);
//取得ALPHA混合方案。
virtual ccBlendFunc getBlendFunc(void);
//结点树遍历当前结点时调用的函数。
void visit();
private:
//更新所有粒子的矩形顶点缓冲信息块的索引。.
void updateAllAtlasIndexes();
//扩大矩形顶点缓冲信息块的数组的容量。
void increaseAtlasCapacityTo(unsigned int quantity);
//通过Z值取得矩形顶点缓冲信息块的索引。
unsigned int searchNewPositionInChildrenForZ(int z);
//取得子结点在设置Z排序中z值前的位置和设置后的位置
void getCurrentIndex(unsigned int* oldIndex, unsigned int* newIndex, CCNode* child, int z);
//将一个粒子系统做为子结点加入当前批次结点。
unsigned int addChildHelper(CCParticleSystem* child, int z, int aTag);
//更新ALPHA混合方案。
void updateBlendFunc(void);
// CCTextureAtlas类是用来对当前粒子的批次结点中所有粒子所使用的顶点缓冲区进行管理的类,它以数组的方式保存了显示所有粒子所需的四边形的顶点结构信息。在“Cocos2d-x中图字原理之深入分析”一文中有详细源码分析。我们可称之为“同纹理的顶点缓冲区管理器”.
CC_SYNTHESIZE(CCTextureAtlas*, m_pTextureAtlas, TextureAtlas);
private:
//ALPHA混合方案。
ccBlendFunc m_tBlendFunc;
};
NS_CC_END
对应的CPP:
#include "CCParticleBatchNode.h"
#include "textures/CCTextureCache.h"
#include "textures/CCTextureAtlas.h"
#include "ccConfig.h"
#include "ccMacros.h"
#include "effects/CCGrid.h"
#include "support/CCPointExtension.h"
#include "CCParticleSystem.h"
#include "shaders/CCShaderCache.h"
#include "shaders/CCGLProgram.h"
#include "shaders/ccGLStateCache.h"
#include "support/base64.h"
#include "support/zip_support/ZipUtils.h"
#include "platform/CCFileUtils.h"
#include "kazmath/GL/matrix.h"
//Cocos2d命名空间
NS_CC_BEGIN
//构造
CCParticleBatchNode::CCParticleBatchNode()
: m_pTextureAtlas(NULL)
{
}
//析构
CCParticleBatchNode::~CCParticleBatchNode()
{
//释放“同纹理的顶点缓冲区管理器”。
CC_SAFE_RELEASE(m_pTextureAtlas);
}
//创建粒子系统的批次结点,参数一为对应的纹理,参数二为创建批次结点所能容纳的最大粒子数量。内部调用create实现。
CCParticleBatchNode* CCParticleBatchNode::batchNodeWithTexture(CCTexture2D *tex, unsigned int capacity/* = kCCParticleDefaultCapacity*/)
{
return CCParticleBatchNode::createWithTexture(tex, capacity);
}
//上面的创建函数的create实现。
CCParticleBatchNode* CCParticleBatchNode::createWithTexture(CCTexture2D *tex, unsigned int capacity/* = kCCParticleDefaultCapacity*/)
{
//先new出实例对象,然后调用初始化函数,之后交由内存管理器进行引用计数器的管理。
CCParticleBatchNode * p = new CCParticleBatchNode();
if( p && p->initWithTexture(tex, capacity))
{
p->autorelease();
return p;
}
//如果失败,释放并置空。
CC_SAFE_DELETE(p);
return NULL;
}
//创建粒子系统的批次结点,参数一为图片文件名称,参数二为创建批次结点所能容纳的最大粒子数量。内部调用create实现。
CCParticleBatchNode* CCParticleBatchNode::batchNodeWithFile(const char* imageFile, unsigned int capacity/* = kCCParticleDefaultCapacity*/)
{
return CCParticleBatchNode::create(imageFile, capacity);
}
//上面的创建函数的create实现。
CCParticleBatchNode* CCParticleBatchNode::create(const char* imageFile, unsigned int capacity/* = kCCParticleDefaultCapacity*/)
{
//先new出实例对象,然后调用初始化函数,之后交由内存管理器进行引用计数器的管理。
CCParticleBatchNode * p = new CCParticleBatchNode();
if( p && p->initWithFile(imageFile, capacity))
{
p->autorelease();
return p;
}
//如果失败,释放并置空。
CC_SAFE_DELETE(p);
return NULL;
}
//初始化函数。
bool CCParticleBatchNode::initWithTexture(CCTexture2D *tex, unsigned int capacity)
{
//实例化一个“同纹理的顶点缓冲区管理器”。
m_pTextureAtlas = new CCTextureAtlas();
//使用纹理对象和容量大小初始化这个管理器。
m_pTextureAtlas->initWithTexture(tex, capacity);
// 创建一个CCArray,初始化容量大小,用于存放所有的粒子结点。
m_pChildren = new CCArray();
m_pChildren->initWithCapacity(capacity);
// 初始化ALPHA混合方案。
m_tBlendFunc.src = CC_BLEND_SRC;
m_tBlendFunc.dst = CC_BLEND_DST;
//设置所用的Shader代码片段 setShaderProgram(CCShaderCache::sharedShaderCache()->programForKey(kCCShader_PositionTextureColor));
return true;
}
//与上面不同参数的初始化函数。
bool CCParticleBatchNode::initWithFile(const char* fileImage, unsigned int capacity)
{
//先由图片名称创建出对应的纹理对象,并调用初始化函数。
CCTexture2D *tex = CCTextureCache::sharedTextureCache()->addImage(fileImage);
return initWithTexture(tex, capacity);
}
//系统在遍历结点时调用的函数。
void CCParticleBatchNode::visit()
{
//如果不显示,直接返回。
if (!m_bIsVisible)
{
return;
}
//将当前程序所用的矩阵先压栈保存。
kmGLPushMatrix();
//如果m_pGrid有值被激活则开启渲染到纹理(此节可参看本博“Cocos2d-x 2.0 网格动画深入分析”一文。
if ( m_pGrid && m_pGrid->isActive())
{
m_pGrid->beforeDraw();
transformAncestors();
}
//进行矩阵的变换。
transform();
//绘制。
draw();
//如果m_pGrid有值并被激活,则关闭渲染到纹理。这样当前结点上所有绘制的图像都被输出到m_pGrid对应的纹理中了。
if ( m_pGrid && m_pGrid->isActive())
{
m_pGrid->afterDraw(this);
}
//将之前压栈的矩阵恢复成当前所用的矩阵。
kmGLPopMatrix();
}
//重载基类的相应函数。
//加入一个粒子系统子结点。
void CCParticleBatchNode::addChild(CCNode * child)
{
CCNode::addChild(child);
}
//加入一个粒子系统子结点。
void CCParticleBatchNode::addChild(CCNode * child, int zOrder)
{
CCNode::addChild(child, zOrder);
}
//加入一个粒子系统子结点。
void CCParticleBatchNode::addChild(CCNode * child, int zOrder, int tag)
{
//一大堆有效性判断。
CCAssert( child != NULL, "Argument must be non-NULL");
CCAssert( dynamic_cast<CCParticleSystem*>(child) != NULL, "CCParticleBatchNode only supports CCQuadParticleSystems as children");
CCParticleSystem* pChild = (CCParticleSystem*)child;
CCAssert( pChild->getTexture()->getName() == m_pTextureAtlas->getTexture()->getName(), "CCParticleSystem is not using the same texture id");
// 如果当前粒子的批次优化结点中存储的粒子数量为0,则设置相应的ALPHA混合方案。
if( m_pChildren->count() == 0 )
{
setBlendFunc(pChild->getBlendFunc());
}
//有效性判断。
CCAssert( m_tBlendFunc.src == pChild->getBlendFunc().src && m_tBlendFunc.dst == pChild->getBlendFunc().dst, "Can't add a PaticleSystem that uses a differnt blending function");
//将粒子系统做为当前批次结点的相应索引位置的子结点。
unsigned int pos = addChildHelper(pChild,zOrder,tag);
//定义一个临时变量存储当前批次结点中“同纹理的顶点缓冲区管理器”中对应矩形顶点缓冲块的索引。
unsigned int atlasIndex = 0;
//如果不是第一个。
if (pos != 0)
{
//取得对应的粒子系统。
CCParticleSystem* p = (CCParticleSystem*)m_pChildren->objectAtIndex(pos-1);
//取得当前粒子系统的“同纹理的顶点缓冲区管理器”中对应矩形顶点缓冲块的索引加上粒子的总数量存入atlasIndex。
atlasIndex = p->getAtlasIndex() + p->getTotalParticles();
}
else
{
//如果是第一个,就直接设置atlasIndex为0。
atlasIndex = 0;
}
//将粒子系统与atlasIndex做为Z排序值插入相应的容器。
insertChild(pChild, atlasIndex);
// 设置粒子系统使用当前的批次结点。
pChild->setBatchNode(this);
}
//将一个粒子系统做为当前批次结点的子结点。
unsigned int CCParticleBatchNode::addChildHelper(CCParticleSystem* child, int z, int aTag)
{
//有效性判断。
CCAssert( child != NULL, "Argument must be non-nil");
CCAssert( child->getParent() == NULL, "child already added. It can't be added again");
//如果存储子结点的容器指针为空,则使用new创建出对应的容器,并初始化容量为4。
if( ! m_pChildren )
{
m_pChildren = new CCArray();
m_pChildren->initWithCapacity(4);
}
//取出子结点容器中z位置在Z排序中的索引位置。
unsigned int pos = searchNewPositionInChildrenForZ(z);
//将子结点放入容器的相应位置。
m_pChildren->insertObject(child, pos);
//设置子结点的tag值。
child->setTag(aTag);
//设置Z排序值。
child->_setZOrder(z);
//设置父结点为当前批次结点。
child->setParent(this);
//如果在运行中,调用相应的初始化函数。
if( m_bIsRunning )
{
child->onEnter();
child->onEnterTransitionDidFinish();
}
return pos;
}
//对指定的粒子系统重新排序。
void CCParticleBatchNode::reorderChild(CCNode * child, int zOrder)
{
//有效性判断。
CCAssert( child != NULL, "Child must be non-NULL");
CCAssert( dynamic_cast<CCParticleSystem*>(child) != NULL, "CCParticleBatchNode only supports CCQuadParticleSystems as children");
CCAssert( m_pChildren->containsObject(child), "Child doesn't belong to batch" );
//转换为粒子系统。
CCParticleSystem* pChild = (CCParticleSystem*)(child);
//如果本身顺序就与指定排序索引相同,直接返回。
if( zOrder == child->getZOrder() )
{
return;
}
// 如果只有一个子结点,就不用排序了。
if( m_pChildren->count() > 1)
{
//定义临时变量,用于存储指定结点插入前的索引和插入后的索引。
unsigned int newIndex = 0, oldIndex = 0;
//取得这两个索引值。
getCurrentIndex(&oldIndex, &newIndex, pChild, zOrder);
//如果索引有改动。
if( oldIndex != newIndex )
{
//子结点的引用计数器加一。
pChild->retain();
//将旧的索引位置的结点从容器中删除。
m_pChildren->removeObjectAtIndex(oldIndex);
//将子结点以新的索引位置放入容器中。
m_pChildren->insertObject(pChild, newIndex);
//子结点的引用计数器减一。
pChild->release();
// 将粒子系统的“同纹理的顶点缓冲区管理器”中对应矩形顶点缓冲块的索引保存到oldAtlasIndex中。
unsigned int oldAtlasIndex = pChild->getAtlasIndex();
// 更新所有的索引位置。
updateAllAtlasIndexes();
//定义临时变量用于取出新的相应粒子系统的“同纹理的顶点缓冲区管理器”中对应矩形顶点缓冲块的索引。
unsigned int newAtlasIndex = 0;
for( unsigned int i=0;i < m_pChildren->count();i++)
{
CCParticleSystem* pNode = (CCParticleSystem*)m_pChildren->objectAtIndex(i);
if( pNode == pChild )
{
newAtlasIndex = pChild->getAtlasIndex();
break;
}
}
//将oldAtlasIndex位置的数据拷到newAtlasIndex位置保持数组中信息连续有效。
m_pTextureAtlas->moveQuadsFromIndex(oldAtlasIndex, pChild->getTotalParticles(), newAtlasIndex);
//更新一下粒子系统。
pChild->updateWithNoTime();
}
}
//重新设置Z排序值。
pChild->_setZOrder(zOrder);
}
//取得粒子系统子结点在Z排序的结点数组中的位置和设置排序值z后的位置
void CCParticleBatchNode::getCurrentIndex(unsigned int* oldIndex, unsigned int* newIndex, CCNode* child, int z)
{
bool foundCurrentIdx = false;
bool foundNewIdx = false;
int minusOne = 0;
unsigned int count = m_pChildren->count();
//遍历所有的子结点。
for( unsigned int i=0; i < count; i++ )
{
//取出相应的结点。
CCNode* pNode = (CCNode *)m_pChildren->objectAtIndex(i);
// 如果Z值大于z了,就记录索引返回给新的索引参数。
if( pNode->getZOrder() > z && ! foundNewIdx )
{
*newIndex = i;
foundNewIdx = true;
//新索引和旧索引都找到时break。
if( foundCurrentIdx && foundNewIdx )
{
break;
}
}
//如果结点相同。就记录索引返回给旧的索引参数。
if( child == pNode )
{
*oldIndex = i;
foundCurrentIdx = true;
//如果未找到新的索引,则将minuseOne值由0改为-1。
if( ! foundNewIdx )
{
minusOne = -1;
}
//新索引和旧索引都找到时break。
if( foundCurrentIdx && foundNewIdx )
{
break;
}
}
}
//如果未找到新的索引,则将新的索引参数的值填为最大值。
if( ! foundNewIdx )
{
*newIndex = count;
}
//
*newIndex += minusOne;
}
//通过z值来取得结点在Z排序的结点数组中的索引。
unsigned int CCParticleBatchNode::searchNewPositionInChildrenForZ(int z)
{
//取得子结点的数量。
unsigned int count = m_pChildren->count();
//遍历子结点,找到Z结点在Z排序的结点数组中大于z值的第一个结点的索引。
for( unsigned int i=0; i < count; i++ )
{
CCNode *child = (CCNode *)m_pChildren->objectAtIndex(i);
if (child->getZOrder() > z)
{
return i;
}
}
//如果找不到,返回最大值。
return count;
}
//将粒子系统子结点从批次结点中移除。
void CCParticleBatchNode::removeChild(CCNode* child, bool cleanup)
{
// 无效返回.
if (child == NULL)
{
return;
}
//有效性判断.
CCAssert( dynamic_cast<CCParticleSystem*>(child) != NULL, "CCParticleBatchNode only supports CCQuadParticleSystems as children");
CCAssert(m_pChildren->containsObject(child), "CCParticleBatchNode doesn't contain the sprite. Can't remove it");
//将对应的子结点指针转化为粒子系统指针.
CCParticleSystem* pChild = (CCParticleSystem*)child;
//移除对应的子结点.
CCNode::removeChild(pChild, cleanup);
// 从“同纹理的顶点缓冲区管理器”中找到相应的矩形顶点缓冲块的索引位置然后移除相应粒子数量的矩形顶点缓冲信息块.并使用memmove将后面的数据拷到删除位置保持矩形顶点缓冲信息块的数组信息连续有效。
m_pTextureAtlas->removeQuadsAtIndex(pChild->getAtlasIndex(), pChild->getTotalParticles());
//在“同纹理的顶点缓冲区管理器”中从参数一指定位置后相应粒子数量的矩形顶点缓冲信息块都置零。这里即将上面数组中更新的矩形顶点缓冲信息块数量之后的内存置零。
m_pTextureAtlas->fillWithEmptyQuadsFromIndex(m_pTextureAtlas->getTotalQuads(), pChild->getTotalParticles());
// 设置被移除的粒子系统不使用批次结点.
pChild->setBatchNode(NULL);
//重建“同纹理的顶点缓冲区管理器”中所有的矩形顶点缓冲信息块的索引.
updateAllAtlasIndexes();
}
//通过索引移除相应的粒子系统。
void CCParticleBatchNode::removeChildAtIndex(unsigned int index, bool doCleanup)
{
removeChild((CCParticleSystem *)m_pChildren->objectAtIndex(index),doCleanup);
}
//清空所有使用当前批次结点的粒子系统。
void CCParticleBatchNode::removeAllChildrenWithCleanup(bool doCleanup)
{
//遍历所有的粒子系统调用setBatchNode函数,参数为NULL。即将所有使用当前批次结点的粒子系统都设置不使用批次结点。
arrayMakeObjectsPerformSelectorWithObject(m_pChildren, setBatchNode, NULL, CCParticleSystem*);
//清空所有的子结点。
CCNode::removeAllChildrenWithCleanup(doCleanup);
//清空“同纹理的顶点缓冲区管理器”中所有的矩形顶点缓冲信息块。
m_pTextureAtlas->removeAllQuads();
}
//绘制批次结点。
void CCParticleBatchNode::draw(void)
{
CC_PROFILER_STOP("CCParticleBatchNode - draw");
//如果矩形顶点缓冲信息块的数量为0直接返回。
if( m_pTextureAtlas->getTotalQuads() == 0 )
{
return;
}
//开始使用Shader进行绘制结点。
CC_NODE_DRAW_SETUP();
//设置Opengl采用相应的ALPHA混合方案。
ccGLBlendFunc( m_tBlendFunc.src, m_tBlendFunc.dst );
//调用drawQuads来绘制所有矩形顶点缓冲构成的图形。
m_pTextureAtlas->drawQuads();
CC_PROFILER_STOP("CCParticleBatchNode - draw");
}
//扩增矩形顶点缓冲信息块组数的容量。
void CCParticleBatchNode::increaseAtlasCapacityTo(unsigned int quantity)
{
//打印日志。
CCLOG("cocos2d: CCParticleBatchNode: resizing TextureAtlas capacity from [%lu] to [%lu].",
(long)m_pTextureAtlas->getCapacity(),
(long)quantity);
//重新调整矩形顶点缓冲信息块数组的大小。
if( ! m_pTextureAtlas->resizeCapacity(quantity) ) {
// serious problems
CCLOGWARN("cocos2d: WARNING: Not enough memory to resize the atlas");
CCAssert(false,"XXX: CCParticleBatchNode #increaseAtlasCapacity SHALL handle this assert");
}
}
//设置对应索引的粒子无效。
void CCParticleBatchNode::disableParticle(unsigned int particleIndex)
{
//取出对应索引的矩形顶点缓冲信息块并将大小置0.
ccV3F_C4B_T2F_Quad* quad = &((m_pTextureAtlas->getQuads())[particleIndex]);
quad->br.vertices.x = quad->br.vertices.y = quad->tr.vertices.x = quad->tr.vertices.y = quad->tl.vertices.x = quad->tl.vertices.y = quad->bl.vertices.x = quad->bl.vertices.y = 0.0f;
}
// 将一个粒子系统按照指定的索引位置做为子结点加入批次结点。
void CCParticleBatchNode::insertChild(CCParticleSystem* pSystem, unsigned int index)
{
//用index做为粒子系统使用的矩形顶点缓冲信息块索引。
pSystem->setAtlasIndex(index);
//如果“同纹理的顶点缓冲区管理器”中所有的矩形顶点缓冲信息块的数量在直接加入粒子系统的相应数量之后超过了容量,需要扩增容量。
if(m_pTextureAtlas->getTotalQuads() + pSystem->getTotalParticles() > m_pTextureAtlas->getCapacity())
{
//扩增一下“同纹理的顶点缓冲区管理器”中所有的矩形顶点缓冲信息块的数组容量。
increaseAtlasCapacityTo(m_pTextureAtlas->getTotalQuads() + pSystem->getTotalParticles());
//将数组中新增的尾部粒子数量大小的矩形顶点缓冲信息块内存数据清零。
m_pTextureAtlas->fillWithEmptyQuadsFromIndex(m_pTextureAtlas->getCapacity() - pSystem->getTotalParticles(), pSystem->getTotalParticles());
}
// 粒子系统不是最后一个结点。则需要将index位置粒子数量的矩形顶点缓冲信息移到插入新的信息之后的位置,这样对应位置的矩形顶点缓冲信息块就空出来了。
if (pSystem->getAtlasIndex() + pSystem->getTotalParticles() != m_pTextureAtlas->getTotalQuads())
{
m_pTextureAtlas->moveQuadsFromIndex(index, index+pSystem->getTotalParticles());
}
//按照粒子系统的总的粒子数量增加矩形顶点缓冲信息块的数量记数值。
m_pTextureAtlas->increaseTotalQuadsWith(pSystem->getTotalParticles());
//重建所有的粒子的“同纹理的顶点缓冲区管理器”的矩形顶点缓冲信息块的索引。
updateAllAtlasIndexes();
}
//重建所有的粒子的“同纹理的顶点缓冲区管理器”的矩形顶点缓冲信息块的索引。
void CCParticleBatchNode::updateAllAtlasIndexes()
{
//定义临时变量用于循环取值.
CCObject *pObj = NULL;
unsigned int index = 0;
//遍历所有的子结点容器,取出每个元素存入到pObj中.
CCARRAY_FOREACH(m_pChildren,pObj)
{
//取出每个元素,转换成为粒子系统指针.
CCParticleSystem* child = (CCParticleSystem*)pObj;
//设置对应的粒子系统所对应的“同纹理的顶点缓冲区管理器”的矩形顶点缓冲信息块的索引.
child->setAtlasIndex(index);
//索引递增相应粒子数量.
index += child->getTotalParticles();
}
}
// 更新ALPHA混合状态方案.
void CCParticleBatchNode::updateBlendFunc(void)
{
//如果纹理有ALPHA通道,设置其相应的ALPHA混合方案.
if( ! m_pTextureAtlas->getTexture()->hasPremultipliedAlpha()) {
m_tBlendFunc.src = GL_SRC_ALPHA;
m_tBlendFunc.dst = GL_ONE_MINUS_SRC_ALPHA;
}
}
//设置粒子的批次结点所使用的纹理对象。
void CCParticleBatchNode::setTexture(CCTexture2D* texture)
{
//设置当前“同纹理的顶点缓冲区管理器”的对应纹理图。
m_pTextureAtlas->setTexture(texture);
//如果纹理没有ALPHA通道并且ALPHA混合方案与指定方案相同,则重新设置相应的ALPHA混合方案。
if( texture && ! texture->hasPremultipliedAlpha() && ( m_tBlendFunc.src == CC_BLEND_SRC && m_tBlendFunc.dst == CC_BLEND_DST ) )
{
m_tBlendFunc.src = GL_SRC_ALPHA;
m_tBlendFunc.dst = GL_ONE_MINUS_SRC_ALPHA;
}
}
//取得当前粒子的批次结点所用的纹理对象指针。
CCTexture2D* CCParticleBatchNode::getTexture(void)
{
return m_pTextureAtlas->getTexture();
}
//设置ALPHA混合方案。
void CCParticleBatchNode::setBlendFunc(ccBlendFunc blendFunc)
{
m_tBlendFunc = blendFunc;
}
//取得ALPHA混合方案。
ccBlendFunc CCParticleBatchNode::getBlendFunc(void)
{
return m_tBlendFunc;
}
NS_CC_END
粒子的批次结点讲完了,我们来总结一下:
批次结点通过m_pTextureAtlas来对使用相同纹理的多个粒子系统中的粒子进行矩形顶点缓冲区的管理,这些粒子系统以子结点的形式按照Z排序存放在m_pChildren中。批次结点可以根据设置的纹理是否有ALPHA通道来设置透明度混合和高亮两种混合方案,并在渲染时使用。渲染时通过m_pTextureAtlas来进行绘制处理,一个批次内将使用相同纹理的所有粒子系统的所有粒子渲染出来。大大优化了渲染状态和纹理的切换开销,提升了渲染效率。
但是,如何指定粒子系统放在相应的批次结点中呢?这个还需要后面我们继续分析研究。
下面我们继续学习粒子系统,打开CCParicleSystem.h:
#ifndef __CCPARTICLE_SYSTEM_H__
#define __CCPARTICLE_SYSTEM_H__
#include "CCProtocols.h"
#include "base_nodes/CCNode.h"
#include "cocoa/CCDictionary.h"
#include "cocoa/CCString.h"
//使用Cocos2d命名空间
NS_CC_BEGIN
//.h中要用到CCParticleBatchNode类。
class CCParticleBatchNode;
//一些发射器的状态类型设置枚举。
enum {
//发射器永远激活
kCCParticleDurationInfinity = -1,
//开始与结束时粒子数量相等
kCCParticleStartSizeEqualToEndSize = -1,
//开始与结束时粒子的旋转角度相等。
kCCParticleStartRadiusEqualToEndRadius = -1,
//上面枚举的一些别名
kParticleStartSizeEqualToEndSize = kCCParticleStartSizeEqualToEndSize,
kParticleDurationInfinity = kCCParticleDurationInfinity,
};
//粒子发射器的两种模式
enum {
//重力模式 [让粒子向一个中心点移动或离开一个中心点,比如火,烟]
kCCParticleModeGravity,
//环型模式[让粒子沿着一个圆形旋转,比如螺旋]
kCCParticleModeRadius,
};
//粒子的两种运动方式
typedef enum {
//自由方式:粒子的运动是自由的无拘无束的,移动不受任何结点的影响。
kCCPositionTypeFree,
//相对方式:粒子的运动是相对于父结点的坐标系。比如可以将粒子关联到一个精灵上,让粒子跟随精力一起移动。
kCCPositionTypeRelative,
//集群方式:这种方式粒子跟随发射器移动。
kCCPositionTypeGrouped,
}tCCPositionType;
// 兼容之前的版本的对应枚举值。
enum {
kPositionTypeFree = kCCPositionTypeFree,
kPositionTypeGrouped = kCCPositionTypeGrouped,
};
//这里就是包含粒子信息的粒子结构体了。我们来深入的看一下它的属性。
typedef struct sCCParticle {
CCPoint pos;//位置
CCPoint startPos;//起始位置
ccColor4F color;//颜色
ccColor4F deltaColor;//颜色变化量
float size;//大小
float deltaSize;//大小变化量
float rotation;//旋转角度
float deltaRotation;//旋转角度变化量。
float timeToLive;//生命值。
unsigned int atlasIndex;//对应批次结点中的矩形顶点缓冲块的索引。
//描述变化方式的成员结构 (重力加速度模式)
struct {
CCPoint dir; //运动方向
float radialAccel; //旋转加速度
float tangentialAccel; //切线加速度
} modeA;
//描述变化方式的成员结构(环型模式)
struct {
float angle; //起始旋转角度
float degreesPerSecond;// 每秒的旋转弧度
float radius; // 起始旋转角度
float deltaRadius; // 每次更新时旋转角度变化量。
} modeB;
}tCCParticle;
需要用到纹理对象,这里声明一下。
class CCTexture2D;
//粒子系统类的定义,一个粒子系统是由结点类和纹理接口类派生。
class CC_DLL CCParticleSystem : public CCNode, public CCTextureProtocol
{
protected:
//对应的PLIST文件
std::string m_sPlistFile;
//开始到当前的运行秒数
float m_fElapsed;
// 不同变化模式的信息结构
//重力加速度模式
struct {
//重力加速度
CCPoint gravity;
//速度值
float speed;
//速度值的变化量
float speedVar;
//相关加速值
float tangentialAccel;
//相关加速值变化量
float tangentialAccelVar;
//旋转加速值
float radialAccel;
//旋转加速值变化量
float radialAccelVar;
} modeA;
//环型移动模式
struct {
//起始角度值
float startRadius;
//起始角度值变化量
float startRadiusVar;
//结束角度值
float endRadius;
//结束角度值变化量
float endRadiusVar;
//每秒旋转角度值
float rotatePerSecond;
//每秒旋转角度值变化量
float rotatePerSecondVar;
} modeB;
//粒子的动态数组
tCCParticle *m_pParticles;
//色彩调整
// BOOL colorModulate;
//每秒的粒子发射数
float m_fEmitCounter;
//! particle idx
unsigned int m_uParticleIdx;
// 优化选项
//CC_UPDATE_PARTICLE_IMP updateParticleImp;
//SEL updateParticleSel;
//渲染精灵所用的粒子批次结点动态数组
CC_PROPERTY(CCParticleBatchNode*, m_pBatchNode, BatchNode);
// 当前系统在批次结点数组中的索引
CC_SYNTHESIZE(unsigned int, m_uAtlasIndex, AtlasIndex);
//是否缩放和旋转。
bool m_bTransformSystemDirty;
//已经创建的粒子数量
unsigned int m_uAllocatedParticles;
//发射器是否被激活。
bool m_bIsActive;
//当前已经发射的粒子数量。
CC_PROPERTY_READONLY(unsigned int, m_uParticleCount, ParticleCount)
//发射器将运行的时间。
CC_PROPERTY(float, m_fDuration, Duration)
//发射器的原位置点。
CC_PROPERTY_PASS_BY_REF(CCPoint, m_tSourcePosition, SourcePosition)
//当前发射器的位置变量。
CC_PROPERTY_PASS_BY_REF(CCPoint, m_tPosVar, PosVar)
//初始时的粒子生命值。
CC_PROPERTY(float, m_fLife, Life)
//粒子生命值的变化量。
CC_PROPERTY(float, m_fLifeVar, LifeVar)
//粒子的初始角度。
CC_PROPERTY(float, m_fAngle, Angle)
//粒子的角度变化量。
CC_PROPERTY(float, m_fAngleVar, AngleVar)
//相关公共函数。
public:
//重力加速度模式的变量值的存取函数。
virtual const CCPoint& getGravity();
virtual void setGravity(const CCPoint& g);
virtual float getSpeed();
virtual void setSpeed(float speed);
virtual float getSpeedVar();
virtual void setSpeedVar(float speed);
virtual float getTangentialAccel();
virtual void setTangentialAccel(float t);
virtual float getTangentialAccelVar();
virtual void setTangentialAccelVar(float t);
virtual float getRadialAccel();
virtual void setRadialAccel(float t);
virtual float getRadialAccelVar();
virtual void setRadialAccelVar(float t);
//环型移动模式的变量值的存取函数。
virtual float getStartRadius();
virtual void setStartRadius(float startRadius);
virtual float getStartRadiusVar();
virtual void setStartRadiusVar(float startRadiusVar);
virtual float getEndRadius();
virtual void setEndRadius(float endRadius);
virtual float getEndRadiusVar();
virtual void setEndRadiusVar(float endRadiusVar);
virtual float getRotatePerSecond();
virtual void setRotatePerSecond(float degrees);
virtual float getRotatePerSecondVar();
virtual void setRotatePerSecondVar(float degrees);
//设置缩放
virtual void setScale(float s);
//设置旋转
virtual void setRotation(float newRotation);
//设置单方向轴的缩放
virtual void setScaleX(float newScaleX);
virtual void setScaleY(float newScaleY);
//是否被激活
virtual bool isActive();
//混合状态是否为亮模式
virtual bool isBlendAdditive();
//设置是否高亮模式
virtual void setBlendAdditive(bool value);
//粒子的开始大小的属性和存取访问接口
CC_PROPERTY(float, m_fStartSize, StartSize)
//粒子的开始大小的变化值属性和存取访问接口
CC_PROPERTY(float, m_fStartSizeVar, StartSizeVar)
//粒子的结束大小的属性和存取访问接口
CC_PROPERTY(float, m_fEndSize, EndSize)
//粒子的结束大小的变化值属性和存取访问接口
CC_PROPERTY(float, m_fEndSizeVar, EndSizeVar)
//粒子的起始颜色的属性和存取访问接口
CC_PROPERTY_PASS_BY_REF(ccColor4F, m_tStartColor, StartColor)
//粒子的起始颜色的变化值属性和存取访问接口
CC_PROPERTY_PASS_BY_REF(ccColor4F, m_tStartColorVar, StartColorVar)
//粒子的结束颜色的属性和存取访问接口
CC_PROPERTY_PASS_BY_REF(ccColor4F, m_tEndColor, EndColor)
//粒子的结束颜色的变化值属性和存取访问接口
CC_PROPERTY_PASS_BY_REF(ccColor4F, m_tEndColorVar, EndColorVar)
//粒子的初始化角度值属性和存取访问接口
CC_PROPERTY(float, m_fStartSpin, StartSpin)
//粒子的初始化角度变化值属性和存取访问接口
CC_PROPERTY(float, m_fStartSpinVar, StartSpinVar)
//粒子的结束角度值属性和存取访问接口
CC_PROPERTY(float, m_fEndSpin, EndSpin)
//粒子的结束角度变化值属性和存取访问接口
CC_PROPERTY(float, m_fEndSpinVar, EndSpinVar)
//发射器的发射速度值属性和存取访问接口
CC_PROPERTY(float, m_fEmissionRate, EmissionRate)
//最大粒子数量值属性和存取访问接口
CC_PROPERTY(unsigned int, m_uTotalParticles, TotalParticles)
//粒子所用的纹理对象实例指针和存取访问接口
CC_PROPERTY(CCTexture2D*, m_pTexture, Texture)
//粒子渲染所用的混合状态方案和存取访问接口
CC_PROPERTY(ccBlendFunc, m_tBlendFunc, BlendFunc)
//是否使用透晨度来影响修改颜色值和存取访问接口
CC_PROPERTY(bool, m_bOpacityModifyRGB, OpacityModifyRGB)
//混合状态是否是使用源混合状态 = GL_SRC_ALPHA,目标混合状态 = GL_ONE 的混合叠加方案
bool m_bIsBlendAdditive;
//粒子的移动方式:1,自由方式 2,集群方式
CC_PROPERTY(tCCPositionType, m_ePositionType, PositionType)
protected:
//在运行结束后是否被自动移除。
bool m_bIsAutoRemoveOnFinish;
public:
//取得运行结束后是否被自动移除。
virtual bool isAutoRemoveOnFinish();
//设置运行结束后是否被自动移除。
virtual void setAutoRemoveOnFinish(bool var);
//发射器模式:1,重力加速度模式 2 环型模式
CC_PROPERTY(int, m_nEmitterMode, EmitterMode)
public:
//构造函数。
CCParticleSystem();
//析构函数。
virtual ~CCParticleSystem();
//静态的由PLIST文件创建粒子系统的函数,参数为PLIST文件名,内部调用create来进行实现。
CC_DEPRECATED_ATTRIBUTE static CCParticleSystem * particleWithFile(const char *plistFile);
//上面的create实现。
static CCParticleSystem * create(const char *plistFile);
//粒子系统的初始化。
bool init();
//从一个PLIST文件中初始化粒子系统。
bool initWithFile(const char *plistFile);
//从一个词典中初始化粒子系统。
bool initWithDictionary(CCDictionary *dictionary);
//初始化粒子总数量。
virtual bool initWithTotalParticles(unsigned int numberOfParticles);
//为发射器增加一个粒子。
bool addParticle();
//初始化一个粒子。
void initParticle(tCCParticle* particle);
//暂停当前粒子系统。
void stopSystem();
//重置当前粒子系统
void resetSystem();
//当前粒子系统是否已经将所有粒子发射。
bool isFull();
//用于被派生类重载的接口。
virtual void updateQuadWithParticle(tCCParticle* particle, const CCPoint& newPosition);
//用于被派生类重载的接口。
virtual void postStep();
//更新粒子系统。
virtual void update(float dt);
//不需要时间流逝的更新粒子系统。
virtual void updateWithNoTime(void);
protected:
//更新混合状态方案
virtual void updateBlendFunc();
};
CPP文件:
#include "CCParticleSystem.h"
#include "CCParticleBatchNode.h"
#include "ccTypes.h"
#include "textures/CCTextureCache.h"
#include "textures/CCTextureAtlas.h"
#include "support/base64.h"
#include "support/CCPointExtension.h"
#include "platform/CCFileUtils.h"
#include "platform/CCImage.h"
#include "platform/platform.h"
#include "support/zip_support/ZipUtils.h"
#include "CCDirector.h"
#include "support/CCProfiling.h"
// opengl
#include "CCGL.h"
//使用Cocos2d-x命名空间。
NS_CC_BEGIN
//粒子系统的构造函数。
CCParticleSystem::CCParticleSystem()
:m_sPlistFile("")
,m_fElapsed(0)
,m_pParticles(NULL)
,m_fEmitCounter(0)
,m_uParticleIdx(0)
,m_bIsActive(true)
,m_uParticleCount(0)
,m_fDuration(0)
,m_tSourcePosition(CCPointZero)
,m_tPosVar(CCPointZero)
,m_fLife(0)
,m_fLifeVar(0)
,m_fAngle(0)
,m_fAngleVar(0)
,m_fStartSize(0)
,m_fStartSizeVar(0)
,m_fEndSize(0)
,m_fEndSizeVar(0)
,m_fStartSpin(0)
,m_fStartSpinVar(0)
,m_fEndSpin(0)
,m_fEndSpinVar(0)
,m_fEmissionRate(0)
,m_uTotalParticles(0)
,m_pTexture(NULL)
,m_bOpacityModifyRGB(false)
,m_bIsBlendAdditive(false)
,m_ePositionType(kCCPositionTypeFree)
,m_bIsAutoRemoveOnFinish(false)
,m_nEmitterMode(kCCParticleModeGravity)
,m_pBatchNode(NULL)
,m_uAtlasIndex(0)
,m_bTransformSystemDirty(false)
,m_uAllocatedParticles(0)
{
//大量的数据初始化工作。
modeA.gravity = CCPointZero;
modeA.speed = 0;
modeA.speedVar = 0;
modeA.tangentialAccel = 0;
modeA.tangentialAccelVar = 0;
modeA.radialAccel = 0;
modeA.radialAccelVar = 0;
modeB.startRadius = 0;
modeB.startRadiusVar = 0;
modeB.endRadius = 0;
modeB.endRadiusVar = 0;
modeB.rotatePerSecond = 0;
modeB.rotatePerSecondVar = 0;
m_tBlendFunc.src = CC_BLEND_SRC;
m_tBlendFunc.dst = CC_BLEND_DST;
}
// 从PLIST文件中创建粒子系统。内部调用create进行实现。
CCParticleSystem * CCParticleSystem::particleWithFile(const char *plistFile)
{
return CCParticleSystem::create(plistFile);
}
//从PLIST文件中创建粒子系统的具体实现。
CCParticleSystem * CCParticleSystem::create(const char *plistFile)
{
//创建一个粒子系统,由PLIST文件进行初始化并交由内存管理器进行引用计数器的管理。
CCParticleSystem *pRet = new CCParticleSystem();
if (pRet && pRet->initWithFile(plistFile))
{
pRet->autorelease();
return pRet;
}
//如果失败,释放并置空。
CC_SAFE_DELETE(pRet);
return pRet;
}
//粒子系统的初始化。
bool CCParticleSystem::init()
{
//初始化粒子的总数为150个。
return initWithTotalParticles(150);
}
//从PLIST文件中初始化粒子系统。
bool CCParticleSystem::initWithFile(const char *plistFile)
{
bool bRet = false;
//取得PLIST的全路径。
m_sPlistFile = CCFileUtils::sharedFileUtils()->fullPathFromRelativePath(plistFile);
//从PLIST文件中创建出数据项与数据值对应关系表的词典。
CCDictionary *dict = CCDictionary::createWithContentsOfFileThreadSafe(m_sPlistFile.c_str());
//有效性判断。
CCAssert( dict != NULL, "Particles: file not found");
//由词典中的所有数据来进行初始化当前粒子系统。
bRet = this->initWithDictionary(dict);
//释放词典。
dict->release();
return bRet;
}
//由词典来初始化当前的粒子系统。
bool CCParticleSystem::initWithDictionary(CCDictionary *dictionary)
{
//定义临时变量。
bool bRet = false;
unsigned char *buffer = NULL;
unsigned char *deflated = NULL;
CCImage *image = NULL;
//
do
{
//从词典中取得粒子的最大数量值。
int maxParticles = dictionary->valueForKey("maxParticles")->intValue();
//初始化粒子容量。
if(this->initWithTotalParticles(maxParticles))
{
// 从词典中取得角度及角度变化量的值。
m_fAngle = dictionary->valueForKey("angle")->floatValue();
m_fAngleVar = dictionary->valueForKey("angleVariance")->floatValue();
//从词典中取得动画时长的值。
m_fDuration = dictionary->valueForKey("duration")->floatValue();
//从词典中取得混合方案
m_tBlendFunc.src = dictionary->valueForKey("blendFuncSource")->intValue();
m_tBlendFunc.dst = dictionary->valueForKey("blendFuncDestination")->intValue();
//从词典中取得起始颜色
m_tStartColor.r = dictionary->valueForKey("startColorRed")->floatValue();
m_tStartColor.g = dictionary->valueForKey("startColorGreen")->floatValue();
m_tStartColor.b = dictionary->valueForKey("startColorBlue")->floatValue();
m_tStartColor.a = dictionary->valueForKey("startColorAlpha")->floatValue();
//从词典中取得起始颜色变化量
m_tStartColorVar.r = dictionary->valueForKey("startColorVarianceRed")->floatValue();
m_tStartColorVar.g = dictionary->valueForKey("startColorVarianceGreen")->floatValue();
m_tStartColorVar.b = dictionary->valueForKey("startColorVarianceBlue")->floatValue();
m_tStartColorVar.a = dictionary->valueForKey("startColorVarianceAlpha")->floatValue();
//从词典中取得结束的颜色值。
m_tEndColor.r = dictionary->valueForKey("finishColorRed")->floatValue();
m_tEndColor.g = dictionary->valueForKey("finishColorGreen")->floatValue();
m_tEndColor.b = dictionary->valueForKey("finishColorBlue")->floatValue();
m_tEndColor.a = dictionary->valueForKey("finishColorAlpha")->floatValue();
//从词典中取得结束的颜色变化量值。
m_tEndColorVar.r = dictionary->valueForKey("finishColorVarianceRed")->floatValue();
m_tEndColorVar.g = dictionary->valueForKey("finishColorVarianceGreen")->floatValue();
m_tEndColorVar.b = dictionary->valueForKey("finishColorVarianceBlue")->floatValue();
m_tEndColorVar.a = dictionary->valueForKey("finishColorVarianceAlpha")->floatValue();
//从词典中取得粒子的起始大小。
m_fStartSize = dictionary->valueForKey("startParticleSize")->floatValue();
m_fStartSizeVar = dictionary->valueForKey("startParticleSizeVariance")->floatValue();
//从词典中取得粒子的终止大小。
m_fEndSize = dictionary->valueForKey("finishParticleSize")->floatValue();
m_fEndSizeVar = dictionary->valueForKey("finishParticleSizeVariance")->floatValue();
//从词典中取得源位置。
float x = dictionary->valueForKey("sourcePositionx")->floatValue();
float y = dictionary->valueForKey("sourcePositiony")->floatValue();
this->setPosition( ccp(x,y) );
//从词典中取得源位置变化量。
m_tPosVar.x = dictionary->valueForKey("sourcePositionVariancex")->floatValue();
m_tPosVar.y = dictionary->valueForKey("sourcePositionVariancey")->floatValue();
//从词典中取得粒子的起始旋转角度。
m_fStartSpin = dictionary->valueForKey("rotationStart")->floatValue();
//从词典中取得粒子的起始旋转角度变化值。
m_fStartSpinVar = dictionary->valueForKey("rotationStartVariance")->floatValue();
//从词典中取得粒子的终止旋转角度。
m_fEndSpin= dictionary->valueForKey("rotationEnd")->floatValue();
//从词典中取得粒子的终止旋转角度变化值。
m_fEndSpinVar= dictionary->valueForKey("rotationEndVariance")->floatValue();
//从词典中取得粒子的发射器类型。
m_nEmitterMode = dictionary->valueForKey("emitterType")->intValue();
//重力加速度模式
if( m_nEmitterMode == kCCParticleModeGravity )
{
//从词典中取得粒子的重力加速度。
modeA.gravity.x = dictionary->valueForKey("gravityx")->floatValue();
modeA.gravity.y = dictionary->valueForKey("gravityy")->floatValue();
//从词典中取得粒子的速度及变化量。
modeA.speed = dictionary->valueForKey("speed")->floatValue();
modeA.speedVar = dictionary->valueForKey("speedVariance")->floatValue();
//从词典中取得粒子的旋转加速度及变化量。
modeA.radialAccel = dictionary->valueForKey("radialAcceleration")->floatValue();
modeA.radialAccelVar = dictionary->valueForKey("radialAccelVariance")->floatValue();
//从词典中取得粒子的切线加速度及变化量。
modeA.tangentialAccel = dictionary->valueForKey("tangentialAcceleration")->floatValue();
modeA.tangentialAccelVar = dictionary->valueForKey("tangentialAccelVariance")->floatValue();
}
// 环型模式
else if( m_nEmitterMode == kCCParticleModeRadius )
{
//从词典中取得粒子的最大角度。
modeB.startRadius = dictionary->valueForKey("maxRadius")->floatValue();
//从词典中取得粒子的最大角度变化值。
modeB.startRadiusVar = dictionary->valueForKey("maxRadiusVariance")->floatValue();
//从词典中取得粒子的最小角度。
modeB.endRadius = dictionary->valueForKey("minRadius")->floatValue();
//竟然没有从词典中取得粒子的最小角度变化值而是直接设为0。好吧!
modeB.endRadiusVar = 0.0f;
//从词典中取得粒子的每秒旋转圈数。
modeB.rotatePerSecond = dictionary->valueForKey("rotatePerSecond")->floatValue();
//从词典中取得粒子的每秒旋转圈数变化量。
modeB.rotatePerSecondVar = dictionary->valueForKey("rotatePerSecondVariance")->floatValue();
} else {//类型无效的错误提示。
CCAssert( false, "Invalid emitterType in config file");
CC_BREAK_IF(true);
}
// 从词典中取得粒子的生命值及变化量。
m_fLife = dictionary->valueForKey("particleLifespan")->floatValue();
m_fLifeVar = dictionary->valueForKey("particleLifespanVariance")->floatValue();
//计数出发射器的发射速度。
m_fEmissionRate = m_uTotalParticles / m_fLife;
//如果一个批次结点被使用着,则不能直接取得内部的纹理对象。
if (!m_pBatchNode)
{
// 设置修改透明度不影响RGB值。
m_bOpacityModifyRGB = false;
// 从词典中取得所用的纹理图片名称。
const char* textureName = dictionary->valueForKey("textureFileName")->getCString();
// 通过文件管理器查找到相应的全路径字符串。
std::string fullpath = CCFileUtils::sharedFileUtils()->fullPathFromRelativeFile(textureName, m_sPlistFile.c_str());
//定义纹理对象指针并置空。
CCTexture2D *tex = NULL;
//如果纹理名称有效。
if (strlen(textureName) > 0)
{
// 先取得当前文件管理器是否进行相关提示。
bool bNotify = CCFileUtils::sharedFileUtils()->isPopupNotify();
// 在这里指定不需要弹出文件读取出错的提示对话框。为什么呢?
CCFileUtils::sharedFileUtils()->setPopupNotify(false);
// 将文件加载到纹理管理器中并返回纹理对象指针。
tex = CCTextureCache::sharedTextureCache()->addImage(fullpath.c_str());
//重置文件读取出错的提示对话框。
CCFileUtils::sharedFileUtils()->setPopupNotify(bNotify);
}
//如果纹理对象指针有效,设置为当前粒子系统所用的纹理贴图。
if (tex)
{
setTexture(tex);
}
else
{
//如果无效,则不是从文件中加载,而是直接从数据中创建纹理。
//从词典中取得所用的纹理图片数据返回给textureData。
const char *textureData = dictionary->valueForKey("textureImageData")->getCString();
//数据的有效性判断。
CCAssert(textureData, "");
//取得数据的长度。
int dataLen = strlen(textureData);
if(dataLen != 0)
{
//注意base64Decode提供了Base64解码过程。返回解码后的数据到buffer,返回长度值decodeLen。
int decodeLen = base64Decode((unsigned char*)textureData, (unsigned int)dataLen, &buffer);
//有效性判断。
CCAssert( buffer != NULL, "CCParticleSystem: error decoding textureImageData");
CC_BREAK_IF(!buffer);
// ZipUtils::ccInflateMemory 提供了gzip解压的过程。返回解压后的数据到deflated,返回长度值decodeLen。
int deflatedLen = ZipUtils::ccInflateMemory(buffer, decodeLen, &deflated);
//有效性判断。
CCAssert( deflated != NULL, "CCParticleSystem: error ungzipping textureImageData");
CC_BREAK_IF(!deflated);
//创建一个新的CCImage,返回其实例对象指针给image。
image = new CCImage();
//通过解压后的数据来填充CCImage的图像数据。
bool isOK = image->initWithImageData(deflated, deflatedLen);
//判断是否成功。
CCAssert(isOK, "CCParticleSystem: error init image with Data");
CC_BREAK_IF(!isOK);
//将这个image加入到纹理管理器中,并设置给当前粒子系统使用。
setTexture(CCTextureCache::sharedTextureCache()->addUIImage(image, fullpath.c_str()));
//image的释放处理。
image->release();
}
}
//纹理的有效性判断。
CCAssert( this->m_pTexture != NULL, "CCParticleSystem: error loading the texture");
}
bRet = true;
}
} while (0);
//释放buffer和deflated。
CC_SAFE_DELETE_ARRAY(buffer);
CC_SAFE_DELETE_ARRAY(deflated);
return bRet;
}
//通过粒子的最大数量来初始化粒子系统。
bool CCParticleSystem::initWithTotalParticles(unsigned int numberOfParticles)
{
//记录最大数量。
m_uTotalParticles = numberOfParticles;
//先释放原来的粒子数组信息。
CC_SAFE_FREE(m_pParticles);
//重新申请粒子数组对应的相应内存。
m_pParticles = (tCCParticle*)calloc(m_uTotalParticles, sizeof(tCCParticle));
//如果申请内存失败,提示出错,释放并返回false。
if( ! m_pParticles )
{
CCLOG("Particle system: not enough memory");
this->release();
return false;
}
//记录当前数组中的粒子的数量。
m_uAllocatedParticles = numberOfParticles;
//如果对应的粒子的批次结点有效。
if (m_pBatchNode)
{
//遍历并填充相应粒子对应的批次结点中矩形顶点缓冲信息块的索引。
for (unsigned int i = 0; i < m_uTotalParticles; i++)
{
m_pParticles[i].atlasIndex=i;
}
}
// 默认激活。
m_bIsActive = true;
// 默认的ALPHA混合方案。
m_tBlendFunc.src = CC_BLEND_SRC;
m_tBlendFunc.dst = CC_BLEND_DST;
// 默认的移动类型。
m_ePositionType = kCCPositionTypeFree;
// 默认的运动模式。
m_nEmitterMode = kCCParticleModeGravity;
//默认在结束后不自动释放当前粒子系统。
m_bIsAutoRemoveOnFinish = false;
//设置不使用矩阵转换,更新更快一些,
m_bTransformSystemDirty = false;
// 在动画作束后更新。
this->scheduleUpdateWithPriority(1);
return true;
}
//析构函数。
CCParticleSystem::~CCParticleSystem()
{
unscheduleUpdate();
//释放粒子数组。
CC_SAFE_FREE(m_pParticles);
//释放纹理。
CC_SAFE_RELEASE(m_pTexture);
}
//增加一个新的粒子。
bool CCParticleSystem::addParticle()
{
//如果当前粒子系统已经达到最大量则返回。
if (this->isFull())
{
return false;
}
//否则获取数组中当前数量对应的索引位置取出来相应的粒子并初始化。
tCCParticle * particle = &m_pParticles[ m_uParticleCount ];
this->initParticle(particle);
++m_uParticleCount;
return true;
}
//初始化粒子。
void CCParticleSystem::initParticle(tCCParticle* particle)
{
// 初始化生命时间。CCRANDOM_MINUS1_1()是取一个(-1,1)之间的随机值。
particle->timeToLive = m_fLife + m_fLifeVar * CCRANDOM_MINUS1_1();
// 将随机值限定大于0。
particle->timeToLive = MAX(0, particle->timeToLive);
// 初始化一个以m_tSourcePosition位置为中心的随机位置,最大变化范围为m_tPosVar。
particle->pos.x = m_tSourcePosition.x + m_tPosVar.x * CCRANDOM_MINUS1_1();
particle->pos.y = m_tSourcePosition.y + m_tPosVar.y * CCRANDOM_MINUS1_1();
// 初始化一个以m_tStartColor为基本色的随机色,最大变化范围为m_ tStartColorVar。
ccColor4F start;
start.r = clampf(m_tStartColor.r + m_tStartColorVar.r * CCRANDOM_MINUS1_1(), 0, 1);
start.g = clampf(m_tStartColor.g + m_tStartColorVar.g * CCRANDOM_MINUS1_1(), 0, 1);
start.b = clampf(m_tStartColor.b + m_tStartColorVar.b * CCRANDOM_MINUS1_1(), 0, 1);
start.a = clampf(m_tStartColor.a + m_tStartColorVar.a * CCRANDOM_MINUS1_1(), 0, 1);
// 初始化一个以m_tEndColor为基本色的随机色,最大范围为m_ tEndColorVar。
ccColor4F end;
end.r = clampf(m_tEndColor.r + m_tEndColorVar.r * CCRANDOM_MINUS1_1(), 0, 1);
end.g = clampf(m_tEndColor.g + m_tEndColorVar.g * CCRANDOM_MINUS1_1(), 0, 1);
end.b = clampf(m_tEndColor.b + m_tEndColorVar.b * CCRANDOM_MINUS1_1(), 0, 1);
end.a = clampf(m_tEndColor.a + m_tEndColorVar.a * CCRANDOM_MINUS1_1(), 0, 1);
// 初始化粒子的颜色和每次更新时的变化量。
particle->color = start;
particle->deltaColor.r = (end.r - start.r) / particle->timeToLive;
particle->deltaColor.g = (end.g - start.g) / particle->timeToLive;
particle->deltaColor.b = (end.b - start.b) / particle->timeToLive;
particle->deltaColor.a = (end.a - start.a) / particle->timeToLive;
// 初始化一个以m_fStartSize为基本大小的随机大小值,最大变化范围为m_fStartSizeVar。
float startS = m_fStartSize + m_fStartSizeVar * CCRANDOM_MINUS1_1();
//限定大小。
startS = MAX(0, startS);
//设置大小。
particle->size = startS;
//如果终止大小为设定的值-1,即代表大小不随更新变化。
if( m_fEndSize == kCCParticleStartSizeEqualToEndSize )
{
particle->deltaSize = 0;
}
else
{
//否则计算出大小每次更新时的变化量。
float endS = m_fEndSize + m_fEndSizeVar * CCRANDOM_MINUS1_1();
endS = MAX(0, endS); // No negative values
particle->deltaSize = (endS - startS) / particle->timeToLive;
}
// 随机的旋转起始和终止角度。
float startA = m_fStartSpin + m_fStartSpinVar * CCRANDOM_MINUS1_1();
float endA = m_fEndSpin + m_fEndSpinVar * CCRANDOM_MINUS1_1();
//保存起始角度,并计算每次更新的旋转角度。
particle->rotation = startA;
particle->deltaRotation = (endA - startA) / particle->timeToLive;
//如果是自由方式的位置设置方式,初始化起点为0,0点。
if( m_ePositionType == kCCPositionTypeFree )
{
//将当前坐标系中零零点转换为世界坐标系中的位置。
currentPosition = this->convertToWorldSpace(CCPointZero);
particle->startPos = this->convertToWorldSpace(CCPointZero);
}
else if ( m_ePositionType == kCCPositionTypeRelative )
{
//如果是相对方式的位置设置方式,初始化起点位置为当前粒子系统坐标系的位置。
particle->startPos = m_tPosition;
}
// 设置方向值,是一个弧度,由以m_fAngle为基础的随机范围为m_fAngleVar所生成的角度转化而成。
float a = CC_DEGREES_TO_RADIANS( m_fAngle + m_fAngleVar * CCRANDOM_MINUS1_1() );
// 如果是重力加速度模式。
if (m_nEmitterMode == kCCParticleModeGravity)
{
//由方向值计算出X,Y的分量的角度。
CCPoint v(cosf( a ), sinf( a ));
// s 为此模式下的速度,是以modeA.speed为基础的随机范围为modeA.speedVar的随机值。
float s = modeA.speed + modeA.speedVar * CCRANDOM_MINUS1_1();
// 将上面的信息相乘的结果做为方向保存到粒子的modeA的方向值中。
particle->modeA.dir = ccpMult( v, s );
// 旋转加速度的值为modeA.radialAccel为基础的随机范围为modeA.radialAccelVar的值。
particle->modeA.radialAccel = modeA.radialAccel + modeA.radialAccelVar * CCRANDOM_MINUS1_1();
// 切线加速度的值为modeA.tangentialAccel为基础的随机范围为modeA.tangentialAccelVar的值。
particle->modeA.tangentialAccel = modeA.tangentialAccel + modeA.tangentialAccelVar * CCRANDOM_MINUS1_1();
}
// 如果是环形模式。
else
{
// 设置起始弧度和结束弧度。也是以相应值为基础进行随机计算。
float startRadius = modeB.startRadius + modeB.startRadiusVar * CCRANDOM_MINUS1_1();
float endRadius = modeB.endRadius + modeB.endRadiusVar * CCRANDOM_MINUS1_1();
//填充到粒子的数据值中。
particle->modeB.radius = startRadius;
//如果结束弧度值为指定的值,则在更新过程中不进行旋转变化。
if(modeB.endRadius == kCCParticleStartRadiusEqualToEndRadius)
{
particle->modeB.deltaRadius = 0;
}
else
{
//否则计算每次更新的弧度变化量。
particle->modeB.deltaRadius = (endRadius - startRadius) / particle->timeToLive;
}
//保存方向值弧度。并计算每秒的旋转弧度。
particle->modeB.angle = a;
particle->modeB.degreesPerSecond = CC_DEGREES_TO_RADIANS(modeB.rotatePerSecond + modeB.rotatePerSecondVar * CCRANDOM_MINUS1_1());
}
}
//停止粒子系统。
void CCParticleSystem::stopSystem()
{
m_bIsActive = false;
m_fElapsed = m_fDuration;
m_fEmitCounter = 0;
}
//重新启动粒子系统。
void CCParticleSystem::resetSystem()
{
m_bIsActive = true;
m_fElapsed = 0;
//遍历每个粒子,设置生命值为0。
for (m_uParticleIdx = 0; m_uParticleIdx < m_uParticleCount; ++m_uParticleIdx)
{
tCCParticle *p = &m_pParticles[m_uParticleIdx];
p->timeToLive = 0;
}
}
//粒子系统是否已经填满。
bool CCParticleSystem::isFull()
{
return (m_uParticleCount == m_uTotalParticles);
}
// 粒子系统的更新函数。
void CCParticleSystem::update(float dt)
{
//
CC_PROFILER_START_CATEGORY(kCCProfilerCategoryParticles , "CCParticleSystem - update");
//如果是激活状态,并且发射速度大于0
if (m_bIsActive && m_fEmissionRate)
{
//计算出产生每个粒子的平均时间间隔。
float rate = 1.0f / m_fEmissionRate;
//如果当前粒子数量仍小于最大数量。对发射器的时间计数器加上当前帧的时间间隔值dt。
if (m_uParticleCount < m_uTotalParticles)
{
m_fEmitCounter += dt;
}
//Whle循环,看当前积累的时间间隔积足够创建多少粒子就创建多少粒子。
while (m_uParticleCount < m_uTotalParticles && m_fEmitCounter > rate)
{
this->addParticle();
m_fEmitCounter -= rate;
}
//记录时间间隔。
m_fElapsed += dt;
//如时总的动画时长为-1并且
if (m_fDuration != -1 && m_fDuration < m_fElapsed)
{
this->stopSystem();
}
}
//设置粒子的索引值。
m_uParticleIdx = 0;
//定义一个临时结构变量存储当前位置,初始化为零零点。
CCPoint currentPosition = CCPointZero;
//如果自由模式
if (m_ePositionType == kCCPositionTypeFree)
{
//将当前坐标系中零零点转换为世界坐标系中的位置。
currentPosition = this->convertToWorldSpace(CCPointZero);
}
else if (m_ePositionType == kCCPositionTypeRelative)
{
//如果是相对模式,保存当前坐标系的位置。
currentPosition = m_tPosition;
}
//如果显示标记为true
if (m_bIsVisible)
{
//遍历每一个粒子,进行更新.
while (m_uParticleIdx < m_uParticleCount)
{
//取得对应的粒子.
tCCParticle *p = &m_pParticles[m_uParticleIdx];
// 生命值的减少.
p->timeToLive -= dt;
//如果生命值大于0.则进行更新.
if (p->timeToLive > 0)
{
//如果是重力加速度模式
if (m_nEmitterMode == kCCParticleModeGravity)
{
//定义临时变量存储位置,旋转角度,切角
CCPoint tmp, radial, tangential;
//初始化角度值为零零点.
radial = CCPointZero;
// 旋转角度的计算.
if (p->pos.x || p->pos.y)
{
//向量归一化.
radial = ccpNormalize(p->pos);
}
//设置切角度为当前角度。
tangential = radial;
// 旋转角度的乘以速度值.
radial = ccpMult(radial, p->modeA.radialAccel);
// 切角度变化计算.
float newy = tangential.x;
tangential.x = -tangential.y;
tangential.y = newy;
//
tangential = ccpMult(tangential, p->modeA.tangentialAccel);
// 位置的计算.
tmp = ccpAdd( ccpAdd( radial, tangential), modeA.gravity);
tmp = ccpMult( tmp, dt);
p->modeA.dir = ccpAdd( p->modeA.dir, tmp);
tmp = ccpMult(p->modeA.dir, dt);
p->pos = ccpAdd( p->pos, tmp );
}
//
else
{
// 如果是环形模式。
// 更新粒子的角度。
p->modeB.angle += p->modeB.degreesPerSecond * dt;
p->modeB.radius += p->modeB.deltaRadius * dt;
// 计算粒子的位置。
p->pos.x = - cosf(p->modeB.angle) * p->modeB.radius;
p->pos.y = - sinf(p->modeB.angle) * p->modeB.radius;
}
// 颜色的变化计算。
p->color.r += (p->deltaColor.r * dt);
p->color.g += (p->deltaColor.g * dt);
p->color.b += (p->deltaColor.b * dt);
p->color.a += (p->deltaColor.a * dt);
// 大小的变化计算。
p->size += (p->deltaSize * dt);
p->size = MAX( 0, p->size );
// 角度。
p->rotation += (p->deltaRotation * dt);
//
CCPoint newPos;
//如果是自由模式或者相对模式
if (m_ePositionType == kCCPositionTypeFree || m_ePositionType == kCCPositionTypeRelative)
{
//
CCPoint diff = ccpSub( currentPosition, p->startPos );
newPos = ccpSub(p->pos, diff);
}
else
{
newPos = p->pos;
}
//如果使用批次结点,加上当前粒子系统坐标系的位置。为什么呢?因为矩阵转换不会影响批次结点。
if (m_pBatchNode)
{
newPos.x+=m_tPosition.x;
newPos.y+=m_tPosition.y;
}
//更新粒子对应批次结点中的矩形顶点缓冲信息。
updateQuadWithParticle(p, newPos);
// 更新粒子计数器。
++m_uParticleIdx;
}
else
{
// 如果生命值小于0.
//
int currentIndex = p->atlasIndex;
if( m_uParticleIdx != m_uParticleCount-1 )
{
m_pParticles[m_uParticleIdx] = m_pParticles[m_uParticleCount-1];
}
//如果使用批次结点,通过设置对应的矩形顶点缓冲中大小为0达到不显示指定粒子的目的。
if (m_pBatchNode)
{
//disable the switched particle
m_pBatchNode->disableParticle(m_uAtlasIndex+currentIndex);
//将最后一个粒子信息中的
m_pParticles[m_uParticleCount-1].atlasIndex = currentIndex;
}
//粒子数量减一。
--m_uParticleCount;
//如果粒子数量减为0并且设置自动删除粒子系统。
if( m_uParticleCount == 0 && m_bIsAutoRemoveOnFinish )
{
//停止更新并从父结点中删除自已。
this->unscheduleUpdate();
m_pParent->removeChild(this, true);
return;
}
}
}
//
m_bTransformSystemDirty = false;
}
//如果不使用批次结点进行优化,则调用用户重载的虚函数postStep进行更新处理。
if (! m_pBatchNode)
{
postStep();
}
CC_PROFILER_STOP_CATEGORY(kCCProfilerCategoryParticles , "CCParticleSystem - update");
}
//不增加时间变化的更新
void CCParticleSystem::updateWithNoTime(void)
{
this->update(0.0f);
}
//需要被重载的更新粒子的对应矩形顶点缓冲信息块的虚函数。
void CCParticleSystem::updateQuadWithParticle(tCCParticle* particle, const CCPoint& newPosition)
{
CC_UNUSED_PARAM(particle);
CC_UNUSED_PARAM(newPosition);
// should be overriden
}
//需要被重载的粒子更新处理函数。
void CCParticleSystem::postStep()
{
// should be overriden
}
//设置当前粒子系统所用的纹理对象。
void CCParticleSystem::setTexture(CCTexture2D* var)
{
//如果纹理对象不同,则释放旧的纹理对象,设置为新的纹理对象,并更新ALPHA混合方案。
if (m_pTexture != var)
{
CC_SAFE_RETAIN(var);
CC_SAFE_RELEASE(m_pTexture);
m_pTexture = var;
updateBlendFunc();
}
}
//更新ALPHA混合方案。为了方便理解,先了解一下常用的混合状态值的意义:
GL_ZERO: 表示使用0.0作为因子,实际上相当于不使用这种颜色参与混合运算。
GL_ONE: 表示使用1.0作为因子,实际上相当于完全的使用了这种颜色参与混合运算。
GL_SRC_ALPHA:表示使用源颜色的alpha值来作为因子。
GL_DST_ALPHA:表示使用目标颜色的alpha值来作为因子。
GL_ONE_MINUS_SRC_ALPHA:表示用1.0减去源颜色的alpha值来作为因子。
GL_ONE_MINUS_DST_ALPHA:表示用1.0减去目标颜色的alpha值来作为因子。 除此以外,还有GL_SRC_COLOR(把源颜色的四个分量分别作为因子的四个分量)、GL_ONE_MINUS_SRC_COLOR、 GL_DST_COLOR、GL_ONE_MINUS_DST_COLOR等,前两个在OpenGL旧版本中只能用于设置目标因子,后两个在OpenGL 旧版本中只能用于设置源因子。新版本的OpenGL则没有这个限制,并且支持新的GL_CONST_COLOR(设定一种常数颜色,将其四个分量分别作为 因子的四个分量)、GL_ONE_MINUS_CONST_COLOR、GL_CONST_ALPHA、 GL_ONE_MINUS_CONST_ALPHA。另外还有GL_SRC_ALPHA_SATURATE。新版本的OpenGL还允许颜色的alpha 值和RGB值采用不同的混合因子。
void CCParticleSystem::updateBlendFunc()
{
//有效性判断。
CCAssert(! m_pBatchNode, "Can't change blending functions when the particle is being batched");
//如果纹理对象指针有效,取出其是否有ALPHA通道,并将有ALPHA通道的设为ALPHA混合方案,
if(m_pTexture)
{
bool premultiplied = m_pTexture->hasPremultipliedAlpha();
m_bOpacityModifyRGB = false;
if( m_pTexture && ( m_tBlendFunc.src == CC_BLEND_SRC && m_tBlendFunc.dst == CC_BLEND_DST ) )
{
//如果有ALPHA通道,设置标记m_bOpacityModifyRGB为true.
if( premultiplied )
{
m_bOpacityModifyRGB = true;
}
else
{
//如果没有ALPHA通道,使用glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);,则表示源颜色乘以自身的alpha 值,目标颜色乘以1.0减去源颜色的alpha值,这样一来,源颜色的alpha值越大,则产生的新颜色中源颜色所占比例就越大,而目标颜色所占比例则减 小。这种情况下,我们可以简单的将源颜色的alpha值理解为“不透明度”。这也是混合时最常用的方式。
m_tBlendFunc.src = GL_SRC_ALPHA;
m_tBlendFunc.dst = GL_ONE_MINUS_SRC_ALPHA;
}
}
}
}
//取得当前粒子系统的纹理对象.
CCTexture2D * CCParticleSystem::getTexture()
{
return m_pTexture;
}
// 设置粒子的混合方案是否采用高亮模式
void CCParticleSystem::setBlendAdditive(bool additive)
{
//如果是高亮模式
if( additive )
{
//设置混合方案的源和目标状态值
m_tBlendFunc.src = GL_SRC_ALPHA;
m_tBlendFunc.dst = GL_ONE;
}
else
{
//如果不是高亮模式,则根据是否有ALPHA通道来进行相应的方案设置.
if( m_pTexture && ! m_pTexture->hasPremultipliedAlpha() )
{
m_tBlendFunc.src = GL_SRC_ALPHA;
m_tBlendFunc.dst = GL_ONE_MINUS_SRC_ALPHA;
}
else
{
//下面的两个渲染状态值为别是对应GL_ONE和GL_ONE_MINUS_SRC_ALPHA.
m_tBlendFunc.src = CC_BLEND_SRC;
m_tBlendFunc.dst = CC_BLEND_DST;
}
}
}
//取得是否采用了高亮方式.
bool CCParticleSystem::isBlendAdditive()
{
return( m_tBlendFunc.src == GL_SRC_ALPHA && m_tBlendFunc.dst == GL_ONE);
}
// 后面是一大堆属性设置函数.不贴了,大家都看得懂.
总结一下:
粒子系统提供了对于粒子信息结构体tCCParticle的数组控制管理,通过tCCParticle中有限的数据属性来摸拟自然界粒子的存在与运动规律。从其中需要重载的函数我们知道,Cocos2d-x的一些粒子系统的具体演示都是由这个基础的粒子系统类进行派生和相应函数重载实现的。所以重点是理解属性的意义和掌握将来我们如何进行扩展。
在粒子系统中有函数setBatchNode来设置所用的批次结点。我们将在二部曲中再具体分析。
课后作业:
(1)理解批次优化的概念,提高游戏研发的思维习惯。
(2)参考DX或Opengl的一般粒子系统的实现,实做一个小的粒子系统。
(3)找到不同的ALPHA混合方案并进行测试和理解,或者找到相应的博客资源进行学习。
这篇博客时间跨度较大,近期红孩儿除了写博之外,也在每天深夜坚持写新的工具-“红孩儿游戏工具箱”,一个巨无霸的游戏开发工具箱。包括切图,拼图,帧动画与骨骼动画,粒子系统编辑,界面编辑,场景编辑,字体编辑等多项功能。以立图将游戏开发形成一套形之有效的工具化流程。
#分享视频# http://url.cn/BUYo0v “红孩儿游戏工具箱”之切图编辑
#分享视频# http://url.cn/A4hkpf 红孩儿游戏工具箱之图片合并编辑
#分享视频# http://url.cn/CXe3w3 新做的“红孩儿游戏工具箱”之关键帧动画演示
#分享视频# http://url.cn/9fUJt9 新做的“红孩儿游戏工具箱”之融合动画演示
#分享视频# http://url.cn/DngRC8 红孩儿游戏工具箱-骨骼动画编辑演示
#分享视频# http://url.cn/B1DwtD 红孩儿游戏工具箱-界面编辑器之面板与按钮编辑
欢迎大家到我的微博上关注我随时更新发布的视频。再次感谢大家的支撑!
新浪微博: http://weibo.com/u/1834515945
腾讯微博:http://t.qq.com/honghaier_2005