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 第3章 axios&Vue过渡动画

    promise 它将我们从回调地狱中解脱出来 创建和使用 var fs = require('fs') // 创建promise // reslove表示执行成功后调用的回调函数 // reject表 ...

  2. MySQL5.7默认打开ONLY_FULL_GROUP_BY模式问题与解决方案

    MySQL5.7后将sql_mode的ONLY_FULL_GROUP_BY模式默认设置为打开状态,这样一来,很多之前的sql语句可能会出现错误,错误信息如下: Error Code: 1055. Ex ...

  3. 异常处理之try catch finally

    package com.sxt.wrapper.test2; /* 0418 * 异常处理 * 采用异常处理的好处:保证程序发生异常后可以继续执行 * e.printStaceTrace:打印堆栈信息 ...

  4. java future模式 所线程实现异步调用(转载

    java future模式 所线程实现异步调用(转载) 在多线程交互的中2,经常有一个线程需要得到另个一线程的计算结果,我们常用的是Future异步模式来加以解决.Future顾名思意,有点像期货市场 ...

  5. laravel5.4 前后台未登陆,跳转到各自的页面

    https://www.imooc.com/wenda/detail/378208?t=266634 laravel我做了前后台登陆,后台未登录跳转到前台登陆页面了. 我想让后台未登入跳转到后台登陆页 ...

  6. Jquery FormData文件异步上传 快速指南

    网站中文件的异步上传是个比较麻烦的问题,不过现在通过jquery 可以很容易的解决这个问题: 使用jquery2.1版本,较老版本不支持异步文件上传功能: 表单代码: <form id=&quo ...

  7. 【原生JS】写最简单的图片轮播

    非常简单的一个大图轮播,通过将控制显示位置来进行轮播效果,写来给正在学习的新手朋友们参考交流. 先看效果:(实际效果没有这么快) 先看布局: <div id="display" ...

  8. HDU 2546 01背包问题

    这里5元是个什么意思呢.差不多就是特殊情况了. 就是说最贵的那个东西先不买.并且最后要留下5元去买那个最贵的. 也就是说对现在金钱-5 拿剩下的钱去对减去最贵的商品后的商品dp.看这些剩下的钱能买多少 ...

  9. 版本号/缓存刷新 laravel mix函数

    很多开发者会给编译的前端资源添加时间戳或者唯一令牌后缀以强制浏览器加载最新版本而不是代码的缓存副本.Mix 可以使用 version 方法为你处理这种场景. version 方法会自动附加唯一哈希到已 ...

  10. POJ 1321 深搜dfs

    思路其实挺简单的,为什么我想不到呢!!! 原因分析:(1)题目还是做少了 (2)做题目的时候在放音乐 (3)最近脑袋都不愿意想思路总是想一些无用的 改进:(1)以后做题目坚决不开音乐,QQ直接挂隐身 ...