@coze-arch/cli 0.0.36-alpha.b293fc → 0.0.36-alpha.e6ca88

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (96) hide show
  1. package/README.md +66 -104
  2. package/docs/deploy.md +172 -0
  3. package/lib/__templates__/expo/.cozeproj/scripts/dev_run.sh +73 -18
  4. package/lib/__templates__/expo/pnpm-lock.yaml +5 -5
  5. package/lib/__templates__/expo/server/package.json +1 -1
  6. package/lib/__templates__/native-static/.coze +2 -0
  7. package/lib/__templates__/native-static/scripts/serve.ps1 +7 -0
  8. package/lib/__templates__/nextjs/.coze +5 -0
  9. package/lib/__templates__/nextjs/scripts/build.ps1 +16 -0
  10. package/lib/__templates__/nextjs/scripts/dev.ps1 +63 -0
  11. package/lib/__templates__/nextjs/scripts/dev.sh +82 -1
  12. package/lib/__templates__/nextjs/scripts/prepare.ps1 +18 -0
  13. package/lib/__templates__/nextjs/scripts/start.ps1 +11 -0
  14. package/lib/__templates__/nextjs/scripts/validate.ps1 +8 -0
  15. package/lib/__templates__/nextjs/template.config.js +3 -1
  16. package/lib/__templates__/nuxt-vue/.coze +5 -0
  17. package/lib/__templates__/nuxt-vue/scripts/build.ps1 +12 -0
  18. package/lib/__templates__/nuxt-vue/scripts/dev.ps1 +63 -0
  19. package/lib/__templates__/nuxt-vue/scripts/dev.sh +82 -1
  20. package/lib/__templates__/nuxt-vue/scripts/prepare.ps1 +12 -0
  21. package/lib/__templates__/nuxt-vue/scripts/start.ps1 +11 -0
  22. package/lib/__templates__/nuxt-vue/scripts/validate.ps1 +8 -0
  23. package/lib/__templates__/phaser/.coze +5 -0
  24. package/lib/__templates__/phaser/README.md +8 -0
  25. package/lib/__templates__/phaser/_gitignore +1 -0
  26. package/lib/__templates__/phaser/package.json +1 -1
  27. package/lib/__templates__/phaser/scripts/build.ps1 +12 -0
  28. package/lib/__templates__/phaser/scripts/dev.ps1 +49 -0
  29. package/lib/__templates__/phaser/scripts/dev.sh +74 -3
  30. package/lib/__templates__/phaser/scripts/prepare.ps1 +8 -0
  31. package/lib/__templates__/phaser/scripts/spawn-detached.cjs +29 -0
  32. package/lib/__templates__/phaser/scripts/start.ps1 +49 -0
  33. package/lib/__templates__/phaser/scripts/start.sh +74 -3
  34. package/lib/__templates__/phaser/scripts/validate.ps1 +7 -0
  35. package/lib/__templates__/phaser/src/global.d.ts +3 -0
  36. package/lib/__templates__/phaser/src/scene/BootScene.ts +4 -0
  37. package/lib/__templates__/phaser/src/scene/DevLoadingScene.ts +3 -0
  38. package/lib/__templates__/phaser/src/utils/mark-editable.ts +8 -1
  39. package/lib/__templates__/phaser/vite.config.ts +1 -1
  40. package/lib/__templates__/phaser-static/.coze +15 -0
  41. package/lib/__templates__/phaser-static/README.md +40 -0
  42. package/lib/__templates__/phaser-static/_gitignore +6 -0
  43. package/lib/__templates__/phaser-static/assets/image_sequence/coze-game-loading/frame_0001.png +0 -0
  44. package/lib/__templates__/phaser-static/assets/image_sequence/coze-game-loading/frame_0002.png +0 -0
  45. package/lib/__templates__/phaser-static/assets/image_sequence/coze-game-loading/frame_0003.png +0 -0
  46. package/lib/__templates__/phaser-static/assets/image_sequence/coze-game-loading/frame_0004.png +0 -0
  47. package/lib/__templates__/phaser-static/assets/image_sequence/coze-game-loading/frame_0005.png +0 -0
  48. package/lib/__templates__/phaser-static/assets/image_sequence/coze-game-loading/frame_0006.png +0 -0
  49. package/lib/__templates__/phaser-static/assets/image_sequence/coze-game-loading/frame_0007.png +0 -0
  50. package/lib/__templates__/phaser-static/assets/image_sequence/coze-game-loading/frame_0008.png +0 -0
  51. package/lib/__templates__/phaser-static/assets/image_sequence/coze-game-loading/frame_0009.png +0 -0
  52. package/lib/__templates__/phaser-static/assets/image_sequence/coze-game-loading/frame_0010.png +0 -0
  53. package/lib/__templates__/phaser-static/assets/image_sequence/coze-game-loading/frame_0011.png +0 -0
  54. package/lib/__templates__/phaser-static/assets/image_sequence/coze-game-loading/frame_0012.png +0 -0
  55. package/lib/__templates__/phaser-static/assets/image_sequence/coze-game-loading/frame_0013.png +0 -0
  56. package/lib/__templates__/phaser-static/assets/image_sequence/coze-game-loading/frame_0014.png +0 -0
  57. package/lib/__templates__/phaser-static/assets/image_sequence/coze-game-loading/frame_0015.png +0 -0
  58. package/lib/__templates__/phaser-static/assets/image_sequence/coze-game-loading/frame_0016.png +0 -0
  59. package/lib/__templates__/phaser-static/assets/image_sequence/coze-game-loading/frame_0017.png +0 -0
  60. package/lib/__templates__/phaser-static/assets/image_sequence/coze-game-loading/frame_0018.png +0 -0
  61. package/lib/__templates__/phaser-static/assets/image_sequence/coze-game-loading/frame_0019.png +0 -0
  62. package/lib/__templates__/phaser-static/assets/image_sequence/coze-game-loading/frame_0020.png +0 -0
  63. package/lib/__templates__/phaser-static/assets/image_sequence/coze-game-loading/frame_0021.png +0 -0
  64. package/lib/__templates__/phaser-static/assets/image_sequence/coze-game-loading/frame_0022.png +0 -0
  65. package/lib/__templates__/phaser-static/assets/image_sequence/coze-game-loading/frame_0023.png +0 -0
  66. package/lib/__templates__/phaser-static/assets/image_sequence/coze-game-loading/frame_0024.png +0 -0
  67. package/lib/__templates__/phaser-static/assets/image_sequence/coze-game-loading/manifest.json +11 -0
  68. package/lib/__templates__/phaser-static/debug-runtime/flow-graph.json +20 -0
  69. package/lib/__templates__/phaser-static/index.html +17 -0
  70. package/lib/__templates__/phaser-static/scripts/serve.ps1 +7 -0
  71. package/lib/__templates__/phaser-static/src/assets.js +15 -0
  72. package/lib/__templates__/phaser-static/src/main.js +26 -0
  73. package/lib/__templates__/phaser-static/src/phaser.js +7 -0
  74. package/lib/__templates__/phaser-static/src/scene/BootScene.js +205 -0
  75. package/lib/__templates__/phaser-static/src/scene/DevLoadingScene.js +67 -0
  76. package/lib/__templates__/phaser-static/src/style.css +28 -0
  77. package/lib/__templates__/phaser-static/src/utils/asset-loader.js +267 -0
  78. package/lib/__templates__/phaser-static/src/utils/index.js +2 -0
  79. package/lib/__templates__/phaser-static/src/utils/mark-editable.js +3 -0
  80. package/lib/__templates__/phaser-static/template.config.js +35 -0
  81. package/lib/__templates__/taro/.cozeproj/scripts/dev_run.sh +63 -26
  82. package/lib/__templates__/taro/pnpm-lock.yaml +5 -5
  83. package/lib/__templates__/taro/server/package.json +1 -1
  84. package/lib/__templates__/templates.json +18 -0
  85. package/lib/__templates__/vite/.coze +5 -0
  86. package/lib/__templates__/vite/scripts/build.ps1 +16 -0
  87. package/lib/__templates__/vite/scripts/dev.ps1 +63 -0
  88. package/lib/__templates__/vite/scripts/dev.sh +82 -1
  89. package/lib/__templates__/vite/scripts/prepare.ps1 +18 -0
  90. package/lib/__templates__/vite/scripts/start.ps1 +11 -0
  91. package/lib/__templates__/vite/scripts/validate.ps1 +8 -0
  92. package/lib/__templates__/vite/template.config.js +3 -1
  93. package/lib/cli.js +3491 -214
  94. package/lib/deploy/package-project.js +244 -0
  95. package/lib/deploy/package-project.py +183 -0
  96. package/package.json +5 -4
