@kurot/cli 1.1.0 → 1.1.1

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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Kelvin
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/package.json CHANGED
@@ -1,22 +1,22 @@
1
1
  {
2
2
  "name": "@kurot/cli",
3
- "version": "1.1.0",
3
+ "version": "1.1.1",
4
4
  "description": "Kurot CLI - build tool for the Kurot game engine",
5
+ "author": "Kelvin <kyia.x52@gmail.com>",
6
+ "license": "MIT",
5
7
  "type": "module",
8
+ "main": "./dist/define.js",
9
+ "types": "./dist/define.d.ts",
6
10
  "bin": {
7
11
  "kurot": "./dist/index.js"
8
12
  },
9
- "main": "./dist/define.js",
10
- "types": "./dist/define.d.ts",
11
13
  "files": [
12
14
  "dist",
13
15
  "templates",
14
16
  "README.md",
15
- "CHANGELOG.md"
17
+ "CHANGELOG.md",
18
+ "LICENSE"
16
19
  ],
17
- "publishConfig": {
18
- "access": "public"
19
- },
20
20
  "scripts": {
21
21
  "build": "tsc --build",
22
22
  "dev": "tsc --watch",
@@ -36,10 +36,20 @@
36
36
  "typescript": "^5.6.0",
37
37
  "vitest": "^4.1.4"
38
38
  },
39
- "packageManager": "pnpm@10.33.0",
39
+ "keywords": [
40
+ "kurot",
41
+ "cli",
42
+ "game-engine",
43
+ "exml",
44
+ "typescript"
45
+ ],
40
46
  "repository": {
41
47
  "type": "git",
42
48
  "url": "git+https://github.com/kurot-engine/kurot.git",
43
49
  "directory": "packages/cli"
50
+ },
51
+ "packageManager": "pnpm@10.33.0",
52
+ "publishConfig": {
53
+ "access": "public"
44
54
  }
45
55
  }
@@ -1,10 +1,10 @@
1
1
  /**
2
- * Kurot 空白项目模板
2
+ * Kurot empty project template.
3
3
  *
4
- * 最小化入口,只依赖 @kurot/core,不含 UI 组件。
5
- * 适用于纯 Canvas 渲染、自定义绘制、动画实验等场景。
4
+ * A minimal entry point that depends only on @kurot/core and contains no UI components.
5
+ * Suitable for Canvas rendering, custom drawing, and animation experiments.
6
6
  *
7
- * 生命周期:constructor → ADDED_TO_STAGE → $onAddToStage
7
+ * Lifecycle: constructor → ADDED_TO_STAGE → $onAddToStage
8
8
  */
9
9
  import { createPlayer, Sprite, Event } from '@kurot/core';
10
10
 
@@ -15,12 +15,12 @@ class Main extends Sprite {
15
15
  }
16
16
 
17
17
  private onAddedToStage(_event: Event): void {
18
- // 在这里编写你的游戏逻辑
18
+ // Add your game logic here.
19
19
  console.log('Kurot game started');
20
20
  }
21
21
  }
22
22
 
23
- // ── 启动 ──────────────────────────────────────────────────────────────────
23
+ // ── Bootstrap ─────────────────────────────────────────────────────────────
24
24
  const app = createPlayer({
25
25
  canvas: document.getElementById('gameCanvas') as HTMLCanvasElement,
26
26
  contentWidth: 640,
@@ -3,6 +3,9 @@
3
3
  "target": "ES2022",
4
4
  "module": "ESNext",
5
5
  "moduleResolution": "bundler",
6
+ "paths": {
7
+ "@/*": ["./src/*"]
8
+ },
6
9
  "strict": true,
7
10
  "skipLibCheck": true,
8
11
  },
@@ -12,5 +12,8 @@ export default {
12
12
  },
13
13
  exml: {
14
14
  themeFile: 'resource/default.thm.json',
15
+ namespaces: {
16
+ game: 'src/game-components.ts',
17
+ },
15
18
  },
16
19
  };
