【玩转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();
// self, not super
if(this->initWithTotalParticles(maxParticles))
{
// Emitter name in particle designer 2.0
_configName = valueMap["configName"].asString();

// angle
_angle = valueMap["angle"].asFloat();
_angleVar = valueMap["angleVariance"].asFloat();

// duration
_duration = valueMap["duration"].asFloat();

// blend function
if (_configName.length()>0)
{
_blendFunc.src = valueMap["blendFuncSource"].asFloat();
}
else
{
_blendFunc.src = valueMap["blendFuncSource"].asInt();
}
_blendFunc.dst = valueMap["blendFuncDestination"].asInt();

// color
_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();

// particle size
_startSize = valueMap["startParticleSize"].asFloat();
_startSizeVar = valueMap["startParticleSizeVariance"].asFloat();
_endSize = valueMap["finishParticleSize"].asFloat();
_endSizeVar = valueMap["finishParticleSizeVariance"].asFloat();

// position
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();

// Spinning
_startSpin = valueMap["rotationStart"].asFloat();
_startSpinVar = valueMap["rotationStartVariance"].asFloat();
_endSpin= valueMap["rotationEnd"].asFloat();
_endSpinVar= valueMap["rotationEndVariance"].asFloat();

_emitterMode = (Mode) valueMap["emitterType"].asInt();

// Mode A: Gravity + tangential accel + radial accel
if (_emitterMode == Mode::GRAVITY)
{
// gravity
modeA.gravity.x = valueMap["gravityx"].asFloat();
modeA.gravity.y = valueMap["gravityy"].asFloat();

// speed
modeA.speed = valueMap["speed"].asFloat();
modeA.speedVar = valueMap["speedVariance"].asFloat();

// radial acceleration
modeA.radialAccel = valueMap["radialAcceleration"].asFloat();
modeA.radialAccelVar = valueMap["radialAccelVariance"].asFloat();

// tangential acceleration
modeA.tangentialAccel = valueMap["tangentialAcceleration"].asFloat();
modeA.tangentialAccelVar = valueMap["tangentialAccelVariance"].asFloat();

// rotation is dir
modeA.rotationIsDir = valueMap["rotationIsDir"].asBool();
}

// or Mode B: radius movement
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 span
_life = valueMap["particleLifespan"].asFloat();
_lifeVar = valueMap["particleLifespanVariance"].asFloat();

// emission Rate
_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;


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(后记:引擎没有集成,想使用更好的方式优化,结果不了了之。)

文章目录
  1. 1. ParticleSystemQuad
  2. 2. 如何优化?
  3. 3. 如何使用?
  4. 4. 源码下载
,