动画

预备工作:将gif转换成一个个帧的图片

使用在线工具Piskel将gif导出为png的zip包

原理

对象类在draw的时候,每隔几帧换一个图片

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
class GuaAnimation {
constructor(game) {
this.game = game
this.frames = []
for (let i = 0; i < 3; i++) {
// let name = 'player'+i
let name = `player${i}`
let t = this.game.getImgFromName(name)
//当一个变量用一会儿就不用的时候,就用一个字母代替
this.frames.push(t)
}
this.texture = this.frames[0]
this.frameIndex = 0;
this.frameCount = 3;
this.x = 50
this.y = 50
}

static new(game) {
return new this(game)
}

update() {
this.frameCount--
if (this.frameCount <= 0) {
this.frameCount = 3
this.frameIndex = (this.frameIndex + 1) % this.frames.length
this.texture = this.frames[this.frameIndex]
}
//这样使得每三帧切换一次图片
}

draw() {
log(this.frameIndex)
this.game.drawImage(this)
}
}

如果有多种状态

frames数组需要改为animations对象,animations对象中存着不同状态下图片的数组

this.texture = this.animations[this.animationName][0]

或者采用一个函数(会重复使用的地方都可以用函数替换)

1
2
3
frames(){
return this.animations[this.animationName]
}

实现翻转图片操作

心得

  1. 当一个变量生命周期很短的情况下,就用一个字母来做变量名

  2. 每三帧换一次图片,使用这种写法。注意触类旁通

  3. 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    this.frameIndex = 0; 

    this.frameCount = 3;

    update() {
    this.frameCount--
    if (this.frameCount <= 0) {
    this.frameCount = 3
    this.frameIndex = (this.frameIndex + 1) % this.frames.length
    this.texture = this.frames[this.frameIndex]
    }
    }

拓展知识

在Javascript中使用模板字符串

简单版本

1
//使用反引号 (``) 来代替普通字符串中的用双引号和单引号。模板字符串可以包含特定语法(${expression})的占位符

复杂版本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var createStrWithFormat = function (string) {
try {
var args = arguments;
var pattern = new RegExp("%([0-9]+)", "g");
return String(string).replace(pattern
, function (match, index) {
if (index == 0 || index >= args.length) {
throw "Invalid index in format string";
}
return args[index];
});
} catch (e) {
log("createWithFormat error : " + e);
return "";
}
}
createStrWithFormat("this is a %1 speaking", "male")

马里奥01

目标

实现对Nes文件中图像的提取(上)

工具

调试所需的浏览器使用FirefoxDeveloperEdition,因为有跨域问题,只有火狐开发者版本可以。

原理

红白机的图像技术的核心是所谓的 tile 和 block。所谓 tile 就是一块 8x8 像素大小的区域,一个 block 就是 16x16 大小的区域。多个以上的结构对齐,就产生了我们看到的图像。

详见https://zhuanlan.zhihu.com/p/34144965

一个Tile 8x8 像素

每个像素2 bits

所以一个Tile 16 bytes

将所有资源以这种形式提取出来显示,即可以找到需要的图像

实现

每页显示8X8个Tiles,将每个像素都扩大十倍。设置画布设置为640X640

通过ajax拿到数据,实现drawNes方法

drawNes

drawNes方法负责画满canvas

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const drawNes = bytes => {
let canvas = e("#id-canvas")
let context = canvas.getContext('2d')
let tileNum = 8//一页8x8个tiles
let tileSize = 8//一个tile有8x8个像素
let pixelwidth = 10//每个像素的宽度
let numberOfBytesPerTile = 16//16个字节一个图块
for (let i = 0; i < tileNum; i++) {
for (let j = 0; j < tileNum; j++) {
//算出bytes
let x = j * tileSize * pixelwidth
let y = i * tileSize * pixelwidth
let index = window.offset + (i * tileNum + j) * numberOfBytesPerTile
//其实应该drawTile更准确
drawTile(context, bytes.slice(index), x, y, pixelwidth)
}
}
}

drawTile

draw每个tile

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
const drawTile = (context, data, x, y, pixelwidth) =>{
const colors = [
'white',
'#fe1000',
'#ffb010',
'#aa3030',
]
let w = pixelwidth
let h = pixelwidth
for (let i = 0; i < 8; i++) {
let p1 = data[i]
let p2 = data[i + 8]
for (let j = 0; j < 8; j++) {
//8 bit per line
//在j循环中每一次画一个像素点
//78 69
//0100 1110 0100 0101
let c1 = (p1 >> (7 - j)) & 0b00000001
let c2 = (p2 >> (7 - j)) & 0b00000001
let pixel = (c2 << 1) + c1
// if (pixel == 0){
// continue
// }
let color = colors[pixel]
context.fillStyle = color
let px = x + j * w
let py = y + i * h
context.fillRect(px, py, w, h)
}
}
}

