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");
}

从零开始のcocos2dx生活(七)ParticleSystem的更多相关文章

  1. 从零开始のcocos2dx生活(二)Node

    节点 Node 文章目录 节点 Node 前言 变量初始化 创建一个节点对象 获取节点依赖的计数器 获取节点的描述(获取节点的Tag) 节点的局部层顺序值(LocalZOrder) 设置节点的Loca ...

  2. 从零开始のcocos2dx生活(十一)TableView

    目录 简述 主要变量 主要方法 setVerticalFillOrder reloadData cellAtIndex updateCellAtIndex insertCellAtIndex remo ...

  3. 从零开始のcocos2dx生活(十)ScrollView

    目录 简介 基础变量 ScrollViewDelegate Direction _dragging _container _touchMoved _bounceable _touchLength 方法 ...

  4. 从零开始のcocos2dx生活(九)CCBReader

    NodeLoaderLibrary是用来存储节点加载器类型的类,通过registerDefaultNodeLoaders()可以注册所有默认类型的加载器 在CocosBuilder的使用手册中: 1. ...

  5. 从零开始のcocos2dx生活(八)ParticleSystemQuad

    https://learnopengl-cn.github.io/01%20Getting%20started/04%20Hello%20Triangle/#_1 写的真的非常好-最近没时间拜读,只看 ...

  6. 从零开始のcocos2dx生活(六)EventDispatcher

    EventDispatcher可能是所有的里面比较不容易理解也不容易看的 我说自己的理解可能会误导到你们-[索了你们看不下去>< 我写了几乎所有的代码的注释,有的是废话跳过就好 主要的代码 ...

  7. 从零开始のcocos2dx生活(一)内存管理

    cocos中所有的对象都是继承自Ref基类,Ref的职责就是对对象进行引用计数管理 内存管理中最重要的是三个方法retain().release().autorelease() 在cocos中创建对象 ...

  8. 从零开始のcocos2dx生活(五)ActionEase

    文章目录 sineEaseIn sineEaseOut sineEaseInOut expoEaseIn expoEaseOut expoEaseInOut easeIn easeOut easeIn ...

  9. 从零开始のcocos2dx生活(四)ActionManager

    文章目录 初始化构造函数 析构函数 删除哈希元素 分配存放动作对象的空间 通过索引移除动作 暂停动作 恢复动作 暂停所有的动作 恢复所有的动作 添加动作 移除所有的动作 移除target中的所有动作 ...

随机推荐

  1. @hdu - 6329@ Problem K. Transport Construction

    目录 @description@ @solution@ @accepted code@ @details@ @description@ 给定 n 个点,第 i 个点位于 (xi, yi). 在第 i ...

  2. webstorm破解教程

    1.下载地址 官网:https://www.jetbrains.com/webstorm/ 下载好之后按照提示安装即可,这里就不再多说了.下面直接说说如何使用补丁破解. 2.使用补丁破解 (http: ...

  3. django 中models表的多对一,多对多的理解

    django 表的理解 好处:设计的好,会清晰,易于理解和维护,后期开发事半功倍,一目了然. 1. 一对一的表,两表的属性实际上完全可以合并成一个表,共用一个主键即可: 2. 一对多的表,可以设中间关 ...

  4. 2018-8-10-git-使用-VisualStudio-比较分支更改

    title author date CreateTime categories git 使用 VisualStudio 比较分支更改 lindexi 2018-08-10 19:16:52 +0800 ...

  5. Oracle - @和@@、&与&& 的区别

    2018-01-15 15:36:17 袭冷 阅读数 4783更多 分类专栏: DB   版权声明:本文为博主原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明. 本文链接 ...

  6. BraveOS正式版发布,希望大家下载使用

    废话不多说,直接贴图才是王道 这里是DOS系统+默认官方(Platform系统) 下载地址:http://pan.baidu.com/s/1eQINwx8 (引导进Platform系统后,默认管理员帐 ...

  7. [转]【译】.NET Core 3.0 中的新变化

    .NET Core 3.0 是 .NET Core 平台的下一主要版本.本文回顾了 .Net Core 发展历史,并展示了它是如何从基本支持 Web 和数据工作负载的版本 1,发展成为能够运行 Web ...

  8. Python--day24--继承面试题

    输出:(打印的是Dog.func而不是Animal.func) __init__方法如果本生的类具有的话,父类的__init__方法就不在调用,没有才调用父类的__init__方法 派生属性: 如果既 ...

  9. SpringData Jpa、Hibernate、Jpa 三者之间的关系

    JPA规范与ORM框架之间的关系是怎样的呢? JPA规范本质上就是一种ORM规范,注意不是ORM框架--因为JPA并未提供ORM实现,它只是制订了一些规范,提供了一些编程的API接口,但具体实现则由服 ...

  10. Roslyn 如何使用 MSBuild Copy 复制文件

    本文告诉大家如何在 MSBuild 里使用 Copy 复制文件 需要知道 Rosyln 是 MSBuild 的 dotnet core 版本. 在 MSBuild 里可以使用很多命令,本文告诉大家如何 ...