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. Vue.js 第5章 webpack配置

    为什么我们需要打包构建工具:因为我们以后做项目的时候,会使用到很多种不同的工具或者语言,这些工具或者语言其实浏览器并不支持 webpack 是一个现代 JavaScript 应用程序的模块打包器(mo ...

  2. 利用阿里云容器服务打通TensorFlow持续训练链路

    本系列将利用Docker和阿里云容器服务,帮助您上手TensorFlow的机器学习方案 第一篇:打造TensorFlow的实验环境 第二篇:轻松搭建TensorFlow Serving集群 第三篇:打 ...

  3. behavior planning——inputs to transition functions

    the answer is that we have to pass all  of the data into transition function except for the previous ...

  4. @noi.ac - 493@ trade

    目录 @description@ @solution@ @part - 1@ @part - 2@ @part - 3@ @part - 4@ @accepted code@ @details@ @d ...

  5. HTML的基本结构和标签分类

    HTML:超文本标记语言 HTML基本结构 <!DOCTYPE html> <html> <head> <meta charset="utf-8&q ...

  6. Jieba分词包(一)——解析主函数cut

    1. 解析主函数cut Jieba分词包的主函数在jieba文件夹下的__init__.py中,在这个py文件中有个cut的函数,这个就是控制着整个jieba分词包的主函数.    cut函数的定义如 ...

  7. Python--day19--sys模块

    sys模块 sys模块是与python解释器交互的一个接口 sys.argv 命令行参数List,第一个元素是程序本身路径 sys.exit(n) 退出程序,正常退出时exit(0),错误退出sys. ...

  8. Pytorch使用GPU

    pytorch如何使用GPU在本文中,我将介绍简单如何使用GPU pytorch是一个非常优秀的深度学习的框架,具有速度快,代码简洁,可读性强的优点. 我们使用pytorch做一个简单的回归. 首先准 ...

  9. 2018-2-13-win10-uwp-ContentDialog-点确定不关闭

    title author date CreateTime categories win10 uwp ContentDialog 点确定不关闭 lindexi 2018-2-13 17:23:3 +08 ...

  10. mpvue的坑,持续更新-.-

    mpvue... 坑 怎么说呢,去github看一下,发现还是有很多问题没有解决... 不支持filter 亲,到现在还没有支持filter哦.只能用替代方法了,用computed或者渲染前先处理数据 ...