CCParticleSystem是用来设置粒子效果的类
1、粒子分为两种模式:重力模式 和 半径模式
重力模式独占属性:
- gravity 重力方向,Vec2类型,可以分别指定不同方向的重力大小
- speed 粒子运动的速度
- radialAccel 向心加速度
- tangentialAccel 切向加速度
- rotationIsDir 自转方向
半径模式独占属性:
- startRadius 开始半径
- endRadius 结束半径
- rotatePerSecond 每秒旋转多少角度
两种模式共有属性:
- angle 粒子发射时的角度
- duration 发射器的生存时间
- isActive 发射器活动状态(启用/暂停)
- life 粒子生存时间
- emissionRate 粒子的发射率 = _totalParticles / _life
- emitCounter 每秒发射多少粒子
- totalParticles 存在的最大粒子数
- particleCount 目前存在的粒子数量
- totalParticleCountFactor 影响总粒子数的参数 默认为1.0f
- allocatedParticles 存在的最大粒子数,在ParticleSystemQuad中设置粒子时使用
- texture 存储创建粒子的纹理
- startSize/endSize 粒子开始和结束大小
- startColor/endColor 粒子开始和结束颜色
所有的var都是用来表示 差异随机数
2、positionType:用来存储粒子的位置模式
粒子位置有三种模式:FREE、RELATIVE、GROUPED
FREE:(完全自由)粒子发射之后,位置相对于世界坐标系,发射器移动不影响已经发射的粒子
RELATIVE:粒子位置相对于发射器,发射器跟随父节点移动时,粒子也会跟着发射器移动;如果手动(触摸/点击)点击改变了发射器的位置,已经发射出去的粒子还会按照原来的路径移动。
GROUPED:粒子位置相对于发射器,发射器跟随父节点移动时,粒子也会跟着发射器移动了;如果手动(触摸/点击)改变了发射器的位置,粒子和发射器会一起移动,也就是说粒子相对于发射器的位置不会变
3、在initWithDictionary中读取了plist文件中的数据,并进行了赋值,最后通过判断调用setTexture方法来设置粒子
4、粒子的实质是通过读取plist文件存储的纹理数据。
放几段代码
//初始化,这个方法可以配合着cocos2dx DEMO中自带的plist文件阅读
bool ParticleSystem::initWithDictionary(ValueMap& dictionary, const std::string& dirname)
{
bool ret = false;
unsigned char *buffer = nullptr;
unsigned char *deflated = nullptr;
Image *image = nullptr;
do
{
int maxParticles = dictionary["maxParticles"].asInt(); //获取文件中设置的最大粒子个数
// self, not super
//通过粒子个数初始化粒子
if(this->initWithTotalParticles(maxParticles))
{
// Emitter name in particle designer 2.0
_configName = dictionary["configName"].asString();
// angle
_angle = dictionary["angle"].asFloat();
_angleVar = dictionary["angleVariance"].asFloat();
// duration
//发射器的生存时间
_duration = dictionary["duration"].asFloat();
// blend function
if (!_configName.empty())
{
_blendFunc.src = dictionary["blendFuncSource"].asFloat();
}
else
{
_blendFunc.src = dictionary["blendFuncSource"].asInt();
}
_blendFunc.dst = dictionary["blendFuncDestination"].asInt();
// color
//开始颜色
_startColor.r = dictionary["startColorRed"].asFloat();
_startColor.g = dictionary["startColorGreen"].asFloat();
_startColor.b = dictionary["startColorBlue"].asFloat();
_startColor.a = dictionary["startColorAlpha"].asFloat();
//颜色方差
_startColorVar.r = dictionary["startColorVarianceRed"].asFloat();
_startColorVar.g = dictionary["startColorVarianceGreen"].asFloat();
_startColorVar.b = dictionary["startColorVarianceBlue"].asFloat();
_startColorVar.a = dictionary["startColorVarianceAlpha"].asFloat();
//结束颜色
_endColor.r = dictionary["finishColorRed"].asFloat();
_endColor.g = dictionary["finishColorGreen"].asFloat();
_endColor.b = dictionary["finishColorBlue"].asFloat();
_endColor.a = dictionary["finishColorAlpha"].asFloat();
//颜色方差
_endColorVar.r = dictionary["finishColorVarianceRed"].asFloat();
_endColorVar.g = dictionary["finishColorVarianceGreen"].asFloat();
_endColorVar.b = dictionary["finishColorVarianceBlue"].asFloat();
_endColorVar.a = dictionary["finishColorVarianceAlpha"].asFloat();
// particle size
// 粒子 开始和结束 的 大小和方差
_startSize = dictionary["startParticleSize"].asFloat();
_startSizeVar = dictionary["startParticleSizeVariance"].asFloat();
_endSize = dictionary["finishParticleSize"].asFloat();
_endSizeVar = dictionary["finishParticleSizeVariance"].asFloat();
// position
float x = dictionary["sourcePositionx"].asFloat();
float y = dictionary["sourcePositiony"].asFloat();
if(!_sourcePositionCompatible) {
this->setSourcePosition(Vec2(x, y));
}
else {
this->setPosition(Vec2(x, y));
}
_posVar.x = dictionary["sourcePositionVariancex"].asFloat();
_posVar.y = dictionary["sourcePositionVariancey"].asFloat();
// Spinning 旋转
_startSpin = dictionary["rotationStart"].asFloat();
_startSpinVar = dictionary["rotationStartVariance"].asFloat();
_endSpin= dictionary["rotationEnd"].asFloat();
_endSpinVar= dictionary["rotationEndVariance"].asFloat();
_emitterMode = (Mode) dictionary["emitterType"].asInt();
// Mode A: Gravity + tangential accel + radial accel
// 模式A是重力模式
if (_emitterMode == Mode::GRAVITY)
{
// gravity 重力方向
modeA.gravity.x = dictionary["gravityx"].asFloat();
modeA.gravity.y = dictionary["gravityy"].asFloat();
// speed 重力速度
modeA.speed = dictionary["speed"].asFloat();
modeA.speedVar = dictionary["speedVariance"].asFloat();
// radial acceleration 径向加速度
modeA.radialAccel = dictionary["radialAcceleration"].asFloat();
modeA.radialAccelVar = dictionary["radialAccelVariance"].asFloat();
// tangential acceleration 切向加速度
modeA.tangentialAccel = dictionary["tangentialAcceleration"].asFloat();
modeA.tangentialAccelVar = dictionary["tangentialAccelVariance"].asFloat();
// rotation is dir 旋转方向
modeA.rotationIsDir = dictionary["rotationIsDir"].asBool();
}
// or Mode B: radius movement 半径运动
// 模式B是半径模式
else if (_emitterMode == Mode::RADIUS)
{
if (!_configName.empty())
{
modeB.startRadius = dictionary["maxRadius"].asInt();
}
else
{
modeB.startRadius = dictionary["maxRadius"].asFloat();
}
modeB.startRadiusVar = dictionary["maxRadiusVariance"].asFloat();
if (!_configName.empty())
{
modeB.endRadius = dictionary["minRadius"].asInt();
}
else
{
modeB.endRadius = dictionary["minRadius"].asFloat();
}
if (dictionary.find("minRadiusVariance") != dictionary.end())
{
modeB.endRadiusVar = dictionary["minRadiusVariance"].asFloat();
}
else
{
modeB.endRadiusVar = 0.0f;
}
if (!_configName.empty())
{
modeB.rotatePerSecond = dictionary["rotatePerSecond"].asInt();
}
else
{
modeB.rotatePerSecond = dictionary["rotatePerSecond"].asFloat();
}
modeB.rotatePerSecondVar = dictionary["rotatePerSecondVariance"].asFloat();
} else {
CCASSERT( false, "Invalid emitterType in config file");
CC_BREAK_IF(true);
}
// life span 粒子生存时间
_life = dictionary["particleLifespan"].asFloat();
_lifeVar = dictionary["particleLifespanVariance"].asFloat();
// emission Rate 发射率 = 粒子个数 / 粒子生存时间 每秒钟发射多少粒子
_emissionRate = _totalParticles / _life;
//don't get the internal texture if a batchNode is used
//如果使用batchNode,不要获取内部纹理?
if (!_batchNode)
{
// Set a compatible default for the alpha transfer
_opacityModifyRGB = false;
// texture
// Try to get the texture from the cache
// 尝试获取文件中提供的纹理文件名
std::string textureName = dictionary["textureFileName"].asString();
// 对纹理文件名从后往前找'/' ,正常来说找不到,文件名没有'/' 如:"fire.png"
size_t rPos = textureName.rfind('/');
//如果找到了 (找不到的... 所以不会进去,可以在下面设断点试一下
if (rPos != string::npos)
{
//截取包括'/'的纹理文件夹地址
string textureDir = textureName.substr(0, rPos + 1);
if (!dirname.empty() && textureDir != dirname)
{
textureName = textureName.substr(rPos+1); //获取纹理名称
textureName = dirname + textureName; //拼接完整文件路径
}
}
//一般会进这个判断
else if (!dirname.empty() && !textureName.empty())
{
textureName = dirname + textureName; //获取纹理的路径+文件名
}
Texture2D *tex = nullptr;
if (!textureName.empty())
{
// set not pop-up message box when load image failed
// 设置加载图像失败时不弹出消息框
bool notify = FileUtils::getInstance()->isPopupNotify();
FileUtils::getInstance()->setPopupNotify(false);
//通过纹理名获取纹理
tex = Director::getInstance()->getTextureCache()->addImage(textureName);
// reset the value of UIImage notify
FileUtils::getInstance()->setPopupNotify(notify);
}
//如果找到了纹理,就通过找到的纹理设置粒子 //这个判断进不来
if (tex)
{
setTexture(tex);
}
//在plist的最后有一大串字符,那就是纹理数据了
else if( dictionary.find("textureImageData") != dictionary.end() )
{
std::string textureData = dictionary.at("textureImageData").asString();
CCASSERT(!textureData.empty(), "textureData can't be empty!");
auto dataLen = textureData.size();
if (dataLen != 0)
{
// if it fails, try to get it from the base64-gzipped data
int decodeLen = base64Decode((unsigned char*)textureData.c_str(), (unsigned int)dataLen, &buffer);
CCASSERT( buffer != nullptr, "CCParticleSystem: error decoding textureImageData");
CC_BREAK_IF(!buffer);
ssize_t deflatedLen = ZipUtils::inflateMemory(buffer, decodeLen, &deflated);
CCASSERT( deflated != nullptr, "CCParticleSystem: error ungzipping textureImageData");
CC_BREAK_IF(!deflated);
// For android, we should retain it in VolatileTexture::addImage which invoked in Director::getInstance()->getTextureCache()->addUIImage()
image = new (std::nothrow) Image();
//使用文件中的图片数据初始化image
bool isOK = image->initWithImageData(deflated, deflatedLen);
CCASSERT(isOK, "CCParticleSystem: error init image with Data");
CC_BREAK_IF(!isOK); //初始化失败就跳出if
//使用获取的image初始化纹理
setTexture(Director::getInstance()->getTextureCache()->addImage(image, _plistFile + textureName));
//初始化纹理后可以释放image
image->release();
}
}
_yCoordFlipped = dictionary.find("yCoordFlipped") == dictionary.end() ? 1 : dictionary.at("yCoordFlipped").asInt();
if( !this->_texture)
CCLOGWARN("cocos2d: Warning: ParticleSystemQuad system without a texture");
}
ret = true;
}
} while (0);
free(buffer);
free(deflated);
return ret;
}
//停止粒子系统
void ParticleSystem::stopSystem()
{
_isActive = false; //不活动
_elapsed = _duration; //直接把度过的时间设置为最终要执行的时间,间接停止了
_emitCounter = 0; //每秒发射粒子设为0
}
//每帧刷新
void ParticleSystem::update(float dt)
{
CC_PROFILER_START_CATEGORY(kProfilerCategoryParticles , "CCParticleSystem - update");
//是活动的、有发射率的
if (_isActive && _emissionRate)
{
//发射速率的倒数
float rate = 1.0f / _emissionRate;
//__totalParticleCountFactor影响粒子总数,可以设置,默认是1.0
int totalParticles = static_cast<int>(_totalParticles * __totalParticleCountFactor);
//issue #1201, prevent bursts of particles, due to too high emitCounter
// 判断粒子个数是否小于设定的个数
if (_particleCount < totalParticles)
{
_emitCounter += dt; //用于下面计算粒子个数
if (_emitCounter < 0.f) //这里的判断可能是emitCounter是否已经超过了float的最大值
_emitCounter = 0.f;
}
//这里的emitCount可能会获得两种值
//1、粒子的发射速率 < 设定的还未发射的粒子数,emitCount=粒子发射率*度过的时间
//2、粒子的发射速率 > 设定的还未发射的粒子数,emitCount=设定的还未发射的粒子数
//这里确保了粒子的最低发射数量,如果还未发射的粒子数小一些,那么发射器是有能力发射这么多的,
//但是现在不用发射率的那么多粒子,只需要把还未发射的粒子补齐就可以了。
//如果发射速率小一些,那就需要全力的发射,保障粒子发射。
int emitCount = MIN(totalParticles - _particleCount, _emitCounter / rate);
addParticles(emitCount);
_emitCounter -= rate * emitCount; //去掉已经发射的
_elapsed += dt;//记录度过的时间
if (_elapsed < 0.f)
_elapsed = 0.f;
if (_duration != DURATION_INFINITY && _duration < _elapsed)
{
this->stopSystem();
}
}
{
//更新每个粒子存在的时间
for (int i = 0; i < _particleCount; ++i)
{
_particleData.timeToLive[i] -= dt;
}
//更新存活粒子数量
for (int i = 0; i < _particleCount; ++i)
{
if (_particleData.timeToLive[i] <= 0.0f)
{
int j = _particleCount - 1;
while (j > 0 && _particleData.timeToLive[j] <= 0)
{
_particleCount--;
j--;
}
_particleData.copyParticle(i, _particleCount - 1);
if (_batchNode)
{
//disable the switched particle
int currentIndex = _particleData.atlasIndex[i];
_batchNode->disableParticle(_atlasIndex + currentIndex);
//switch indexes
_particleData.atlasIndex[_particleCount - 1] = currentIndex;
}
--_particleCount;
if( _particleCount == 0 && _isAutoRemoveOnFinish )
{
this->unscheduleUpdate();
_parent->removeChild(this, true);
return;
}
}
}
//按照不同的模式,根据时间刷新参数
if (_emitterMode == Mode::GRAVITY)
{
for (int i = 0 ; i < _particleCount; ++i)
{
particle_point tmp, radial = {0.0f, 0.0f}, tangential;
// radial acceleration
if (_particleData.posx[i] || _particleData.posy[i])
{
normalize_point(_particleData.posx[i], _particleData.posy[i], &radial);
}
tangential = radial;
radial.x *= _particleData.modeA.radialAccel[i];
radial.y *= _particleData.modeA.radialAccel[i];
// tangential acceleration
std::swap(tangential.x, tangential.y);
tangential.x *= - _particleData.modeA.tangentialAccel[i];
tangential.y *= _particleData.modeA.tangentialAccel[i];
// (gravity + radial + tangential) * dt
tmp.x = radial.x + tangential.x + modeA.gravity.x;
tmp.y = radial.y + tangential.y + modeA.gravity.y;
tmp.x *= dt;
tmp.y *= dt;
_particleData.modeA.dirX[i] += tmp.x;
_particleData.modeA.dirY[i] += tmp.y;
// this is cocos2d-x v3.0
// if (_configName.length()>0 && _yCoordFlipped != -1)
// this is cocos2d-x v3.0
tmp.x = _particleData.modeA.dirX[i] * dt * _yCoordFlipped;
tmp.y = _particleData.modeA.dirY[i] * dt * _yCoordFlipped;
_particleData.posx[i] += tmp.x;
_particleData.posy[i] += tmp.y;
}
}
else
{
//Why use so many for-loop separately instead of putting them together?
//When the processor needs to read from or write to a location in memory,
//it first checks whether a copy of that data is in the cache.
//And every property's memory of the particle system is continuous,
//for the purpose of improving cache hit rate, we should process only one property in one for-loop AFAP.
//It was proved to be effective especially for low-end machine.
for (int i = 0; i < _particleCount; ++i)
{
_particleData.modeB.angle[i] += _particleData.modeB.degreesPerSecond[i] * dt;
}
for (int i = 0; i < _particleCount; ++i)
{
_particleData.modeB.radius[i] += _particleData.modeB.deltaRadius[i] * dt;
}
for (int i = 0; i < _particleCount; ++i)
{
_particleData.posx[i] = - cosf(_particleData.modeB.angle[i]) * _particleData.modeB.radius[i];
}
for (int i = 0; i < _particleCount; ++i)
{
_particleData.posy[i] = - sinf(_particleData.modeB.angle[i]) * _particleData.modeB.radius[i] * _yCoordFlipped;
}
}
//color r,g,b,a
//刷新颜色
for (int i = 0 ; i < _particleCount; ++i)
{
_particleData.colorR[i] += _particleData.deltaColorR[i] * dt;
}
for (int i = 0 ; i < _particleCount; ++i)
{
_particleData.colorG[i] += _particleData.deltaColorG[i] * dt;
}
for (int i = 0 ; i < _particleCount; ++i)
{
_particleData.colorB[i] += _particleData.deltaColorB[i] * dt;
}
for (int i = 0 ; i < _particleCount; ++i)
{
_particleData.colorA[i] += _particleData.deltaColorA[i] * dt;
}
//size
//刷新大小
for (int i = 0 ; i < _particleCount; ++i)
{
_particleData.size[i] += (_particleData.deltaSize[i] * dt);
_particleData.size[i] = MAX(0, _particleData.size[i]);
}
//angle
//刷新角度
for (int i = 0 ; i < _particleCount; ++i)
{
_particleData.rotation[i] += _particleData.deltaRotation[i] * dt;
}
//刷新粒子矩阵
updateParticleQuads();
//刷新完成,脏标记设为false
_transformSystemDirty = false;
}
// only update gl buffer when visible
// 刷新GL缓冲区
if (_visible && ! _batchNode)
{
postStep();
}
CC_PROFILER_STOP_CATEGORY(kProfilerCategoryParticles , "CCParticleSystem - update");
}