Cocos2d-x的优化

Cocos2d-JS

对象缓存池

###简介

对于频繁创建和销毁的对象(如子弹、敌人)使用cc.pool对象。

注:使用cc.pool之前需要在project.json的modules数组中添加ccpool项

###使用

  1. 让类支持cc.pool管理:类必须实现reuse和unuse方法。因为对象的缓存通过cc.pool.putInPool(object)函数实现,该函数会调用该对象的unuse方法。一般情况下,在unuse方法中完成对象被放入缓冲池前的操作。
  2. 预备工作:首次创建好一定量的对象,并把他们放入缓冲池。

在游戏场景结束,切换场景的时候,调用cc.pool.drainAllPools()清空对象缓存池。

代码示例

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
var SupportPoolHero = cc.Sprite.extend({
_hp : 0, // 血量
_sp : 0, // 愤怒
_mp : 0, // 魔法
ctor: function (hp, mp, sp) {
this._super(res.sh_node_64_png);
this.initData(hp, mp, sp);
},
initData: function (hp, mp, sp) {
this._hp, this._mp, this._sp = hp, mp, sp;
},
unuse: function () {
this._hp = this._mp = this._sp = 0;
this.retain(); // 使其引用计数+1,防止被内存回收
this.removeFromParent();
},
reuse: function (hp, mp, sp) {
this.initData(hp, mp, sp);
}
});

SupportPoolHero.create = function (hp, mp, sp) {
return new SupportPoolHero(hp, mp, sp)
};

SupportPoolHero.reCreate = function (hp, mp, sp) {
// 判断[对象缓冲池中是否有可用对象]
if (cc.pool.hasObject(SupportPoolHero)){
// 从对象缓冲池中获取对象
return cc.pool.getFromPool(SupportPoolHero, hp, mp, sp);
}else{
// 创建一个新的对象
return SupportPoolHero.create(hp, mp, sp);
}
};




var CCPoolLayer = cc.Layer.extend({
createCount : 3000,
createHeroList : [],
reCreateHeroList : [],
timeList : {},
ctor : function(){
this._super();
// 加载[菜单]_2种方式创建对象
this.loadMenu();
// 加载[标签]_加载时间反馈
this.loadLabelInfo();
// 为pool准备对象
this.prepareHeroForPool();
},
// 加载[菜单]_2种方式创建对象
loadMenu : function(){
// 创建[标签]_按钮文本说明
var labCreate = new cc.LabelTTF("直接创建" + this.createCount + "个精灵!", "Arial", 24);
var labReCreate = new cc.LabelTTF("使用pool创建" + this.createCount + "个精灵!", "Arial", 24);

// 创建[菜单项]
var itemCreate = new cc.MenuItemLabel(labCreate, this.addSpriteByCreate, this);
var itemReCreate = new cc.MenuItemLabel(labReCreate, this.addHeorByPool, this);

// 创建[菜单]
var menu = new cc.Menu(itemCreate, itemReCreate);
menu.alignItemsHorizontallyWithPadding(150);
this.addChild(menu);
},
// 加载[标签]_加载时间反馈
loadLabelInfo : function(){
// 创建[标签]
this.labDirect = new cc.LabelTTF("直接创建耗费:", "Arial", 18);
this.labDirect.setPosition(cc.pAdd(cc.visibleRect.center, cc.p(-230, -65)));
this.labDirect.anchorY = 0;
this.addChild(this.labDirect);

// 创建[标签]
this.poolLabel = new cc.LabelTTF("使用pool创建耗费:", "Arial", 18);
this.poolLabel.setPosition(cc.pAdd(cc.visibleRect.center, cc.p(200, -65)));
this.poolLabel.anchorY = 0;
this.addChild(this.poolLabel);
},
// 准备工作[创建好对象,并把他们放入对象缓冲池]
prepareHeroForPool : function(){
for (var i = 0; i < this.createCount; i++) {
var node = SupportPoolHero.create(0, 0, 0);
this.addChild(node);
cc.pool.putInPool(node);
}
},
// 添加[精灵]_直接生成
addSpriteByCreate: function () {
this.createHeroList = [];
this.timeStart("directly");
for (var i = 0; i < this.createCount; i++) {
var node = SupportPoolHero.create(1, 2, 3);
this.createHeroList.push(node);
this.addChild(node);
node.setPosition(10 * i, cc.visibleRect.height * 0.8);
}
this.setDirectLabel(this.timeEnd("directly"));
this.scheduleOnce(function(){
for (var i = 0; i < this.createHeroList.length; i++) {
this.createHeroList[i].removeFromParent(true);
}
this.createHeroList = [];
}, 0.5);
},
// 从pool中获取英雄
addHeorByPool: function () {
this.reCreateHeroList = [];
this.timeStart("use Pool");
for (var i = 0; i < this.createCount; i++) {
var node = SupportPoolHero.reCreate(4, 5, 6);
this.reCreateHeroList.push(node);
this.addChild(node);
node.setPosition(10 * i, cc.visibleRect.height * 0.8);
}
this.setPoolLabel(this.timeEnd("use Pool"));
this.scheduleOnce(function(){
for (var i = 0; i < this.reCreateHeroList.length; i++) {
cc.pool.putInPool(this.reCreateHeroList[i]);
}
this.reCreateHeroList = [];
}, 0.5);
},
timeStart: function (name) {
this.timeList[name] = {startTime: Date.now(), EndTime: 0, DeltaTime: 0};
},
timeEnd: function (name) {
var obj = this.timeList[name];
obj.EndTime = Date.now();
obj.DeltaTime = obj.EndTime - obj.startTime;
return obj.DeltaTime;
},
setDirectLabel: function (time) {
if (time == 0) {
time = "<1";
}
this.labDirect.setString("直接创建耗费:" + time + "ms");
},
setPoolLabel: function (time) {
if (time == 0) {
time = "<1";
}
this.poolLabel.setString("使用pool创建耗费::" + time + "ms");
}
});