绑定点击事件

1
2
3
4
5
6
<div class="gua-controls">
<button data-action="change_offset" data-offset="-1024">-1024</button>
<button data-action="change_offset" data-offset="-16">-16</button>
<button data-action="change_offset" data-offset="16">16</button>
<button data-action="change_offset" data-offset="1024">1024</button>
</div>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
const actions = {
change_offset(offset) {
window.offset += offset
e('h3').innerHTML = window.offset
drawNes(window.bytes)
},
}
const bindEvent = () => {
e('.gua-controls').addEventListener('click', event => {
let action = event.target.dataset.action
let offset = Number(event.target.dataset.offset)
actions[action] && actions[action](offset)
})
}

通过表的方式来替代if语句

ES-1

OpenGL ES的主要作用是将3D场景绘制到2D屏幕上,在图形学中,这一过程通过一系列的渲染管线完成。

API就是用来向各个阶段提供一些数据和状态指令,以使渲染管线能够按照要求正确地将物体绘制在屏幕上的。

左边的客户端程序通过调用OpenGL ES接口,将顶点、着色器程序,纹理,以及其他GL状态参数传入右边的GL服务端,然后在客户端调用绘制命令(如DrawArray),GL便会对输入的图元逐一执行渲染管线的每个阶段,然后将每个像素的颜色值写入帧缓冲中,最后,视窗系统就可以将帧缓冲中的颜色值显示在屏幕上了。

顶点着色器和片段着色器是可编程的过程

顶点数组

在传入OpenGL ES之前,应用程序应该将3D模型转换成一组图元的集合。每个图元都是一个点、线段或者三角形。每个图元由一个或者多个顶点组成,每个顶点定义一个点,一条边的一端或者三角形的一个角。每个顶点关联一些数据,这些数据包括顶点坐标、颜色法线向量,纹理坐标

通过顶点缓冲对象(vbo)绑定顶点数组,

顶点着色器

顶点着色器可以使用顶点数据来计算该顶点的坐标、颜色、光照、纹理坐标等。顶点坐标最重要的任务是执行顶点坐标变换,将本地坐标转换为裁剪坐标系的坐标值。顶点着色器用于输出顶点在裁剪坐标系中的坐标的变量为gl_Position。

另一个功能是向后面的片段着色器提供一组Varing变量

图元装配

光栅化

对图元中的片段进行采样,以决定哪些片段位于图元之内。在计算出每个片段的坐标值之后,片段着色器就能使用这个坐标值对该片段进行着色了,这个片段为最终屏幕上的一个像素点

片段着色器

主要作用是计算每一个片段的颜色值

片段测试

像素所有权测试,裁剪测试,模板测试,深度测试,混合,抖动

Cocos2d-x精灵

在Cocos2d-x中,精灵用Sprite类表示,它将一张纹理的一部分或者全部矩形区域绘制在屏幕上。我们可以使用精灵表来减少OpenGL ES绘制的次数

Sprite实现了TextureProtocol接口通过指定一张纹理及该纹理上的一个区域,就可以创建一个Sprite对象。

我们可以通过setTexture方法来动态修改Sprite关联的纹理,从而实现精灵动画。也可以使用SpriteFrame来高效地播放精灵动画。setTexture方法对新的texture执行retain操作,增加其引用计数

所有对Sprite绘制属性的修改(混合模式除外)最终都表现为对一个结构体类型变量quad的修改。

它用来记录一个矩形图元的4个顶点的顶点属性。由于OpenGL ES不支持多边形图元,实际是两个三角形

可以通过颜色混合和颜色叠加来影响OpenGL ES的绘制结果

颜色混合用来指定当前Sprite与颜色缓冲区中相同位置颜色值的混合方式。

精灵表

利用精灵表,我们可以对同一次绘制指定更多的顶点来减少绘制次数

SpriteFrameCache加载精灵表,缓存每一个精灵的信息。SpriteFrameCache通过解析配置文件将精灵表中的每个精灵存储为一个SpriteFrame对象,可以通过SpriteFrame创建一个Sprite

并不提供移除对应于Texture2D对象的功能。因为一个Texture2D对象可能被其他非SpriteFrame对象引用,所以,如果我们确定一个精灵表不再被使用,还应该移除对应的Texture2D对象

Cocos2d-x纹理

3D图形渲染最重要的工作之一是将纹理应用到物体表面,这个过程主要发生在片段着色器工作的阶段,使用光栅化阶段插值计算得出的纹理坐标从纹理中采样,然后对片段进行着色,可以处理丰富的特效,如光照、阴影等。

