Cocos Creator 3.6.0 构建模板与 settings.json 升级指南

本文将介绍 Cocos Creator 3.6.0 构建模板与 settings.json 升级注意事项。

在 3.6 版本之前,引擎的代码中都包含了一部分无法被剔除的内置 effect 的数据,这部分数据极大的占用了引擎的包体与拖慢了解析引擎代码的时间。对于大部分项目来说是一种资源浪费。

在之前版本中,构建模板中的 application.ejsgame.ejs(某些平台是 index.ejs)中包含了不少引擎启动流程的逻辑代码,这部分逻辑代码较为复杂,定制过程中容易出现问题,需要更加易用与稳定的引擎启动流程的定制方案。

此外,在之前版本中,游戏的主要配置数据存储在 settings.json 中, 在游戏运行过程中无法访问这部分数据,对于一些插件来说是极不方便的。

针对以上几个问题,我们在 Cocos Creator 3.6 中,为了优化引擎包体,更好地维护引擎的业务代码,同时能够提供更多的定制空间,我们重构了引擎的启动流程并提供了一个新的 settings 配置模块 。在重构的过程中,虽然我们希望能尽可能地保证之前版本项目构建模板与 settings.json 的兼容性,但是某些方面还是引入了一些无法兼容的情况。在这些情况下,你需要手动升级项目构建模板与配置文件的定制内容。

  • 美术策划 而言,项目中的所有资源,例如场景、动画和 Prefab 都不需要修改,也不需要升级。
  • 程序插件开发者 而言,影响主要体现在原先项目中定制的构建模板和 settings.json 需要调整为新的方式。

以下将详细介绍这部分内容。

需要手动升级的情况

  • 你在项目中有定制过构建模板中的 application.ejs
  • 你在项目中有定制过构建模板中的 game.ejsindex.ejs
  • 你在项目中有定制过生成后的 settings.json

升级步骤

将定制过的 application.ejs 迁移到新的模板上

application.ejs 在 v3.6 版本中改动较大,主要有以下两个方面的改动:

提供了一个更加简单的 Application 类型的定义

  1. class Application {
  2. init (cc: any): Promise<void> | void;
  3. start(): Promise<void>;
  4. }

我们提供了形如以上的 Application 的定义,我们在其中提供了两个生命周期回调:

  • init 生命周期函数会在引擎代码加载后被调用,引擎模块将作为参数传入,你可以在 init 函数中监听引擎启动流程的事件,并在对应事件触发时,执行一些定制逻辑。
  • start 生命周期函数会在 init 函数后被调用,你需要在此函数中以你需要的方式启动并运行引擎。

