@coze-arch/cli 0.0.37 → 0.0.39-alpha.1699cf

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 (168) hide show
  1. package/README.md +69 -104
  2. package/docs/deploy.md +181 -0
  3. package/lib/__templates__/expo/.coze +5 -0
  4. package/lib/__templates__/expo/.cozeproj/scripts/dev_build.ps1 +10 -0
  5. package/lib/__templates__/expo/.cozeproj/scripts/dev_build.sh +4 -0
  6. package/lib/__templates__/expo/.cozeproj/scripts/dev_run.ps1 +74 -0
  7. package/lib/__templates__/expo/.cozeproj/scripts/dev_run.sh +108 -19
  8. package/lib/__templates__/expo/.cozeproj/scripts/prod_build.ps1 +15 -0
  9. package/lib/__templates__/expo/.cozeproj/scripts/prod_run.ps1 +10 -0
  10. package/lib/__templates__/expo/.cozeproj/scripts/prod_run.sh +1 -1
  11. package/lib/__templates__/expo/.cozeproj/scripts/server_dev_run.sh +1 -1
  12. package/lib/__templates__/expo/.cozeproj/scripts/validate.ps1 +7 -0
  13. package/lib/__templates__/expo/client/metro.config.js +3 -1
  14. package/lib/__templates__/expo/pnpm-lock.yaml +5 -5
  15. package/lib/__templates__/expo/server/package.json +2 -2
  16. package/lib/__templates__/expo/server/src/index.ts +1 -1
  17. package/lib/__templates__/expo/template.config.js +11 -2
  18. package/lib/__templates__/native-static/.coze +4 -2
  19. package/lib/__templates__/native-static/scripts/serve.ps1 +7 -0
  20. package/lib/__templates__/nextjs/.coze +5 -0
  21. package/lib/__templates__/nextjs/package.json +7 -2
  22. package/lib/__templates__/nextjs/pnpm-lock.yaml +59 -334
  23. package/lib/__templates__/nextjs/scripts/build.ps1 +16 -0
  24. package/lib/__templates__/nextjs/scripts/dev.ps1 +63 -0
  25. package/lib/__templates__/nextjs/scripts/dev.sh +234 -7
  26. package/lib/__templates__/nextjs/scripts/prepare.ps1 +18 -0
  27. package/lib/__templates__/nextjs/scripts/start.ps1 +11 -0
  28. package/lib/__templates__/nextjs/scripts/validate.ps1 +8 -0
  29. package/lib/__templates__/nextjs/template.config.js +4 -2
  30. package/lib/__templates__/nuxt-vue/.coze +5 -0
  31. package/lib/__templates__/nuxt-vue/package.json +1 -1
  32. package/lib/__templates__/nuxt-vue/pnpm-lock.yaml +5 -5
  33. package/lib/__templates__/nuxt-vue/scripts/build.ps1 +12 -0
  34. package/lib/__templates__/nuxt-vue/scripts/dev.ps1 +63 -0
  35. package/lib/__templates__/nuxt-vue/scripts/dev.sh +232 -3
  36. package/lib/__templates__/nuxt-vue/scripts/prepare.ps1 +12 -0
  37. package/lib/__templates__/nuxt-vue/scripts/start.ps1 +11 -0
  38. package/lib/__templates__/nuxt-vue/scripts/validate.ps1 +8 -0
  39. package/lib/__templates__/phaser/.coze +18 -0
  40. package/lib/__templates__/phaser/README.md +45 -0
  41. package/lib/__templates__/phaser/_gitignore +23 -0
  42. package/lib/__templates__/phaser/_npmrc +14 -0
  43. package/lib/__templates__/phaser/assets/image_sequence/coze-game-loading/frame_0001.png +0 -0
  44. package/lib/__templates__/phaser/assets/image_sequence/coze-game-loading/frame_0002.png +0 -0
  45. package/lib/__templates__/phaser/assets/image_sequence/coze-game-loading/frame_0003.png +0 -0
  46. package/lib/__templates__/phaser/assets/image_sequence/coze-game-loading/frame_0004.png +0 -0
  47. package/lib/__templates__/phaser/assets/image_sequence/coze-game-loading/frame_0005.png +0 -0
  48. package/lib/__templates__/phaser/assets/image_sequence/coze-game-loading/frame_0006.png +0 -0
  49. package/lib/__templates__/phaser/assets/image_sequence/coze-game-loading/frame_0007.png +0 -0
  50. package/lib/__templates__/phaser/assets/image_sequence/coze-game-loading/frame_0008.png +0 -0
  51. package/lib/__templates__/phaser/assets/image_sequence/coze-game-loading/frame_0009.png +0 -0
  52. package/lib/__templates__/phaser/assets/image_sequence/coze-game-loading/frame_0010.png +0 -0
  53. package/lib/__templates__/phaser/assets/image_sequence/coze-game-loading/frame_0011.png +0 -0
  54. package/lib/__templates__/phaser/assets/image_sequence/coze-game-loading/frame_0012.png +0 -0
  55. package/lib/__templates__/phaser/assets/image_sequence/coze-game-loading/frame_0013.png +0 -0
  56. package/lib/__templates__/phaser/assets/image_sequence/coze-game-loading/frame_0014.png +0 -0
  57. package/lib/__templates__/phaser/assets/image_sequence/coze-game-loading/frame_0015.png +0 -0
  58. package/lib/__templates__/phaser/assets/image_sequence/coze-game-loading/frame_0016.png +0 -0
  59. package/lib/__templates__/phaser/assets/image_sequence/coze-game-loading/frame_0017.png +0 -0
  60. package/lib/__templates__/phaser/assets/image_sequence/coze-game-loading/frame_0018.png +0 -0
  61. package/lib/__templates__/phaser/assets/image_sequence/coze-game-loading/frame_0019.png +0 -0
  62. package/lib/__templates__/phaser/assets/image_sequence/coze-game-loading/frame_0020.png +0 -0
  63. package/lib/__templates__/phaser/assets/image_sequence/coze-game-loading/frame_0021.png +0 -0
  64. package/lib/__templates__/phaser/assets/image_sequence/coze-game-loading/frame_0022.png +0 -0
  65. package/lib/__templates__/phaser/assets/image_sequence/coze-game-loading/frame_0023.png +0 -0
  66. package/lib/__templates__/phaser/assets/image_sequence/coze-game-loading/frame_0024.png +0 -0
  67. package/lib/__templates__/phaser/assets/image_sequence/coze-game-loading/manifest.json +11 -0
  68. package/lib/__templates__/phaser/debug-runtime/flow-graph.json +20 -0
  69. package/lib/__templates__/phaser/index.html +12 -0
  70. package/lib/__templates__/phaser/package.json +31 -0
  71. package/lib/__templates__/phaser/pnpm-lock.yaml +661 -0
  72. package/lib/__templates__/phaser/scripts/build.ps1 +12 -0
  73. package/lib/__templates__/phaser/scripts/build.sh +8 -0
  74. package/lib/__templates__/phaser/scripts/dev.ps1 +49 -0
  75. package/lib/__templates__/phaser/scripts/dev.sh +82 -0
  76. package/lib/__templates__/phaser/scripts/prepare.ps1 +8 -0
  77. package/lib/__templates__/phaser/scripts/prepare.sh +7 -0
  78. package/lib/__templates__/phaser/scripts/spawn-detached.cjs +29 -0
  79. package/lib/__templates__/phaser/scripts/start.ps1 +49 -0
  80. package/lib/__templates__/phaser/scripts/start.sh +82 -0
  81. package/lib/__templates__/phaser/scripts/validate.ps1 +7 -0
  82. package/lib/__templates__/phaser/scripts/validate.sh +7 -0
  83. package/lib/__templates__/phaser/src/assets.ts +15 -0
  84. package/lib/__templates__/phaser/src/global.d.ts +39 -0
  85. package/lib/__templates__/phaser/src/main.ts +28 -0
  86. package/lib/__templates__/phaser/src/scene/BootScene.ts +213 -0
  87. package/lib/__templates__/phaser/src/scene/DevLoadingScene.ts +69 -0
  88. package/lib/__templates__/phaser/src/style.css +28 -0
  89. package/lib/__templates__/phaser/src/types.ts +32 -0
  90. package/lib/__templates__/phaser/src/utils/asset-loader.ts +319 -0
  91. package/lib/__templates__/phaser/src/utils/index.ts +2 -0
  92. package/lib/__templates__/phaser/src/utils/mark-editable.ts +13 -0
  93. package/lib/__templates__/phaser/template.config.js +43 -0
  94. package/lib/__templates__/phaser/tsconfig.json +23 -0
  95. package/lib/__templates__/phaser/vite.config.ts +96 -0
  96. package/lib/__templates__/phaser-static/.coze +15 -0
  97. package/lib/__templates__/phaser-static/README.md +40 -0
  98. package/lib/__templates__/phaser-static/_gitignore +6 -0
  99. package/lib/__templates__/phaser-static/assets/image_sequence/coze-game-loading/frame_0001.png +0 -0
  100. package/lib/__templates__/phaser-static/assets/image_sequence/coze-game-loading/frame_0002.png +0 -0
  101. package/lib/__templates__/phaser-static/assets/image_sequence/coze-game-loading/frame_0003.png +0 -0
  102. package/lib/__templates__/phaser-static/assets/image_sequence/coze-game-loading/frame_0004.png +0 -0
  103. package/lib/__templates__/phaser-static/assets/image_sequence/coze-game-loading/frame_0005.png +0 -0
  104. package/lib/__templates__/phaser-static/assets/image_sequence/coze-game-loading/frame_0006.png +0 -0
  105. package/lib/__templates__/phaser-static/assets/image_sequence/coze-game-loading/frame_0007.png +0 -0
  106. package/lib/__templates__/phaser-static/assets/image_sequence/coze-game-loading/frame_0008.png +0 -0
  107. package/lib/__templates__/phaser-static/assets/image_sequence/coze-game-loading/frame_0009.png +0 -0
  108. package/lib/__templates__/phaser-static/assets/image_sequence/coze-game-loading/frame_0010.png +0 -0
  109. package/lib/__templates__/phaser-static/assets/image_sequence/coze-game-loading/frame_0011.png +0 -0
  110. package/lib/__templates__/phaser-static/assets/image_sequence/coze-game-loading/frame_0012.png +0 -0
  111. package/lib/__templates__/phaser-static/assets/image_sequence/coze-game-loading/frame_0013.png +0 -0
  112. package/lib/__templates__/phaser-static/assets/image_sequence/coze-game-loading/frame_0014.png +0 -0
  113. package/lib/__templates__/phaser-static/assets/image_sequence/coze-game-loading/frame_0015.png +0 -0
  114. package/lib/__templates__/phaser-static/assets/image_sequence/coze-game-loading/frame_0016.png +0 -0
  115. package/lib/__templates__/phaser-static/assets/image_sequence/coze-game-loading/frame_0017.png +0 -0
  116. package/lib/__templates__/phaser-static/assets/image_sequence/coze-game-loading/frame_0018.png +0 -0
  117. package/lib/__templates__/phaser-static/assets/image_sequence/coze-game-loading/frame_0019.png +0 -0
  118. package/lib/__templates__/phaser-static/assets/image_sequence/coze-game-loading/frame_0020.png +0 -0
  119. package/lib/__templates__/phaser-static/assets/image_sequence/coze-game-loading/frame_0021.png +0 -0
  120. package/lib/__templates__/phaser-static/assets/image_sequence/coze-game-loading/frame_0022.png +0 -0
  121. package/lib/__templates__/phaser-static/assets/image_sequence/coze-game-loading/frame_0023.png +0 -0
  122. package/lib/__templates__/phaser-static/assets/image_sequence/coze-game-loading/frame_0024.png +0 -0
  123. package/lib/__templates__/phaser-static/assets/image_sequence/coze-game-loading/manifest.json +11 -0
  124. package/lib/__templates__/phaser-static/debug-runtime/flow-graph.json +20 -0
  125. package/lib/__templates__/phaser-static/index.html +17 -0
  126. package/lib/__templates__/phaser-static/scripts/serve.ps1 +7 -0
  127. package/lib/__templates__/phaser-static/src/assets.js +15 -0
  128. package/lib/__templates__/phaser-static/src/main.js +26 -0
  129. package/lib/__templates__/phaser-static/src/phaser.js +7 -0
  130. package/lib/__templates__/phaser-static/src/scene/BootScene.js +205 -0
  131. package/lib/__templates__/phaser-static/src/scene/DevLoadingScene.js +67 -0
  132. package/lib/__templates__/phaser-static/src/style.css +28 -0
  133. package/lib/__templates__/phaser-static/src/utils/asset-loader.js +267 -0
  134. package/lib/__templates__/phaser-static/src/utils/index.js +2 -0
  135. package/lib/__templates__/phaser-static/src/utils/mark-editable.js +3 -0
  136. package/lib/__templates__/phaser-static/template.config.js +35 -0
  137. package/lib/__templates__/pi-agent/package.json +1 -1
  138. package/lib/__templates__/pi-agent/pnpm-lock.yaml +5 -5
  139. package/lib/__templates__/taro/.coze +6 -0
  140. package/lib/__templates__/taro/.cozeproj/scripts/deploy_build.ps1 +10 -0
  141. package/lib/__templates__/taro/.cozeproj/scripts/deploy_run.ps1 +8 -0
  142. package/lib/__templates__/taro/.cozeproj/scripts/deploy_run.sh +1 -1
  143. package/lib/__templates__/taro/.cozeproj/scripts/dev_build.ps1 +3 -0
  144. package/lib/__templates__/taro/.cozeproj/scripts/dev_run.ps1 +62 -0
  145. package/lib/__templates__/taro/.cozeproj/scripts/dev_run.sh +178 -68
  146. package/lib/__templates__/taro/.cozeproj/scripts/pack.ps1 +7 -0
  147. package/lib/__templates__/taro/.cozeproj/scripts/validate.ps1 +7 -0
  148. package/lib/__templates__/taro/AGENTS.md +11 -7
  149. package/lib/__templates__/taro/README.md +4 -4
  150. package/lib/__templates__/taro/config/index.ts +10 -2
  151. package/lib/__templates__/taro/pnpm-lock.yaml +5 -5
  152. package/lib/__templates__/taro/server/package.json +1 -1
  153. package/lib/__templates__/taro/server/src/main.ts +8 -2
  154. package/lib/__templates__/templates.json +51 -1
  155. package/lib/__templates__/vite/.coze +5 -0
  156. package/lib/__templates__/vite/package.json +1 -1
  157. package/lib/__templates__/vite/pnpm-lock.yaml +5 -5
  158. package/lib/__templates__/vite/scripts/build.ps1 +16 -0
  159. package/lib/__templates__/vite/scripts/dev.ps1 +63 -0
  160. package/lib/__templates__/vite/scripts/dev.sh +110 -1
  161. package/lib/__templates__/vite/scripts/prepare.ps1 +18 -0
  162. package/lib/__templates__/vite/scripts/start.ps1 +11 -0
  163. package/lib/__templates__/vite/scripts/validate.ps1 +8 -0
  164. package/lib/__templates__/vite/template.config.js +4 -2
  165. package/lib/cli.js +3719 -211
  166. package/lib/deploy/package-project.js +244 -0
  167. package/lib/deploy/package-project.py +183 -0
  168. package/package.json +5 -2
