Cocos Creator 3.6.0 Build Template and settings.json Upgrade Guide

This article will introduce Cocos Creator 3.6.0 build templates and settings.json upgrade considerations.

Prior to 3.6, the engine code contained some built-in effect data that could not be removed, which took up a lot of the engine package and slowed down the time to parse the engine code. For most projects, this is a waste of resources.

In the previous version, application.ejs and game.ejs (index.ejs for some platforms) in the build template contained a lot of logical code for the engine startup process, which was more complex and prone to problems during customization, and needed a more easy-to-use and stable customization solution for the engine startup process.

In addition, in the previous version, the main configuration data of the game was stored in settings.json, which was not accessible during the game runtime, which was extremely inconvenient for some plugins.

To address these issues, in Cocos Creator 3.6 we refactored the engine startup process and provided a new settings configuration module in order to optimize the engine package, better maintain the engine’s business code, and provide more room for customization. During the refactoring process, although we wanted to ensure as much compatibility as possible between the previous version of the project build template and settings.json, some aspects of the refactoring introduced some incompatibilities. In these cases, you will need to manually upgrade the project build template with the custom content of the configuration file.

  • For Artists and Designers, all resources in the project, such as scenes, animations, and Prefab, do not need to be modified and do not need to be upgraded.
  • For Programmers and Plugin Developers, the impact is mainly on the custom build templates and settings.json in the original project that need to be adapted to the new way.

This section is described in more detail below.

Cases where manual upgrade is required

  • The project has a custom build template with application.ejs in it.
  • The project has a custom build template with game.ejs or index.ejs.
  • The project has a custom generated settings.json.

Upgrade steps

Migrate the customized application.ejs to the new template

The application.ejs has been changed significantly in v3.6, with two main changes.

Provides a simpler definition of the Application type

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

We provide the definition of an Application like the one above, where we provide two lifecycle callbacks.

  • The init lifecycle function will be called after the engine code is loaded, the engine module will be passed as a parameter and you can listen for events in the init function for the engine start process and execute some custom logic when the corresponding event is triggered.
  • The start lifecycle function will be called after the init function and you need to start and run the engine in the way you want in this function.

Here is the simplest template we have implemented for application.ejs with the corresponding explanation.

  1. let cc;
  2. export class Application {
  3. constructor () {
  4. this.settingsPath = '<%= settingsJsonPath %>'; // settings.json file path, usually passed in by the editor when building, you can also specify your own path
  5. this.showFPS = <%= showFPS %>; // Whether or not to open the profiler, usually passed in when the editor is built, but you can also specify the value you want
  6. }
  7. init (engine) {
  8. cc = engine;
  9. cc.game.onPostBaseInitDelegate.add(this.onPostInitBase.bind(this)); // Listening for engine start process events onPostBaseInitDelegate
  10. cc.game.onPostSubsystemInitDelegate.add(this.onPostSystemInit.bind(this)); // Listening for engine start process events onPostSubsystemInitDelegate
  11. }
  12. onPostInitBase () {
  13. // cc.settings.overrideSettings('assets', 'server', '');
  14. // Implement some custom logic
  15. }
  16. onPostSystemInit () {
  17. // Implement some custom logic
  18. }
  19. start () {
  20. return cc.game.init({ // Run the engine with the required parameters
  21. debugMode: <%= debugMode %> ? cc.DebugMode.INFO : cc.DebugMode.ERROR,
  22. settingsPath: this.settingsPath, // Pass in the settings.json path
  23. overrideSettings: { // Override part of the data in the configuration file, this field will be described in detail below
  24. // assets: {
  25. // preloadBundles: [{ bundle: 'main', version: 'xxx' }],
  26. // }
  27. profiling: {
  28. showFPS: this.showFPS,
  29. }
  30. }
  31. }).then(() => cc.game.run());
  32. }
  33. }
  • This template listens to events in the init function during the engine startup process, and can execute some custom logic when the corresponding event is triggered. game.init triggers events at various stages during execution, see the API description of game.init, where the events at different stages can use different engine capabilities, and the sequence of triggered events is as follows.

    1. -PreBaseInitEvent, no engine capability can be used
    2. -Base module initialization (logging, sys, settings)
    3. -PostBaseInitEvent, can use the capabilities in the base module
    4. -PreInfrastructureInitEvent, can use the capabilities in the base module
    5. -Infrastructure module initialization (assetManager, builtinResMgr, gfxDevice, screen, Layer, macro)
    6. -PostInfrastructureInitEvent, can use the capabilities in the infrastructure module, infrastructure module
    7. -PreSubsystemInitEvent, can use the capabilities of the infrastructure module, infrastructure module
    8. -Subsystem module initialization (animation, physics, tween, ui, middleware, etc.)
    9. -PostSubsystemInitEvent, can use capabilities from base module, infrastructure module, subsystem module
    10. -EngineInitedEvent, can use the capabilities of the base module, infrastructure module, subsystem module
    11. -PreProjectDataInitEvent, which can use the capabilities of the base module, infrastructure module, and subsystem module
    12. -ProjectDataInit(GamePlayScripts, resources, etc)
    13. -PostProjectDataInitEvent, which can use capabilities from the base module, infrastructure module, subsystem module, and project data
    14. -GameInitedEvent, can use all engine capabilities
  • The start function is called to initialize and run the engine. You can control the start of the engine by passing in custom parameters when calling game.init. Note the overrideSettings field, which can be used to override some configuration data in the configuration file to affect the start of the engine. This field will be discussed in detail in the second part.