下面是我们实现的一份最简单的 application.ejs 的模板与其对应的解释:

  1. let cc;
  2. export class Application {
  3. constructor () {
  4. this.settingsPath = '<%= settingsJsonPath %>'; // settings.json 文件路径,通常由编辑器构建时传入,你也可以指定自己的路径
  5. this.showFPS = <%= showFPS %>; // 是否打开 profiler, 通常由编辑器构建时传入,你也可以指定你需要的值
  6. }
  7. init (engine) {
  8. cc = engine;
  9. cc.game.onPostBaseInitDelegate.add(this.onPostInitBase.bind(this)); // 监听引擎启动流程事件 onPostBaseInitDelegate
  10. cc.game.onPostSubsystemInitDelegate.add(this.onPostSystemInit.bind(this)); // 监听引擎启动流程事件 onPostSubsystemInitDelegate
  11. }
  12. onPostInitBase () {
  13. // cc.settings.overrideSettings('assets', 'server', '');
  14. // 实现一些自定义的逻辑
  15. }
  16. onPostSystemInit () {
  17. // 实现一些自定义的逻辑
  18. }
  19. start () {
  20. return cc.game.init({ // 以需要的参数运行引擎
  21. debugMode: <%= debugMode %> ? cc.DebugMode.INFO : cc.DebugMode.ERROR,
  22. settingsPath: this.settingsPath, // 传入 settings.json 路径
  23. overrideSettings: { // 对配置文件中的部分数据进行覆盖,第二部分会详细介绍这个字段
  24. // assets: {
  25. // preloadBundles: [{ bundle: 'main', version: 'xxx' }],
  26. // }
  27. profiling: {
  28. showFPS: this.showFPS,
  29. }
  30. }
  31. }).then(() => cc.game.run());
  32. }
  33. }
  • 此模板中在 init 函数中监听了引擎启动流程中的事件,可以在对应事件触发时执行一些自定义逻辑。 game.init 在执行过程中会触发各个阶段的事件,可参考 game.init 的 API 说明,其中不同的阶段的事件可以使用不同的引擎能力,触发的事件顺序如下所示:

    1. -PreBaseInitEvent,不可以使用任何引擎能力
    2. -基础模块初始化(logging, sys, settings)
    3. -PostBaseInitEvent,可使用基础模块中的能力
    4. -PreInfrastructureInitEvent,可使用基础模块中的能力
    5. -基础设施模块的初始化(assetManager, builtinResMgr, gfxDevice, screen, Layer, macro)
    6. -PostInfrastructureInitEvent,可使用基础模块,基础设施模块中的能力
    7. -PreSubsystemInitEvent,可使用基础模块,基础设施模块中的能力
    8. -子系统模块初始化(animation, physics, tween, ui, middleware 等)
    9. -PostSubsystemInitEvent,可使用基础模块,基础设施模块,子系统模块中的能力
    10. -EngineInitedEvent,可使用基础模块,基础设施模块,子系统模块中的能力
    11. -PreProjectDataInitEvent,可使用基础模块,基础设施模块,子系统模块中的能力
    12. -项目数据初始化(GamePlayScripts, resources, etc)
    13. -PostProjectDataInitEvent,可使用基础模块,基础设施模块,子系统模块,项目数据中的能力
    14. -GameInitedEvent,可使用引擎所有能力
  • start 函数中调用了引擎的初始化与运行。你可以通过在调用 game.init 时传入自定义的参数来控制引擎的启动。需要注意的是其中的 overrideSettings 字段,此字段可用于覆盖配置文件中的一些配置数据,从而影响引擎的启动。第二部分将详细说到此字段。

新的 application.ejs 比之前版本更加简洁和易定制,你可以参考此模板实现你自己的 application.ejs 并在其中执行你的自定义逻辑。

引擎启动流程相关的逻辑代码迁移到了引擎中

