@playcraft/cli 0.0.53 → 0.0.55
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/dist/commands/create.js +164 -98
- package/dist/index.js +4 -1
- package/dist/scaffold/templates.js +35 -0
- package/package.json +3 -3
- package/project-templates/playable-template-phaser/.playcraftignore +10 -0
- package/project-templates/playable-template-phaser/CLAUDE.md +178 -0
- package/project-templates/playable-template-phaser/PLAYCRAFT-MIGRATION.md +328 -0
- package/project-templates/playable-template-phaser/README.md +381 -0
- package/project-templates/playable-template-phaser/assets/brain.png +0 -0
- package/project-templates/playable-template-phaser/assets/button.png +0 -0
- package/project-templates/playable-template-phaser/assets/interact.png +0 -0
- package/project-templates/playable-template-phaser/assets/logo.webp +0 -0
- package/project-templates/playable-template-phaser/assets/warn_brain.png +0 -0
- package/project-templates/playable-template-phaser/builds.config.js +12 -0
- package/project-templates/playable-template-phaser/globals.d.ts +2 -0
- package/project-templates/playable-template-phaser/index.html +12 -0
- package/project-templates/playable-template-phaser/package.json +45 -0
- package/project-templates/playable-template-phaser/scripts/base64-to-mp3.js +340 -0
- package/project-templates/playable-template-phaser/scripts/convert-to-webp.js +370 -0
- package/project-templates/playable-template-phaser/src/Game.ts +154 -0
- package/project-templates/playable-template-phaser/src/config/LoadingConfig.ts +40 -0
- package/project-templates/playable-template-phaser/src/config/ThemeConfig.ts +81 -0
- package/project-templates/playable-template-phaser/src/index.css +18 -0
- package/project-templates/playable-template-phaser/src/index.html +9 -0
- package/project-templates/playable-template-phaser/src/index.ts +30 -0
- package/project-templates/playable-template-phaser/src/json5.d.ts +4 -0
- package/project-templates/playable-template-phaser/src/managers/AudioManager.ts +559 -0
- package/project-templates/playable-template-phaser/src/scenes/MainScene.ts +301 -0
- package/project-templates/playable-template-phaser/src/scenes/PreloadScene.ts +256 -0
- package/project-templates/playable-template-phaser/src/theme/README.md +194 -0
- package/project-templates/playable-template-phaser/src/theme/blue-theme/assets/.gitkeep +24 -0
- package/project-templates/playable-template-phaser/src/theme/blue-theme/index.ts +27 -0
- package/project-templates/playable-template-phaser/src/theme/blue-theme/theme.data.json5 +19 -0
- package/project-templates/playable-template-phaser/src/theme/green-theme/assets/.gitkeep +3 -0
- package/project-templates/playable-template-phaser/src/theme/green-theme/index.ts +20 -0
- package/project-templates/playable-template-phaser/src/theme/green-theme/theme.data.json5 +19 -0
- package/project-templates/playable-template-phaser/src/theme/index.ts +1 -0
- package/project-templates/playable-template-phaser/src/theme/red-theme/assets/.gitkeep +3 -0
- package/project-templates/playable-template-phaser/src/theme/red-theme/index.ts +20 -0
- package/project-templates/playable-template-phaser/src/theme/red-theme/theme.data.json5 +19 -0
- package/project-templates/playable-template-phaser/src/theme/theme.metadata.md +60 -0
- package/project-templates/playable-template-phaser/src/theme/theme.schema.json5 +149 -0
- package/project-templates/playable-template-phaser/src/ui/BrainAgeUI.ts +681 -0
- package/project-templates/playable-template-phaser/src/ui/CountdownUI.ts +565 -0
- package/project-templates/playable-template-phaser/src/ui/PlayButton.ts +51 -0
- package/project-templates/playable-template-phaser/src/ui/ScoreUI.ts +207 -0
- package/project-templates/playable-template-phaser/src/ui/SuccessUI.ts +43 -0
- package/project-templates/playable-template-phaser/src/ui/index.ts +15 -0
- package/project-templates/playable-template-phaser/tsconfig.json +26 -0
- package/project-templates/playable-template-phaser/vite.config.ts +64 -0
- package/project-templates/playable-template-phaser//346/226/260/346/211/213/345/274/200/345/217/221/346/214/207/345/215/227.md +209 -0
- package/project-templates/playable-template-three/README.md +795 -0
- package/project-templates/playable-template-three/assets/Stork.glb +0 -0
- package/project-templates/playable-template-three/assets/button.png +0 -0
- package/project-templates/playable-template-three/assets/logo.webp +0 -0
- package/project-templates/playable-template-three/build.json +7 -0
- package/project-templates/playable-template-three/builds.config.example.js +151 -0
- package/project-templates/playable-template-three/builds.config.js +158 -0
- package/project-templates/playable-template-three/package.json +36 -0
- package/project-templates/playable-template-three/scripts/convert-to-webp.js +370 -0
- package/project-templates/playable-template-three/scripts/theme-builds.js +514 -0
- package/project-templates/playable-template-three/src/Game.ts +215 -0
- package/project-templates/playable-template-three/src/config/LoadingConfig.ts +27 -0
- package/project-templates/playable-template-three/src/config/ThemeConfig.ts +36 -0
- package/project-templates/playable-template-three/src/global.d.ts +108 -0
- package/project-templates/playable-template-three/src/index.css +18 -0
- package/project-templates/playable-template-three/src/index.html +9 -0
- package/project-templates/playable-template-three/src/index.ts +20 -0
- package/project-templates/playable-template-three/src/managers/AudioManager.ts +538 -0
- package/project-templates/playable-template-three/src/scenes/PreloadScene.ts +305 -0
- package/project-templates/playable-template-three/src/theme/README.md +225 -0
- package/project-templates/playable-template-three/src/theme/blue-theme/assets/.gitkeep +28 -0
- package/project-templates/playable-template-three/src/theme/blue-theme/index.ts +45 -0
- package/project-templates/playable-template-three/src/theme/current.ts +12 -0
- package/project-templates/playable-template-three/src/theme/green-theme/assets/.gitkeep +3 -0
- package/project-templates/playable-template-three/src/theme/green-theme/index.ts +34 -0
- package/project-templates/playable-template-three/src/theme/red-theme/assets/.gitkeep +3 -0
- package/project-templates/playable-template-three/src/theme/red-theme/index.ts +34 -0
- package/project-templates/playable-template-three/tsconfig.json +24 -0
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
import * as Phaser from 'phaser';
|
|
2
|
+
import { sdk } from '@playcraft/adsdk';
|
|
3
|
+
import { AudioManager } from '../managers/AudioManager';
|
|
4
|
+
import { GameThemeConfig } from '../config/ThemeConfig';
|
|
5
|
+
// 导入 UI 组件
|
|
6
|
+
import { BrainAgeUI } from '../ui/BrainAgeUI';
|
|
7
|
+
import { CountdownUI } from '../ui/CountdownUI';
|
|
8
|
+
import { ScoreUI } from '../ui/ScoreUI';
|
|
9
|
+
import { SuccessUI } from '../ui/SuccessUI';
|
|
10
|
+
import { PlayButton } from '../ui/PlayButton';
|
|
11
|
+
// 导入大脑图片资源
|
|
12
|
+
import brainIcon from 'assets/brain.png';
|
|
13
|
+
import warnBrainIcon from 'assets/warn_brain.png';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* 主游戏场景
|
|
17
|
+
*
|
|
18
|
+
* 负责:
|
|
19
|
+
* - 游戏主要逻辑
|
|
20
|
+
* - UI 交互
|
|
21
|
+
* - 应用主题配置
|
|
22
|
+
*
|
|
23
|
+
* ========================================
|
|
24
|
+
* 以下为 UI 组件使用示例,请根据实际需求裁剪
|
|
25
|
+
* ========================================
|
|
26
|
+
*/
|
|
27
|
+
export class MainScene extends Phaser.Scene {
|
|
28
|
+
private installButton!: Phaser.GameObjects.Container;
|
|
29
|
+
public audioManager!: AudioManager;
|
|
30
|
+
|
|
31
|
+
// --- UI 组件实例(按需选用) ---
|
|
32
|
+
private brainAgeUI?: BrainAgeUI;
|
|
33
|
+
private countdownUI?: CountdownUI;
|
|
34
|
+
private scoreUI?: ScoreUI;
|
|
35
|
+
private isGameOver: boolean = false;
|
|
36
|
+
// 匹配成功次数(用于进度追踪)
|
|
37
|
+
private matchCount: number = 0;
|
|
38
|
+
// 目标匹配次数(进度到 100% 所需次数)
|
|
39
|
+
private readonly totalMatchCount: number = 2;
|
|
40
|
+
|
|
41
|
+
// 挑战是否已开始(用于标记首次交互)
|
|
42
|
+
private challengeStarted: boolean = false;
|
|
43
|
+
|
|
44
|
+
// 演示定时器
|
|
45
|
+
private demoTimer?: number;
|
|
46
|
+
|
|
47
|
+
constructor() {
|
|
48
|
+
super({ key: 'MainScene' });
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
create() {
|
|
52
|
+
// 应用主题背景色
|
|
53
|
+
this.cameras.main.setBackgroundColor(GameThemeConfig.BACKGROUND_COLOR);
|
|
54
|
+
|
|
55
|
+
// 初始化 AudioManager
|
|
56
|
+
this.audioManager = new AudioManager({
|
|
57
|
+
bgmVolume: 0.5,
|
|
58
|
+
effectVolume: 0.6,
|
|
59
|
+
enableBackgroundPause: true,
|
|
60
|
+
watchdogInterval: 500
|
|
61
|
+
});
|
|
62
|
+
this.audioManager.init(this);
|
|
63
|
+
|
|
64
|
+
// ========================================
|
|
65
|
+
// 示例 1:ScoreUI — 积分显示
|
|
66
|
+
// 注意:ScoreUI 已改为纯 DOM 实现
|
|
67
|
+
// ========================================
|
|
68
|
+
this.scoreUI = new ScoreUI(40, 15, 0); // x, y, 初始分数
|
|
69
|
+
|
|
70
|
+
// 启动演示定时器:每秒 +10 分
|
|
71
|
+
this.demoTimer = window.setInterval(() => {
|
|
72
|
+
this.scoreUI?.add(10);
|
|
73
|
+
}, 1000);
|
|
74
|
+
|
|
75
|
+
// ========================================
|
|
76
|
+
// 示例 2:CountdownUI — LED 数码管倒计时(自动开始)
|
|
77
|
+
// 注意:CountdownUI 已改为纯 DOM 实现,创建后自动倒计时
|
|
78
|
+
// 参数:x坐标, y坐标, 配置对象
|
|
79
|
+
// 传入 window.innerWidth / 2 实现水平居中
|
|
80
|
+
// ========================================
|
|
81
|
+
this.countdownUI = new CountdownUI(window.innerWidth / 2, 40, {
|
|
82
|
+
initialSeconds: 30,
|
|
83
|
+
warnTime: 10,
|
|
84
|
+
onGameOver: () => this.onGameOver('countdown'),
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
// ========================================
|
|
88
|
+
// 示例 3:BrainAgeUI — 脑龄面板(自动开始)
|
|
89
|
+
// 注意:BrainAgeUI 已改为纯 DOM 实现,创建后自动计时
|
|
90
|
+
// 使用从 assets 导入的图片资源
|
|
91
|
+
// ========================================
|
|
92
|
+
this.brainAgeUI = new BrainAgeUI(30, 80, brainIcon, {
|
|
93
|
+
scale: 0.65,
|
|
94
|
+
}, {
|
|
95
|
+
initial: 60, max: 100, warningAge: 90,
|
|
96
|
+
iconUrl: brainIcon, // 正常状态图片(从 assets 导入)
|
|
97
|
+
warnIconUrl: warnBrainIcon, // 警告状态图片(从 assets 导入)
|
|
98
|
+
onGameOver: () => this.onGameOver('brainAge'),
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
// ========================================
|
|
102
|
+
// Install 按钮(模板自带)
|
|
103
|
+
// ========================================
|
|
104
|
+
this.installButton = this.add.container(this.cameras.main.centerX, this.cameras.main.centerY);
|
|
105
|
+
const animationContainer = this.add.container(0, 0);
|
|
106
|
+
this.installButton.add(animationContainer);
|
|
107
|
+
const buttonBackground = this.add.image(0, 0, 'button');
|
|
108
|
+
buttonBackground.setScale(0.35);
|
|
109
|
+
const installText = this.add
|
|
110
|
+
.text(0, 0, 'Install', {
|
|
111
|
+
fontFamily: 'cursive',
|
|
112
|
+
fontSize: GameThemeConfig.FONT_SIZE_BUTTON,
|
|
113
|
+
color: GameThemeConfig.TEXT_COLOR,
|
|
114
|
+
fontStyle: 'bold'
|
|
115
|
+
})
|
|
116
|
+
.setOrigin(0.5);
|
|
117
|
+
installText.setShadow(4, 4, '#fffc6a', 9, true, true);
|
|
118
|
+
animationContainer.add([buttonBackground, installText]);
|
|
119
|
+
this.tweens.add({
|
|
120
|
+
targets: animationContainer,
|
|
121
|
+
scaleX: 1.1,
|
|
122
|
+
scaleY: 1.1,
|
|
123
|
+
duration: 800,
|
|
124
|
+
yoyo: true,
|
|
125
|
+
repeat: -1,
|
|
126
|
+
ease: 'Sine.easeInOut'
|
|
127
|
+
});
|
|
128
|
+
buttonBackground.setInteractive({ useHandCursor: true });
|
|
129
|
+
buttonBackground.on('pointerdown', () => {
|
|
130
|
+
// 首次点击时标记挑战开始
|
|
131
|
+
if (!this.challengeStarted) {
|
|
132
|
+
this.challengeStarted = true;
|
|
133
|
+
sdk.playcraftChallengeStart();
|
|
134
|
+
}
|
|
135
|
+
sdk.playcraftInstall();
|
|
136
|
+
});
|
|
137
|
+
// 全部加载完成,触发页面可以交互
|
|
138
|
+
sdk.playcraftInteractive();
|
|
139
|
+
|
|
140
|
+
// ========================================
|
|
141
|
+
// 调试按钮:手动触发埋点事件
|
|
142
|
+
// ========================================
|
|
143
|
+
this.createDebugButtons();
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* 匹配成功时调用(示例)
|
|
148
|
+
*/
|
|
149
|
+
onMatchSuccess(): void {
|
|
150
|
+
// 首次交互时标记挑战开始
|
|
151
|
+
if (!this.challengeStarted) {
|
|
152
|
+
this.challengeStarted = true;
|
|
153
|
+
sdk.playcraftChallengeStart();
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
this.scoreUI?.add(5);
|
|
157
|
+
this.brainAgeUI?.onMatchSuccess();
|
|
158
|
+
this.matchCount++;
|
|
159
|
+
const percent = Math.min(100, Math.floor((this.matchCount / this.totalMatchCount) * 100));
|
|
160
|
+
sdk.recordPlaycraftChallengeProgress(percent);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* 创建调试按钮(开发测试用)
|
|
165
|
+
*/
|
|
166
|
+
private createDebugButtons(): void {
|
|
167
|
+
const buttonWidth = 140;
|
|
168
|
+
const buttonHeight = 50;
|
|
169
|
+
const buttonSpacing = 10;
|
|
170
|
+
const startX = 20;
|
|
171
|
+
// 使用 cameras.main.height 获取相机高度
|
|
172
|
+
const startY = this.cameras.main.height - 180;
|
|
173
|
+
|
|
174
|
+
console.log('[调试按钮] 创建调试按钮,屏幕高度:', this.cameras.main.height);
|
|
175
|
+
|
|
176
|
+
// 按钮配置
|
|
177
|
+
const buttons = [
|
|
178
|
+
{
|
|
179
|
+
text: '挑战开始',
|
|
180
|
+
color: 0x9C27B0,
|
|
181
|
+
hoverColor: 0xAB47BC,
|
|
182
|
+
onClick: () => {
|
|
183
|
+
if (!this.challengeStarted) {
|
|
184
|
+
this.challengeStarted = true;
|
|
185
|
+
sdk.playcraftChallengeStart();
|
|
186
|
+
console.log('[调试] 手动触发挑战开始');
|
|
187
|
+
} else {
|
|
188
|
+
console.log('[调试] 挑战已经开始过了');
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
},
|
|
192
|
+
{
|
|
193
|
+
text: '匹配成功',
|
|
194
|
+
color: 0x4CAF50,
|
|
195
|
+
hoverColor: 0x66BB6A,
|
|
196
|
+
onClick: () => this.onMatchSuccess()
|
|
197
|
+
},
|
|
198
|
+
{
|
|
199
|
+
text: '发送成功',
|
|
200
|
+
color: 0x2196F3,
|
|
201
|
+
hoverColor: 0x42A5F5,
|
|
202
|
+
onClick: () => sdk.playcraftRecordSuccess()
|
|
203
|
+
},
|
|
204
|
+
{
|
|
205
|
+
text: '发送失败',
|
|
206
|
+
color: 0xF44336,
|
|
207
|
+
hoverColor: 0xEF5350,
|
|
208
|
+
onClick: () => sdk.playcraftRecordFailed()
|
|
209
|
+
}
|
|
210
|
+
];
|
|
211
|
+
|
|
212
|
+
buttons.forEach((config, index) => {
|
|
213
|
+
const y = startY + index * (buttonHeight + buttonSpacing);
|
|
214
|
+
|
|
215
|
+
// 创建按钮背景(直接创建,不使用 container)
|
|
216
|
+
const background = this.add.rectangle(startX + buttonWidth / 2, y + buttonHeight / 2, buttonWidth, buttonHeight, config.color, 0.9);
|
|
217
|
+
background.setStrokeStyle(2, 0xffffff, 0.8);
|
|
218
|
+
background.setDepth(1000); // 设置高层级,确保在最上层
|
|
219
|
+
|
|
220
|
+
// 创建按钮文字
|
|
221
|
+
const text = this.add.text(startX + buttonWidth / 2, y + buttonHeight / 2, config.text, {
|
|
222
|
+
fontFamily: 'Arial',
|
|
223
|
+
fontSize: '16px',
|
|
224
|
+
color: '#ffffff',
|
|
225
|
+
fontStyle: 'bold'
|
|
226
|
+
}).setOrigin(0.5);
|
|
227
|
+
text.setDepth(1001); // 文字层级比背景高
|
|
228
|
+
|
|
229
|
+
// 添加交互
|
|
230
|
+
background.setInteractive({ useHandCursor: true });
|
|
231
|
+
|
|
232
|
+
background.on('pointerover', () => {
|
|
233
|
+
background.setFillStyle(config.hoverColor, 0.9);
|
|
234
|
+
background.setScale(1.05);
|
|
235
|
+
text.setScale(1.05);
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
background.on('pointerout', () => {
|
|
239
|
+
background.setFillStyle(config.color, 0.9);
|
|
240
|
+
background.setScale(1);
|
|
241
|
+
text.setScale(1);
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
background.on('pointerdown', () => {
|
|
245
|
+
background.setScale(0.95);
|
|
246
|
+
text.setScale(0.95);
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
background.on('pointerup', () => {
|
|
250
|
+
background.setScale(1.05);
|
|
251
|
+
text.setScale(1.05);
|
|
252
|
+
config.onClick();
|
|
253
|
+
console.log(`[调试按钮] ${config.text} 被点击`);
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
console.log(`[调试按钮] 创建按钮 "${config.text}",位置: (${startX + buttonWidth / 2}, ${y + buttonHeight / 2})`);
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* 游戏通关时调用(示例)
|
|
262
|
+
*/
|
|
263
|
+
onGameWin(): void {
|
|
264
|
+
new SuccessUI(this).show();
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* 游戏结束处理(示例)
|
|
269
|
+
*/
|
|
270
|
+
onGameOver(reason: 'countdown' | 'brainAge'): void {
|
|
271
|
+
if (this.isGameOver) return;
|
|
272
|
+
this.isGameOver = true;
|
|
273
|
+
|
|
274
|
+
if (reason === 'countdown') {
|
|
275
|
+
this.countdownUI?.showGameOver();
|
|
276
|
+
} else {
|
|
277
|
+
this.brainAgeUI?.showGameOver();
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
new PlayButton(this).show(
|
|
281
|
+
this.scale.width / 2,
|
|
282
|
+
this.scale.height / 2 + 60,
|
|
283
|
+
0.45,
|
|
284
|
+
302,
|
|
285
|
+
);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
update() {
|
|
289
|
+
if (this.installButton) {
|
|
290
|
+
this.installButton.setPosition(this.cameras.main.centerX, this.cameras.main.centerY);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
shutdown() {
|
|
295
|
+
// 清理演示定时器
|
|
296
|
+
if (this.demoTimer) {
|
|
297
|
+
clearInterval(this.demoTimer);
|
|
298
|
+
this.demoTimer = undefined;
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
}
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Preload 场景(原 Loading 场景)
|
|
3
|
+
* 显示 App Logo、名称、评分、描述和加载动画
|
|
4
|
+
* 支持横竖屏自适应
|
|
5
|
+
*
|
|
6
|
+
* 负责预加载所有游戏资源,加载完成后启动 MainScene
|
|
7
|
+
*/
|
|
8
|
+
import * as Phaser from 'phaser';
|
|
9
|
+
import { sdk } from '@playcraft/adsdk';
|
|
10
|
+
import { LoadingConfig } from '../config/LoadingConfig';
|
|
11
|
+
// 导入所有需要预加载的资源
|
|
12
|
+
import buttonBg from 'assets/button.png';
|
|
13
|
+
// UI 组件依赖的资源
|
|
14
|
+
import interactImage from 'assets/interact.png';
|
|
15
|
+
import brainIcon from 'assets/brain.png';
|
|
16
|
+
import warnBrainIcon from 'assets/warn_brain.png';
|
|
17
|
+
|
|
18
|
+
export class PreloadScene extends Phaser.Scene {
|
|
19
|
+
// UI 元素
|
|
20
|
+
private logoElement!: HTMLImageElement;
|
|
21
|
+
private loadingText!: Phaser.GameObjects.Text;
|
|
22
|
+
private gamepadIcon!: Phaser.GameObjects.Text;
|
|
23
|
+
|
|
24
|
+
// 动画相关
|
|
25
|
+
private dotCount: number = 0;
|
|
26
|
+
private dotTimer!: Phaser.Time.TimerEvent;
|
|
27
|
+
|
|
28
|
+
// 加载状态
|
|
29
|
+
private minTimeReached: boolean = false;
|
|
30
|
+
private resourcesLoaded: boolean = false;
|
|
31
|
+
private interactLoaded: boolean = false;
|
|
32
|
+
|
|
33
|
+
constructor() {
|
|
34
|
+
super({ key: 'PreloadScene' });
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
preload(): void {
|
|
38
|
+
// 在这里预加载所有游戏资源
|
|
39
|
+
this.load.image('button', buttonBg);
|
|
40
|
+
// UI 组件依赖的资源
|
|
41
|
+
this.load.image('brain', brainIcon);
|
|
42
|
+
this.load.image('warn_brain', warnBrainIcon);
|
|
43
|
+
|
|
44
|
+
// 监听加载完成事件
|
|
45
|
+
this.load.on('complete', () => {
|
|
46
|
+
console.log('Resources loaded');
|
|
47
|
+
this.resourcesLoaded = true;
|
|
48
|
+
this.tryStartGame();
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
// 手动加载 interact 合图(通过 Image 对象,包含 playBtn、dangerTip 等帧)
|
|
52
|
+
const interactImg = new Image();
|
|
53
|
+
interactImg.onload = () => {
|
|
54
|
+
this.textures.addImage('interact', interactImg);
|
|
55
|
+
const tex = this.textures.get('interact');
|
|
56
|
+
// 定义合图帧区域(x, y, width, height)
|
|
57
|
+
tex.add('successBg', 0, 20, 13, 650, 440);
|
|
58
|
+
tex.add('playBtn', 0, 710, 278, 685, 200);
|
|
59
|
+
tex.add('failBg', 0, 20, 504, 650, 480);
|
|
60
|
+
tex.add('finger', 0, 1232, 588, 400, 400);
|
|
61
|
+
tex.add('aperture', 0, 1650, 850, 160, 160);
|
|
62
|
+
tex.add('dangerTip', 0, 700, 480, 530, 530);
|
|
63
|
+
console.log('Interact atlas loaded');
|
|
64
|
+
this.interactLoaded = true;
|
|
65
|
+
this.tryStartGame();
|
|
66
|
+
};
|
|
67
|
+
interactImg.src = interactImage;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
create(): void {
|
|
71
|
+
this.createUI();
|
|
72
|
+
|
|
73
|
+
// 最小显示时间计时器
|
|
74
|
+
this.time.delayedCall(LoadingConfig.SCENE_DURATION, () => {
|
|
75
|
+
console.log('Min time reached');
|
|
76
|
+
this.minTimeReached = true;
|
|
77
|
+
this.tryStartGame();
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
private createUI(): void {
|
|
82
|
+
const width = this.scale.width;
|
|
83
|
+
const height = this.scale.height;
|
|
84
|
+
const isLandscape = width > height;
|
|
85
|
+
const centerX = width / 2;
|
|
86
|
+
|
|
87
|
+
// 根据画布尺寸计算缩放比例(基于设计尺寸)
|
|
88
|
+
const baseWidth = isLandscape ? 667 : 375;
|
|
89
|
+
const baseHeight = isLandscape ? 375 : 667;
|
|
90
|
+
const scaleX = width / baseWidth;
|
|
91
|
+
const scaleY = height / baseHeight;
|
|
92
|
+
const scale = Math.min(scaleX, scaleY, 2); // 限制最大缩放
|
|
93
|
+
|
|
94
|
+
// 根据横竖屏获取基础布局配置,再乘以缩放比例
|
|
95
|
+
const logoSize = Math.round((isLandscape ? 80 : 120) * scale);
|
|
96
|
+
const titleFontSize = Math.round((isLandscape ? 24 : 32) * scale);
|
|
97
|
+
const starsFontSize = Math.round((isLandscape ? 18 : 24) * scale);
|
|
98
|
+
const descFontSize = Math.round((isLandscape ? 12 : 14) * scale);
|
|
99
|
+
const descMaxWidth = Math.round((isLandscape ? 300 : 280) * scale);
|
|
100
|
+
const iconFontSize = Math.round((isLandscape ? 18 : 24) * scale);
|
|
101
|
+
const loadingFontSize = Math.round((isLandscape ? 14 : 18) * scale);
|
|
102
|
+
const gap = Math.round((isLandscape ? 8 : 12) * scale);
|
|
103
|
+
|
|
104
|
+
// 位置计算
|
|
105
|
+
const logoY = height * (isLandscape ? 0.35 : 0.28);
|
|
106
|
+
const titleY = logoY + logoSize / 2 + Math.round((isLandscape ? 15 : 25) * scale);
|
|
107
|
+
const starsY = titleY + Math.round((isLandscape ? 25 : 35) * scale);
|
|
108
|
+
const descY = starsY + Math.round((isLandscape ? 20 : 30) * scale);
|
|
109
|
+
const loadingY = height * (isLandscape ? 0.85 : 0.88);
|
|
110
|
+
|
|
111
|
+
// Logo - 使用 DOM 元素显示,避免 canvas 缩放锯齿
|
|
112
|
+
this.createDomLogo(centerX, logoY, logoSize);
|
|
113
|
+
|
|
114
|
+
// 标题
|
|
115
|
+
this.add.text(centerX, titleY, LoadingConfig.APP_TITLE, {
|
|
116
|
+
fontFamily: 'Arial, sans-serif',
|
|
117
|
+
fontSize: `${titleFontSize}px`,
|
|
118
|
+
color: '#ffffff',
|
|
119
|
+
fontStyle: 'bold'
|
|
120
|
+
}).setOrigin(0.5);
|
|
121
|
+
|
|
122
|
+
// 五星评分
|
|
123
|
+
this.add.text(centerX, starsY, LoadingConfig.APP_STARS, {
|
|
124
|
+
fontFamily: 'Arial, sans-serif',
|
|
125
|
+
fontSize: `${starsFontSize}px`,
|
|
126
|
+
color: '#ffc107'
|
|
127
|
+
}).setOrigin(0.5);
|
|
128
|
+
|
|
129
|
+
// 描述文字
|
|
130
|
+
this.add.text(centerX, descY, LoadingConfig.APP_DESC, {
|
|
131
|
+
fontFamily: 'Arial, sans-serif',
|
|
132
|
+
fontSize: `${descFontSize}px`,
|
|
133
|
+
color: '#888888',
|
|
134
|
+
align: 'center',
|
|
135
|
+
wordWrap: { width: descMaxWidth }
|
|
136
|
+
}).setOrigin(0.5, 0);
|
|
137
|
+
|
|
138
|
+
// 游戏手柄图标 + Loading 文字(组合居中)
|
|
139
|
+
// 先创建文字获取宽度
|
|
140
|
+
this.loadingText = this.add.text(0, 0, `${LoadingConfig.LOADING_TEXT}...`, {
|
|
141
|
+
fontFamily: 'Arial, sans-serif',
|
|
142
|
+
fontSize: `${loadingFontSize}px`,
|
|
143
|
+
color: '#888888'
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
// 计算组合总宽度并居中
|
|
147
|
+
const textWidth = this.loadingText.width;
|
|
148
|
+
const iconWidth = iconFontSize;
|
|
149
|
+
const totalWidth = iconWidth + gap + textWidth;
|
|
150
|
+
const startX = centerX - totalWidth / 2;
|
|
151
|
+
|
|
152
|
+
// 放置图标
|
|
153
|
+
this.gamepadIcon = this.add.text(startX + iconWidth / 2, loadingY, LoadingConfig.GAMEPAD_ICON, {
|
|
154
|
+
fontFamily: 'Arial, sans-serif',
|
|
155
|
+
fontSize: `${iconFontSize}px`
|
|
156
|
+
}).setOrigin(0.5);
|
|
157
|
+
|
|
158
|
+
// 添加跳跃动画
|
|
159
|
+
this.startBounceAnimation(loadingY, isLandscape, scale);
|
|
160
|
+
|
|
161
|
+
// 放置文字(紧跟图标后面)
|
|
162
|
+
this.loadingText.setPosition(startX + iconWidth + gap, loadingY);
|
|
163
|
+
this.loadingText.setOrigin(0, 0.5);
|
|
164
|
+
|
|
165
|
+
// 启动点号动画
|
|
166
|
+
this.startDotAnimation();
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// 尝试启动游戏(需要满足两个条件:资源加载完成 && 最小显示时间已到)
|
|
170
|
+
private tryStartGame(): void {
|
|
171
|
+
if (this.resourcesLoaded && this.minTimeReached && this.interactLoaded) {
|
|
172
|
+
console.log('All conditions met, starting MainScene');
|
|
173
|
+
// 移除 DOM logo 元素
|
|
174
|
+
this.removeDomLogo();
|
|
175
|
+
// 停止当前场景
|
|
176
|
+
this.scene.stop('PreloadScene');
|
|
177
|
+
// 启动主游戏场景
|
|
178
|
+
this.scene.start('MainScene');
|
|
179
|
+
// 通知 SDK 游戏开始
|
|
180
|
+
sdk.playcraftStart();
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
private startDotAnimation(): void {
|
|
185
|
+
this.dotTimer = this.time.addEvent({
|
|
186
|
+
delay: LoadingConfig.DOT_ANIM_DELAY,
|
|
187
|
+
callback: () => {
|
|
188
|
+
this.dotCount = (this.dotCount + 1) % 4;
|
|
189
|
+
const dots = '.'.repeat(this.dotCount);
|
|
190
|
+
this.loadingText.setText(LoadingConfig.LOADING_TEXT + dots);
|
|
191
|
+
},
|
|
192
|
+
loop: true
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// 图标跳跃动画
|
|
197
|
+
private startBounceAnimation(baseY: number, isLandscape: boolean, scale: number): void {
|
|
198
|
+
const bounceHeight = Math.round((isLandscape ? LoadingConfig.BOUNCE_HEIGHT_LANDSCAPE : LoadingConfig.BOUNCE_HEIGHT_PORTRAIT) * scale);
|
|
199
|
+
|
|
200
|
+
this.tweens.add({
|
|
201
|
+
targets: this.gamepadIcon,
|
|
202
|
+
y: baseY - bounceHeight,
|
|
203
|
+
duration: LoadingConfig.BOUNCE_DURATION,
|
|
204
|
+
ease: 'Quad.easeOut',
|
|
205
|
+
yoyo: true,
|
|
206
|
+
repeat: -1,
|
|
207
|
+
repeatDelay: LoadingConfig.BOUNCE_REPEAT_DELAY
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
shutdown(): void {
|
|
212
|
+
if (this.dotTimer) {
|
|
213
|
+
this.dotTimer.destroy();
|
|
214
|
+
}
|
|
215
|
+
this.removeDomLogo();
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// 移除 DOM logo 元素
|
|
219
|
+
private removeDomLogo(): void {
|
|
220
|
+
if (this.logoElement && this.logoElement.parentNode) {
|
|
221
|
+
this.logoElement.parentNode.removeChild(this.logoElement);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// 创建 DOM logo 元素
|
|
226
|
+
private createDomLogo(centerX: number, centerY: number, size: number): void {
|
|
227
|
+
// 获取 canvas 元素和其在页面中的位置
|
|
228
|
+
const canvas = this.game.canvas;
|
|
229
|
+
const canvasRect = canvas.getBoundingClientRect();
|
|
230
|
+
|
|
231
|
+
// 计算缩放比例(canvas 实际显示大小 vs 游戏内部尺寸)
|
|
232
|
+
const scaleX = canvasRect.width / this.scale.width;
|
|
233
|
+
const scaleY = canvasRect.height / this.scale.height;
|
|
234
|
+
|
|
235
|
+
// 计算 logo 在页面中的实际位置和大小
|
|
236
|
+
const actualSize = size * scaleX;
|
|
237
|
+
const actualX = canvasRect.left + (centerX * scaleX) - (actualSize / 2);
|
|
238
|
+
const actualY = canvasRect.top + (centerY * scaleY) - (actualSize / 2);
|
|
239
|
+
|
|
240
|
+
// 创建 img 元素
|
|
241
|
+
this.logoElement = document.createElement('img');
|
|
242
|
+
this.logoElement.src = LoadingConfig.LOGO_IMAGE;
|
|
243
|
+
this.logoElement.style.cssText = `
|
|
244
|
+
position: absolute;
|
|
245
|
+
left: ${actualX}px;
|
|
246
|
+
top: ${actualY}px;
|
|
247
|
+
width: ${actualSize}px;
|
|
248
|
+
height: ${actualSize}px;
|
|
249
|
+
border-radius: 15%;
|
|
250
|
+
pointer-events: none;
|
|
251
|
+
z-index: 1000;
|
|
252
|
+
`;
|
|
253
|
+
|
|
254
|
+
document.body.appendChild(this.logoElement);
|
|
255
|
+
}
|
|
256
|
+
}
|