@@ -0,0 +1,69 @@
1
+ import Phaser from 'phaser';
2
+
3
+ import { markEditable } from '@/utils';
4
+
5
+ import { DEV_LOADING_RUNTIME_KEYS } from './BootScene';
6
+
7
+ /**
8
+ * 本场景用于为兜底场景展示
9
+ */
10
+ export class DevLoadingScene extends Phaser.Scene {
11
+ constructor() {
12
+ super('dev-loading');
13
+ }
14
+
15
+ create(): void {
16
+ const { centerX, centerY } = this.cameras.main;
17
+
18
+ markEditable(
19
+ 'dev-loading.mascot',
20
+ this.add
21
+ .sprite(centerX, centerY - 82, DEV_LOADING_RUNTIME_KEYS.texture)
22
+ .setScale(0.64)
23
+ .play(DEV_LOADING_RUNTIME_KEYS.animation),
24
+ { label: '开发中吉祥物' },
25
+ );
26
+
27
+ markEditable(
28
+ 'dev-loading.title',
29
+ this.add
30
+ .text(centerX, centerY + 92, '游戏开发中', {
31
+ color: '#18181b',
32
+ fontFamily: '"PingFang SC", "Microsoft YaHei", sans-serif',
33
+ fontSize: '32px',
34
+ fontStyle: 'bold',
35
+ })
36
+ .setOrigin(0.5),
37
+ { label: '开发中标题' },
38
+ );
39
+
40
+ markEditable(
41
+ 'dev-loading.description',
42
+ this.add
43
+ .text(centerX, centerY + 138, '请稍后,游戏世界即将呈现', {
44
+ color: '#71717a',
45
+ fontFamily: '"PingFang SC", "Microsoft YaHei", sans-serif',
46
+ fontSize: '18px',
47
+ })
48
+ .setOrigin(0.5),
49
+ { label: '开发中说明文案' },
50
+ );
51
+
52
+ const loadingDots = Array.from({ length: 3 }, (_value, index) =>
53
+ this.add.circle(centerX + (index - 1) * 18, centerY + 178, 4, 0xf43f75),
54
+ );
55
+
56
+ loadingDots.forEach((dot, index) => {
57
+ this.tweens.add({
58
+ targets: dot,
59
+ alpha: 0.25,
60
+ scale: 0.7,
61
+ duration: 520,
62
+ delay: index * 160,
63
+ ease: 'Sine.inOut',
64
+ repeat: -1,
65
+ yoyo: true,
66
+ });
67
+ });
68
+ }
69
+ }
@@ -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,32 @@
1
+ export type AssetSources = Readonly<Record<string, string>>;
2
+
3
+ export interface AssetCollections {
4
+ images?: AssetSources;
5
+ imageSequences?: AssetSources;
6
+ videos?: AssetSources;
7
+ audios?: AssetSources;
8
+ }
9
+
10
+ export interface ImageSequenceManifest {
11
+ schema_version: string;
12
+ id: string;
13
+ type: 'image_sequence';
14
+ name: string;
15
+ properties: {
16
+ fps: number;
17
+ frame_count: number;
18
+ };
19
+ sources: readonly string[];
20
+ }
21
+
22
+ export type LoadedAsset =
23
+ | {
24
+ kind: 'image' | 'video' | 'audio';
25
+ key: string;
26
+ }
27
+ | {
28
+ kind: 'image-sequence';
29
+ key: string;
30
+ texture: string;
31
+ animation: string;
32
+ };
@@ -0,0 +1,319 @@
1
+ import type Phaser from 'phaser';
2
+
3
+ import { AUDIOS, IMAGES, IMAGE_SEQUENCES, VIDEOS } from '../assets';
4
+ import type {
5
+ AssetCollections,
6
+ AssetSources,
7
+ ImageSequenceManifest,
8
+ LoadedAsset,
9
+ } from '../types';
10
+
11
+ const FRAME_KEY_MARKER = ':frame:';
12
+ const MANIFEST_KEY_SUFFIX = ':manifest';
13
+
14
+ const getFrameKey = (assetKey: string, frameIndex: number): string =>
15
+ `${assetKey}${FRAME_KEY_MARKER}${String(frameIndex + 1).padStart(4, '0')}`;
16
+
17
+ const getManifestKey = (assetKey: string): string =>
18
+ `${assetKey}${MANIFEST_KEY_SUFFIX}`;
19
+
20
+ const getFrameSource = (directory: string, frameIndex: number): string =>
21
+ `${directory}/frame_${String(frameIndex + 1).padStart(4, '0')}.png`;
22
+
23
+ export const resolveAssetUrl = (source: string): string =>
24
+ new URL(source, document.baseURI).href;
25
+
26
+ const isImageSequenceManifest = (
27
+ value: unknown,
28
+ ): value is ImageSequenceManifest => {
29
+ if (!value || typeof value !== 'object') {
30
+ return false;
31
+ }
32
+
33
+ const manifest = value as Partial<ImageSequenceManifest>;
34
+ return (
35
+ manifest.type === 'image_sequence' &&
36
+ typeof manifest.name === 'string' &&
37
+ typeof manifest.properties?.fps === 'number' &&
38
+ typeof manifest.properties.frame_count === 'number' &&
39
+ manifest.properties.frame_count > 0
40
+ );
41
+ };
42
+
43
+ const getImageSequenceManifest = (
44
+ scene: Phaser.Scene,
45
+ assetKey: string,
46
+ ): ImageSequenceManifest => {
47
+ const manifest: unknown = scene.cache.json.get(getManifestKey(assetKey));
48
+ if (!isImageSequenceManifest(manifest)) {
49
+ throw new Error(`Invalid image sequence manifest: ${assetKey}`);
50
+ }
51
+
52
+ return manifest;
53
+ };
54
+
55
+ const queueImageSequenceFrames = (
56
+ scene: Phaser.Scene,
57
+ assetKey: string,
58
+ directory: string,
59
+ manifest: ImageSequenceManifest,
60
+ ): void => {
61
+ Array.from(
62
+ { length: manifest.properties.frame_count },
63
+ (_value, frameIndex) => {
64
+ scene.load.image(
65
+ getFrameKey(assetKey, frameIndex),
66
+ resolveAssetUrl(getFrameSource(directory, frameIndex)),
67
+ );
68
+ },
69
+ );
70
+ };
71
+
72
+ const queueImages = (scene: Phaser.Scene, images: AssetSources): number => {
73
+ Object.entries(images).forEach(([assetKey, source]) => {
74
+ scene.load.image(assetKey, resolveAssetUrl(source));
75
+ });
76
+ return Object.keys(images).length;
77
+ };
78
+
79
+ const queueVideos = (scene: Phaser.Scene, videos: AssetSources): number => {
80
+ Object.entries(videos).forEach(([assetKey, source]) => {
81
+ scene.load.video(assetKey, resolveAssetUrl(source));
82
+ });
83
+ return Object.keys(videos).length;
84
+ };
85
+
86
+ const queueAudios = (scene: Phaser.Scene, audios: AssetSources): number => {
87
+ Object.entries(audios).forEach(([assetKey, source]) => {
88
+ scene.load.audio(assetKey, resolveAssetUrl(source));
89
+ });
90
+ return Object.keys(audios).length;
91
+ };
92
+
93
+ const queueImageSequences = (
94
+ scene: Phaser.Scene,
95
+ imageSequences: AssetSources,
96
+ ): number => {
97
+ return Object.entries(imageSequences).reduce(
98
+ (fileCount, [assetKey, directory]) => {
99
+ const manifestKey = getManifestKey(assetKey);
100
+ const cachedManifest: unknown = scene.cache.json.get(manifestKey);
101
+
102
+ if (isImageSequenceManifest(cachedManifest)) {
103
+ queueImageSequenceFrames(
104
+ scene,
105
+ assetKey,
106
+ directory,
107
+ cachedManifest,
108
+ );
109
+ return fileCount + cachedManifest.properties.frame_count;
110
+ }
111
+
112
+ scene.load.once(
113
+ `filecomplete-json-${manifestKey}`,
114
+ (_key: string, _type: string, data: unknown) => {
115
+ if (!isImageSequenceManifest(data)) {
116
+ throw new Error(`Invalid image sequence manifest: ${assetKey}`);
117
+ }
118
+
119
+ queueImageSequenceFrames(scene, assetKey, directory, data);
120
+ },
121
+ );
122
+
123
+ scene.load.json(
124
+ manifestKey,
125
+ resolveAssetUrl(`${directory}/manifest.json`),
126
+ );
127
+ return fileCount + 1;
128
+ },
129
+ 0,
130
+ );
131
+ };
132
+
133
+ export const loadAssets = (
134
+ scene: Phaser.Scene,
135
+ collections: AssetCollections,
136
+ ): number =>
137
+ queueImages(scene, collections.images ?? {}) +
138
+ queueImageSequences(scene, collections.imageSequences ?? {}) +
139
+ queueVideos(scene, collections.videos ?? {}) +
140
+ queueAudios(scene, collections.audios ?? {});
141
+
142
+ export const getImageSequenceRuntimeKeys = (
143
+ assetKey: string,
144
+ ): { texture: string; animation: string } => ({
145
+ texture: getFrameKey(assetKey, 0),
146
+ animation: `${assetKey}:animation`,
147
+ });
148
+
149
+ export const getAssetKeyFromFileKey = (fileKey: string): string =>
150
+ fileKey.split(FRAME_KEY_MARKER)[0]?.replace(MANIFEST_KEY_SUFFIX, '') ??
151
+ fileKey;
152
+
153
+ export const getAssetLabelFromFileKey = (
154
+ fileKey: string,
155
+ collections: AssetCollections,
156
+ ): string => {
157
+ const assetKey = getAssetKeyFromFileKey(fileKey);
158
+
159
+ if (assetKey in (collections.images ?? {})) {
160
+ return `图片 ${assetKey}`;
161
+ }
162
+ if (assetKey in (collections.imageSequences ?? {})) {
163
+ return `帧动画 ${assetKey}`;
164
+ }
165
+ if (assetKey in (collections.videos ?? {})) {
166
+ return `视频 ${assetKey}`;
167
+ }
168
+ if (assetKey in (collections.audios ?? {})) {
169
+ return `音频 ${assetKey}`;
170
+ }
171
+
172
+ return assetKey;
173
+ };
174
+
175
+ export const registerImageSequenceAnimations = (
176
+ scene: Phaser.Scene,
177
+ imageSequences: AssetSources,
178
+ ): void => {
179
+ Object.keys(imageSequences).forEach(assetKey => {
180
+ const manifest = getImageSequenceManifest(scene, assetKey);
181
+ const animationKey = getImageSequenceRuntimeKeys(assetKey).animation;
182
+ if (scene.anims.exists(animationKey)) {
183
+ return;
184
+ }
185
+
186
+ scene.anims.create({
187
+ key: animationKey,
188
+ frames: Array.from(
189
+ { length: manifest.properties.frame_count },
190
+ (_value, frameIndex) => ({ key: getFrameKey(assetKey, frameIndex) }),
191
+ ),
192
+ frameRate: manifest.properties.fps,
193
+ repeat: -1,
194
+ });
195
+ });
196
+ };
197
+
198
+ const findAsset = (
199
+ assetKey: string,
200
+ ): { collections: AssetCollections; loadedAsset: LoadedAsset } => {
201
+ const matches: Array<{
202
+ collections: AssetCollections;
203
+ loadedAsset: LoadedAsset;
204
+ }> = [];
205
+
206
+ if (Object.hasOwn(IMAGES, assetKey)) {
207
+ matches.push({
208
+ collections: { images: { [assetKey]: IMAGES[assetKey] } },
209
+ loadedAsset: { kind: 'image', key: assetKey },
210
+ });
211
+ }
212
+ if (Object.hasOwn(IMAGE_SEQUENCES, assetKey)) {
213
+ matches.push({
214
+ collections: {
215
+ imageSequences: { [assetKey]: IMAGE_SEQUENCES[assetKey] },
216
+ },
217
+ loadedAsset: {
218
+ kind: 'image-sequence',
219
+ key: assetKey,
220
+ ...getImageSequenceRuntimeKeys(assetKey),
221
+ },
222
+ });
223
+ }
224
+ if (Object.hasOwn(VIDEOS, assetKey)) {
225
+ matches.push({
226
+ collections: { videos: { [assetKey]: VIDEOS[assetKey] } },
227
+ loadedAsset: { kind: 'video', key: assetKey },
228
+ });
229
+ }
230
+ if (Object.hasOwn(AUDIOS, assetKey)) {
231
+ matches.push({
232
+ collections: { audios: { [assetKey]: AUDIOS[assetKey] } },
233
+ loadedAsset: { kind: 'audio', key: assetKey },
234
+ });
235
+ }
236
+
237
+ if (matches.length === 0) {
238
+ throw new Error(`Asset not found: ${assetKey}`);
239
+ }
240
+ if (matches.length > 1) {
241
+ throw new Error(`Duplicate asset key: ${assetKey}`);
242
+ }
243
+
244
+ return matches[0];
245
+ };
246
+
247
+ const isAssetLoaded = (
248
+ scene: Phaser.Scene,
249
+ loadedAsset: LoadedAsset,
250
+ ): boolean => {
251
+ switch (loadedAsset.kind) {
252
+ case 'image':
253
+ return scene.textures.exists(loadedAsset.key);
254
+ case 'image-sequence':
255
+ return scene.anims.exists(loadedAsset.animation);
256
+ case 'video':
257
+ return scene.cache.video.exists(loadedAsset.key);
258
+ case 'audio':
259
+ return scene.cache.audio.exists(loadedAsset.key);
260
+ }
261
+ };
262
+
263
+ interface LoaderFileLike {
264
+ key: string;
265
+ }
266
+
267
+ export const loadAsset = async (
268
+ scene: Phaser.Scene,
269
+ assetKey: string,
270
+ ): Promise<LoadedAsset> => {
271
+ const { collections, loadedAsset } = findAsset(assetKey);
272
+ if (isAssetLoaded(scene, loadedAsset)) {
273
+ return loadedAsset;
274
+ }
275
+
276
+ return new Promise<LoadedAsset>((resolve, reject) => {
277
+ const cleanup = (): void => {
278
+ scene.load.off('complete', onComplete);
279
+ scene.load.off('loaderror', onLoadError);
280
+ };
281
+
282
+ const onComplete = (): void => {
283
+ cleanup();
284
+ try {
285
+ if (loadedAsset.kind === 'image-sequence') {
286
+ registerImageSequenceAnimations(
287
+ scene,
288
+ collections.imageSequences ?? {},
289
+ );
290
+ }
291
+ resolve(loadedAsset);
292
+ } catch (error) {
293
+ reject(error);
294
+ }
295
+ };
296
+
297
+ const onLoadError = (file: LoaderFileLike): void => {
298
+ if (getAssetKeyFromFileKey(file.key) !== assetKey) {
299
+ return;
300
+ }
301
+
302
+ cleanup();
303
+ reject(new Error(`Failed to load asset: ${assetKey}`));
304
+ };
305
+
306
+ scene.load.once('complete', onComplete);
307
+ scene.load.on('loaderror', onLoadError);
308
+
309
+ try {
310
+ loadAssets(scene, collections);
311
+ if (!scene.load.isLoading()) {
312
+ scene.load.start();
313
+ }
314
+ } catch (error) {
315
+ cleanup();
316
+ reject(error);
317
+ }
318
+ });
319
+ };
@@ -0,0 +1,2 @@
1
+ export * from './asset-loader';
2
+ export * from './mark-editable';
@@ -0,0 +1,13 @@
1
+ import type Phaser from 'phaser';
2
+
3
+ export interface MarkEditableOptions {
4
+ label?: string;
5
+ }
6
+
7
+ export const markEditable = <T extends Phaser.GameObjects.GameObject>(
8
+ locator: string,
9
+ gameObject: T,
10
+ options?: MarkEditableOptions,
11
+ ): T =>
12
+ window.PhaserBridge?.markEditable?.(locator, gameObject, options) ??
13
+ gameObject;
@@ -0,0 +1,43 @@
1
+
2
+
3
+
4
+
5
+
6
+
7
+
8
+
9
+
10
+ export const paramsSchema = {
11
+ type: 'object',
12
+ properties: {
13
+ appName: {
14
+ type: 'string',
15
+ minLength: 1,
16
+ pattern: '^[a-z0-9-]+$',
17
+ description:
18
+ 'Application name (lowercase, alphanumeric and hyphens only)',
19
+ },
20
+ port: {
21
+ type: 'number',
22
+ default: 5000,
23
+ minimum: 1024,
24
+ maximum: 65535,
25
+ description: 'Development and preview server port',
26
+ },
27
+ },
28
+ required: [],
29
+ additionalProperties: false,
30
+ };
31
+
32
+ const config = {
33
+ description:
34
+ 'Phaser(2D 游戏):`coze-dev init ${COZE_WORKSPACE_PATH} --template phaser`\n' +
35
+ '- 适用:基于 Phaser 3、Vite 和 TypeScript 的 2D Web 游戏。',
36
+ paramsSchema,
37
+ defaultParams: {
38
+ appName: 'phaser-game',
39
+ port: 5000,
40
+ },
41
+ };
42
+
43
+ export default config;
@@ -0,0 +1,23 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "useDefineForClassFields": true,
5
+ "lib": ["ES2022", "DOM", "DOM.Iterable"],
6
+ "module": "ESNext",
7
+ "moduleResolution": "Bundler",
8
+ "baseUrl": ".",
9
+ "paths": {
10
+ "@/*": ["src/*"]
11
+ },
12
+ "allowImportingTsExtensions": true,
13
+ "isolatedModules": true,
14
+ "moduleDetection": "force",
15
+ "noEmit": true,
16
+ "strict": true,
17
+ "skipLibCheck": true,
18
+ "esModuleInterop": true,
19
+ "forceConsistentCasingInFileNames": true,
20
+ "types": ["node"]
21
+ },
22
+ "include": ["src", "vite.config.ts"]
23
+ }
@@ -0,0 +1,96 @@
1
+ import { cp } from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { defineConfig, type Plugin } from "vite";
4
+ import serveStatic from "serve-static";
5
+
6
+ const DEV_PHASER_BRIDGE_URL =
7
+ "https://lf-coze-web-cdn.coze.cn/obj/eden-cn/lm-lgvj/ljhwZthlaukjlkulzlp/coze-game/js/phaser-bridge.min.js";
8
+
9
+ function injectDevPhaserBridge(): Plugin {
10
+ return {
11
+ name: "phaser-dev-bridge",
12
+ transformIndexHtml() {
13
+ return [
14
+ {
15
+ tag: "script",
16
+ attrs: { src: DEV_PHASER_BRIDGE_URL },
17
+ injectTo: "head",
18
+ },
19
+ ];
20
+ },
21
+ };
22
+ }
23
+
24
+ function mountDevAssets(directory: string): Plugin {
25
+ const serveAssets = serveStatic(directory, {
26
+ cacheControl: false,
27
+ dotfiles: "allow",
28
+ fallthrough: false,
29
+ index: false,
30
+ redirect: false,
31
+ setHeaders(response) {
32
+ response.setHeader("Cache-Control", "no-cache");
33
+ },
34
+ });
35
+
36
+ return {
37
+ name: "phaser-dev-assets",
38
+ configureServer(server) {
39
+ server.middlewares.use("/assets", (request, response, next) => {
40
+ const isRead = request.method === "GET" || request.method === "HEAD";
41
+ if (!isRead || request.url?.includes("?")) {
42
+ response.statusCode = 404;
43
+ response.end("Not Found\n");
44
+ return;
45
+ }
46
+ serveAssets(request, response, next);
47
+ });
48
+ },
49
+ };
50
+ }
51
+
52
+ function copyGameAssets(directory: string): Plugin {
53
+ return {
54
+ name: "phaser-build-assets",
55
+ async writeBundle(outputOptions) {
56
+ const outputDirectory = path.resolve(
57
+ import.meta.dirname,
58
+ outputOptions.dir ?? "dist",
59
+ );
60
+
61
+ await cp(directory, path.join(outputDirectory, "assets"), {
62
+ recursive: true,
63
+ force: true,
64
+ filter(source) {
65
+ return path.basename(source) !== ".DS_Store";
66
+ },
67
+ });
68
+ },
69
+ };
70
+ }
71
+
72
+ export default defineConfig(() => {
73
+ const isDev = process.env.COZE_PHASER_GAME_ENV === "DEV";
74
+ const assetsDirectory = path.join(import.meta.dirname, "assets");
75
+
76
+ return {
77
+ resolve: {
78
+ alias: {
79
+ "@": path.resolve(import.meta.dirname, "src"),
80
+ },
81
+ },
82
+ server: {
83
+ // 游戏需用户手动刷新,防止游玩过程中丢失游戏状态
84
+ hmr: false,
85
+ },
86
+ plugins: [
87
+ copyGameAssets(assetsDirectory),
88
+ ...(isDev
89
+ ? [
90
+ injectDevPhaserBridge(),
91
+ mountDevAssets(assetsDirectory),
92
+ ]
93
+ : []),
94
+ ],
95
+ };
96
+ });
@@ -0,0 +1,15 @@
1
+ [project]
2
+ entrypoint = "index.html"
3
+ requires = ["python-3.12"]
4
+ project_type = "game"
5
+ game_type = "phaser"
6
+
7
+ [dev]
8
+ build = []
9
+ run = ["python3", "-m", "http.server", "${DEPLOY_RUN_PORT}", "--bind", "0.0.0.0"]
10
+ run_win = ["powershell.exe", "-NoProfile", "-ExecutionPolicy", "Bypass", "-File", "./scripts/serve.ps1"]
11
+
12
+ [deploy]
13
+ build = []
14
+ run = ["python3", "-m", "http.server", "${DEPLOY_RUN_PORT}", "--bind", "0.0.0.0"]
15
+ run_win = ["powershell.exe", "-NoProfile", "-ExecutionPolicy", "Bypass", "-File", "./scripts/serve.ps1"]
@@ -0,0 +1,40 @@
1
+ # <%= appName %>
2
+
3
+ 这是一个基于浏览器原生 JavaScript ESM 的 Phaser 2D 游戏项目,由扣子编程 CLI 创建。
4
+
5
+ Phaser 3.90.0 与 Phaser Bridge 从 CDN 加载;项目不依赖 npm、Vite 或 TypeScript。请用静态 HTTP 服务访问,直接打开 `index.html` 不支持 ESM 模块与素材加载。
6
+
7
+ ## 快速开始
8
+
9
+ ```bash
10
+ coze-dev dev
11
+ coze-dev build
12
+ coze-dev start
13
+ ```
14
+
15
+ `build` 没有编译步骤;开发与生产预览均由 Python `http.server` 提供静态文件服务。
16
+
17
+ ## 文件结构
18
+
19
+ ```text
20
+ ├── assets/ # 图片、序列帧、视频和音频
21
+ ├── debug-runtime/ # 调试 Flow
22
+ ├── src/assets.js # 素材 key 与路径
23
+ ├── src/main.js # 游戏入口
24
+ ├── src/phaser.js # Phaser 全局对象的 ESM 包装
25
+ ├── src/scene/BootScene.js # 启动与全量加载
26
+ └── src/utils/asset-loader.js # 批量和单素材加载
27
+ ```
28
+
29
+ - 素材按类型放在 `assets/`,并登记到 `src/assets.js` 对应的分类表。
30
+ - 序列帧只登记文件夹;Loader 从 `manifest.json` 读取帧数和帧率。
31
+ - `BootScene` 加载游戏素材;运行时可用 `loadAsset(this, key)` 单独加载。
32
+ - 所有本地模块都使用原生 ESM 的显式 `.js` 后缀。
33
+
34
+ ## 可视化编辑标识
35
+
36
+ 用 `markEditable` 标记可在编辑器中调整的对象;第三个参数可选,仅用于编辑器展示名称:
37
+
38
+ ```js
39
+ markEditable('ui.start-button', button, { label: '开始按钮' });
40
+ ```
@@ -0,0 +1,6 @@
1
+ .codegraph/
2
+ .cozeproj/reduction
3
+ .preview
4
+
5
+ .DS_Store
6
+ ._*