cocos2dx粒子引擎的分析-1

先分析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

第二篇文章对于变量讲解的比较好,虽然我是在实现后才找到的。。。

猜你喜欢

转载自blog.csdn.net/bull521/article/details/82715735
今日推荐