对纹理进行各个层面的优化是一项重要的工作,我们需要知道关于纹理的一切内容,才能知道应该从哪方面以及怎样对其进行优化。

在Cocos2d-x中主要通过Texture2D类来创造和管理OpenGL ES 纹理。

光栅化

光栅化阶段用于将投影到2D屏幕上的图元转换为帧缓冲中整数坐标位置上的片段,每个片段都会包含颜色、深度和模板值。每个片段的颜色值由片段着色器决定,片段着色器会使用光栅化生成的一些易变变量(Varying)

步骤

首先确定视窗上哪些整数的片段需要被图元覆盖

对图元进行插值运算,得出每个片段的颜色和深度,这些数据送入片段操作进行处理,最后的结果将用于更新帧缓冲上该位置的信息。

多重采样

反锯齿

也称抗锯齿和边缘柔化,是一种用于消除显示器输出的画面中图像边缘出现锯齿的技术。锯齿通常是因为高分辨率信号以低分辨率显示或者无法准确计算出3D图形左边是导致的图形混叠而产生的。

使用多重采样技术解决这个问题,帧缓冲上的每个像素基于附近多个位置的采样共同决定的一个颜色值,使用而外的多重采样缓冲区来存储这些额外采样点的颜色、深度和模板值

默认cocos不开启,手动修改CCEAGLView初始化的multiSampling参数来开启

在开启多重采样之后,缓冲区的像素信息不再被存储到帧缓冲中,而是被存储到一个特殊的多重采样的缓冲区中,注意,多重采样只是针对多边形的边缘进行抗锯齿处理,对应用程序性能的影响相对较小,

纹理坐标

纹理坐标以纹理的左下角为左边原点,两种度量形式:一个顶点在纹理中的纹理坐标通常用(u,v)表示,u和v的最大值分别是纹理的宽度和高度,它的值通常由客户端应用程序提供,在片段着色器中使用的片段纹理坐标通常用(s,t)表示,其取值范围为(0.0,0.0)~(1.0,1.0)。这一规化过程是在光栅化阶段完成的

##像素矩形

它表示一个矩形区域的二维像素数组,它可以用来表示一个矩形区域的颜色、深度或者模板值

存储颜色值的像素矩形可以通过TexImage2D及其相关命令定义,并将数据由客户端内存中传输至GL服务端内存中,或者通过ReadPixels及相关命令将数据从帧缓冲区中读取到客户端内存中,其中将客户端的颜色数据传输到GL服务端的过程称为解包(Unpack),而与之相反,将服务端像素矩形中的像素数据读取到客户端的过程称为打包(Pack)。

通过TexImage2D及相关命令定义的像素矩形称为纹理。纹理由纹理像素组成,简称纹素。一个2D纹理由一个二维的纹素数组组成,纹理的宽度表示列数,高度表示数组的行数,数组中的第一个元素表示纹理左下角的像素点

纹理数据的传输

##客户端图像格式

在将纹理传输到GL服务端以供片段着色器等使用时,我们必须指明纹理在服务端以怎样的方式存储,这需要通过TexImage2D等命令来设置。

纹理对象和加载纹理

在绘图管线中,纹理主要在片段着色器中被使用。每一次绘制命令(glDrawArray或者glDrawElements)执行时,需要告诉OpenGL ES当前管线中使用的一个或者多个纹理,这涉及管理纹理对象,绑定当前纹理对象,将纹理数据加载到OpenGL ES内存中等命令操作。

一个纹理对象是一个容器,它持有该纹理被使用时需要用到的所有数据,包括图像像素数据、过滤模式、拓展模式等。在OpenGL ES中,用一个无符号整数表示该纹理对象的名称,纹理对象用glGenTextures命令创建

创建之后,必须绑定才能操作,在这里OpenGL ES通过设定当前纹理对象对其进行操作,设定当前纹理对象之后,后续(直到纹理对象被删除或者下一个纹理绑定命令被执行之前)的操作将作用在该纹理对象上。glBindTexture命令

glTexImage2D命令加载纹理。一旦该命令执行,会立即将图像像素数据从客户端传输到服务端内存中,后续对客户端数据的修改不会影响OpenGL ES中绑定的纹理数据。因此,客户端在将数据加载到OpenGL ES内存中之后,应该立即删除客户端中的缓存对象。

纹理缓存管理

我们可以通过选择适当的图片格式,或者使用压缩纹理来减少纹理对内存的占用。