@@ -0,0 +1,205 @@
1
+ import { Phaser } from '../phaser.js';
2
+ import {
3
+ getAssetLabelFromFileKey,
4
+ getImageSequenceRuntimeKeys,
5
+ loadAssets,
6
+ markEditable,
7
+ registerImageSequenceAnimations,
8
+ } from '../utils/index.js';
9
+ import { AUDIOS, IMAGES, IMAGE_SEQUENCES, VIDEOS } from '../assets.js';
10
+
11
+ const LOADING_TEXT_STYLE = {
12
+ color: '#71717a',
13
+ fontFamily: '"PingFang SC", "Microsoft YaHei", sans-serif',
14
+ fontSize: '18px',
15
+ };
16
+
17
+ const DEV_LOADING_ASSET_KEY = 'coze-game-loading';
18
+ const devLoadingSource = IMAGE_SEQUENCES[DEV_LOADING_ASSET_KEY];
19
+
20
+ if (!devLoadingSource) {
21
+ throw new Error('Image sequence asset not found: ' + DEV_LOADING_ASSET_KEY);
22
+ }
23
+
24
+ const BOOT_IMAGE_SEQUENCES = {
25
+ [DEV_LOADING_ASSET_KEY]: devLoadingSource,
26
+ };
27
+
28
+ const BOOT_ASSETS = {
29
+ imageSequences: BOOT_IMAGE_SEQUENCES,
30
+ };
31
+
32
+ const GAME_IMAGE_SEQUENCES = Object.fromEntries(
33
+ Object.entries(IMAGE_SEQUENCES).filter(
34
+ ([assetKey]) => assetKey !== DEV_LOADING_ASSET_KEY,
35
+ ),
36
+ );
37
+
38
+ const GAME_ASSETS = {
39
+ images: IMAGES,
40
+ imageSequences: GAME_IMAGE_SEQUENCES,
41
+ videos: VIDEOS,
42
+ audios: AUDIOS,
43
+ };
44
+
45
+ export const DEV_LOADING_RUNTIME_KEYS =
46
+ getImageSequenceRuntimeKeys(DEV_LOADING_ASSET_KEY);
47
+
48
+ export class BootScene extends Phaser.Scene {
49
+ loadingText;
50
+
51
+ progressBar;
52
+
53
+ progressText;
54
+
55
+ loadFailed = false;
56
+
57
+ constructor() {
58
+ super('boot');
59
+ }
60
+
61
+ preload() {
62
+ loadAssets(this, BOOT_ASSETS);
63
+ }
64
+
65
+ create() {
66
+ registerImageSequenceAnimations(this, BOOT_IMAGE_SEQUENCES);
67
+ this.createLoadingView();
68
+
69
+ const queuedFileCount = loadAssets(this, GAME_ASSETS);
70
+ if (queuedFileCount === 0) {
71
+ this.finishLoading();
72
+ return;
73
+ }
74
+
75
+ this.load.on(Phaser.Loader.Events.FILE_PROGRESS, this.onGameFile, this);
76
+ this.load.on(Phaser.Loader.Events.PROGRESS, this.onProgress, this);
77
+ this.load.once(
78
+ Phaser.Loader.Events.FILE_LOAD_ERROR,
79
+ this.onLoadError,
80
+ this,
81
+ );
82
+ this.load.once(Phaser.Loader.Events.COMPLETE, this.finishLoading, this);
83
+ this.load.start();
84
+ }
85
+
86
+ createLoadingView() {
87
+ const { centerX, centerY } = this.cameras.main;
88
+
89
+ markEditable(
90
+ 'boot.loading-mascot',
91
+ this.add
92
+ .sprite(centerX, centerY - 58, DEV_LOADING_RUNTIME_KEYS.texture)
93
+ .setScale(0.56)
94
+ .play(DEV_LOADING_RUNTIME_KEYS.animation),
95
+ { label: '加载吉祥物' },
96
+ );
97
+
98
+ this.loadingText = markEditable(
99
+ 'boot.loading-text',
100
+ this.add
101
+ .text(centerX, centerY + 120, '正在加载游戏资源…', LOADING_TEXT_STYLE)
102
+ .setOrigin(0.5),
103
+ { label: '加载状态文案' },
104
+ );
105
+
106
+ this.progressBar = markEditable(
107
+ 'boot.loading-progress-bar',
108
+ this.add.graphics(),
109
+ { label: '加载进度条' },
110
+ );
111
+ this.renderProgressBar(0);
112
+
113
+ this.progressText = markEditable(
114
+ 'boot.loading-progress',
115
+ this.add
116
+ .text(centerX, centerY + 190, '0%', LOADING_TEXT_STYLE)
117
+ .setOrigin(0.5),
118
+ { label: '加载进度文本' },
119
+ );
120
+ }
121
+
122
+ onGameFile = file => {
123
+ const assetLabel = getAssetLabelFromFileKey(file.key, GAME_ASSETS);
124
+ this.loadingText?.setText('正在加载' + assetLabel);
125
+ };
126
+
127
+ onProgress = progress => {
128
+ this.renderProgressBar(progress);
129
+ this.progressText?.setText(Math.round(progress * 100) + '%');
130
+ };
131
+
132
+ onLoadError = file => {
133
+ this.loadFailed = true;
134
+ const assetLabel = getAssetLabelFromFileKey(file.key, GAME_ASSETS);
135
+ this.loadingText?.setText(assetLabel + '加载失败');
136
+ };
137
+
138
+ renderProgressBar(progress) {
139
+ if (!this.progressBar) {
140
+ return;
141
+ }
142
+
143
+ const { centerX, centerY } = this.cameras.main;
144
+ const width = 300;
145
+ const height = 14;
146
+ const x = centerX - width / 2;
147
+ const y = centerY + 154;
148
+ const padding = 3;
149
+ const innerWidth = width - padding * 2;
150
+ const filledWidth = Phaser.Math.Clamp(progress, 0, 1) * innerWidth;
151
+
152
+ this.progressBar.clear();
153
+ this.progressBar.fillStyle(0x102536, 0.12);
154
+ this.progressBar.fillRoundedRect(x, y, width, height, height / 2);
155
+ this.progressBar.lineStyle(2, 0x102536, 0.28);
156
+ this.progressBar.strokeRoundedRect(x, y, width, height, height / 2);
157
+
158
+ if (filledWidth <= 0) {
159
+ return;
160
+ }
161
+
162
+ this.progressBar.fillStyle(0xef648d, 1);
163
+ this.progressBar.fillRoundedRect(
164
+ x + padding,
165
+ y + padding,
166
+ filledWidth,
167
+ height - padding * 2,
168
+ (height - padding * 2) / 2,
169
+ );
170
+
171
+ this.progressBar.fillStyle(0x165ca8, 1);
172
+ this.progressBar.fillCircle(
173
+ Phaser.Math.Clamp(
174
+ x + padding + filledWidth,
175
+ x + padding + 5,
176
+ x + width - padding - 5,
177
+ ),
178
+ y + height / 2,
179
+ 5,
180
+ );
181
+ }
182
+
183
+ finishLoading = () => {
184
+ this.removeLoaderListeners();
185
+
186
+ if (this.loadFailed) {
187
+ this.progressText?.setText('请刷新页面重试');
188
+ return;
189
+ }
190
+
191
+ registerImageSequenceAnimations(this, GAME_IMAGE_SEQUENCES);
192
+ this.scene.start('dev-loading');
193
+ };
194
+
195
+ removeLoaderListeners() {
196
+ this.load.off(Phaser.Loader.Events.FILE_PROGRESS, this.onGameFile, this);
197
+ this.load.off(Phaser.Loader.Events.PROGRESS, this.onProgress, this);
198
+ this.load.off(
199
+ Phaser.Loader.Events.FILE_LOAD_ERROR,
200
+ this.onLoadError,
201
+ this,
202
+ );
203
+ this.load.off(Phaser.Loader.Events.COMPLETE, this.finishLoading, this);
204
+ }
205
+ }
@@ -0,0 +1,67 @@
1
+ import { Phaser } from '../phaser.js';
2
+ import { markEditable } from '../utils/index.js';
3
+ import { DEV_LOADING_RUNTIME_KEYS } from './BootScene.js';
4
+
5
+ /**
6
+ * 本场景用于为兜底场景展示
7
+ */
8
+ export class DevLoadingScene extends Phaser.Scene {
9
+ constructor() {
10
+ super('dev-loading');
11
+ }
12
+
13
+ create() {
14
+ const { centerX, centerY } = this.cameras.main;
15
+
16
+ markEditable(
17
+ 'dev-loading.mascot',
18
+ this.add
19
+ .sprite(centerX, centerY - 82, DEV_LOADING_RUNTIME_KEYS.texture)
20
+ .setScale(0.64)
21
+ .play(DEV_LOADING_RUNTIME_KEYS.animation),
22
+ { label: '开发中吉祥物' },
23
+ );
24
+
25
+ markEditable(
26
+ 'dev-loading.title',
27
+ this.add
28
+ .text(centerX, centerY + 92, '游戏开发中', {
29
+ color: '#18181b',
30
+ fontFamily: '"PingFang SC", "Microsoft YaHei", sans-serif',
31
+ fontSize: '32px',
32
+ fontStyle: 'bold',
33
+ })
34
+ .setOrigin(0.5),
35
+ { label: '开发中标题' },
36
+ );
37
+
38
+ markEditable(
39
+ 'dev-loading.description',
40
+ this.add
41
+ .text(centerX, centerY + 138, '请稍后,游戏世界即将呈现', {
42
+ color: '#71717a',
43
+ fontFamily: '"PingFang SC", "Microsoft YaHei", sans-serif',
44
+ fontSize: '18px',
45
+ })
46
+ .setOrigin(0.5),
47
+ { label: '开发中说明文案' },
48
+ );
49
+
50
+ const loadingDots = Array.from({ length: 3 }, (_value, index) =>
51
+ this.add.circle(centerX + (index - 1) * 18, centerY + 178, 4, 0xf43f75),
52
+ );
53
+
54
+ loadingDots.forEach((dot, index) => {
55
+ this.tweens.add({
56
+ targets: dot,
57
+ alpha: 0.25,
58
+ scale: 0.7,
59
+ duration: 520,
60
+ delay: index * 160,
61
+ ease: 'Sine.inOut',
62
+ repeat: -1,
63
+ yoyo: true,
64
+ });
65
+ });
66
+ }
67
+ }
@@ -0,0 +1,28 @@
1
+ :root {
2
+ color-scheme: dark;
3
+ font-family: Arial, sans-serif;
4
+ background: #000;
5
+ }
6
+
7
+ * {
8
+ box-sizing: border-box;
9
+ }
10
+
11
+ html,
12
+ body,
13
+ #game {
14
+ width: 100%;
15
+ height: 100%;
16
+ margin: 0;
17
+ overflow: hidden;
18
+ background: #000;
19
+ }
20
+
21
+ #game {
22
+ min-width: 0;
23
+ min-height: 0;
24
+ }
25
+
26
+ #game canvas {
27
+ display: block;
28
+ }
@@ -0,0 +1,267 @@
1
+ import { AUDIOS, IMAGES, IMAGE_SEQUENCES, VIDEOS } from '../assets.js';
2
+
3
+ const FRAME_KEY_MARKER = ':frame:';
4
+ const MANIFEST_KEY_SUFFIX = ':manifest';
5
+
6
+ const getFrameKey = (assetKey, frameIndex) =>
7
+ assetKey +
8
+ FRAME_KEY_MARKER +
9
+ String(frameIndex + 1).padStart(4, '0');
10
+
11
+ const getManifestKey = assetKey => assetKey + MANIFEST_KEY_SUFFIX;
12
+
13
+ const getFrameSource = (directory, frameIndex) =>
14
+ directory + '/frame_' + String(frameIndex + 1).padStart(4, '0') + '.png';
15
+
16
+ export const resolveAssetUrl = source => new URL(source, document.baseURI).href;
17
+
18
+ const isImageSequenceManifest = value => {
19
+ if (!value || typeof value !== 'object') {
20
+ return false;
21
+ }
22
+
23
+ return (
24
+ value.type === 'image_sequence' &&
25
+ typeof value.name === 'string' &&
26
+ typeof value.properties?.fps === 'number' &&
27
+ typeof value.properties.frame_count === 'number' &&
28
+ value.properties.frame_count > 0
29
+ );
30
+ };
31
+
32
+ const getImageSequenceManifest = (scene, assetKey) => {
33
+ const manifest = scene.cache.json.get(getManifestKey(assetKey));
34
+ if (!isImageSequenceManifest(manifest)) {
35
+ throw new Error('Invalid image sequence manifest: ' + assetKey);
36
+ }
37
+
38
+ return manifest;
39
+ };
40
+
41
+ const queueImageSequenceFrames = (scene, assetKey, directory, manifest) => {
42
+ Array.from(
43
+ { length: manifest.properties.frame_count },
44
+ (_value, frameIndex) => {
45
+ scene.load.image(
46
+ getFrameKey(assetKey, frameIndex),
47
+ resolveAssetUrl(getFrameSource(directory, frameIndex)),
48
+ );
49
+ },
50
+ );
51
+ };
52
+
53
+ const queueImages = (scene, images) => {
54
+ Object.entries(images).forEach(([assetKey, source]) => {
55
+ scene.load.image(assetKey, resolveAssetUrl(source));
56
+ });
57
+ return Object.keys(images).length;
58
+ };
59
+
60
+ const queueVideos = (scene, videos) => {
61
+ Object.entries(videos).forEach(([assetKey, source]) => {
62
+ scene.load.video(assetKey, resolveAssetUrl(source));
63
+ });
64
+ return Object.keys(videos).length;
65
+ };
66
+
67
+ const queueAudios = (scene, audios) => {
68
+ Object.entries(audios).forEach(([assetKey, source]) => {
69
+ scene.load.audio(assetKey, resolveAssetUrl(source));
70
+ });
71
+ return Object.keys(audios).length;
72
+ };
73
+
74
+ const queueImageSequences = (scene, imageSequences) =>
75
+ Object.entries(imageSequences).reduce(
76
+ (fileCount, [assetKey, directory]) => {
77
+ const manifestKey = getManifestKey(assetKey);
78
+ const cachedManifest = scene.cache.json.get(manifestKey);
79
+
80
+ if (isImageSequenceManifest(cachedManifest)) {
81
+ queueImageSequenceFrames(scene, assetKey, directory, cachedManifest);
82
+ return fileCount + cachedManifest.properties.frame_count;
83
+ }
84
+
85
+ scene.load.once(
86
+ 'filecomplete-json-' + manifestKey,
87
+ (_key, _type, data) => {
88
+ if (!isImageSequenceManifest(data)) {
89
+ throw new Error('Invalid image sequence manifest: ' + assetKey);
90
+ }
91
+
92
+ queueImageSequenceFrames(scene, assetKey, directory, data);
93
+ },
94
+ );
95
+
96
+ scene.load.json(
97
+ manifestKey,
98
+ resolveAssetUrl(directory + '/manifest.json'),
99
+ );
100
+ return fileCount + 1;
101
+ },
102
+ 0,
103
+ );
104
+
105
+ export const loadAssets = (scene, collections) =>
106
+ queueImages(scene, collections.images ?? {}) +
107
+ queueImageSequences(scene, collections.imageSequences ?? {}) +
108
+ queueVideos(scene, collections.videos ?? {}) +
109
+ queueAudios(scene, collections.audios ?? {});
110
+
111
+ export const getImageSequenceRuntimeKeys = assetKey => ({
112
+ texture: getFrameKey(assetKey, 0),
113
+ animation: assetKey + ':animation',
114
+ });
115
+
116
+ export const getAssetKeyFromFileKey = fileKey =>
117
+ fileKey.split(FRAME_KEY_MARKER)[0]?.replace(MANIFEST_KEY_SUFFIX, '') ??
118
+ fileKey;
119
+
120
+ export const getAssetLabelFromFileKey = (fileKey, collections) => {
121
+ const assetKey = getAssetKeyFromFileKey(fileKey);
122
+
123
+ if (assetKey in (collections.images ?? {})) {
124
+ return '图片 ' + assetKey;
125
+ }
126
+ if (assetKey in (collections.imageSequences ?? {})) {
127
+ return '帧动画 ' + assetKey;
128
+ }
129
+ if (assetKey in (collections.videos ?? {})) {
130
+ return '视频 ' + assetKey;
131
+ }
132
+ if (assetKey in (collections.audios ?? {})) {
133
+ return '音频 ' + assetKey;
134
+ }
135
+
136
+ return assetKey;
137
+ };
138
+
139
+ export const registerImageSequenceAnimations = (scene, imageSequences) => {
140
+ Object.keys(imageSequences).forEach(assetKey => {
141
+ const manifest = getImageSequenceManifest(scene, assetKey);
142
+ const animationKey = getImageSequenceRuntimeKeys(assetKey).animation;
143
+ if (scene.anims.exists(animationKey)) {
144
+ return;
145
+ }
146
+
147
+ scene.anims.create({
148
+ key: animationKey,
149
+ frames: Array.from(
150
+ { length: manifest.properties.frame_count },
151
+ (_value, frameIndex) => ({ key: getFrameKey(assetKey, frameIndex) }),
152
+ ),
153
+ frameRate: manifest.properties.fps,
154
+ repeat: -1,
155
+ });
156
+ });
157
+ };
158
+
159
+ const findAsset = assetKey => {
160
+ const matches = [];
161
+
162
+ if (Object.hasOwn(IMAGES, assetKey)) {
163
+ matches.push({
164
+ collections: { images: { [assetKey]: IMAGES[assetKey] } },
165
+ loadedAsset: { kind: 'image', key: assetKey },
166
+ });
167
+ }
168
+ if (Object.hasOwn(IMAGE_SEQUENCES, assetKey)) {
169
+ matches.push({
170
+ collections: {
171
+ imageSequences: { [assetKey]: IMAGE_SEQUENCES[assetKey] },
172
+ },
173
+ loadedAsset: {
174
+ kind: 'image-sequence',
175
+ key: assetKey,
176
+ ...getImageSequenceRuntimeKeys(assetKey),
177
+ },
178
+ });
179
+ }
180
+ if (Object.hasOwn(VIDEOS, assetKey)) {
181
+ matches.push({
182
+ collections: { videos: { [assetKey]: VIDEOS[assetKey] } },
183
+ loadedAsset: { kind: 'video', key: assetKey },
184
+ });
185
+ }
186
+ if (Object.hasOwn(AUDIOS, assetKey)) {
187
+ matches.push({
188
+ collections: { audios: { [assetKey]: AUDIOS[assetKey] } },
189
+ loadedAsset: { kind: 'audio', key: assetKey },
190
+ });
191
+ }
192
+
193
+ if (matches.length === 0) {
194
+ throw new Error('Asset not found: ' + assetKey);
195
+ }
196
+ if (matches.length > 1) {
197
+ throw new Error('Duplicate asset key: ' + assetKey);
198
+ }
199
+
200
+ return matches[0];
201
+ };
202
+
203
+ const isAssetLoaded = (scene, loadedAsset) => {
204
+ switch (loadedAsset.kind) {
205
+ case 'image':
206
+ return scene.textures.exists(loadedAsset.key);
207
+ case 'image-sequence':
208
+ return scene.anims.exists(loadedAsset.animation);
209
+ case 'video':
210
+ return scene.cache.video.exists(loadedAsset.key);
211
+ case 'audio':
212
+ return scene.cache.audio.exists(loadedAsset.key);
213
+ default:
214
+ return false;
215
+ }
216
+ };
217
+
218
+ export const loadAsset = async (scene, assetKey) => {
219
+ const { collections, loadedAsset } = findAsset(assetKey);
220
+ if (isAssetLoaded(scene, loadedAsset)) {
221
+ return loadedAsset;
222
+ }
223
+
224
+ return new Promise((resolve, reject) => {
225
+ const cleanup = () => {
226
+ scene.load.off('complete', onComplete);
227
+ scene.load.off('loaderror', onLoadError);
228
+ };
229
+
230
+ const onComplete = () => {
231
+ cleanup();
232
+ try {
233
+ if (loadedAsset.kind === 'image-sequence') {
234
+ registerImageSequenceAnimations(
235
+ scene,
236
+ collections.imageSequences ?? {},
237
+ );
238
+ }
239
+ resolve(loadedAsset);
240
+ } catch (error) {
241
+ reject(error);
242
+ }
243
+ };
244
+
245
+ const onLoadError = file => {
246
+ if (getAssetKeyFromFileKey(file.key) !== assetKey) {
247
+ return;
248
+ }
249
+
250
+ cleanup();
251
+ reject(new Error('Failed to load asset: ' + assetKey));
252
+ };
253
+
254
+ scene.load.once('complete', onComplete);
255
+ scene.load.on('loaderror', onLoadError);
256
+
257
+ try {
258
+ loadAssets(scene, collections);
259
+ if (!scene.load.isLoading()) {
260
+ scene.load.start();
261
+ }
262
+ } catch (error) {
263
+ cleanup();
264
+ reject(error);
265
+ }
266
+ });
267
+ };
@@ -0,0 +1,2 @@
1
+ export * from './asset-loader.js';
2
+ export * from './mark-editable.js';
@@ -0,0 +1,3 @@
1
+ export const markEditable = (locator, gameObject, options) =>
2
+ window.PhaserBridge?.markEditable?.(locator, gameObject, options) ??
3
+ gameObject;
@@ -0,0 +1,35 @@
1
+
2
+
3
+
4
+
5
+
6
+
7
+
8
+
9
+ export const paramsSchema = {
10
+ type: 'object',
11
+ properties: {
12
+ appName: {
13
+ type: 'string',
14
+ minLength: 1,
15
+ pattern: '^[a-z0-9-]+$',
16
+ description:
17
+ 'Application name (lowercase, alphanumeric and hyphens only)',
18
+ },
19
+ },
20
+ required: [],
21
+ additionalProperties: false,
22
+ };
23
+
24
+ const config = {
25
+ description:
26
+ 'Phaser Static(纯静态 2D 游戏):coze-dev init ${COZE_WORKSPACE_PATH} --template phaser-static\n' +
27
+ '- 适用:浏览器原生 JavaScript ESM 的 Phaser 3 游戏。\n' +
28
+ '- Phaser 与 Phaser Bridge 从 CDN 加载;无需 npm、Vite 或 TypeScript。',
29
+ paramsSchema,
30
+ defaultParams: {
31
+ appName: 'phaser-static-game',
32
+ },
33
+ };
34
+
35
+ export default config;