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
164
165
166
167
168
169
170
171
| // DrawCall 分析器
class DrawCallProfiler {
private drawCalls: number = 0;
private vertices: number = 0;
private triangles: number = 0;
private history: DrawCallStats[] = [];
record(): void {
this.drawCalls = cc.renderer.drawCalls;
this.vertices = cc.renderer.renderedVertices;
this.triangles = cc.renderer.renderedTriangles;
this.history.push({
frame: this.history.length,
drawCalls: this.drawCalls,
vertices: this.vertices,
triangles: this.triangles,
timestamp: Date.now()
});
// 只保留最近 100 帧数据
if (this.history.length > 100) {
this.history.shift();
}
}
getAverageDrawCalls(): number {
if (this.history.length === 0) return 0;
const sum = this.history.reduce((s, h) => s + h.drawCalls, 0);
return Math.round(sum / this.history.length);
}
// 分析高 DrawCall 场景
analyzeHighDrawCallScenes(): SceneAnalysis[] {
const threshold = this.getAverageDrawCalls() * 1.5;
return this.history
.filter(h => h.drawCalls > threshold)
.map(h => ({
frame: h.frame,
drawCalls: h.drawCalls,
potentialCauses: this.identifyCauses(h)
}));
}
}
// 合批渲染系统
class BatchRenderSystem {
private batchGroups = new Map<string, cc.Node[]>();
// 按材质分组
groupByMaterial(nodes: cc.Node[]): void {
this.batchGroups.clear();
for (const node of nodes) {
const sprite = node.getComponent(cc.Sprite);
if (!sprite) continue;
const material = sprite.getMaterial(0);
const key = this.getMaterialKey(material);
if (!this.batchGroups.has(key)) {
this.batchGroups.set(key, []);
}
this.batchGroups.get(key)!.push(node);
}
}
// 自动合批
autoBatch(): void {
for (const [key, nodes] of this.batchGroups) {
if (nodes.length < 3) continue; // 至少3个才合批
this.mergeNodes(nodes);
}
}
private mergeNodes(nodes: cc.Node[]): void {
// 合并多个节点为单个 DrawCall
const firstNode = nodes[0];
const spriteBatch = firstNode.getComponent(cc.Sprite)!;
// 设置合批模式
spriteBatch.setType(cc.Sprite.Type.SIMPLE);
spriteBatch.setSizeMode(cc.Sprite.SizeMode.CUSTOM);
// 合并几何数据
const vertices = this.collectVertices(nodes);
const indices = this.generateIndices(vertices.length);
// 创建共享几何体
const mesh = new cc.Mesh();
mesh.init(vertices, indices);
mesh.setBoundingBox(new cc.AABB());
// 应用到所有节点
for (const node of nodes) {
const sprite = node.getComponent(cc.Sprite)!;
sprite.setMaterial(0, spriteBatch.getMaterial(0));
sprite.setMesh(mesh);
}
}
}
// 自动图集系统
class AutoAtlasSystem {
private atlasCache = new Map<string, cc.SpriteAtlas>();
// 动态创建图集
async createDynamicAtlas(
textures: string[],
maxSize: number = 2048
): Promise<cc.SpriteAtlas> {
// 加载所有纹理
const assets = await Promise.all(
textures.map(t => this.loadTexture(t))
);
// 使用装箱算法排列纹理
const packResult = this.packTextures(assets, maxSize, maxSize);
// 创建图集
const atlasTexture = new cc.Texture2D();
atlasTexture.initWithElement(packResult.canvas);
atlasTexture.handleLoadedTexture();
// 生成帧数据
const frames = packResult.rects.map((rect, i) => ({
name: textures[i],
rect: new cc.Rect(rect.x, rect.y, rect.width, rect.height),
offset: cc.v2(0, 0),
originalSize: cc.size(assets[i].width, assets[i].height),
rotated: false
}));
const atlas = cc.SpriteAtlas.createWithTexture(atlasTexture, frames);
this.atlasCache.set('dynamic', atlas);
return atlas;
}
// MaxRects 装箱算法
private packTextures(
textures: cc.Texture2D[],
maxWidth: number,
maxHeight: number
): PackResult {
const packer = new MaxRects();
const rectMap = new Map<cc.Texture2D, cc.Rect>();
// 按面积排序(大者优先)
const sorted = [...textures].sort((a, b) =>
(b.width * b.height) - (a.width * a.height)
);
for (const texture of sorted) {
const rect = packer.insert(texture.width, texture.height);
if (!rect) {
console.warn('Atlas full, consider increasing size');
break;
}
rectMap.set(texture, rect);
}
return {
canvas: this.drawAtlas(rectMap),
rects: Array.from(rectMap.values())
};
}
}
|