@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,305 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Preload 场景
|
|
3
|
+
* 负责预加载所有游戏资源,显示 Loading UI
|
|
4
|
+
* 支持横竖屏自适应
|
|
5
|
+
*/
|
|
6
|
+
import { LoadingConfig } from '../config/LoadingConfig';
|
|
7
|
+
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';
|
|
8
|
+
import storkModel from 'assets/Stork.glb';
|
|
9
|
+
import buttonBg from 'assets/button.png';
|
|
10
|
+
|
|
11
|
+
export class PreloadScene {
|
|
12
|
+
private container: HTMLDivElement;
|
|
13
|
+
private loadingText: HTMLSpanElement;
|
|
14
|
+
private gamepadIcon: HTMLSpanElement;
|
|
15
|
+
|
|
16
|
+
// 动画相关
|
|
17
|
+
private dotCount: number = 0;
|
|
18
|
+
private dotTimer: number | null = null;
|
|
19
|
+
private bounceAnimation: Animation | null = null;
|
|
20
|
+
|
|
21
|
+
// 状态
|
|
22
|
+
private minTimeReached: boolean = false;
|
|
23
|
+
private resourcesLoaded: boolean = false;
|
|
24
|
+
private onReadyCallback: ((resources: PreloadResources) => void) | null = null;
|
|
25
|
+
|
|
26
|
+
// 加载的资源
|
|
27
|
+
private resources: PreloadResources = {
|
|
28
|
+
buttonImage: null,
|
|
29
|
+
storkModel: null
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
constructor() {
|
|
33
|
+
this.container = this.createContainer();
|
|
34
|
+
document.body.appendChild(this.container);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
private createContainer(): HTMLDivElement {
|
|
38
|
+
const container = document.createElement('div');
|
|
39
|
+
container.id = 'loading-container';
|
|
40
|
+
container.style.cssText = `
|
|
41
|
+
position: fixed;
|
|
42
|
+
top: 0;
|
|
43
|
+
left: 0;
|
|
44
|
+
width: 100%;
|
|
45
|
+
height: 100%;
|
|
46
|
+
background: #1c1c1c;
|
|
47
|
+
display: flex;
|
|
48
|
+
flex-direction: column;
|
|
49
|
+
align-items: center;
|
|
50
|
+
justify-content: center;
|
|
51
|
+
z-index: 9999;
|
|
52
|
+
font-family: Arial, sans-serif;
|
|
53
|
+
overflow: hidden;
|
|
54
|
+
`;
|
|
55
|
+
|
|
56
|
+
// 获取屏幕方向和尺寸
|
|
57
|
+
const width = window.innerWidth;
|
|
58
|
+
const height = window.innerHeight;
|
|
59
|
+
const isLandscape = width > height;
|
|
60
|
+
|
|
61
|
+
// 根据屏幕尺寸计算缩放比例(基于设计尺寸)
|
|
62
|
+
const baseWidth = isLandscape ? 667 : 375;
|
|
63
|
+
const baseHeight = isLandscape ? 375 : 667;
|
|
64
|
+
const scaleX = width / baseWidth;
|
|
65
|
+
const scaleY = height / baseHeight;
|
|
66
|
+
const scale = Math.min(scaleX, scaleY, 1.5); // 限制最大缩放
|
|
67
|
+
|
|
68
|
+
// 根据横竖屏获取基础布局配置,再乘以缩放比例
|
|
69
|
+
const logoSize = Math.round((isLandscape ? 80 : 120) * scale);
|
|
70
|
+
const titleFontSize = Math.round((isLandscape ? 24 : 32) * scale);
|
|
71
|
+
const starsFontSize = Math.round((isLandscape ? 18 : 24) * scale);
|
|
72
|
+
const descFontSize = Math.round((isLandscape ? 12 : 14) * scale);
|
|
73
|
+
const iconFontSize = Math.round((isLandscape ? 18 : 24) * scale);
|
|
74
|
+
const loadingFontSize = Math.round((isLandscape ? 14 : 18) * scale);
|
|
75
|
+
const gap = Math.round((isLandscape ? 8 : 12) * scale);
|
|
76
|
+
const contentGap = Math.round((isLandscape ? 10 : 20) * scale);
|
|
77
|
+
|
|
78
|
+
// 内容容器
|
|
79
|
+
const contentWrapper = document.createElement('div');
|
|
80
|
+
contentWrapper.style.cssText = `
|
|
81
|
+
display: flex;
|
|
82
|
+
flex-direction: column;
|
|
83
|
+
align-items: center;
|
|
84
|
+
gap: ${contentGap}px;
|
|
85
|
+
`;
|
|
86
|
+
|
|
87
|
+
// Logo
|
|
88
|
+
const logo = document.createElement('img');
|
|
89
|
+
logo.src = LoadingConfig.LOGO_IMAGE;
|
|
90
|
+
logo.style.cssText = `
|
|
91
|
+
width: ${logoSize}px;
|
|
92
|
+
height: ${logoSize}px;
|
|
93
|
+
object-fit: contain;
|
|
94
|
+
border-radius: ${Math.round(12 * scale)}px;
|
|
95
|
+
`;
|
|
96
|
+
contentWrapper.appendChild(logo);
|
|
97
|
+
|
|
98
|
+
// 标题
|
|
99
|
+
const title = document.createElement('div');
|
|
100
|
+
title.textContent = LoadingConfig.APP_TITLE;
|
|
101
|
+
title.style.cssText = `
|
|
102
|
+
font-size: ${titleFontSize}px;
|
|
103
|
+
color: #ffffff;
|
|
104
|
+
font-weight: bold;
|
|
105
|
+
`;
|
|
106
|
+
contentWrapper.appendChild(title);
|
|
107
|
+
|
|
108
|
+
// 五星评分
|
|
109
|
+
const stars = document.createElement('div');
|
|
110
|
+
stars.textContent = LoadingConfig.APP_STARS;
|
|
111
|
+
stars.style.cssText = `
|
|
112
|
+
font-size: ${starsFontSize}px;
|
|
113
|
+
color: #ffc107;
|
|
114
|
+
`;
|
|
115
|
+
contentWrapper.appendChild(stars);
|
|
116
|
+
|
|
117
|
+
// 描述文字
|
|
118
|
+
const desc = document.createElement('div');
|
|
119
|
+
desc.textContent = LoadingConfig.APP_DESC;
|
|
120
|
+
desc.style.cssText = `
|
|
121
|
+
font-size: ${descFontSize}px;
|
|
122
|
+
color: #888888;
|
|
123
|
+
text-align: center;
|
|
124
|
+
white-space: pre-line;
|
|
125
|
+
max-width: ${Math.round((isLandscape ? 300 : 280) * scale)}px;
|
|
126
|
+
`;
|
|
127
|
+
contentWrapper.appendChild(desc);
|
|
128
|
+
|
|
129
|
+
container.appendChild(contentWrapper);
|
|
130
|
+
|
|
131
|
+
// Loading 区域(底部)
|
|
132
|
+
const loadingWrapper = document.createElement('div');
|
|
133
|
+
loadingWrapper.style.cssText = `
|
|
134
|
+
position: absolute;
|
|
135
|
+
bottom: ${isLandscape ? '15%' : '8%'};
|
|
136
|
+
display: flex;
|
|
137
|
+
align-items: center;
|
|
138
|
+
gap: ${gap}px;
|
|
139
|
+
white-space: nowrap;
|
|
140
|
+
`;
|
|
141
|
+
|
|
142
|
+
// 游戏手柄图标
|
|
143
|
+
this.gamepadIcon = document.createElement('span');
|
|
144
|
+
this.gamepadIcon.textContent = LoadingConfig.GAMEPAD_ICON;
|
|
145
|
+
this.gamepadIcon.style.cssText = `
|
|
146
|
+
font-size: ${iconFontSize}px;
|
|
147
|
+
display: inline-block;
|
|
148
|
+
`;
|
|
149
|
+
loadingWrapper.appendChild(this.gamepadIcon);
|
|
150
|
+
|
|
151
|
+
// Loading 文字(使用固定宽度避免点号动画导致左右摆动)
|
|
152
|
+
this.loadingText = document.createElement('span');
|
|
153
|
+
this.loadingText.textContent = `${LoadingConfig.LOADING_TEXT}...`;
|
|
154
|
+
this.loadingText.style.cssText = `
|
|
155
|
+
font-size: ${loadingFontSize}px;
|
|
156
|
+
color: #888888;
|
|
157
|
+
display: inline-block;
|
|
158
|
+
text-align: left;
|
|
159
|
+
`;
|
|
160
|
+
// 先添加到 DOM 获取最大宽度,然后固定宽度
|
|
161
|
+
loadingWrapper.appendChild(this.loadingText);
|
|
162
|
+
// 延迟设置固定宽度(确保字体渲染完成)
|
|
163
|
+
requestAnimationFrame(() => {
|
|
164
|
+
const maxWidth = this.loadingText.offsetWidth;
|
|
165
|
+
this.loadingText.style.width = `${maxWidth}px`;
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
container.appendChild(loadingWrapper);
|
|
169
|
+
|
|
170
|
+
return container;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* 启动 Preload 流程
|
|
175
|
+
* @param onReady 资源加载完成后的回调
|
|
176
|
+
*/
|
|
177
|
+
public start(onReady: (resources: PreloadResources) => void): void {
|
|
178
|
+
this.onReadyCallback = onReady;
|
|
179
|
+
|
|
180
|
+
// 启动动画
|
|
181
|
+
this.startDotAnimation();
|
|
182
|
+
this.startBounceAnimation();
|
|
183
|
+
|
|
184
|
+
// 开始加载资源
|
|
185
|
+
this.preloadResources();
|
|
186
|
+
|
|
187
|
+
// 最小显示时间计时器
|
|
188
|
+
setTimeout(() => {
|
|
189
|
+
console.log('Min time reached');
|
|
190
|
+
this.minTimeReached = true;
|
|
191
|
+
this.tryStartGame();
|
|
192
|
+
}, LoadingConfig.SCENE_DURATION);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* 预加载所有资源
|
|
197
|
+
*/
|
|
198
|
+
private preloadResources(): void {
|
|
199
|
+
let loadedCount = 0;
|
|
200
|
+
const totalCount = 2; // 按钮图片 + 模型
|
|
201
|
+
|
|
202
|
+
const checkComplete = () => {
|
|
203
|
+
loadedCount++;
|
|
204
|
+
console.log(`Resources loaded: ${loadedCount}/${totalCount}`);
|
|
205
|
+
|
|
206
|
+
if (loadedCount === totalCount) {
|
|
207
|
+
console.log('All resources loaded');
|
|
208
|
+
this.resourcesLoaded = true;
|
|
209
|
+
this.tryStartGame();
|
|
210
|
+
}
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
// 加载按钮图片
|
|
214
|
+
const buttonImage = new Image();
|
|
215
|
+
buttonImage.src = buttonBg;
|
|
216
|
+
buttonImage.onload = () => {
|
|
217
|
+
this.resources.buttonImage = buttonImage;
|
|
218
|
+
checkComplete();
|
|
219
|
+
};
|
|
220
|
+
buttonImage.onerror = () => {
|
|
221
|
+
console.error('Failed to load button image');
|
|
222
|
+
checkComplete(); // 即使失败也继续
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
// 加载 3D 模型
|
|
226
|
+
const loader = new GLTFLoader();
|
|
227
|
+
loader.load(
|
|
228
|
+
storkModel,
|
|
229
|
+
(gltf) => {
|
|
230
|
+
this.resources.storkModel = gltf;
|
|
231
|
+
checkComplete();
|
|
232
|
+
},
|
|
233
|
+
undefined,
|
|
234
|
+
(error) => {
|
|
235
|
+
console.error('Failed to load stork model:', error);
|
|
236
|
+
checkComplete(); // 即使失败也继续
|
|
237
|
+
}
|
|
238
|
+
);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* 尝试启动游戏(需要满足两个条件:资源加载完成 && 最小显示时间已到)
|
|
243
|
+
*/
|
|
244
|
+
private tryStartGame(): void {
|
|
245
|
+
if (this.resourcesLoaded && this.minTimeReached) {
|
|
246
|
+
console.log('All conditions met, starting game');
|
|
247
|
+
this.hide();
|
|
248
|
+
if (this.onReadyCallback) {
|
|
249
|
+
this.onReadyCallback(this.resources);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
private startDotAnimation(): void {
|
|
255
|
+
this.dotTimer = window.setInterval(() => {
|
|
256
|
+
this.dotCount = (this.dotCount + 1) % 4;
|
|
257
|
+
const dots = '.'.repeat(this.dotCount);
|
|
258
|
+
this.loadingText.textContent = LoadingConfig.LOADING_TEXT + dots;
|
|
259
|
+
}, LoadingConfig.DOT_ANIM_DELAY);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
private startBounceAnimation(): void {
|
|
263
|
+
const width = window.innerWidth;
|
|
264
|
+
const height = window.innerHeight;
|
|
265
|
+
const isLandscape = width > height;
|
|
266
|
+
const baseScale = Math.min(width / (isLandscape ? 667 : 375), height / (isLandscape ? 375 : 667), 1.5);
|
|
267
|
+
const bounceHeight = Math.round((isLandscape ? LoadingConfig.BOUNCE_HEIGHT_LANDSCAPE : LoadingConfig.BOUNCE_HEIGHT_PORTRAIT) * baseScale);
|
|
268
|
+
|
|
269
|
+
// 使用 Web Animations API
|
|
270
|
+
this.bounceAnimation = this.gamepadIcon.animate([
|
|
271
|
+
{ transform: 'translateY(0)' },
|
|
272
|
+
{ transform: `translateY(-${bounceHeight}px)` },
|
|
273
|
+
{ transform: 'translateY(0)' }
|
|
274
|
+
], {
|
|
275
|
+
duration: LoadingConfig.BOUNCE_DURATION * 2 + LoadingConfig.BOUNCE_REPEAT_DELAY,
|
|
276
|
+
iterations: Infinity,
|
|
277
|
+
easing: 'ease-out'
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
private hide(): void {
|
|
282
|
+
// 停止动画
|
|
283
|
+
if (this.dotTimer) {
|
|
284
|
+
clearInterval(this.dotTimer);
|
|
285
|
+
this.dotTimer = null;
|
|
286
|
+
}
|
|
287
|
+
if (this.bounceAnimation) {
|
|
288
|
+
this.bounceAnimation.cancel();
|
|
289
|
+
this.bounceAnimation = null;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// 移除 DOM
|
|
293
|
+
if (this.container && this.container.parentNode) {
|
|
294
|
+
this.container.parentNode.removeChild(this.container);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* 预加载资源接口
|
|
301
|
+
*/
|
|
302
|
+
export interface PreloadResources {
|
|
303
|
+
buttonImage: HTMLImageElement | null;
|
|
304
|
+
storkModel: any; // GLTF
|
|
305
|
+
}
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
# 主题系统说明
|
|
2
|
+
|
|
3
|
+
## 概述
|
|
4
|
+
|
|
5
|
+
主题系统允许你为同一个 3D 游戏创建多个视觉风格变体,通过配置文件的方式抽离颜色、光照、样式等数据,而不需要重写整个游戏逻辑。
|
|
6
|
+
|
|
7
|
+
## 目录结构
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
src/theme/
|
|
11
|
+
├── README.md # 本文档
|
|
12
|
+
├── current.ts # 当前主题入口(批量构建时自动切换)
|
|
13
|
+
├── blue-theme/ # 蓝色主题文件夹
|
|
14
|
+
│ ├── index.ts # 蓝色主题配置
|
|
15
|
+
│ └── assets/ # 蓝色主题专属资源
|
|
16
|
+
├── green-theme/ # 绿色主题文件夹
|
|
17
|
+
│ ├── index.ts # 绿色主题配置
|
|
18
|
+
│ └── assets/ # 绿色主题专属资源
|
|
19
|
+
└── red-theme/ # 红色主题文件夹
|
|
20
|
+
├── index.ts # 红色主题配置
|
|
21
|
+
└── assets/ # 红色主题专属资源
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
**为什么使用文件夹结构?**
|
|
25
|
+
|
|
26
|
+
每个主题是一个独立的文件夹,不仅可以包含配置文件,还可以包含主题专属的资源(如 3D 模型、纹理贴图、音频等),扩展性更强。
|
|
27
|
+
|
|
28
|
+
## 使用方法
|
|
29
|
+
|
|
30
|
+
### 开发时切换主题
|
|
31
|
+
|
|
32
|
+
修改 `current.ts` 的 import 语句:
|
|
33
|
+
|
|
34
|
+
```typescript
|
|
35
|
+
// 使用蓝色主题
|
|
36
|
+
import config from './blue-theme';
|
|
37
|
+
|
|
38
|
+
// 使用绿色主题
|
|
39
|
+
import config from './green-theme';
|
|
40
|
+
|
|
41
|
+
// 使用红色主题
|
|
42
|
+
import config from './red-theme';
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### 批量构建多主题
|
|
46
|
+
|
|
47
|
+
参考根目录的 `builds.config.example.js` 配置主题批量构建。
|
|
48
|
+
|
|
49
|
+
## 配置说明
|
|
50
|
+
|
|
51
|
+
每个主题文件夹需要包含一个 `index.ts` 文件,导出 `ThemeConfig` 对象:
|
|
52
|
+
|
|
53
|
+
```typescript
|
|
54
|
+
// src/theme/blue-theme/index.ts
|
|
55
|
+
import { ThemeConfig } from '../../config/ThemeConfig';
|
|
56
|
+
|
|
57
|
+
// 如果有主题专属资源,可以这样引入
|
|
58
|
+
import customModel from './assets/model.glb';
|
|
59
|
+
import customTexture from './assets/texture.png';
|
|
60
|
+
|
|
61
|
+
const config: ThemeConfig = {
|
|
62
|
+
name: '蓝色主题',
|
|
63
|
+
id: 'blue',
|
|
64
|
+
|
|
65
|
+
// 场景配置
|
|
66
|
+
sceneBackground: 0x001a2d, // 深蓝色背景
|
|
67
|
+
|
|
68
|
+
// 光照配置
|
|
69
|
+
ambientLightColor: 0xffffff,
|
|
70
|
+
ambientLightIntensity: 2,
|
|
71
|
+
directionalLightColor: 0xffffff,
|
|
72
|
+
directionalLightIntensity: 2,
|
|
73
|
+
|
|
74
|
+
// UI 配置
|
|
75
|
+
uiPrimaryColor: '#4a90e2',
|
|
76
|
+
uiTextColor: '#ffffff',
|
|
77
|
+
buttonStyle: {
|
|
78
|
+
fontSize: '35px',
|
|
79
|
+
fontWeight: 'bold',
|
|
80
|
+
textShadow: 'rgb(255 252 106 / 63%) 4px 3px 9px',
|
|
81
|
+
},
|
|
82
|
+
|
|
83
|
+
// 游戏配置
|
|
84
|
+
modelScale: 0.5,
|
|
85
|
+
rotationSpeed: 0.01,
|
|
86
|
+
|
|
87
|
+
// 可选:主题专属资源
|
|
88
|
+
customAssets: {
|
|
89
|
+
model: customModel,
|
|
90
|
+
texture: customTexture,
|
|
91
|
+
},
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
export default config;
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### 主题专属资源
|
|
98
|
+
|
|
99
|
+
将主题特定的资源放在 `assets/` 目录下:
|
|
100
|
+
|
|
101
|
+
```
|
|
102
|
+
blue-theme/
|
|
103
|
+
├── index.ts
|
|
104
|
+
└── assets/
|
|
105
|
+
├── model.glb # 主题专属 3D 模型
|
|
106
|
+
├── texture.png # 主题专属纹理
|
|
107
|
+
└── theme-music.mp3 # 主题专属音乐
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
在配置文件中引用:
|
|
111
|
+
|
|
112
|
+
```typescript
|
|
113
|
+
import customModel from './assets/model.glb';
|
|
114
|
+
import customTexture from './assets/texture.png';
|
|
115
|
+
|
|
116
|
+
const config: ThemeConfig = {
|
|
117
|
+
// ...
|
|
118
|
+
customAssets: {
|
|
119
|
+
model: customModel,
|
|
120
|
+
texture: customTexture,
|
|
121
|
+
},
|
|
122
|
+
};
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
## 添加新主题
|
|
126
|
+
|
|
127
|
+
1. 在 `src/theme/` 下创建新文件夹,如 `purple-theme/`
|
|
128
|
+
2. 在文件夹内创建 `index.ts` 和 `assets/` 目录
|
|
129
|
+
3. 复制现有主题的 `index.ts` 内容并修改配置
|
|
130
|
+
4. (可选)添加主题专属资源到 `assets/` 目录
|
|
131
|
+
5. 在 `current.ts` 中切换到新主题测试
|
|
132
|
+
6. 批量构建时会自动识别新主题
|
|
133
|
+
|
|
134
|
+
**示例**:
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
# 创建新主题文件夹
|
|
138
|
+
mkdir -p src/theme/purple-theme/assets
|
|
139
|
+
|
|
140
|
+
# 创建配置文件
|
|
141
|
+
touch src/theme/purple-theme/index.ts
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
```typescript
|
|
145
|
+
// src/theme/purple-theme/index.ts
|
|
146
|
+
import { ThemeConfig } from '../../config/ThemeConfig';
|
|
147
|
+
|
|
148
|
+
const config: ThemeConfig = {
|
|
149
|
+
name: '紫色主题',
|
|
150
|
+
id: 'purple',
|
|
151
|
+
|
|
152
|
+
sceneBackground: 0x2d1a3d, // 深紫色背景
|
|
153
|
+
ambientLightColor: 0xffccff,
|
|
154
|
+
ambientLightIntensity: 1.8,
|
|
155
|
+
directionalLightColor: 0xffaaff,
|
|
156
|
+
directionalLightIntensity: 2.1,
|
|
157
|
+
|
|
158
|
+
uiPrimaryColor: '#9b59b6',
|
|
159
|
+
uiTextColor: '#ffffff',
|
|
160
|
+
buttonStyle: {
|
|
161
|
+
fontSize: '35px',
|
|
162
|
+
fontWeight: 'bold',
|
|
163
|
+
textShadow: 'rgb(200 150 255 / 70%) 4px 3px 9px',
|
|
164
|
+
},
|
|
165
|
+
|
|
166
|
+
modelScale: 0.5,
|
|
167
|
+
rotationSpeed: 0.01,
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
export default config;
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
## 配置项说明
|
|
174
|
+
|
|
175
|
+
| 配置项 | 类型 | 说明 |
|
|
176
|
+
| --------------------------- | ------- | ----------------------------------- |
|
|
177
|
+
| `name` | string | 主题名称(用于显示) |
|
|
178
|
+
| `id` | string | 主题唯一标识(用于文件命名) |
|
|
179
|
+
| `sceneBackground` | number | 场景背景色(十六进制,如 0x001a2d) |
|
|
180
|
+
| `ambientLightColor` | number | 环境光颜色 |
|
|
181
|
+
| `ambientLightIntensity` | number | 环境光强度(0-5) |
|
|
182
|
+
| `directionalLightColor` | number | 方向光颜色 |
|
|
183
|
+
| `directionalLightIntensity` | number | 方向光强度(0-5) |
|
|
184
|
+
| `uiPrimaryColor` | string | UI 主色调(CSS 颜色) |
|
|
185
|
+
| `uiTextColor` | string | 文字颜色(CSS 颜色) |
|
|
186
|
+
| `buttonStyle` | object | 按钮样式配置 |
|
|
187
|
+
| `modelScale` | number? | 模型缩放比例(可选) |
|
|
188
|
+
| `cameraDistance` | number? | 相机距离(可选) |
|
|
189
|
+
| `rotationSpeed` | number? | 旋转速度(可选) |
|
|
190
|
+
| `customAssets` | object? | 主题专属资源(可选) |
|
|
191
|
+
|
|
192
|
+
## 在游戏中使用主题
|
|
193
|
+
|
|
194
|
+
```typescript
|
|
195
|
+
import themeConfig from './theme/current';
|
|
196
|
+
|
|
197
|
+
// 设置场景背景色
|
|
198
|
+
this.scene.background = new THREE.Color(themeConfig.sceneBackground);
|
|
199
|
+
|
|
200
|
+
// 使用光照配置
|
|
201
|
+
const ambientLight = new THREE.AmbientLight(
|
|
202
|
+
themeConfig.ambientLightColor,
|
|
203
|
+
themeConfig.ambientLightIntensity,
|
|
204
|
+
);
|
|
205
|
+
|
|
206
|
+
// 使用 UI 配置
|
|
207
|
+
button.style.color = themeConfig.uiTextColor;
|
|
208
|
+
button.style.fontSize = themeConfig.buttonStyle.fontSize;
|
|
209
|
+
|
|
210
|
+
// 使用主题专属资源
|
|
211
|
+
if (themeConfig.customAssets?.model) {
|
|
212
|
+
const loader = new GLTFLoader();
|
|
213
|
+
loader.load(themeConfig.customAssets.model, (gltf) => {
|
|
214
|
+
scene.add(gltf.scene);
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
## 注意事项
|
|
220
|
+
|
|
221
|
+
1. **主题是纯数据配置**:不要在主题文件中编写游戏逻辑
|
|
222
|
+
2. **保持接口一致**:所有主题应该实现相同的配置接口
|
|
223
|
+
3. **使用 TypeScript**:类型检查确保配置正确
|
|
224
|
+
4. **资源管理**:主题专属资源放在 `assets/` 目录,公共资源放在项目根目录的 `assets/`
|
|
225
|
+
5. **批量构建**:使用 `npm run builds` 会自动为每个主题构建所有渠道
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# 主题专属资源目录
|
|
2
|
+
|
|
3
|
+
将主题特定的资源文件放在这里,例如:
|
|
4
|
+
- 特定的 3D 模型
|
|
5
|
+
- 主题专属的纹理贴图
|
|
6
|
+
- 特殊的音频文件
|
|
7
|
+
|
|
8
|
+
示例:
|
|
9
|
+
```
|
|
10
|
+
assets/
|
|
11
|
+
├── model.glb
|
|
12
|
+
├── texture.png
|
|
13
|
+
└── theme-music.mp3
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
在主题配置中引用:
|
|
17
|
+
```typescript
|
|
18
|
+
import modelAsset from './assets/model.glb';
|
|
19
|
+
import textureAsset from './assets/texture.png';
|
|
20
|
+
|
|
21
|
+
const config: ThemeConfig = {
|
|
22
|
+
// ...
|
|
23
|
+
customAssets: {
|
|
24
|
+
model: modelAsset,
|
|
25
|
+
texture: textureAsset,
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
```
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 蓝色主题配置
|
|
3
|
+
* 适合海洋、天空、科技类游戏
|
|
4
|
+
*/
|
|
5
|
+
import { ThemeConfig } from '../../config/ThemeConfig';
|
|
6
|
+
|
|
7
|
+
// ==================== 主题专属资源引入 ====================
|
|
8
|
+
// 如果有主题特定的资源,可以这样引入:
|
|
9
|
+
// import customModel from './assets/model.glb';
|
|
10
|
+
// import customTexture from './assets/texture.png';
|
|
11
|
+
|
|
12
|
+
const config: ThemeConfig = {
|
|
13
|
+
name: '蓝色主题',
|
|
14
|
+
id: 'blue',
|
|
15
|
+
|
|
16
|
+
// 场景配置
|
|
17
|
+
sceneBackground: 0x001a2d, // 深蓝色背景
|
|
18
|
+
|
|
19
|
+
// 光照配置
|
|
20
|
+
ambientLightColor: 0xffffff,
|
|
21
|
+
ambientLightIntensity: 2,
|
|
22
|
+
directionalLightColor: 0xffffff,
|
|
23
|
+
directionalLightIntensity: 2,
|
|
24
|
+
|
|
25
|
+
// UI 配置
|
|
26
|
+
uiPrimaryColor: '#4a90e2',
|
|
27
|
+
uiTextColor: '#ffffff',
|
|
28
|
+
buttonStyle: {
|
|
29
|
+
fontSize: '35px',
|
|
30
|
+
fontWeight: 'bold',
|
|
31
|
+
textShadow: 'rgb(255 252 106 / 63%) 4px 3px 9px'
|
|
32
|
+
},
|
|
33
|
+
|
|
34
|
+
// 游戏配置
|
|
35
|
+
modelScale: 0.5,
|
|
36
|
+
rotationSpeed: 0.01,
|
|
37
|
+
|
|
38
|
+
// 可选:主题特定资源
|
|
39
|
+
// customAssets: {
|
|
40
|
+
// model: customModel,
|
|
41
|
+
// texture: customTexture,
|
|
42
|
+
// },
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export default config;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 绿色主题配置
|
|
3
|
+
* 适合自然、森林、环保类游戏
|
|
4
|
+
*/
|
|
5
|
+
import { ThemeConfig } from '../../config/ThemeConfig';
|
|
6
|
+
|
|
7
|
+
const config: ThemeConfig = {
|
|
8
|
+
name: '绿色主题',
|
|
9
|
+
id: 'green',
|
|
10
|
+
|
|
11
|
+
// 场景配置
|
|
12
|
+
sceneBackground: 0x1a3d1a, // 深绿色背景
|
|
13
|
+
|
|
14
|
+
// 光照配置
|
|
15
|
+
ambientLightColor: 0xffffcc,
|
|
16
|
+
ambientLightIntensity: 1.8,
|
|
17
|
+
directionalLightColor: 0xffffaa,
|
|
18
|
+
directionalLightIntensity: 2.2,
|
|
19
|
+
|
|
20
|
+
// UI 配置
|
|
21
|
+
uiPrimaryColor: '#4caf50',
|
|
22
|
+
uiTextColor: '#ffffff',
|
|
23
|
+
buttonStyle: {
|
|
24
|
+
fontSize: '35px',
|
|
25
|
+
fontWeight: 'bold',
|
|
26
|
+
textShadow: 'rgb(255 255 150 / 70%) 4px 3px 9px'
|
|
27
|
+
},
|
|
28
|
+
|
|
29
|
+
// 游戏配置
|
|
30
|
+
modelScale: 0.5,
|
|
31
|
+
rotationSpeed: 0.01
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export default config;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 红色主题配置
|
|
3
|
+
* 适合动作、冒险、激情类游戏
|
|
4
|
+
*/
|
|
5
|
+
import { ThemeConfig } from '../../config/ThemeConfig';
|
|
6
|
+
|
|
7
|
+
const config: ThemeConfig = {
|
|
8
|
+
name: '红色主题',
|
|
9
|
+
id: 'red',
|
|
10
|
+
|
|
11
|
+
// 场景配置
|
|
12
|
+
sceneBackground: 0x3d1a1a, // 深红色背景
|
|
13
|
+
|
|
14
|
+
// 光照配置
|
|
15
|
+
ambientLightColor: 0xffcccc,
|
|
16
|
+
ambientLightIntensity: 1.9,
|
|
17
|
+
directionalLightColor: 0xffaaaa,
|
|
18
|
+
directionalLightIntensity: 2.1,
|
|
19
|
+
|
|
20
|
+
// UI 配置
|
|
21
|
+
uiPrimaryColor: '#e74c3c',
|
|
22
|
+
uiTextColor: '#ffffff',
|
|
23
|
+
buttonStyle: {
|
|
24
|
+
fontSize: '35px',
|
|
25
|
+
fontWeight: 'bold',
|
|
26
|
+
textShadow: 'rgb(255 200 106 / 80%) 4px 3px 9px'
|
|
27
|
+
},
|
|
28
|
+
|
|
29
|
+
// 游戏配置
|
|
30
|
+
modelScale: 0.5,
|
|
31
|
+
rotationSpeed: 0.01
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export default config;
|