我们将之前在 application.ejs 中与引擎启动相关的逻辑迁移到了引擎中,包括 settings.json 的加载(loadSettingsJson),js 插件的加载(loadJsList),项目 bundle 的加载(loadAssetBundle)等等。所以现在你 无法在 application.ejs 直接修改引擎启动流程,但你可以通过 game.init 中传入的 overrideSettings 字段来覆盖配置文件,从而影响启动流程,另外你也可以监听引擎启动过程中的事件,并执行一些自定义逻辑。下面将详细介绍引擎的启动流程定制如何迁移到新的机制上。如果你的项目有对引擎启动流程进行定制,请参考以下升级方法进行迁移:

  • 如果你在老的模板中引擎启动的特定阶段插入了自定义的代码逻辑,例如做了如下定制:

    1. function start ({
    2. findCanvas,
    3. }) {
    4. let settings;
    5. let cc;
    6. return Promise.resolve()
    7. .then(() => topLevelImport('cc'))
    8. .then(() => customLogic1()) // 自定义逻辑 1
    9. .then((engine) => {
    10. cc = engine;
    11. return loadSettingsJson(cc);
    12. })
    13. .then(() => customLogic2()) // 自定义逻辑 2
    14. .then(() => {
    15. settings = window._CCSettings;
    16. return initializeGame(cc, settings, findCanvas)
    17. .then(() => {
    18. if (settings.scriptPackages) {
    19. return loadModulePacks(settings.scriptPackages);
    20. }
    21. })
    22. .then(() => loadJsList(settings.jsList))
    23. .then(() => loadAssetBundle(settings.hasResourcesBundle, settings.hasStartSceneBundle))
    24. .then(() => {
    25. return cc.game.run(() => onGameStarted(cc, settings));
    26. });
    27. }).then(() => customLogic3()); // 自定义逻辑 3
    28. }

    你可以在新的 application.ejs 模板中通过上述所说的监听引擎启动流程中的事件来实现自定义逻辑,例如:

    1. init (engine) {
    2. cc = engine;
    3. cc.game.onPreBaseInitDelegate.add(this.onPreBaseInit.bind(this)); // 监听引擎启动流程事件 onPreBaseInitDelegate
    4. cc.game.onPostBaseInitDelegate.add(this.onPostBaseInit.bind(this)); // 监听引擎启动流程事件 onPostBaseInitDelegate
    5. cc.game.onPostProjectInitDelegate.add(this.onPostProjectInit.bind(this)); // 监听引擎启动流程事件 onPostProjectInitDelegate
    6. }
    7. onPreBaseInit () {
    8. customLogic1(); // 自定义逻辑 1
    9. }
    10. onPostBaseInit () {
    11. customLogic2() // 自定义逻辑 2
    12. }
    13. onPostProjectInit () {
    14. customLogic3() // 自定义逻辑 3
    15. }
  • 如果你在老的模板中定制了物理模块 wasm 的加载,即老模板中的下面这个方法。

    1. <% if (hasPhysicsAmmo) { %>
    2. promise = promise
    3. .then(() => topLevelImport('wait-for-ammo-instantiation'))
    4. .then(({default: waitForAmmoInstantiation}) => {
    5. return waitForAmmoInstantiation(fetchWasm(''));
    6. });
    7. <% } %>

    此方法已改为引擎内部使用,如果需要定制,请自定义引擎,并定制引擎目录下的 cocos/physics/bullet/instantiated.ts 内容。

  • 如果你在老的模板中定制了 settings.json 的加载, 即老模板中的 loadSettingsJson 函数。 此方法已改为引擎内部使用,如果你对这部分存在定制,请分为以下三种情况考虑:

    • 如果你需要定制 settings.json 的路径,你可以在调用 game.init 方式时传入自定义的 settingsPath 路径,例如:

      1. game.init({ settingsPath: this.mySettingsPath });
    • 如果你需要读取 settings.json 中的内容,你可以监听 game.onPostBaseInitDelegate 之后的事件,这些事件中你都可以安全的访问引擎中的 settings 模块,并通过 settings 模块的相关 API 获取对应配置数据,例如:

      1. init (engine) {
      2. cc = engine;
      3. cc.game.onPostBaseInitDelegate.add(this.onPostBaseInit.bind(this));
      4. }
      5. onPostBaseInit () {
      6. const property = cc.settings.querySettings('MyCustomData', 'MyCustomProperty');
      7. }
    • 如果你需要设置配置文件中的内容,你可以通过在调用 game.init 方法时传入 overrideSettings 字段来覆盖一些配置文件中的数据,也可以在 game 的事件回调中去调用 settings.overrideSettings 覆盖配置文件中的数据,从而影响引擎启动流程,例如:

      1. start () {
      2. cc.game.init({
      3. overrideSettings: {
      4. 'profiling': { 'showFPS': true }
      5. }
      6. });
      7. }

      1. onPostBaseInit () {
      2. cc.settings.overrideSettings('profiling', 'showFPS', true);
      3. }
  • 如果你在老的模板中定制了加载 js 插件, 即老模板中的 loadJsList 函数。 此方法已改为引擎内部使用,如果你需要修改要加载的 js 文件列表,你可以通过在调用 game.init 时传入 overrideSettingsplugin 字段来修改,例如:

    1. start () {
    2. cc.game.init({
    3. overrideSettings: {
    4. 'plugins': { 'jsList': ['MyCustom.js'] }
    5. }
    6. });
    7. }
  • 如果你在老的模板中定制了加载项目 Bundle, 即老模板中的 loadAssetBundle 函数。 此方法已改为引擎内部使用,如果你需要修改要加载的 bundle 列表,你可以通过在调用 game.init 时传入 overrideSettingsassets 字段来修改,例如:

    1. start () {
    2. cc.game.init({
    3. overrideSettings: {
    4. 'assets': { 'preloadBundles': [ 'main', 'resources', 'myBundle' ]}
    5. }
    6. });
    7. }
  • 如果你在老的模板中定制了 macros 与 layer 的初始化, 即老模板中的initializeGame 函数。 此方法已改为引擎内部使用,如果你需要修改 macros 与 layer 的列表,你可以通过在调用 game.init 时传入 overrideSettingsengine 字段来修改,例如:

    1. start () {
    2. cc.game.init({
    3. overrideSettings: {
    4. 'engine': {
    5. 'macros': {},
    6. 'customLayers': [],
    7. }
    8. }
    9. });
    10. }
  • 如果你在老的模板中定制了初始场景的加载, 即老模板中的 onGameStarted 函数。 此方法已改为引擎内部使用,如果你需要修改初始场景,你可以通过在调用 game.init 时传入 overrideSettingslaunch 字段来修改,例如:

    1. start () {
    2. cc.game.init({
    3. overrideSettings: {
    4. 'launch': { 'launchScene': 'MyFirstScene' }
    5. }
    6. });
    7. }
  • 如果你在老的模板中定制了传入 game.init 的参数, 即老模板中的 getGameOptions 函数。 请参考最新的 IGameConfig 接口定义,并在 game.init 时传入该参数。