The new application.ejs is much cleaner and more customizable than the previous version, so you can refer to this template to implement your own application.ejs and execute your custom logic in it.

We have migrated the logic related to the engine startup process from application.ejs to the engine, including the loading of settings.json (loadSettingsJson), the loading of js plugins (loadJsList), the loading of project bundles (loadAssetBundle), etc. ), and so on. So now you can’t directly modify the engine startup process in application.ejs, but you can override the configuration file with the overrideSettings field passed in game.init to influence the startup process, and you can also listen to events during the engine startup process and execute some custom logic. The following section describes in detail how the engine startup process customization can be migrated to the new mechanism. If your project has customizations to the engine startup process, please refer to the following upgrade method for migration.

  • If you inserted custom code logic at a specific stage of engine startup in an earlier template, for example by making a customization such as:

    1. function start ({
    2. findCanvas,
    3. }) {
    4. let settings;
    5. let cc;
    6. return Promise.resolve()
    7. .then(() => topLevelImport('cc'))
    8. .then(() => customLogic1()) // Customized Logic 1
    9. .then((engine) => {
    10. cc = engine;
    11. return loadSettingsJson(cc);
    12. })
    13. .then(() => customLogic2()) // Customized Logic 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()); // Customized Logic 3
    28. }

    You can implement custom logic in the new application.ejs template by listening for events in the engine startup process as described above, e.g.

    1. init (engine) {
    2. cc = engine;
    3. cc.game.onPreBaseInitDelegate.add(this.onPreBaseInit.bind(this)); // Listening for engine start process events onPreBaseInitDelegate
    4. cc.game.onPostBaseInitDelegate.add(this.onPostBaseInit.bind(this)); // Listening for engine start process events onPostBaseInitDelegate
    5. cc.game.onPostProjectInitDelegate.add(this.onPostProjectInit.bind(this)); // Listening for engine start process events onPostProjectInitDelegate
    6. }
    7. onPreBaseInit () {
    8. customLogic1(); // Customized Logic 1
    9. }
    10. onPostBaseInit () {
    11. customLogic2() // Customized Logic 2
    12. }
    13. onPostProjectInit () {
    14. customLogic3() // Customized Logic 3
    15. }
  • If you customized the loading of the physical module wasm in the previous template, i.e. the following method in the old template.

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

    This method has been changed for internal use in the engine. If you need to customize it, please customize the engine and customize the cocos/physics/bullet/instantiated.ts content in the engine directory.

  • If you customized the loading of settings.json in the previous template, i.e. the loadSettingsJson function in the previous template. This method has been changed to be used internally by the engine. If you have customizations for this part, please consider it in the following three cases.

    • If you need to customize the path to settings.json, you can pass in a custom settingsPath path when calling the game.init method, e.g:

      1. game.init({ settingsPath: this.mySettingsPath });
    • If you need to read the contents of settings.json, you can listen to the events after game.onPostBaseInitDelegate, where you can safely access the settings module in the engine and get the corresponding configuration data through the relevant API of the settings module, e.g:

      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. }
    • If you need to set the contents of a configuration file, you can override some of the data in the configuration file by passing in the overrideSettings field when calling the game.init method, or you can call settings.overrideSettings in the game’s event callback to override the data in the configuration file. thus affecting the engine startup process, e.g:

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

      Or

      1. onPostBaseInit () {
      2. cc.settings.overrideSettings('profiling', 'showFPS', true);
      3. }
  • If you customized the previous template to load js plugins, i.e. the loadJsList function in the previous template. This method has been changed for internal use in the engine, so if you need to modify the list of js files to be loaded, you can do so by passing in the plugin field of overrideSettings when calling game.init, e.g:

    1. start () {
    2. cc.game.init({
    3. overrideSettings: {
    4. 'plugins': { 'jsList': ['MyCustom.js'] }
    5. }
    6. });
    7. }
  • If you customized the previous template to load item bundles, i.e. the loadAssetBundle function in the previous template. This method has been changed for internal use in the engine, so if you need to modify the list of bundles to be loaded, you can do so by passing in the assets field of overrideSettings when calling game.init, e.g:

    1. start () {
    2. cc.game.init({
    3. overrideSettings: {
    4. 'assets': { 'preloadBundles': [ 'main', 'resources', 'myBundle' ]}
    5. }
    6. });
    7. }
  • If you customized the initialization of macros and layers in the previous template, i.e. the initializeGame function in the previous template. This method has been changed for internal use in the engine, so if you need to modify the list of macros and layers, you can do so by passing in the engine field of overrideSettings when calling game.init, e.g:

    1. start () {
    2. cc.game.init({
    3. overrideSettings: {
    4. 'engine': {
    5. 'macros': {},
    6. 'customLayers': [],
    7. }
    8. }
    9. });
    10. }
  • If you customized the loading of the initial scene in the old template, the onGameStarted function in the previous template. This method has been changed for internal use in the engine, so if you need to modify the initial scene, you can do so by passing in the launch field of overrideSettings when calling game.init, e.g:

    1. start () {
    2. cc.game.init({
    3. overrideSettings: {
    4. 'launch': { 'launchScene': 'MyFirstScene' }
    5. }
    6. });
    7. }
  • If you customized the previous template by passing in the game.init parameter, i.e. the getGameOptions function in the previous template. Please refer to the latest IGameConfig interface definition and pass that parameter in game.init.