@@ -0,0 +1,25 @@
1
+ import { Texture, resource } from '@kurot/core';
2
+ import { DefaultAssetAdapter, type IAssetAdapter } from '@kurot/ui';
3
+
4
+ /**
5
+ * Connects UI asset sources to the project's resource system.
6
+ */
7
+ export class AssetAdapter implements IAssetAdapter {
8
+
9
+ private readonly fallback = new DefaultAssetAdapter();
10
+
11
+ getAsset(
12
+ source: string,
13
+ callback: (content: Texture | undefined, source: string) => void,
14
+ ): void {
15
+ const cached = resource.get<Texture>(source);
16
+ if (cached) {
17
+ callback(cached, source);
18
+ return;
19
+ }
20
+
21
+ resource.load<Texture>(source)
22
+ .then(content => callback(content, source))
23
+ .catch(() => this.fallback.getAsset(source, callback));
24
+ }
25
+ }
@@ -0,0 +1,39 @@
1
+ import { Event, type Stage } from '@kurot/core';
2
+
3
+ /**
4
+ * Manages application-specific foreground and background behavior.
5
+ *
6
+ * Kurot pauses and resumes its ticker automatically. Add project-level
7
+ * behavior such as music, network, or worker handling to this class.
8
+ */
9
+ export class LifecycleHandler {
10
+
11
+ private static _stage?: Stage;
12
+
13
+ static init(stage: Stage): void {
14
+ if (this._stage === stage) return;
15
+
16
+ this.clear();
17
+ this._stage = stage;
18
+ this._stage.addEventListener(Event.DEACTIVATE, this.onPause);
19
+ this._stage.addEventListener(Event.ACTIVATE, this.onResume);
20
+ }
21
+
22
+ static clear(): void {
23
+ if (!this._stage) return;
24
+
25
+ this._stage.removeEventListener(Event.DEACTIVATE, this.onPause);
26
+ this._stage.removeEventListener(Event.ACTIVATE, this.onResume);
27
+ this._stage = undefined;
28
+ }
29
+
30
+ private static onPause = (): void => {
31
+ // Add application-specific background behavior here.
32
+ console.log('paused');
33
+ };
34
+
35
+ private static onResume = (): void => {
36
+ // Add application-specific foreground behavior here.
37
+ console.log('resumed');
38
+ };
39
+ }
@@ -1,138 +1,145 @@
1
1
  /**
2
- * Kurot 标准游戏模板
2
+ * Kurot game project template.
3
3
  *
4
- * 使用 @kurot/core 进行 Canvas 绘制 + @kurot/game 补间动画。
5
- * 通过 Shape、TextField 等基础显示对象构建游戏场景。
4
+ * Builds a responsive interface with @kurot/ui and plays tween animations with @kurot/game.
6
5
  *
7
- * 生命周期:constructor → ADDED_TO_STAGE → $onAddToStage → runGame → loadResource → createGameScene → startAnimation
6
+ * Lifecycle: constructor → ADDED_TO_STAGE → $onAddToStage → runGame → load → createGameScene → startAnimation
8
7
  */
9
- import { createPlayer, TextField, Shape, Event, Stage, Texture, resource } from '@kurot/core';
10
- import { Button, DefaultAssetAdapter, Theme, UILayer, setAssetAdapter } from '@kurot/ui';
8
+ import { createPlayer, Event, resource } from '@kurot/core';
9
+ import { Button, Label, Rect, Theme, UILayer, setAssetAdapter } from '@kurot/ui';
11
10
  import { Tween } from '@kurot/game';
12
- import { LoadingUI } from './LoadingUI';
11
+ import { AssetAdapter } from '@/AssetAdapter';
12
+ import { LifecycleHandler } from '@/LifecycleHandler';
13
+ import { Preloader } from '@/Preloader';
13
14
 
