Cocos Creator 3.2: Custom Build Template
Auf der Suche nach einem Framework für die Umsetzung der PC-Version des Brettspieles Penta Game mit Web-Technologien bin ich vor kurzem auf das chinesische Cocos Creator gestoßen. Die aktuelle Version ist 3.2.0 und deutsche Quellen zum Thema sind leider noch äußerst rar. Daher nun an dieser Stelle ein paar höchst persönliche Eindrücke:
Wenn man den Beschreibungen auf der Homepage folgt, ist das Cocos Framework mit einem großen Funktionsumfang gesegnet, welcher im Detail auf Englisch hier nachgesehen werden kann:
- Youtube Kanal https://www.youtube.com/cocosengine
- Funktions- Umfang https://www.cocos.com/en/creator
- Dokumentation https://docs.cocos.com/creator/3.2/manual/en
Aber davon sind lediglich folgende Eigenschaften für mich ausschlaggebend.
- WYSIWYG Scene Editor bietet eine sehr übersichtliche und intuitiv zu erfassende, komponenten- basierte Entwicklung und Asset Management. Das offiziell nur Windows Installer vorliegen, war für mich kein Problem.
- Als Build Ziel: Web-Desktop bekommt man ohne viel Nachdenken eine Web-Anwendung und darüber hinaus (von mir bislang ungetestet) weitere Optionen in Richtung Native.
- Die Programmierung erfolgt aufbauend auf Standard Web-Technologien vorzugsweise in Type Script oder aber auch Java Script. Dabei bildet NodeJS wohl das Basis Framework für den Creator und alle Meta-Informationen und Konfigurationen werden im JSON-Format gespeichert.
- Zudem liefert der Cocos Creator eine problemlose Anbindung an Visual Studio Code als Code Editor.
- Und mit der MIT Lizenz, wird uns eine überaus Entwickler- freundliche Weiter-Verwertung zugestanden.
Nicht ganz so gut gefallen hat mir:
- Für die Nutzung des Cocos Creator Dash Boards wird ein Online Account in China benötigt, welchen zu administrieren aber trotz voreingestellter Chinesischer Sprache Dank DeepL Online Übersetzer problemlos möglich war. Es musste ja nur die Sprache auf Englisch gestellt werden. :-)
- Wohl den recht kurzen Entwicklungs- Zyklen geschuldet ist, dass im Regelfall online zu findende Programmier- Beispiele früherer Versionen etwas aufwändiger auf 3.2.x zu übertragen sind.
Alles in Allem aber habe ich als Neueinsteiger in der Web-Anwendungs und Spiele-Entwicklung sehr von den Vorarbeiten der Cocos-Engine und Cocos-Framework Entwickler profitieren können. Allen voran waren folgende Punkte sehr hilfreich:
- Organisation der (GUI) Knoten Elemente als Baum.
- WYSWYG und Drag/Drop Eigenschaften Editor für Knoten und Komponenten.
- Event-basierte Interaktionen zwischen den verschiedenen Knoten Typen.
- Relative Positionierung von Kinder Knoten in Bezug zum Eltern Element.
- Vorgegebene Game-Loop.
- Intuitive Life Cycle Callback Funktionen.
- Leicht erweiterbarer Build-Prozess durch Benutzer- definierte Build Scripte.
Soviel also nun zu den Vorzügen des Cocos Creator. Von einer näheren Nutzung des erweiterbaren Build-Prozesses möchte ich Euch aber jetzt anfangen zu berichten.
Custom Build Template
Als Anwendungsfall dient mir der Wunsch nach einem Label innerhalb der Web-Anwendung, welches aus dem JSON-Inhalt der Asset-Datei app.json
die 2 Schlüssel-Werte „buildID“ und „versionID“ ausliest, um seinen Inhalt Text daraus zu bilden. Hier ein aktuelles Beispiel für die
app.json:
1 2 3 4 | { "buildID": 45, "versionID": "0.1.0" } |
Der gewünschte Label-Text wäre dann: „v.0.1.0.045“. Und das dazu passende Komponenten Type Script könnte so aussehen:
lblVersionInfo.ts:
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 | import { _decorator, Component, Label, JsonAsset } from 'cc'; const { ccclass, property } = _decorator; export class AppInfo{ buildID: number = 0; versionID: string = "0.0.0"; } @ccclass('LblVersionInfo') export class LblVersionInfo extends Component { @property(JsonAsset) appJSON: JsonAsset|null = null; start () { const lblComp: Label|null = this.node.getComponent(Label); if (lblComp){ const appInfo: AppInfo = ( this.appJSON && this.appJSON.json ? <AppInfo>this.appJSON.json : new AppInfo() ); lblComp.string = "v." + appInfo.versionID.toString() + "." + appInfo.buildID.toString().padStart(3, "0"); } else console.log( "LblVersionInfo.start: invalid label component:", lblComp); } } |
Es soll an der Stelle nicht weiter kommentiert werden. – Abgesehen vielleicht davon, dass durch die Definition von:
11 12 | @property(JsonAsset) appJSON: JsonAsset|null = null; |
.. der Komponente 'LblVersionInfo' per Drag & Drop direkt im Cocos Creator das app.json
JSON-Asset zugeordnet werden kann. So macht komponieren Spaß :-)
Aber was nützt es, wenn die buildID nicht automatisch bei jedem Build inkrementiert wird?
Wenig. Also muss ein eigenes Build-Script her, welches genau dies für uns übernimmt. Und das ist entsprechend folgender Quelle auch gar nicht so schwierig.
https://docs.cocos.com/creator/3.1/manual/en/editor/publish/custom-build-plugin.html
- Über [Project / Generate Builder Extension] erstellt man eine neue Build Erweiterung
- Führt
npm install
aus demextension
Verzeichnis heraus aus - Und aktiviert über den Extension Manager die neue Erweiterung. (Bei mir: [lokal])
Leider bleiben die Versuche [refresh] und [deaktivieren/aktivieren] der Extension erfolglos, weshalb für jede Änderung die IDE geschlossen werden muss. Zurück zum Dash Board und Projekt erneut öffnen löst das Problem aber.
Ein Blick in die Datei- und Ordnerstruktur zeigt dann jedoch anhand der schieren Unmenge an Zusatz Modulen, dass es so eigentlich nicht bleiben kann. Und so habe ich für mich und hoffentlich auch für Euch hilfreich das Ganze zu einem deutlich kleineren Template wie folgt zusammen gekürzt:
Ordner Struktur:
└───custom-cocos-build-template │ package-lock.json // NodeJS Paket Definition │ package.json // Template Paket Definition │ readme.md // Dokumentation Dummy │ └───dist builder.js // Builder Definition hooks.js // Build Event Call Backs
package-lock.json:
- Wird durch
npm install
erstellt.
package.json:
- Legt unter
contributions.builder
das Build Script fest.
1 2 3 4 5 6 7 8 9 10 11 | { "name": "custom-cocos-build-template", "title": "custom-cocos-build-template", "package_version": 2, "version": "1.0.0", "author": "Mike Ziebeck", "description": "custom-cocos-build-template", "contributions": { "builder": "./dist/builder.js" } } |
builder.js:
- Initialisiert die Builder Hooks
load
undunload
zunächst mitvoid 0
[3] und legt dann mithooks
[6] das Script:"./hooks"
für deren eigentliche Implementierung fest.
1 2 3 4 5 6 7 8 | "use strict"; Object.defineProperty(exports, "__esModule", { value: !0 }), (exports.configs = exports.unload = exports.load = void 0), (exports.configs = { "*": { hooks: "./hooks", }, }); |
hooks.js:
- Stellt nun die eigentliche Funktionalität her. Neben den Hooks, wird hier auch eine minifizierte
__awaiter
Funktion [4-34] definiert, die ich aber für uns mit Hilfe der Vorlage aus:CocosDashboard_1.0.11\resources\.editors\Creator\3.2.0\resources\app.asar.unpacked\node_modules\v-unzip\dist\index.js
wieder aussagekräftig zurück entwickelt habe. Was sie im Detail tut, soll an anderer Stelle näher erläutert werden. Eventuell ja sogar von Dir geneigtem Leser. ;-)
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 | "use strict"; var fs = require("fs"); var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator.throw(value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : (result.value instanceof P ? result.value : new P(function (resolve) { resolve(result.value); }) ).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; Object.defineProperty(exports, "__esModule", { value: !0 }), (exports.unload = exports.onAfterBuild = exports.onAfterCompressSettings = exports.onBeforeCompressSettings = exports.onBeforeBuild = exports.load = exports.throwError = void 0); const PACKAGE_NAME = "custom-cocos-build-template"; function load() { return __awaiter(this, void 0, void 0, function* () { console.debug(`[${PACKAGE_NAME}] Load cocos plugin example.`); }); } function onBeforeBuild(e) { return __awaiter(this, void 0, void 0, function* () { const url = "db://assets/app.json"; var asset = yield Editor.Message.request("asset-db", "query-asset-info", url); var file = fs.readFileSync(asset.file); var json = JSON.parse(file); json.buildID += 1; fs.writeFileSync(asset.file, JSON.stringify(json, null, 4)); yield Editor.Message.request("asset-db", "refresh-asset", url); console.debug(`${PACKAGE_NAME} onBeforeBuild: buildID: ${json.buildID}`); }); } function onBeforeCompressSettings(e, o) { return __awaiter(this, void 0, void 0, function* () { console.debug(`${PACKAGE_NAME} onBeforeCompressSettings`); }); } function onAfterCompressSettings(e, o) { return __awaiter(this, void 0, void 0, function* () { console.debug(`${PACKAGE_NAME} onAfterCompressSettings`); }); } function onAfterBuild(e, o) { return __awaiter(this, void 0, void 0, function* () { console.debug(`${PACKAGE_NAME} onAfterBuild`); }); } function unload() { return __awaiter(this, void 0, void 0, function* () { console.debug(`[${PACKAGE_NAME}] Unload cocos plugin example.`); }); } (exports.throwError = !0), (exports.load = void 0), (exports.onBeforeBuild = onBeforeBuild), (exports.onBeforeCompressSettings = void 0), (exports.onAfterCompressSettings = void 0), (exports.onAfterBuild = void 0), (exports.unload = void 0); |
- Die für uns interessanten Zeilen sind:
- [3]: FileSystem Bibliothek importieren.
- [54-66]: onBeforeBuild(e) Hook implementieren.
- [95]: onBeforeBuild Hook festlegen.
- Download Project Files incl. Build Template:
Und als Sahnehäubchen oben drauf, möchte ich Euch noch von nachfolgender Zeile Quell-Code berichten.
63 | yield Editor.Message.request("asset-db", "refresh-asset", url); |
Dieses kleine, aber äußerst wichtige Code-Fragment sorgt dafür, dass die Änderungen, die wir im onBeforeBuild(e)
Hook am app.json
JSON-Asset vornehmen, auch tatsächlich in den Build-Prozess integriert werden. Ohne - hinkt nämlich die per Build-Prozess erzeugte Version genau einen Schritt der eigentlich aktuellen - hinterher.
Leider ist die Dokumentation diesbezüglich aber ganz dünn besetzt. Sodass man nicht umhin kommt, direkt in den Cocos Creator Dateien nach: "refresh-asset"
zu suchen.
Und unter: CocosDashboard_1.0.11\resources\.editors\Creator\3.2.0\resources
fündig geworden, gilt es nun die vom Framework Electron erzeugte app.asar
via:
npx asar extract app.asar a
zu entpacken.
Hier findet sich dann auch unter a/package.json
die Quelle unserer asset-db
Nachricht. Und man bekommt einen Eindruck von den weiteren Möglichkeiten, die Asset Datenbank von Cocos programmatisch anzusprechen. Für weiteres Forschen in die Richtung habe ich einmal ein paar mögliche Varianten aufgeführt und mit ..
mögliche Parameter frei gelassen:
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 | yield Editor.Message.request( "asset-db", "asset-db:asset-add" ..); yield Editor.Message.request( "asset-db", "asset-db:asset-change" ..); yield Editor.Message.request( "asset-db", "asset-db:asset-delete" ..); yield Editor.Message.request( "asset-db", "asset-db:close" ..); yield Editor.Message.request( "asset-db", "asset-db:ready" ..); yield Editor.Message.request( "asset-db", "copy-asset" ..); yield Editor.Message.request( "asset-db", "create-asset" ..); yield Editor.Message.request( "asset-db", "create-asset-dialog" ..); yield Editor.Message.request( "asset-db", "delete-asset" ..); yield Editor.Message.request( "asset-db", "execute-script" ..); yield Editor.Message.request( "asset-db", "generate-available-url" ..); yield Editor.Message.request( "asset-db", "import-asset" ..); yield Editor.Message.request( "asset-db", "init-asset" ..); yield Editor.Message.request( "asset-db", "move-asset" ..); yield Editor.Message.request( "asset-db", "notice-reload-editor" ..); yield Editor.Message.request( "asset-db", "open-asset" ..); yield Editor.Message.request( "asset-db", "open-devtools" ..); yield Editor.Message.request( "asset-db", "query-all-asset-types" ..); yield Editor.Message.request( "asset-db", "query-all-importer" ..); yield Editor.Message.request( "asset-db", "query-asset-info" ..); yield Editor.Message.request( "asset-db", "query-asset-meta" ..); yield Editor.Message.request( "asset-db", "query-asset-mtime" ..); yield Editor.Message.request( "asset-db", "query-assets" ..); yield Editor.Message.request( "asset-db", "query-db-info" ..); yield Editor.Message.request( "asset-db", "query-db-list" ..); yield Editor.Message.request( "asset-db", "query-path" ..); yield Editor.Message.request( "asset-db", "query-ready" ..); yield Editor.Message.request( "asset-db", "query-url" ..); yield Editor.Message.request( "asset-db", "query-uuid" ..); yield Editor.Message.request( "asset-db", "refresh" ..); yield Editor.Message.request( "asset-db", "refresh-asset" ..); yield Editor.Message.request( "asset-db", "refresh-default-user-data-config" ..); yield Editor.Message.request( "asset-db", "reimport-asset" ..); yield Editor.Message.request( "asset-db", "save-asset" ..); yield Editor.Message.request( "asset-db", "save-asset-meta" ..); |
Viel Spaß beim Erweitern des Build-Prozesses von Cocos Creator 3.2 !