Asroads'Blog 君子不器
CocosCreator2.4.13如何启动场景更改为AssetBundle内的场景
发布于: 2024-02-28 更新于: 2024-03-02 分类于: game 阅读次数: 

CocosCreator从2.4开始可以自定义Asset Bundle,这个功能给开发者带来了很多自由设计素材结构的空间,使得开发者更容易把游戏设计为模块化,使得开发者可以更自由的设置合理分配资源。在自己实践过程中,慢慢的积累了一些开发心得,下面就谈谈如何让CocosCreator启动自己自定义Asset Bundle,其中包括使用插件代替手动更新方案。

目前现状

新建项目

image-20240301160849483

设置启动场景Bundle

image-20240301161514543

构建项目

image-20240301161637054

  1. 此时我们发现 无法勾选 bundle内的场景为启动场景
    image-20240301161725187

  2. 我们查看一下 构建后的settings.jsmain.js 数据内容

image-20240301161917585

image-20240301161940951

项目改造

项目分析

此时我们静下来分析一下,按照CocosCreator的流程,如果我们想启动我们自己的Bundle内的场景,那么必须要加载我们的自定义的Bundle 然后设置启动场景为我们想要的启动场景。下面说一下具体步骤。

  1. 修改main.js 的文件内容 将 我们自己自定义的bundle添加进去,个人建议添加到 内置bundle后面:

原来代码:

1
var bundleRoot = [INTERNAL];

替换后:

1
var bundleRoot = [INTERNAL,"launch"];
  1. 修改setting.js文件(我这里勾选了调试模式,所以没有MD5)

原内容:

1
launchScene: "db://assets/scene/MainScene.fire",

替换后:

1
launchScene: "db://assets/bundles/launch/MiniLaunchScene.fire",
  1. 此时我们使用微信开发者工具验证一下

image-20240301163049419

  1. 此时我们发现可以正常运行,证明思路可行。

进一步优化

此时,我们知道,构建后修改文件,就可以达到我们希望的效果,那么我们如何每次避免手动修改呢?问题来了。

是否可以使用 自定义发布模版 的 build-templates更改呢?

下面我们尝试一下:

image-20240301163619303

但是构建后的setting.js没有被替换
image-20240301164220602

那有没有其他方法呢?答案是有的,插件实现。

修改构建插件

  1. 我们新建一个插件

image-20240301164451071

  1. index.js 内容:备注:此文件对插件无用,默认就行,可以跳过不看
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
// panel/index.js, this filename needs to match the one registered in package.json
Editor.Panel.extend({
// css style for panel
style: `
:host { margin: 5px; }
h2 { color: #f90; }
`,

// html template for panel
template: `
<h2>excel-cmd</h2>
<hr />
<div>State: <span id="label">--</span></div>
<hr />
<ui-button id="btn">Send To Main</ui-button>
`,

// element and variable binding
$: {
btn: '#btn',
label: '#label',
},

// method executed when template and styles are successfully loaded and initialized
ready () {
this.$btn.addEventListener('confirm', () => {
Editor.Ipc.sendToMain('excel-cmd:clicked');
});
},

// register your ipc messages here
messages: {
'excel-cmd:hello' (event) {
this.$label.innerText = 'Hello!';
}
}
});
  1. package.json 内容:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{
"name": "bundle-change-first",
"version": "0.0.1",
"description": "The package template for getting started.",
"author": "jsroads",
"main": "main.js",
"main-menu": {
},
"panel": {
"main": "panel/index.js",
"type": "dockable",
"title": "BundleFast",
"width": 400,
"height": 300
}
}
  1. main.js
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
"use strict";
const fs = require("fs");
const path = require("path");
const miniAdapterActualPlatform = ["bytedance", "wechatgame"];
const webAdapterActualPlatform = ["web-mobile"];



const newLaunchScene = "db://assets/bundles/launch/MiniLaunchScene.fire";

const newLaunchBundle = `"launch"`;
function onBuildFinish(options, callback) {
Editor.log("[bundle-change-first]", options.platform);
if ("web-mobile" === options.platform) {
if (webAdapterActualPlatform.indexOf(options.actualPlatform) === -1) {
callback && callback();
return;
}
modifySettings(options);
modifyMainJS(options);
Editor.success("[bundle-change-first]", "web-mobile首屏启动优化完成");
callback();
} else if ("mini-game" === options.platform) {
if (miniAdapterActualPlatform.indexOf(options.actualPlatform) === -1) {
callback && callback();
return;
}
modifyMiniSettings(options);
modifyMiniMainJS(options);
Editor.success("[bundle-change-first]", "小游戏首屏启动优化完成");
callback();
}
}

