• 从零开始の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");
    }
    
  • 相关阅读:
    SimpleDateFormatter Java中的用法
    线性判别分析浅析及推导
    主成分分析(PCA)原理及推导
    浅说机器学习理论
    给“过拟合”下一个准确且规范的定义
    信息熵和Gini指数的关系
    WinRAR默认压缩格式ZIP
    批量学习和在线学习的区别
    LDA线性判别分析
    主成分分析(PCA)原理与实现
  • 原文地址:https://www.cnblogs.com/sakuraneo/p/11992046.html
Copyright © 2020-2023  润新知