先分析ParticleSystem类的init群代码
bool ParticleSystem::init()
{
return initWithTotalParticles(150);
}
这个是默认的初始化函数,里面调用了initWithTotalParticles()函数。
bool ParticleSystem::initWithFile(const std::string& plistFile)
{
bool ret = false;
//获取粒子特效的完整路径
_plistFile = FileUtils::getInstance()->fullPathForFilename(plistFile);
ValueMap dict = FileUtils::getInstance()->getValueMapFromFile(_plistFile);
CCASSERT( !dict.empty(), "Particles: file not found");
//判断plistFile是否存在路径
string listFilePath = plistFile;
if (listFilePath.find('/') != string::npos)
{
listFilePath = listFilePath.substr(0, listFilePath.rfind('/') + 1);
ret = this->initWithDictionary(dict, listFilePath);
}
else
{
ret = this->initWithDictionary(dict, "");
}
return ret;
}
在上面的函数中,先获取到对应粒子特效文件的完整路径,之后获取到对应文件的键值对,然后判断形参plistFile是否存在路径;最后调用了initWithDictionary()函数
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))
{
//...读取配置文件中的各种信息,并赋值
}
}while(0);
free(buffer);
free(deflated);
return ret;
}
在initWithDiciionary函数体内,也是先调用了initWithTotalParticles()这个函数,接下来先分析这个函数的作用。
bool ParticleSystem::initWithTotalParticles(int numberOfParticles)
{
_totalParticles = numberOfParticles;
//释放原先保存的粒子数据
_particleData.release();
//初始化粒子数据(也可以认为是数组)
if( !_particleData.init(_totalParticles) )
{
CCLOG("Particle system: not enough memory");
this->release();
return false;
}
_allocatedParticles = numberOfParticles;
if (_batchNode)
{
for (int i = 0; i < _totalParticles; i++)
{
_particleData.atlasIndex[i] = i;
}
}
//设置一些默认值
_isActive = true;
_blendFunc = BlendFunc::ALPHA_PREMULTIPLIED;
//移动类型为自由
_positionType = PositionType::FREE;
//采用重力模式
_emitterMode = Mode::GRAVITY;
//默认不进行移除
_isAutoRemoveOnFinish = false;
_transformSystemDirty = false;
return true;
}
这个函数的主要功能就是设置_totalParticles和_allocatedParticles的值,不过在这里可以看到,它们两个是数值上相等的,而对应的意义不同,不过一般情况下_totalParticles的值应该要<=_allocatedParticle的值的;另外一个主要的功能就是_particleData的初始化,那_particleData是什么呢?其实可以简单地认为它是一个类似于vector类的容器,里面包含了一个粒子应该具有的属性,比如速度、位置、颜色、颜色变化量等等。
class CC_DLL ParticleData
{
public:
float* posx;
float* posy;
float* startPosX;
float* startPosY;
float* colorR;
float* colorG;
float* colorB;
float* colorA;
float* deltaColorR;
float* deltaColorG;
float* deltaColorB;
float* deltaColorA;
float* size;
float* deltaSize;
float* rotation;
float* deltaRotation;
float* timeToLive;
unsigned int* atlasIndex;
//! Mode A: 重力模式 方向 旋转加速度和切线加速度
struct{
float* dirX;
float* dirY;
float* radialAccel;
float* tangentialAccel;
} modeA;
//! Mode B: 环形模式
struct{
float* angle;
float* degreesPerSecond;
float* radius;
float* deltaRadius;
} modeB;
unsigned int maxCount;
ParticleData();
bool init(int count);
void release();
unsigned int getMaxCount() { return maxCount; }
//负责进行粒子数据的复制。这里的作用就是把索引为p2的数据赋值给索引为p1的粒子
void copyParticle(int p1, int p2)
{
//...
}
init函数就是对各种属性的初始化:
bool ParticleData::init(int count)
{
maxCount = count;
posx= (float*)malloc(count * sizeof(float));
posy= (float*)malloc(count * sizeof(float));
startPosX= (float*)malloc(count * sizeof(float));
startPosY= (float*)malloc(count * sizeof(float));
colorR= (float*)malloc(count * sizeof(float));
colorG= (float*)malloc(count * sizeof(float));
colorB= (float*)malloc(count * sizeof(float));
colorA= (float*)malloc(count * sizeof(float));
deltaColorR= (float*)malloc(count * sizeof(float));
deltaColorG= (float*)malloc(count * sizeof(float));
deltaColorB= (float*)malloc(count * sizeof(float));
deltaColorA= (float*)malloc(count * sizeof(float));
size= (float*)malloc(count * sizeof(float));
deltaSize= (float*)malloc(count * sizeof(float));
rotation= (float*)malloc(count * sizeof(float));
deltaRotation= (float*)malloc(count * sizeof(float));
timeToLive= (float*)malloc(count * sizeof(float));
atlasIndex= (unsigned int*)malloc(count * sizeof(unsigned int));
modeA.dirX= (float*)malloc(count * sizeof(float));
modeA.dirY= (float*)malloc(count * sizeof(float));
modeA.radialAccel= (float*)malloc(count * sizeof(float));
modeA.tangentialAccel= (float*)malloc(count * sizeof(float));
modeB.angle= (float*)malloc(count * sizeof(float));
modeB.degreesPerSecond= (float*)malloc(count * sizeof(float));
modeB.deltaRadius= (float*)malloc(count * sizeof(float));
modeB.radius= (float*)malloc(count * sizeof(float));
return posx && posy && startPosY && startPosX && colorR && colorG && colorB && colorA &&
deltaColorR && deltaColorG && deltaColorB && deltaColorA && size && deltaSize &&
rotation && deltaRotation && timeToLive && atlasIndex && modeA.dirX && modeA.dirY &&
modeA.radialAccel && modeA.tangentialAccel && modeB.angle && modeB.degreesPerSecond &&
modeB.deltaRadius && modeB.radius;
}
release函数则是对各种在堆中申请的内存进行释放。
ParticleData是3.x后的产物,而在2.x时代则是把粒子的数据集合成一个名为Particle的结构体,然后在ParticleSystem中有一个Particle* _pParticles的动态数组;二者的功能类似。
上面提到的initWithDictionary(ValueMap& dictionary, const std::string& dirname)中读取了文件的各种属性,简单地看一下
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
//对particle desgner 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();
上面的代码主要是获取配置文件的数据,像_angle、_angleVar,这个Var表示的是Variance,这个变量的意思就是角度值上下浮动的数值是多少。至于持续时间,则类似于Action中的持续时间。
// 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();
上面代码中不太明了的就是_sourcePositionCompatible这个判断语句,因为在这个类的初始化列表中有这么一句
, _sourcePositionCompatible(true) // In the furture this member's default value maybe false or be removed.
翻译过来就是:在未来这个成员的默认值也许会改变或者被删除;而在这个类的其他部分是没有修改这个值的,也就是说,这个判断是确定执行的是else那条语句,即
this->setPosition(Vec2(x, y));
// Spinning 起始旋转
_startSpin = dictionary["rotationStart"].asFloat();
_startSpinVar = dictionary["rotationStartVariance"].asFloat();
_endSpin= dictionary["rotationEnd"].asFloat();
_endSpinVar= dictionary["rotationEndVariance"].asFloat();
//发射器模式 有重力模式和环形模式
_emitterMode = (Mode) dictionary["emitterType"].asInt();
注意一下emitterType这个字段,这个表示发射器模式,我在cocos2dx示例里找到的一个粒子配置文件中发现emitterType是包裹在<real>这个标签内的,real表示实数,个人认为应该是<integer>才对,虽然影响不大。还有就是不太理解_startSpin的具体涵义。。。
// Mode A: Gravity + tangential accel + radial accel
//重力 旋转加速度和切线加速度
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();
}
这里则是主要对重力模式下的变量进行赋值,这个是和粒子的重力属性一一对应的,至于那个modeA则是对应于重力模式的结构体变量,包含的字段在上面都进行了初始化。rotationIsDir没有发现这个字段,这样的话在获取这个不存在的字段时,如果使用的是dictionary.at("rotationIsDir")的话,是会出错的,而上面的代码则会返回false,这也是为什么形参dictionary不是const Value&的原因 。
// or Mode B: radius movement 环形移动
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();
}
环形模式下配置文件的读取和对应变量的赋值。
// life span
_life = dictionary["particleLifespan"].asFloat();
_lifeVar = dictionary["particleLifespanVariance"].asFloat();
// emission Rate
_emissionRate = _totalParticles / _life;
这里则是对粒子生命值的读取,注意下这里_emissionRate的值和_totalParticles和_life有联系。
//don't get the internal texture if a batchNode is used
//在不使用批次节点,进行相应纹理的加载
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();
//检测该纹理是否存在路径
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);
}
//加载成功则设置纹理,setTexture是TextureProtocol抽象类中的函数
if (tex)
{
setTexture(tex);
}//纹理加载失败则尝试获取textureImageData对应的值
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();
bool isOK = image->initWithImageData(deflated, deflatedLen);
CCASSERT(isOK, "CCParticleSystem: error init image with Data");
CC_BREAK_IF(!isOK);
setTexture(Director::getInstance()->getTextureCache()->addImage(image, _plistFile + textureName));
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;
}
这里主要就是获取纹理,获取纹理的先后顺序是:先通过文件名进行加载;如果加载失败则寻找是否存在textureImageData这个键值对,如果存在,则先对数据进行base64解码,然后gzip进行解压,之后通过解压的数据生成对应的纹理。
由于粒子图片的体积较小,所以一般情况下是可以把图片信息记录在配置文件中,方便写入的操作则是先gzip进行压缩,然后base64加密。
问题在于我在ubuntu下尝试先把textureImageData的数据单独保存在一个名为file的文件中,然后尝试在命令行进行以下操作:
base64 -d | gunzip > image
先进行base64解码,然后解压缩,之后把数据重定向到image文件中。接下来使用file命令来判断该文件类型
发现image的类型为tiff格式,而图片名称则为particleTexture.png。如果使用图片文件进行加载还需要从tiff到png格式的转换。
参考博文:
https://blog.csdn.net/honghaier/article/details/8537146
https://www.cnblogs.com/shangdahao/archive/2012/04/14/2447571.html
第二篇文章对于变量讲解的比较好,虽然我是在实现后才找到的。。。