批量渲染

一般情况下,将图片按照模块分类,用TexturePacker合成大图,这样合理的管理了图片资源,也更贴近引擎的渲染优化。

cocos2d-x的自动批渲染

见cocos2d-x绘制系统的博文,自动批绘制需要相关的绘制命令在执行顺序上相邻

##SpriteBatchNode技术

原理

批处理绘制精灵,创建一张纹理,然后将多个精灵添加到此纹理中,绘制时直接绘制该纹理,无需单独绘制子节点。因为在Web上,优先采用WebGL渲染,若不支持WebGL,则采用Canvas绘制。只在WebGL情况下有效

渲染每个精灵的过程都会经历“打开-绘制-关闭”的流程,使用SpriteBatchNode的时候只用打开关闭一次。

###源码

需要再看

使用

将纹理加载到内存。

创建SpriteBatchNode。

创建精灵并将其添加到SpriteBatchNode。

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
var BatchNodeLayer = cc.Layer.extend({
batchNode : null,
ctor : function(){
this._super();
// 第一步:加载[资源]
this.loadResource();
// 第二步:加载[BatchNode]
this.loadBatchNode();
// 第三步:加载[8000个精灵]
this.loadSomePrince();
},
// 加载[资源]
loadResource : function(){
cc.textureCache.addImage(res.u13_prince_png);
cc.spriteFrameCache.addSpriteFrames(res.u13_prince_plist);
},
// 加载[SpriteBatchNode]
loadBatchNode : function(){
this.batchNode = new cc.SpriteBatchNode(res.u13_prince_png);
this.addChild(this.batchNode);
},
// 加载[8000个精灵]_并将其添加到this.batchNode中
loadSomePrince : function(){
var node = null;
var pos = cc.p(0, 0);
for (var i = 0; i < 8000; i++) { // 创建8000个精灵
node = new cc.Sprite("#prince_stand_1.png");
//this.batchNode.addChild(node); // 添加到batchNode对象中
this.addChild(node);

pos.x = Math.random() * cc.visibleRect.width;
pos.y = Math.random() * cc.visibleRect.height;
node.setPosition(pos);
}
}
});

烘焙层

对于一些很少发生例如添加删除运行动作的变动的层,我们可以使用图层预烘焙技术。

cc.Layer的bake函数,一个调用了bake函数的层会将其自身以及所有子节点烘焙到一张画布(canvas)上,只要自身以及所有子节点不发生例如添加或者删除子节点以及运行动作等变动行为,那么在下一帧绘制时,引擎会将预烘焙到画布上的内容直接绘制出来。使得原本需要调用多次绘制的层,现在只需要绘制一次。

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
var BakeLayer = cc.Layer.extend({
lastPrince : null,
ctor : function(){
this._super();

this.loadMenu();
this.loadSomePrince();
},
loadMenu : function(){
var itemBake = new cc.MenuItemFont("bake", this.onBake, this);
var itemUnbake = new cc.MenuItemFont("unbake", this.onUnbake, this);
var itemRunAction = new cc.MenuItemFont("run action", this.onRunAction, this);

var menu = new cc.Menu(itemBake, itemUnbake, itemRunAction);
this.addChild(menu, 1);
menu.alignItemsVertically();
menu.x = cc.winSize.width - 100;
menu.y = cc.winSize.height - 80;
},
loadSomePrince : function(){
var node = null;
var pos = cc.p(0, 0);
for (var i = 0; i < 8000; i++) { // 创建8000个精灵
node = new cc.Sprite("#prince_stand_1.png");
this.addChild(node);

// 属性配置
pos.x = Math.random() * cc.winSize.width + 150;
pos.y = Math.random() * cc.winSize.height * 0.65;
node.setPosition(pos);

this.lastPrince = node;
}
},
onBake : function(){
this.bake();
},
onUnbake : function(){
this.unbake();
},
onRunAction : function(){
var move = cc.moveBy(2, cc.p(0, -200));
//this.runAction(move);

this.lastPrince.runAction(move);
}
});

仅在Canvas渲染模式下有效

图片资源优化

在OpenGL/WebGL中,纹理的宽度和高度都必须是2的n次幂,超出则向上取最接近的

使用TP打包

生成.plist配置文件和一个.png。使用代码

cc.textureCache.addImage(res.xx_png);

cc.spriteFrameCache.addSpriteFrames(res.xx_plist);

使用TinyPNG压缩

精灵的创建方式

每个精灵一般都关联着一张纹理贴图,Texture2D对象

1.通过图片资源创建,直接传入图片的路径(不推荐)

2.通过纹理创建

var textrue = cc.textureCache.addImage(res.xx_png);

var node = new Sprite(textrue);

3.精灵表单,先将plist文件和图片加载到内存中,然后再创建

cc.spriteFrameCache.addSpriteFrames(res.xx_plist,res.xx_png);

var node = new Sprite(”#prince。png“);

4.创建带颜色的精灵

如果要纯色块的,setColor和setTextureRect,而不是setContentSize