【玩转cocos2d-x之三十八】粒子系统的加载优化
Cocos2d-x的粒子系统是通过加载plist生成的。plist包含两部分内容:粒子系统属性和粒子纹理。然而每次调用create都会对plist进行读取解析,如果重复地使用同一个粒子效果,这样的调用明显是低效冗余的。所以我们要做的是,将粒子系统属性和粒子纹理分别抽出。
- 将粒子系统属性预加载并全局保存,避免每次进行读取。
- 粒子纹理可视且可以进行纹理打包,加载粒子纹理就和加载普通的图片一样。
本文通过增加ParticleSystemQuad的接口实现对粒子系统属性和纹理帧的直接载入,来提高粒子系统的加载效率和实现内存纹理的优化。
ParticleSystemQuad
首先先看下ParticleSystemQuad,ParticleSystemQuad继承于ParticleSystem,拥有后者的所有特性,并且增加了一些新的特性:
- 粒子大小支持浮点数
- 支持缩放
- 支持选择
- 支持subrect
- 支持批渲染
ParticleSystemQuad同时也是其他特效的父类,创建一个粒子系统的函数调用顺序为:
Create→initWithFile→initWithDictionary
在initWithDictionary中对粒子数据和纹理进行了读取和解析(这部分有兴趣的可以直接看源码)。
如何优化?
参考initWithDictionary的函参
1
| static bool initWithDictionary(ValueMap& dictionary, const std::string& dirname);
|
设计如下接口,当然你要传入文件名也可以:
1 2
| static ParticleSystemQuad * create(ValueMap& valueMap, SpriteFrame *frame); bool initWithValueMap(ValueMap &valueMap, SpriteFrame* frame);
|
源码如下:
1 2 3 4 5 6 7 8 9 10 11
| ParticleSystemQuad * ParticleSystemQuad::create( ValueMap& map, SpriteFrame *frame) { ParticleSystemQuad *ret = new ParticleSystemQuad(); if (ret && ret->initWithValueMap(map, frame)) { ret->autorelease(); return ret; } CC_SAFE_DELETE(ret); return ret; }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163
| bool ParticleSystemQuad::initWithValueMap(ValueMap &valueMap, SpriteFrame* frame) { std::string dirname = ""; bool ret = false; unsigned char *buffer = nullptr; unsigned char *deflated = nullptr; do { int maxParticles = valueMap["maxParticles"].asInt(); if(this->initWithTotalParticles(maxParticles)) { _configName = valueMap["configName"].asString(); _angle = valueMap["angle"].asFloat(); _angleVar = valueMap["angleVariance"].asFloat(); _duration = valueMap["duration"].asFloat(); if (_configName.length()>0) { _blendFunc.src = valueMap["blendFuncSource"].asFloat(); } else { _blendFunc.src = valueMap["blendFuncSource"].asInt(); } _blendFunc.dst = valueMap["blendFuncDestination"].asInt(); _startColor.r = valueMap["startColorRed"].asFloat(); _startColor.g = valueMap["startColorGreen"].asFloat(); _startColor.b = valueMap["startColorBlue"].asFloat(); _startColor.a = valueMap["startColorAlpha"].asFloat(); _startColorVar.r = valueMap["startColorVarianceRed"].asFloat(); _startColorVar.g = valueMap["startColorVarianceGreen"].asFloat(); _startColorVar.b = valueMap["startColorVarianceBlue"].asFloat(); _startColorVar.a = valueMap["startColorVarianceAlpha"].asFloat(); _endColor.r = valueMap["finishColorRed"].asFloat(); _endColor.g = valueMap["finishColorGreen"].asFloat(); _endColor.b = valueMap["finishColorBlue"].asFloat(); _endColor.a = valueMap["finishColorAlpha"].asFloat(); _endColorVar.r = valueMap["finishColorVarianceRed"].asFloat(); _endColorVar.g = valueMap["finishColorVarianceGreen"].asFloat(); _endColorVar.b = valueMap["finishColorVarianceBlue"].asFloat(); _endColorVar.a = valueMap["finishColorVarianceAlpha"].asFloat(); _startSize = valueMap["startParticleSize"].asFloat(); _startSizeVar = valueMap["startParticleSizeVariance"].asFloat(); _endSize = valueMap["finishParticleSize"].asFloat(); _endSizeVar = valueMap["finishParticleSizeVariance"].asFloat(); float x = valueMap["sourcePositionx"].asFloat(); float y = valueMap["sourcePositiony"].asFloat(); this->setPosition( Point(x,y) ); _posVar.x = valueMap["sourcePositionVariancex"].asFloat(); _posVar.y = valueMap["sourcePositionVariancey"].asFloat(); _startSpin = valueMap["rotationStart"].asFloat(); _startSpinVar = valueMap["rotationStartVariance"].asFloat(); _endSpin= valueMap["rotationEnd"].asFloat(); _endSpinVar= valueMap["rotationEndVariance"].asFloat(); _emitterMode = (Mode) valueMap["emitterType"].asInt(); if (_emitterMode == Mode::GRAVITY) { modeA.gravity.x = valueMap["gravityx"].asFloat(); modeA.gravity.y = valueMap["gravityy"].asFloat(); modeA.speed = valueMap["speed"].asFloat(); modeA.speedVar = valueMap["speedVariance"].asFloat(); modeA.radialAccel = valueMap["radialAcceleration"].asFloat(); modeA.radialAccelVar = valueMap["radialAccelVariance"].asFloat(); modeA.tangentialAccel = valueMap["tangentialAcceleration"].asFloat(); modeA.tangentialAccelVar = valueMap["tangentialAccelVariance"].asFloat(); modeA.rotationIsDir = valueMap["rotationIsDir"].asBool(); } else if (_emitterMode == Mode::RADIUS) { if (_configName.length()>0) { modeB.startRadius = valueMap["maxRadius"].asInt(); } else { modeB.startRadius = valueMap["maxRadius"].asFloat(); } modeB.startRadiusVar = valueMap["maxRadiusVariance"].asFloat(); if (_configName.length()>0) { modeB.endRadius = valueMap["minRadius"].asInt(); } else { modeB.endRadius = valueMap["minRadius"].asFloat(); } modeB.endRadiusVar = 0.0f; if (_configName.length()>0) { modeB.rotatePerSecond = valueMap["rotatePerSecond"].asInt(); } else { modeB.rotatePerSecond = valueMap["rotatePerSecond"].asFloat(); } modeB.rotatePerSecondVar = valueMap["rotatePerSecondVariance"].asFloat(); } else { CCASSERT( false, "Invalid emitterType in config file"); CC_BREAK_IF(true); } _life = valueMap["particleLifespan"].asFloat(); _lifeVar = valueMap["particleLifespanVariance"].asFloat(); _emissionRate = _totalParticles / _life; if (!_batchNode) { _opacityModifyRGB = false; if (!_configName.empty()) { _yCoordFlipped = valueMap["yCoordFlipped"].asInt(); } } setDisplayFrame(frame); ret = true; } } while (0); free(buffer); free(deflated); return ret; }
|
注意,这里只是提供了通过粒子系统属性和纹理创建粒子系统的接口,并没有实现对粒子属性的全局保存(可以参考Earth Warriors 3D中ParticleManager的实现)和图片帧的预加载。
粒子属性的获取:
1
| ValueMap FileUtilsApple::getValueMapFromFile(const std::string& filename);
|
图片帧的获取(这个获取方式就比较多了。。。):
1
| SpriteFrame* create(const std::string& filename, const Rect& rect);
|
如何使用?
1 2 3 4
| auto plistData = FileUtils::getInstance()->getValueMapFromFile("Particles/emissionPart.plist"); auto emission_frame = SpriteFrame::create("Images/engine.jpg", Rect(0,0,25,32)); auto emitter = ParticleSystemQuad::create(plistData, emission_frame); _background->addChild(_emitter, 10);
|
源码下载
我也不知道3.0release会不会集成这个功能,这里先发出pull request的链接:https://github.com/cocos2d/cocos2d-x/pull/5979/files(后记:引擎没有集成,想使用更好的方式优化,结果不了了之。)