@coze-arch/cli 0.0.37 → 0.1.3-alpha.32023e

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 (169) hide show
  1. package/README.md +69 -104
  2. package/docs/deploy.md +181 -0
  3. package/lib/__templates__/expo/.cozeproj/scripts/dev_build.sh +7 -3
  4. package/lib/__templates__/expo/.cozeproj/scripts/dev_run.sh +108 -19
  5. package/lib/__templates__/expo/.cozeproj/scripts/prepare-node-modules.sh +142 -0
  6. package/lib/__templates__/expo/.cozeproj/scripts/prod_build.sh +3 -3
  7. package/lib/__templates__/expo/.cozeproj/scripts/prod_run.sh +1 -1
  8. package/lib/__templates__/expo/.cozeproj/scripts/server_dev_run.sh +1 -1
  9. package/lib/__templates__/expo/client/metro.config.js +3 -1
  10. package/lib/__templates__/expo/pnpm-lock.yaml +5 -5
  11. package/lib/__templates__/expo/server/package.json +2 -2
  12. package/lib/__templates__/expo/server/src/index.ts +1 -1
  13. package/lib/__templates__/expo/template.config.js +11 -2
  14. package/lib/__templates__/native-static/.coze +4 -2
  15. package/lib/__templates__/native-static/scripts/serve.ps1 +7 -0
  16. package/lib/__templates__/nextjs/.coze +5 -0
  17. package/lib/__templates__/nextjs/package.json +2 -2
  18. package/lib/__templates__/nextjs/pnpm-lock.yaml +57 -341
  19. package/lib/__templates__/nextjs/scripts/build.ps1 +16 -0
  20. package/lib/__templates__/nextjs/scripts/build.sh +1 -1
  21. package/lib/__templates__/nextjs/scripts/dev.ps1 +63 -0
  22. package/lib/__templates__/nextjs/scripts/dev.sh +234 -7
  23. package/lib/__templates__/nextjs/scripts/prepare-node-modules.sh +142 -0
  24. package/lib/__templates__/nextjs/scripts/prepare.ps1 +18 -0
  25. package/lib/__templates__/nextjs/scripts/prepare.sh +1 -4
  26. package/lib/__templates__/nextjs/scripts/start.ps1 +11 -0
  27. package/lib/__templates__/nextjs/scripts/validate.ps1 +8 -0
  28. package/lib/__templates__/nextjs/template.config.js +0 -57
  29. package/lib/__templates__/nuxt-vue/.coze +5 -0
  30. package/lib/__templates__/nuxt-vue/package.json +1 -1
  31. package/lib/__templates__/nuxt-vue/pnpm-lock.yaml +5 -5
  32. package/lib/__templates__/nuxt-vue/scripts/build.ps1 +12 -0
  33. package/lib/__templates__/nuxt-vue/scripts/build.sh +1 -1
  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-node-modules.sh +142 -0
  37. package/lib/__templates__/nuxt-vue/scripts/prepare.ps1 +12 -0
  38. package/lib/__templates__/nuxt-vue/scripts/prepare.sh +1 -4
  39. package/lib/__templates__/nuxt-vue/scripts/start.ps1 +11 -0
  40. package/lib/__templates__/nuxt-vue/scripts/validate.ps1 +8 -0
  41. package/lib/__templates__/phaser/.coze +18 -0
  42. package/lib/__templates__/phaser/README.md +45 -0
  43. package/lib/__templates__/phaser/_gitignore +23 -0
  44. package/lib/__templates__/phaser/_npmrc +14 -0
  45. package/lib/__templates__/phaser/assets/image_sequence/coze-game-loading/frame_0001.png +0 -0
  46. package/lib/__templates__/phaser/assets/image_sequence/coze-game-loading/frame_0002.png +0 -0
  47. package/lib/__templates__/phaser/assets/image_sequence/coze-game-loading/frame_0003.png +0 -0
  48. package/lib/__templates__/phaser/assets/image_sequence/coze-game-loading/frame_0004.png +0 -0
  49. package/lib/__templates__/phaser/assets/image_sequence/coze-game-loading/frame_0005.png +0 -0
  50. package/lib/__templates__/phaser/assets/image_sequence/coze-game-loading/frame_0006.png +0 -0
  51. package/lib/__templates__/phaser/assets/image_sequence/coze-game-loading/frame_0007.png +0 -0
  52. package/lib/__templates__/phaser/assets/image_sequence/coze-game-loading/frame_0008.png +0 -0
  53. package/lib/__templates__/phaser/assets/image_sequence/coze-game-loading/frame_0009.png +0 -0
  54. package/lib/__templates__/phaser/assets/image_sequence/coze-game-loading/frame_0010.png +0 -0
  55. package/lib/__templates__/phaser/assets/image_sequence/coze-game-loading/frame_0011.png +0 -0
  56. package/lib/__templates__/phaser/assets/image_sequence/coze-game-loading/frame_0012.png +0 -0
  57. package/lib/__templates__/phaser/assets/image_sequence/coze-game-loading/frame_0013.png +0 -0
  58. package/lib/__templates__/phaser/assets/image_sequence/coze-game-loading/frame_0014.png +0 -0
  59. package/lib/__templates__/phaser/assets/image_sequence/coze-game-loading/frame_0015.png +0 -0
  60. package/lib/__templates__/phaser/assets/image_sequence/coze-game-loading/frame_0016.png +0 -0
  61. package/lib/__templates__/phaser/assets/image_sequence/coze-game-loading/frame_0017.png +0 -0
  62. package/lib/__templates__/phaser/assets/image_sequence/coze-game-loading/frame_0018.png +0 -0
  63. package/lib/__templates__/phaser/assets/image_sequence/coze-game-loading/frame_0019.png +0 -0
  64. package/lib/__templates__/phaser/assets/image_sequence/coze-game-loading/frame_0020.png +0 -0
  65. package/lib/__templates__/phaser/assets/image_sequence/coze-game-loading/frame_0021.png +0 -0
  66. package/lib/__templates__/phaser/assets/image_sequence/coze-game-loading/frame_0022.png +0 -0
  67. package/lib/__templates__/phaser/assets/image_sequence/coze-game-loading/frame_0023.png +0 -0
  68. package/lib/__templates__/phaser/assets/image_sequence/coze-game-loading/frame_0024.png +0 -0
  69. package/lib/__templates__/phaser/assets/image_sequence/coze-game-loading/manifest.json +11 -0
  70. package/lib/__templates__/phaser/debug-runtime/flow-graph.json +20 -0
  71. package/lib/__templates__/phaser/index.html +12 -0
  72. package/lib/__templates__/phaser/package.json +31 -0
  73. package/lib/__templates__/phaser/pnpm-lock.yaml +661 -0
  74. package/lib/__templates__/phaser/scripts/build.ps1 +12 -0
  75. package/lib/__templates__/phaser/scripts/build.sh +8 -0
  76. package/lib/__templates__/phaser/scripts/dev.ps1 +49 -0
  77. package/lib/__templates__/phaser/scripts/dev.sh +82 -0
  78. package/lib/__templates__/phaser/scripts/prepare.ps1 +8 -0
  79. package/lib/__templates__/phaser/scripts/prepare.sh +7 -0
  80. package/lib/__templates__/phaser/scripts/spawn-detached.cjs +29 -0
  81. package/lib/__templates__/phaser/scripts/start.ps1 +49 -0
  82. package/lib/__templates__/phaser/scripts/start.sh +82 -0
  83. package/lib/__templates__/phaser/scripts/validate.ps1 +7 -0
  84. package/lib/__templates__/phaser/scripts/validate.sh +7 -0
  85. package/lib/__templates__/phaser/src/assets.ts +15 -0
  86. package/lib/__templates__/phaser/src/global.d.ts +39 -0
  87. package/lib/__templates__/phaser/src/main.ts +28 -0
  88. package/lib/__templates__/phaser/src/scene/BootScene.ts +213 -0
  89. package/lib/__templates__/phaser/src/scene/DevLoadingScene.ts +69 -0
  90. package/lib/__templates__/phaser/src/style.css +28 -0
  91. package/lib/__templates__/phaser/src/types.ts +32 -0
  92. package/lib/__templates__/phaser/src/utils/asset-loader.ts +319 -0
  93. package/lib/__templates__/phaser/src/utils/index.ts +2 -0
  94. package/lib/__templates__/phaser/src/utils/mark-editable.ts +13 -0
  95. package/lib/__templates__/phaser/template.config.js +43 -0
  96. package/lib/__templates__/phaser/tsconfig.json +23 -0
  97. package/lib/__templates__/phaser/vite.config.ts +96 -0
  98. package/lib/__templates__/phaser-static/.coze +15 -0
  99. package/lib/__templates__/phaser-static/README.md +40 -0
  100. package/lib/__templates__/phaser-static/_gitignore +6 -0
  101. package/lib/__templates__/phaser-static/assets/image_sequence/coze-game-loading/frame_0001.png +0 -0
  102. package/lib/__templates__/phaser-static/assets/image_sequence/coze-game-loading/frame_0002.png +0 -0
  103. package/lib/__templates__/phaser-static/assets/image_sequence/coze-game-loading/frame_0003.png +0 -0
  104. package/lib/__templates__/phaser-static/assets/image_sequence/coze-game-loading/frame_0004.png +0 -0
  105. package/lib/__templates__/phaser-static/assets/image_sequence/coze-game-loading/frame_0005.png +0 -0
  106. package/lib/__templates__/phaser-static/assets/image_sequence/coze-game-loading/frame_0006.png +0 -0
  107. package/lib/__templates__/phaser-static/assets/image_sequence/coze-game-loading/frame_0007.png +0 -0
  108. package/lib/__templates__/phaser-static/assets/image_sequence/coze-game-loading/frame_0008.png +0 -0
  109. package/lib/__templates__/phaser-static/assets/image_sequence/coze-game-loading/frame_0009.png +0 -0
  110. package/lib/__templates__/phaser-static/assets/image_sequence/coze-game-loading/frame_0010.png +0 -0
  111. package/lib/__templates__/phaser-static/assets/image_sequence/coze-game-loading/frame_0011.png +0 -0
  112. package/lib/__templates__/phaser-static/assets/image_sequence/coze-game-loading/frame_0012.png +0 -0
  113. package/lib/__templates__/phaser-static/assets/image_sequence/coze-game-loading/frame_0013.png +0 -0
  114. package/lib/__templates__/phaser-static/assets/image_sequence/coze-game-loading/frame_0014.png +0 -0
  115. package/lib/__templates__/phaser-static/assets/image_sequence/coze-game-loading/frame_0015.png +0 -0
  116. package/lib/__templates__/phaser-static/assets/image_sequence/coze-game-loading/frame_0016.png +0 -0
  117. package/lib/__templates__/phaser-static/assets/image_sequence/coze-game-loading/frame_0017.png +0 -0
  118. package/lib/__templates__/phaser-static/assets/image_sequence/coze-game-loading/frame_0018.png +0 -0
  119. package/lib/__templates__/phaser-static/assets/image_sequence/coze-game-loading/frame_0019.png +0 -0
  120. package/lib/__templates__/phaser-static/assets/image_sequence/coze-game-loading/frame_0020.png +0 -0
  121. package/lib/__templates__/phaser-static/assets/image_sequence/coze-game-loading/frame_0021.png +0 -0
  122. package/lib/__templates__/phaser-static/assets/image_sequence/coze-game-loading/frame_0022.png +0 -0
  123. package/lib/__templates__/phaser-static/assets/image_sequence/coze-game-loading/frame_0023.png +0 -0
  124. package/lib/__templates__/phaser-static/assets/image_sequence/coze-game-loading/frame_0024.png +0 -0
  125. package/lib/__templates__/phaser-static/assets/image_sequence/coze-game-loading/manifest.json +11 -0
  126. package/lib/__templates__/phaser-static/debug-runtime/flow-graph.json +20 -0
  127. package/lib/__templates__/phaser-static/index.html +17 -0
  128. package/lib/__templates__/phaser-static/scripts/serve.ps1 +7 -0
  129. package/lib/__templates__/phaser-static/src/assets.js +15 -0
  130. package/lib/__templates__/phaser-static/src/main.js +26 -0
  131. package/lib/__templates__/phaser-static/src/phaser.js +7 -0
  132. package/lib/__templates__/phaser-static/src/scene/BootScene.js +205 -0
  133. package/lib/__templates__/phaser-static/src/scene/DevLoadingScene.js +67 -0
  134. package/lib/__templates__/phaser-static/src/style.css +28 -0
  135. package/lib/__templates__/phaser-static/src/utils/asset-loader.js +267 -0
  136. package/lib/__templates__/phaser-static/src/utils/index.js +2 -0
  137. package/lib/__templates__/phaser-static/src/utils/mark-editable.js +3 -0
  138. package/lib/__templates__/phaser-static/template.config.js +35 -0
  139. package/lib/__templates__/pi-agent/package.json +1 -1
  140. package/lib/__templates__/pi-agent/pnpm-lock.yaml +5 -5
  141. package/lib/__templates__/taro/.cozeproj/scripts/deploy_build.sh +3 -2
  142. package/lib/__templates__/taro/.cozeproj/scripts/deploy_run.sh +1 -1
  143. package/lib/__templates__/taro/.cozeproj/scripts/dev_build.sh +3 -0
  144. package/lib/__templates__/taro/.cozeproj/scripts/dev_run.sh +178 -68
  145. package/lib/__templates__/taro/.cozeproj/scripts/prepare-node-modules.sh +142 -0
  146. package/lib/__templates__/taro/AGENTS.md +11 -7
  147. package/lib/__templates__/taro/README.md +4 -4
  148. package/lib/__templates__/taro/config/index.ts +10 -2
  149. package/lib/__templates__/taro/pnpm-lock.yaml +5 -5
  150. package/lib/__templates__/taro/server/package.json +1 -1
  151. package/lib/__templates__/taro/server/src/main.ts +8 -2
  152. package/lib/__templates__/templates.json +51 -1
  153. package/lib/__templates__/vite/.coze +5 -0
  154. package/lib/__templates__/vite/package.json +1 -1
  155. package/lib/__templates__/vite/pnpm-lock.yaml +5 -5
  156. package/lib/__templates__/vite/scripts/build.ps1 +16 -0
  157. package/lib/__templates__/vite/scripts/build.sh +1 -1
  158. package/lib/__templates__/vite/scripts/dev.ps1 +63 -0
  159. package/lib/__templates__/vite/scripts/dev.sh +110 -1
  160. package/lib/__templates__/vite/scripts/prepare-node-modules.sh +142 -0
  161. package/lib/__templates__/vite/scripts/prepare.ps1 +18 -0
  162. package/lib/__templates__/vite/scripts/prepare.sh +1 -4
  163. package/lib/__templates__/vite/scripts/start.ps1 +11 -0
  164. package/lib/__templates__/vite/scripts/validate.ps1 +8 -0
  165. package/lib/__templates__/vite/template.config.js +0 -57
  166. package/lib/cli.js +3751 -222
  167. package/lib/deploy/package-project.js +244 -0
  168. package/lib/deploy/package-project.py +183 -0
  169. 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
+ ._*