纹理缓存系统的主要目标是使只有当前场景需要显示的纹理驻留在内存中,开发者的职责则是要定义哪些是当前场景需要使用的资源。另外,在纹理使用期间,它应该只被创建一次,并且避免动态加载纹理,我们始终应该在进入一个场景时预加载相关纹理,这是一个耗时的操作,而且是在主线程中完成的。

纹理的生命周期

在Cocos2d-x中,一个Texture2D实例对应OpenGL ES中的一张纹理,initWithMipmaps ()方法。

每个Texture2D实例在未被销毁之前,GPU会一直缓存该纹理对象。如果销毁Texture2D,则会删除对应的纹理对象。

TextureCache提供了对Texture2D对象的更好的管理方式

##TextureCache

TextureCache最终要的功能是为每个纹理的Texture2D对象创建一个索引键,索引键主要有两类:当使用文件名创建纹理的时候,文件所在全路径成为索引键。也可以手动给通过Image创建的纹理分配一个指定的索引键

使用addImageAsync()方法异步加载纹理,通常用在进入一个新场景里面,使用一个加载页面

TextureCache管理下的纹理的生命周期

没有TextureCache的情况下,直接通过string、image或者data创建一个Texture2D对象,这时该对象的引用计数为1.用该对象构建一个Sprite元素,Sprite元素首先对它进行retain以确保纹理对象不被删除,并在被移出场景的时候release,此时Texture2D的生命周期取决于我们是否还要继续使用它。但是无论如何,我们都需要记住每个Texture2D对象对应哪一张图片,而且要让这些独立的Texture2D对象在其他的类中被使用要费一般周折

TextureCache中的每个Texture2D对象的引用计数是1,如果有元素正在使用该纹理,则引用计数为正在使用的元素个数加1.1表示空闲状态,使用removeUnusedTextures()方法来移除空闲的纹理以释放资源。

如果确认某个纹理不再使用,removeTexture(),这时Texture2D对象将执行一次release

场景过度中的资源管理

基于引用计数的资源管理

计数指的是应用程序在逻辑上使用哪些资源

更好的场景过度资源管理

Cocos2d-x没有提供场景或者关卡层面上的资源管理方案。

ResourceManager负责逻辑上的资源引用计数管理。记录每个资源在场景中的引用情况。应用程序只需要和ResourceManager进行交互。

ResourceManager主要作用是解决场景之间的资源共享

纹理所占内存的计算

使用纹理的最佳实践

硬件层面

一般通过引擎完成

程序层面

首先,始终提前加载资源,避免在游戏运行中动态加载资源

其次,减少纹理的内存占用,实时释放那些不再被使用的纹理资源,这要求开发者必须清楚地定义每个资源的生命周期

再次,我们应该将一些小的碎片纹理合成一张大的纹理

第四,使用多级纹理减少内存的占用。对于一些低分辨率的设备,仅上传对应级别的多级纹理

第五,使员工多重纹理,对于一些复杂的UI元素,开发者通常会选择绘制多次,每次单独使用一个纹理,这使得绘制次数增加,占用更多的时间(多次绘制串行执行),以及重复的顶点数据的传输和读取。使用多级纹理一次性将所有问哦李传入进一个渲染管线

最后,使用Alpha预乘来减少透明纹理在场景混合时的计算量

资源层面

选择适当的资源格式

设置正确的defaultAlphaPixelFormat将不同格式的纹理上传GL内存

使用压缩纹理

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

网络-TCP

TCP的包头

包的序号:解决乱序问题

确认序号:

状态位:

窗口大小:通信双方各声明一个窗口,标识当前自己能够处理的能力

三次握手

三次握手除了双方建立连接外,主要还是为了沟通TCP包的序号问题

四次挥手

为何靠谱

客户端没发送一个包,服务器端都有个回复,如果服务端超过一定时间没有回复,客户端就会重新发送这个包,,知道有回复

累计确认,累计应答

网络-UDP

TCP和UDP的区别

TCP是面向连接的,UDP是面向无连接的

所谓的建立连接,是为了在客户端和服务端维护连接,而建立一定的数据结构来维护双方交互的状态。用这样的数据结构来保证所谓的面向连接的特性。

eg:TCP提供可靠交付,通过TCP传输的数据,无差错,不丢失,不重复,按顺序到达。而UDP不保证不丢失,不保证按顺序到达。

TCP是面向字节流的,而UDP继承了IP的特性,基于数据报的,一个一个的发,一个一个的收。

TCP可以有拥塞控制https://mp.weixin.qq.com/s/j0WeTRgJMohIYr-SraMyBw,UDP没有,应用让发就发。

总结

TCP是一个有状态服务(有脑子的服务),它记着发送了没有,接收到没有,发送到哪个了,应该接收哪个了

UDP的包头

很简单,只有端口号