将定制过的 game.ejs 或 index.ejs 迁移到新模板上

game.ejs 与 index.ejs 相较于之前版本没有太大变化,主要变化体现在以下几个点:

  • 我们将 loadJsListFile, fetchWasm, findCanvas 等引擎使用的接口迁移到了引擎内部,如果你对这部分内容存在定制,请自定义引擎,并修改引擎目录下的 pal/env 的内容。
  • 正如第一部分所说,我们整理了 Application 类型的接口定义,而 game.ejs 与 index.ejs 作为 Application 类型的调用者,所以在 Application 接口调用的地方也发生了变化,比如在某些平台我们改成了如下形式:

    1. System.import('<%= applicationJs %>')
    2. .then(({ Application }) => {
    3. return new Application();
    4. }).then((application) => {
    5. return System.import('cc').then((cc) => {
    6. return application.init(cc);
    7. }).then(() => {
    8. return application.start();
    9. });
    10. }).catch((err) => {
    11. console.error(err.toString() + ', stack: ' + err.stack);
    12. });
  • 我们将不同平台上用于适配的代码进行了打包,合并成了单个 js 文件以减少包体,所以在运行代码的地方改为只运行一个 js 文件,例如:

    1. require('./libs/common/engine/index.js');
    2. require('./libs/wrapper/engine/index');
    3. require('./libs/common/cache-manager.js');

    改为了

    1. require('./engine-adapter');

如果你对 game.ejs 与 index.ejs 存在定制的话,只需要将定制迁移到最新的模板上即可,需要 注意 的是如果你的自定义逻辑依赖于 字符串的匹配 进行插入,例如:

  1. const gameTemplateString = readFileSync('game.ejs', 'utf-8');
  2. gameTemplateString = gameTemplateString.replace('require('./libs/common/cache-manager.js');', 'require('./libs/common/cache-manager.js');\nCustomLogic();\n')

则有可能出现匹配字符串失败的地方,你需要在新的模板下进行插入,或者你可以参考第一部分在 application.ejs 中进行定制,新的 application.ejs 模板更加利于定制。

迁移对 settings.json 的定制

我们在 3.6 中增加了 settings 模块用于在游戏中对配置数据进行访问,你可以在 settings.json 中增加自定义数据,然后在运行时使用 settings 模块进行访问,但为了避免模块之间的配置数据互相影响,我们将之前只有一层的配置数据结构,改为了 CategoryProperty 的两层结构,下面是具体的变化:

之前版本:

  1. interface Settings {
  2. CocosEngine: string;
  3. debug: boolean; // 是否是 debug 模式
  4. designResolution: ISettingsDesignResolution; // 设计分辨率
  5. jsList: string[]; // js 插件列表
  6. launchScene: string; // 首场景
  7. preloadAssets: string[], // 预加载资源
  8. platform: string; // 平台名称
  9. renderPipeline: string; // 渲染管线
  10. physics?: IPhysicsConfig; // 物理相关配置
  11. exactFitScreen: boolean; // 在 web 下是否让游戏外框对齐到屏幕上
  12. bundleVers: Record<string, string>; // bundle 版本信息
  13. subpackages: string[]; // bundle 在小游戏上的子包配置
  14. remoteBundles: string[]; // 远程 bundle 的列表
  15. server: string; // 远程服务器地址
  16. hasResourcesBundle: boolean; // 是否存在 resources 包
  17. hasStartSceneBundle: boolean; // 是否存在首场景分包
  18. scriptPackages?: string[]; // 引擎内部使用字段
  19. splashScreen?: ISplashSetting; // SplashScreen 相关配置
  20. customJointTextureLayouts?: ICustomJointTextureLayout[]; // JointTextureLayout 相关的配置
  21. macros?: Record<string, any>; // cc.macros 相关配置
  22. engineModules: string[]; // 引擎模块,预览时使用
  23. customLayers: {name: string, bit: number}[]; // 自定义 layer 的相关配置
  24. orientation?: IOrientation; // 屏幕旋转方向
  25. }

新版本的 Settings.json 文件格式:

  1. interface Settings {
  2. CocosEngine: string;
  3. engine: {
  4. debug: boolean;
  5. macros: Record<string, any>;
  6. customLayers: {name: string, bit: number}[];
  7. platform: string;
  8. engineModules?: string[];
  9. builtinAssets: string[];
  10. };
  11. physics?: IPhysicsConfig;
  12. rendering: {
  13. renderPipeline: string;
  14. renderMode?: number;
  15. };
  16. assets: {
  17. server: string;
  18. remoteBundles: string[];
  19. bundleVers: Record<string, string>;
  20. preloadBundles: { bundle: string, version?: string }[];
  21. importBase?: string;
  22. nativeBase?: string;
  23. subpackages: string[];
  24. preloadAssets: string[];
  25. jsbDownloaderMaxTasks?: number;
  26. jsbDownloaderTimeout?: number;
  27. };
  28. plugins: {
  29. jsList: string[];
  30. };
  31. scripting: {
  32. scriptPackages?: string[];
  33. };
  34. launch: {
  35. launchScene: string;
  36. };
  37. screen: {
  38. frameRate?: number;
  39. exactFitScreen: boolean;
  40. orientation?: IOrientation;
  41. designResolution: ISettingsDesignResolution;
  42. };
  43. splashScreen?: ISplashSetting;
  44. animation: {
  45. customJointTextureLayouts?: ICustomJointTextureLayout[];
  46. };
  47. profiling?: {
  48. showFPS: boolean;
  49. };
  50. }

字段与之前版本几乎保持了一一对应的关系,只是迁移到了某个具体的 Category 下。有几个字段略有变化:

  • 新增 screen 下的 frameRate 字段用于设置目标更新频率
  • 新增 profiling 下的 showFPS 字段用于控制是否开启左下角的统计数据显示
  • 移除了原先的 hasResourcesBundle 与 hasStartSceneBundle 字段,改为统一由 assets 下的 preloadBundles 字段控制
  • 新增 assets 下的 jsbDownloaderMaxTasks, jsbDownloaderTimeout 字段用于控制原生环境下资源的下载并发数与超时时间。

如果你对 settings.json 中的原有数据字段有所定制的话,你需要参考新的格式,将定制内容迁移到新的格式上。如果你有自定义的配置字段,我们建议你将自定义的配置数据放到一个自定的 Category 下,避免与其他模块的数据发生冲突,例如:

  1. interface MyCustomSettings extends Settings {
  2. 'customSettings': {
  3. 'someProperty': string;
  4. };
  5. }

以上就是 3.6 版本构建模板与 settings.json 的升级说明,如果在升级过程中遇到问题,欢迎到 Cocos 的官方社区GitHub 中向我们反馈,感谢!