14
15
  class Main extends UILayer {
16
+ private readonly _preloader = new Preloader();
17
+
15
18
  createChildren(): void {
16
19
  super.createChildren();
17
20
 
18
21
  const stage = this.stage;
19
22
  if (!stage) return;
20
23
 
21
- this.runGame(stage).catch(e => {
22
- console.log(e);
24
+ setAssetAdapter(new AssetAdapter());
25
+ LifecycleHandler.init(stage);
26
+
27
+ void this.runGame().catch(error => {
28
+ console.error('[Main] Unable to start game:', error);
23
29
  });
24
30
  }
25
31
 
26
- private async runGame(stage: Stage): Promise<void> {
27
- await this.loadResource(stage);
28
- this.installResourceAssetAdapter();
29
- await this.loadTheme();
30
- this.createGameScene(stage);
32
+ private async runGame(): Promise<void> {
33
+ await this.createPreloader();
34
+ await this.load();
35
+ this._preloader.destroy();
36
+ this.createGameScene();
31
37
  this.startAnimation();
32
38
  }
33
39
 
34
- /**
35
- * 让 EXML 中的 source="button_up_png" 优先解析为预加载图集的子纹理,
36
- * 普通 URL 图片仍交给默认适配器加载。
37
- */
38
- private installResourceAssetAdapter(): void {
39
- const fallback = new DefaultAssetAdapter();
40
- setAssetAdapter({
41
- getAsset: (source, callback) => {
42
- const texture = resource.get<Texture>(source);
43
- if (texture) {
44
- callback(texture, source);
45
- return;
46
- }
47
- fallback.getAsset(source, callback);
48
- },
49
- });
50
- }
40
+ private async load(): Promise<void> {
41
+ await this.updatePreloader('Loading resource configuration...', 1, 2);
42
+ await resource.loadConfig('resource/default.res.json', 'resource/');
51
43
 
52
- private async loadResource(stage: Stage): Promise<void> {
53
- const loadingView = new LoadingUI();
54
- stage.addChild(loadingView);
44
+ await this.updatePreloader('Loading theme...', 2, 2);
45
+ await this.loadTheme();
55
46
 
56
- try {
57
- await resource.loadConfig('resource/default.res.json', 'resource/');
58
- if (resource.hasGroup('preload')) {
59
- await resource.loadGroup('preload', 0, (loaded, total) => {
60
- loadingView.onProgress(loaded, total);
61
- });
62
- }
63
- } catch {
64
- // 资源配置文件不存在时静默跳过(纯代码项目无需资源配置)
47
+ if (resource.hasGroup('preload')) {
48
+ this._preloader.updateText('Loading resources...');
49
+ await resource.loadGroup('preload', 0, (loaded, total) => {
50
+ this._preloader.onProgress(loaded, total);
51
+ });
65
52
  }
53
+ }
54
+
55
+ private async createPreloader(): Promise<void> {
56
+ await this._preloader.init();
57
+ this.addChild(this._preloader);
58
+ }
66
59
 
67
- stage.removeChild(loadingView);
60
+ private async updatePreloader(message: string, current: number, total: number): Promise<void> {
61
+ this._preloader.updateText(message);
62
+ this._preloader.updateProgress(current, total);
63
+ await this.wait(0.1);
68
64
  }
69
65
 
70
66
  private async loadTheme(): Promise<void> {
71
- // 加载主题(UI 组件默认皮肤映射)
67
+ // Load the theme and its default UI skin mappings.
72
68
  const theme = new Theme('resource/default.thm.json');
73
69
  await new Promise<void>(resolve => theme.addEventListener(Event.COMPLETE, () => resolve()));
74
70
  }
75
71
 
76
- private textfield!: TextField;
72
+ private wait(timeout: number): Promise<void> {
73
+ return new Promise<void>(resolve => {
74
+ setTimeout(resolve, timeout * 1000);
75
+ });
76
+ }
77
+
78
+ private textfield!: Label;
77
79
 
78
80
  /**
79
- * 创建游戏场景
81
+ * Create the game scene.
80
82
  *
81
- * 使用 Shape(矢量绘制)和 TextField(文本)等基础显示对象搭建画面。
83
+ * Build a responsive view with EUI components and constraint-based layout.
82
84
  */
83
- private createGameScene(stage: Stage): void {
84
- const stageW = stage.stageWidth;
85
- const stageH = stage.stageHeight;
86
-
87
- // 背景色块
88
- const sky = new Shape();
89
- sky.graphics.beginFill(0x2d3436, 1);
90
- sky.graphics.drawRect(0, 0, stageW, stageH);
91
- sky.graphics.endFill();
85
+ private createGameScene(): void {
86
+ // Responsive background
87
+ const sky = new Rect();
88
+ sky.left = 0;
89
+ sky.right = 0;
90
+ sky.top = 0;
91
+ sky.bottom = 0;
92
+ sky.fillColor = 0x2d3436;
92
93
  this.addChild(sky);
93
94
 
94
- // 半透明顶栏
95
- const topMask = new Shape();
96
- topMask.graphics.beginFill(0x000000, 0.5);
97
- topMask.graphics.drawRect(0, 0, stageW, 172);
98
- topMask.graphics.endFill();
99
- topMask.y = 33;
95
+ // Translucent header
96
+ const topMask = new Rect();
97
+ topMask.left = 0;
98
+ topMask.right = 0;
99
+ topMask.top = 33;
100
+ topMask.height = 172;
101
+ topMask.fillColor = 0x000000;
102
+ topMask.fillAlpha = 0.5;
100
103
  this.addChild(topMask);
101
104
 
102
- // 标题文本
103
- const colorLabel = new TextField();
105
+ // Title
106
+ const colorLabel = new Label();
104
107
  colorLabel.textColor = 0xffffff;
105
- colorLabel.width = stageW;
108
+ colorLabel.left = 0;
109
+ colorLabel.right = 0;
110
+ colorLabel.top = 80;
111
+ colorLabel.height = 48;
106
112
  colorLabel.textAlign = 'center';
113
+ colorLabel.verticalAlign = 'middle';
107
114
  colorLabel.text = 'Hello Kurot';
108
115
  colorLabel.size = 36;
109
- colorLabel.x = 0;
110
- colorLabel.y = 80;
111
116
  this.addChild(colorLabel);
112
117
 
113
- // 描述文本(用于动画)
114
- const textfield = new TextField();
118
+ // Animated description
119
+ const textfield = new Label();
115
120
  this.addChild(textfield);
116
121
  textfield.alpha = 0;
117
- textfield.width = stageW;
122
+ textfield.left = 0;
123
+ textfield.right = 0;
124
+ textfield.top = 135;
125
+ textfield.height = 36;
118
126
  textfield.textAlign = 'center';
127
+ textfield.verticalAlign = 'middle';
119
128
  textfield.size = 24;
120
129
  textfield.textColor = 0xffffff;
121
- textfield.x = 0;
122
- textfield.y = 135;
123
130
  this.textfield = textfield;
124
131
 
125
- // UI 按钮示例(需要默认主题)
132
+ // UI button using the default theme
126
133
  const button = new Button();
127
134
  button.label = 'Click Me';
128
- button.x = (stageW - 200) / 2;
129
- button.y = 200;
135
+ button.horizontalCenter = 0;
136
+ button.top = 200;
130
137
  button.width = 200;
131
138
  this.addChild(button);
132
139
  }
133
140
 
134
141
  /**
135
- * 播放文本淡入淡出动画
142
+ * Play a looping text fade animation.
136
143
  */
137
144
  private startAnimation(): void {
138
145
  const texts = ['Open-source, Free, Multi-platform', 'Push Game Forward', 'HTML5 Game Engine'];
@@ -153,7 +160,7 @@ class Main extends UILayer {
153
160
  }
154
161
  }
155
162
 
156
- // ── 启动 ──────────────────────────────────────────────────────────────────
163
+ // ── Bootstrap ─────────────────────────────────────────────────────────────
157
164
  const app = createPlayer({
158
165
  canvas: document.getElementById('gameCanvas') as HTMLCanvasElement,
159
166
  contentWidth: 640,
@@ -0,0 +1,112 @@
1
+ import { Group, Label, Rect, UILayer } from '@kurot/ui';
2
+
3
+ /**
4
+ * Displays project startup and resource-loading progress.
5
+ */
6
+ export class Preloader extends UILayer {
7
+
8
+ private readonly _background = new Rect();
9
+ private readonly _title = new Label();
10
+ private readonly _tip = new Label();
11
+ private readonly _progress = new Group();
12
+ private readonly _track = new Rect();
13
+ private readonly _thumb = new Rect();
14
+ private readonly _thumbMask = new Rect();
15
+
16
+ init(): Promise<void> {
17
+ this.createBackground();
18
+ this.createTitle();
19
+ this.createProgress();
20
+ this.createTip();
21
+ return Promise.resolve();
22
+ }
23
+
24
+ onProgress(current: number, total: number): void {
25
+ if (total <= 0) return;
26
+ const width = this.getMaskMaxWidth();
27
+ this._thumbMask.width = width * 0.7 + width * 0.3 * (current / total);
28
+ }
29
+
30
+ updateProgress(current: number, total: number): void {
31
+ if (total <= 0) return;
32
+ this._thumbMask.width = this.getMaskMaxWidth() * 0.7 * current / total;
33
+ }
34
+
35
+ updateText(text: string): void {
36
+ this._tip.text = text;
37
+ }
38
+
39
+ destroy(): void {
40
+ if (this.parent) {
41
+ this.parent.removeChild(this);
42
+ }
43
+ }
44
+
45
+ private createBackground(): void {
46
+ this.addChild(this._background);
47
+ this._background.left = 0;
48
+ this._background.right = 0;
49
+ this._background.top = 0;
50
+ this._background.bottom = 0;
51
+ this._background.fillColor = 0x2d3436;
52
+ }
53
+
54
+ private createTitle(): void {
55
+ this.addChild(this._title);
56
+ this._title.text = 'KUROT';
57
+ this._title.size = 48;
58
+ this._title.bold = true;
59
+ this._title.width = 400;
60
+ this._title.height = 64;
61
+ this._title.textAlign = 'center';
62
+ this._title.verticalAlign = 'middle';
63
+ this._title.horizontalCenter = 0;
64
+ this._title.verticalCenter = -80;
65
+ }
66
+
67
+ private createProgress(): void {
68
+ this.addChild(this._progress);
69
+ this._progress.width = 520;
70
+ this._progress.height = 24;
71
+ this._progress.horizontalCenter = 0;
72
+ this._progress.verticalCenter = 10;
73
+
74
+ this._progress.addChild(this._track);
75
+ this._track.left = 0;
76
+ this._track.right = 0;
77
+ this._track.top = 0;
78
+ this._track.bottom = 0;
79
+ this._track.fillColor = 0xffffff;
80
+
81
+ this._progress.addChild(this._thumb);
82
+ this._thumb.left = 4;
83
+ this._thumb.right = 4;
84
+ this._thumb.top = 4;
85
+ this._thumb.bottom = 4;
86
+ this._thumb.fillColor = 0x00a8ff;
87
+
88
+ this._progress.addChild(this._thumbMask);
89
+ this._thumbMask.left = 4;
90
+ this._thumbMask.top = 4;
91
+ this._thumbMask.bottom = 4;
92
+ this._thumbMask.width = 0;
93
+ this._thumb.mask = this._thumbMask;
94
+ }
95
+
96
+ private createTip(): void {
97
+ this.addChild(this._tip);
98
+ this._tip.text = 'Starting...';
99
+ this._tip.size = 20;
100
+ this._tip.width = 520;
101
+ this._tip.height = 40;
102
+ this._tip.textAlign = 'center';
103
+ this._tip.verticalAlign = 'middle';
104
+ this._tip.horizontalCenter = 0;
105
+ this._tip.verticalCenter = 60;
106
+ }
107
+
108
+ private getMaskMaxWidth(): number {
109
+ const inset = Number(this._thumbMask.left) || 0;
110
+ return this._progress.width - inset * 2;
111
+ }
112
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Central export entry for custom EXML components.
3
+ *
4
+ * Export components used as `<game:ComponentName />` in EXML from this file.
5
+ * This entry is configured by `exml.namespaces.game` in `kurot.config.ts`.
6
+ */
@@ -3,6 +3,9 @@
3
3
  "target": "ES2022",
4
4
  "module": "ESNext",
5
5
  "moduleResolution": "bundler",
6
+ "paths": {
7
+ "@/*": ["./src/*"]
8
+ },
6
9
  "strict": true,
7
10
  "skipLibCheck": true,
8
11
  },
@@ -1,27 +0,0 @@
1
- /**
2
- * 资源加载进度界面
3
- *
4
- * 在资源加载期间显示 "Loading...current/total" 文本。
5
- * 对应 Egret 的 LoadingUI。
6
- */
7
- import { Sprite, TextField } from '@kurot/core';
8
-
9
- class LoadingUI extends Sprite {
10
- private textField: TextField;
11
-
12
- public constructor() {
13
- super();
14
- this.textField = new TextField();
15
- this.addChild(this.textField);
16
- this.textField.y = 300;
17
- this.textField.width = 480;
18
- this.textField.height = 100;
19
- this.textField.textAlign = 'center';
20
- }
21
-
22
- public onProgress(current: number, total: number): void {
23
- this.textField.text = `Loading...${current}/${total}`;
24
- }
25
- }
26
-
27
- export { LoadingUI };