function modifySettings(options) {
const dirPath = path.join(options.dest, "src"); // 假设文件在 src 目录下


// 列出目录中的所有文件
fs.readdir(dirPath, function (err, files) {
if (err) {
console.error("Error reading the directory:", err);
return;
}

// 正则表达式匹配文件名
const regex = /^settings\.[0-9a-f]+\.js$/;
files.forEach(function (file) {
if (regex.test(file)) {
// 匹配到的文件
const filePath = path.join(dirPath, file);
fs.readFile(filePath, "utf8", function (err, data) {
if (err) {
console.error("Error reading the file:", err);
return;
}
// 修改文件内容
let modifiedData = data.replace(/(launchScene\s*:\s*")([^"]*)(")/, `$1${newLaunchScene}$3`);

// 保存修改后的文件
fs.writeFile(filePath, modifiedData, "utf8", function (err) {
if (err) {
console.error("Error writing to the file:", err);
return;
}
console.log("File updated successfully");
});
});
}
});
});
}

function modifyMainJS(options) {
const dirPath = options.dest; //main.js 目录下
// 列出目录中的所有文件
fs.readdir(dirPath, function (err, files) {
if (err) {
console.error("Error reading the directory:", err);
return;
}

// 正则表达式匹配文件名
const regex = /^main\.[0-9a-f]+\.js$/;
files.forEach(function (file) {
if (regex.test(file)) {
// 匹配到的文件
const filePath = path.join(dirPath, file);
fs.readFile(filePath, "utf8", function (err, data) {
if (err) {
console.error("Error reading the file:", err);
return;
}
// 修改文件内容
let modifiedData = data.replace(/var bundleRoot = \[INTERNAL\];/, `var bundleRoot = [INTERNAL,${newLaunchBundle}];`);

// 保存修改后的文件
fs.writeFile(filePath, modifiedData, "utf8", function (err) {
if (err) {
console.error("Error writing to the file:", err);
return;
}
console.log("File updated successfully");
});
});
}
});
});
}

function modifyMiniSettings(options) {
const filePath = path.join(options.dest, "src", "settings.js"); // settings.js 文件的实际路径
fs.readFile(filePath, "utf8", function (err, data) {
if (err) {
console.error("Error reading the file:", err);
return;
}
// 使用正则表达式查找和替换值
let modifiedData = data.replace(/(launchScene\s*:\s*")([^"]*)(")/, `$1${newLaunchScene}$3`);
// modifiedData = modifiedData.replace(/(server\s*:\s*")[^"]*(")/, `$1"${newServer}"`);

// 将修改后的内容写回文件
fs.writeFile(filePath, modifiedData, "utf8", function (err) {
if (err) {
console.error("Error writing to the file:", err);
return;
}
console.log("File updated successfully");
});
});
}

function modifyMiniMainJS(options) {
const filePath = path.join(options.dest, "main.js"); // main.js 文件的实际路径
fs.readFile(filePath, "utf8", function (err, data) {
if (err) {
console.error("Error reading the file:", err);
return;
}

// 使用正则表达式查找和替换值
let modifiedData = data.replace(/var bundleRoot = \[INTERNAL\];/, `var bundleRoot = [INTERNAL,${newLaunchBundle}];`);

// 将修改后的内容写回文件
fs.writeFile(filePath, modifiedData, "utf8", function (err) {
if (err) {
console.error("Error writing to the file:", err);
return;
}
console.log("File updated successfully");
});
});
}

module.exports = {
load() {
// Editor.Builder.on('build-start', onBuildStart);
Editor.Builder.on("build-finished", onBuildFinish);
},

unload() {
// Editor.Builder.removeListener('build-start', onBuildStart);
Editor.Builder.removeListener("build-finished", onBuildFinish);
},
messages: {},
};

如果你的场景和我的不同 注意修改下面两行

1
2
3
const newLaunchScene = "db://assets/bundles/launch/MiniLaunchScene.fire";

const newLaunchBundle = `"launch"`;

验证

我们分别构建web-mobile和微信小游戏发现插件在构建后已经完全按照我们的意愿修改了。如果web 版本想进一步优化,那么就结合我们上面的提到的自定义发布模版 ,具体内容可以参考:CocosCreator构建出web-mobile项目的时候自动刷新定向index.html

image-20240301172005385

其中 index.html 核心内容如下:

image-20240301172159013

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
<script>
// 当前URL
let url = location.href;
// 如果浏览器支持 window.performance 并且页面是通过刷新按钮加载的
if (window.performance && window.performance.getEntriesByType("navigation")[0].type === "reload") {
console.log("用户点击刷新,重新定向");
// 生成随机数和时间戳
const randomNumber = Math.floor(Math.random() * 100000) + 1;
const timestamp = Date.now();
// 如果URL已经包含查询字符串
if (location.search) {
// 正则替换已存在的v和t参数
url = url.replace(/([?&])v=\d*/, '$1v=' + randomNumber);
url = url.replace(/([?&])t=\d*/, '$1t=' + timestamp);

// 如果URL中没有v和t参数,那么添加它们
if (!url.includes('?v=') && !url.includes('&v=')) {
url += '&v=' + randomNumber;
}
if (!url.includes('?t=') && !url.includes('&t=')) {
url += '&t=' + timestamp;
}
} else {
// 如果URL没有查询字符串,那么添加v和t参数
url += '?v=' + randomNumber + '&t=' + timestamp;
}
// 重定向到新的URL
location.href = url;
}else {
console.log("首次进入或用户非刷新的方式进入页面");
// 随机数和时间戳
const randomNumber = Math.floor(Math.random() * 100000) + 1;
const timestamp = Date.now();
// 如果URL已经包含查询字符串
if (location.search) {
// 如果URL中已经有了随机数和时间戳参数,就不再进行重定向
if (url.includes('?v=') || url.includes('&v=') || url.includes('?t=') || url.includes('&t=')) {
console.log("Already redirected, won't redirect again");
} else {
// 如果URL中没有随机数和时间戳参数,添加它们
url += '&v=' + randomNumber;
url += '&t=' + timestamp;
// 重定向到新的URL
location.href = url;
}
} else {
// 如果URL没有查询字符串,那么添加随机数和时间戳参数,并进行重定向
url += '?v=' + randomNumber;
url += '&t=' + timestamp;
location.href = url;
}
}
</script>

总结

做完这些,回过头来看,其实只要我们善于思考和观察源码,分析+验证很多时候还是可以实现我们自己的需求。做技术就是不断的拓展自己的失业,废弃已经不再适用当下的知识,去攫取寻找能够在未来用的上的技术。

--- 本文结束 The End ---