PreloadScene:程序化纹理生成与加载条动画
> 没有一张外部图片,却生成了玩家、敌人、子弹三张贴图。这是怎么做到的?
## 场景概览
`PreloadScene` 是 NEON RUNNER 2077 里最"有料"的场景——它不加载任何外部资源,却用 `make.graphics` 程序化生成了 3 张贴图,还做了一个看起来像在"加载"的进度条。
```ts
// src/scenes/PreloadScene.ts
export class PreloadScene extends Phaser.Scene {
private progressBar!: Phaser.GameObjects.Rectangle;
private progressText!: Phaser.GameObjects.Text;
private percentText!: Phaser.GameObjects.Text;
private barWidth = 480;
preload(): void {
this.generateProceduralAssets();
}
create(): void {
// 创建进度条、加载消息、动画
}
private generateProceduralAssets(): void {
// 生成 player / enemy / bullet 三张贴图
}
}
```
## 为什么不用外部图片
在 2026 年,做霓虹射击游戏完全可以找一张 PNG 素材。但 NEON RUNNER 2077 选择程序化生成,有三个原因:
1. **零网络依赖**:没有外部图片,没有 CDN,没有 404。双击 `index.html` 就能跑。
2. **风格可控**:程序化生成的纹理是几何图形,天然契合霓虹风格。如果用照片素材,风格会"出戏"。
3. **构建简单**:不需要图片压缩、不需要 WebP 转换、不需要 retina 适配。
## 程序化纹理生成:三张贴图
### 玩家纹理(三角形战机)
```ts
const playerGraphics = this.make.graphics({ x: 0, y: 0 }, false)!;
playerGraphics.fillStyle(0xfcee0a, 1);
playerGraphics.beginPath();
playerGraphics.moveTo(0, -16);
playerGraphics.lineTo(12, 12);
playerGraphics.lineTo(0, 6);
playerGraphics.lineTo(-12, 12);
playerGraphics.closePath();
playerGraphics.fillPath();
playerGraphics.lineStyle(2, 0xffffff, 0.9);
playerGraphics.strokePath();
playerGraphics.generateTexture('player', 32, 36);
playerGraphics.destroy();
```
这是一个**霓虹黄的三角形**,顶点在 (0, -16),底边在 (±12, 12)。`fillPath()` 填充黄色,`strokePath()` 用白色描边。`generateTexture('player', 32, 36)` 把这个图形导出为 32×36 的 Canvas 纹理,键名是 `'player'`。
### 敌人纹理(红色圆环)
```ts
const enemyGraphics = this.make.graphics({ x: 0, y: 0 }, false)!;
enemyGraphics.fillStyle(0xff003c, 1);
enemyGraphics.fillCircle(0, 0, 14);
enemyGraphics.lineStyle(2, 0xbd00ff, 1);
enemyGraphics.strokeCircle(0, 0, 14);
enemyGraphics.fillStyle(0xffffff, 1);
enemyGraphics.fillCircle(0, 0, 4);
enemyGraphics.generateTexture('enemy', 32, 32);
enemyGraphics.destroy();
```
红色填充圆(半径 14),紫色描边,中心一个白色小圆点。这个设计让敌人看起来像"被锁定的目标"——红色代表危险,紫色描边增加层次感。
### 子弹纹理(青色矩形)
```ts
const bulletGraphics = this.make.graphics({ x: 0, y: 0 }, false)!;
bulletGraphics.fillStyle(0x00f0ff, 1);
bulletGraphics.fillRoundedRect(-2, -8, 4, 16, 2);
bulletGraphics.generateTexture('bullet', 4, 16);
bulletGraphics.destroy();
```
青色圆角矩形,4×16 像素。`fillRoundedRect(-2, -8, 4, 16, 2)` 的锚点在 (-2, -8),所以子弹的中心在 (0, 0)——这是 Phaser 里精灵的默认原点。
## 加载条动画:看起来像在"加载"
`PreloadScene` 的 `create()` 里做了一个 1800ms 的 `tweens.addCounter` 动画,让进度条从 0% 走到 100%。但关键不是"走完",而是**加载消息跟着进度变化**:
```ts
const LOAD_MESSAGES = [
'> initializing neon runtime...',
'> loading cyberware modules...',
'> calibrating glitch shaders...',
'> syncing holo interface...',
'> arming neon projectiles...',
'> system ready.',
];
```
```ts
onUpdate: (_tween, _key, _target, current) => {
const value = current / 100;
this.progressBar.width = this.barWidth * value;
this.percentText.setText(`${Math.floor(value * 100)}%`);
const idx = Math.min(LOAD_MESSAGES.length - 1, Math.floor(value * LOAD_MESSAGES.length));
this.progressText.setText(LOAD_MESSAGES[idx]);
}
```
`Math.floor(value * LOAD_MESSAGES.length)` 把 0-100 的进度映射到 0-5 的消息索引。进度 0% 显示第一条,17% 显示第二条,33% 显示第三条……100% 显示最后一条。
这个"消息跟着进度走"的设计,让玩家觉得"真的在加载什么"——尽管实际上什么都没加载。这是一种**感知设计**:用文字营造"系统在做事"的感觉。
## 加载完成后的过渡
```ts
onComplete: () => {
this.time.delayedCall(300, () => {
this.cameras.main.fadeOut(500, 10, 10, 15);
this.cameras.main.once('camerafadeoutcomplete', () => {
this.scene.start('MenuScene');
});
});
}
```
进度条走完后,再等 300ms,然后相机淡出(500ms,RGB 10,10,15),淡出完成后切到 `MenuScene`。这个"等 300ms"是为了让玩家看到 "> system ready." 这条消息——如果立刻切走,玩家会觉得"加载完了?还没看清呢"。
## 下一章预告
`PreloadScene` 讲完了,但 NEON RUNNER 2077 的视觉风格真正爆发的地方是 `MenuScene`——故障文字、扫描线、全息面板、键盘/鼠标双输入。下一章我会拆解这个 214 行的场景。
> 程序化纹理的核心原则:**能用代码生成的,就不要用文件**。零依赖、零网络、零风格冲突。
- 点赞
- 收藏
- 关注作者
评论(0)