Migration of customized game.ejs or index.ejs to the new template

game.ejs and index.ejs have not changed much compared to previous versions, the main changes are in the following points.

  • We migrated the interfaces used by the engine such as loadJsListFile, fetchWasm, findCanvas, etc. to the internal part of the engine. If you have customizations for this part, please customize the engine and modify the contents of pal/env in the engine directory.
  • As mentioned in the first part, we have organized the interface definitions for the Application type, and game.ejs and index.ejs as callers of the Application type, so changes have been made in the Application interface calls, for example in some platforms we have changed to the following form:

    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. });
  • We packaged the code used for adaptation on different platforms and merged it into a single js file to reduce the package size, so where the code is run it is instead run as a single js file, e.g:

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

    Changed to:

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

If you have customizations to game.ejs and index.ejs, simply migrate the customizations to the latest template, with the note that if your custom logic relies on string matches for insertion, e.g:

  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')

Then there may be places where the match string fails and you need to insert it under a new template, or you can refer to the first part to customize it in application.ejs, the new application.ejs template is more conducive to customization.

Migrating customizations to settings.json

In 3.6 we added the settings module for in-game access to configuration data, you can add custom data to settings.json and then use the settings module to access it at runtime, but to avoid the configuration data between modules affecting each other, we changed the previous one-layer configuration data structure to a Category and Property, the following are the specific changes.

Previous versions:

  1. interface Settings {
  2. CocosEngine: string;
  3. debug: boolean; // whether debug mode is enabled
  4. designResolution: ISettingsDesignResolution; // design resolution
  5. jsList: string[]; // list of js plugins
  6. launchScene: string; // The first scene
  7. preloadAssets: string[], // preload resources
  8. platform: string; // platform name
  9. renderPipeline: string; // render pipeline
  10. physics?: IPhysicsConfig; // physics-related configuration
  11. exactFitScreen: boolean; // whether or not to align the game frame to the screen under web
  12. bundleVers: Record<string, string>; // bundle version information
  13. subpackages: string[]; // bundle's subpackage configuration on the mini-game
  14. remoteBundles: string[]; // list of remote bundles
  15. server: string; // Address of the remote server
  16. hasResourcesBundle: boolean; // whether the resources bundle exists
  17. hasStartSceneBundle: boolean; // if the first scene bundle exists
  18. scriptPackages?: string[]; // internal fields used by the engine
  19. splashScreen?: ISplashSetting; // SplashScreen related configuration
  20. customJointTextureLayouts?: ICustomJointTextureLayout[]; // JointTextureLayout related configuration
  21. macros?: Record<string, any>; // cc.macros related configuration
  22. engineModules: string[]; // engine modules, used in the preview
  23. customLayers: {name: string, bit: number}[]; // custom layer related configuration
  24. orientation?: IOrientation; // screen rotation direction
  25. }

New version of Settings.json file format is as follows:

  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. }

The fields have remained almost one-to-one with the previous version, except that they have been migrated to a specific Category. A few fields have changed slightly.

  • A new frameRate field under screen has been added to set the target update frequency
  • Added a new showFPS field under profiling to control whether to turn on the statistics display in the lower left corner
  • The original hasResourcesBundle and hasStartSceneBundle fields have been removed and are now controlled by the preloadBundles field under assets.
  • Added jsbDownloaderMaxTasks, jsbDownloaderTimeout fields under assets to control the number of concurrent downloads and timeout of resources in the native environment.

If you have customized the data fields in settings.json, you need to refer to the new format and migrate the customizations to the new format. If you have custom configuration fields, we recommend that you place the custom configuration data under a custom Category to avoid conflicts with other modules’ data, e.g:

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

If you encounter any problems during the upgrade process, please feel free to visit Cocos Creator Forum or GitHub engine/discussions), thank you!