从零开始のcocos2dx生活(七)ParticleSystem

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");
}
原文地址:https://www.cnblogs.com/sakuraneo/p/11992046.html