GlitchText 组件:RGB 分离故障效果实现
> 105 行代码,让文字"抖动"、"偏移"、"闪烁"——这就是赛博朋克的味道。
## 组件概览
`GlitchText` 是 NEON RUNNER 2077 里最"有个性"的 UI 组件。它不是一个简单的文字对象,而是一个**三图层叠加系统**:红色偏移层、青色偏移层、主文字层。
```ts
// src/ui/GlitchText.ts
export class GlitchText {
readonly container: Phaser.GameObjects.Container;
private mainText: Phaser.GameObjects.Text;
private redShift: Phaser.GameObjects.Text;
private cyanShift: Phaser.GameObjects.Text;
private glitchTimer: Phaser.Time.TimerEvent | null = null;
```
## 三图层架构
GlitchText 的核心思想是**复制同一段文字三次,分别用不同颜色和偏移量渲染**:
```ts
// 红色偏移层(RGB 的 R 通道)
this.redShift = scene.add.text(0, 0, text, { ...baseStyle, color: '#ff003c' })
.setOrigin(0.5).setAlpha(0.85);
// 青色偏移层(RGB 的 C 通道,即 G+B)
this.cyanShift = scene.add.text(0, 0, text, { ...baseStyle, color: '#00f0ff' })
.setOrigin(0.5).setAlpha(0.85);
// 主文字层(正常颜色)
this.mainText = scene.add.text(0, 0, text, {
...baseStyle,
color: `#${color.toString(16).padStart(6, '0')}`,
}).setOrigin(0.5);
```
三层文字都放在同一个 `Container` 里,所以移动容器时三层一起动。但每层有自己的位置偏移——红色层往右偏,青色层往左偏,主文字层居中。
## 故障循环:随机偏移 + 随机透明度
```ts
private startGlitchLoop(scene: Phaser.Scene): void {
this.glitchTimer = scene.time.addEvent({
delay: 120,
loop: true,
callback: () => {
const intensity = Math.random();
if (intensity > 0.55) {
const dx = Phaser.Math.Between(-3, 3);
const dy = Phaser.Math.Between(-2, 2);
this.redShift.setPosition(dx, dy);
this.cyanShift.setPosition(-dx, -dy);
this.redShift.setAlpha(0.7 + Math.random() * 0.3);
this.cyanShift.setAlpha(0.7 + Math.random() * 0.3);
} else {
this.redShift.setPosition(0, 0);
this.cyanShift.setPosition(0, 0);
this.redShift.setAlpha(0.0);
this.cyanShift.setAlpha(0.0);
}
},
});
}
```
这个循环每 120ms 触发一次,核心逻辑:
1. 生成一个 0-1 的随机数 `intensity`
2. 如果 `intensity > 0.55`(即 45% 的概率),触发故障:
- 红色层偏移 (dx, dy),青色层偏移 (-dx, -dy)——对称偏移,制造"RGB 分离"感
- 红色和青色层的透明度在 0.7-1.0 之间随机
3. 如果 `intensity <= 0.55`(即 55% 的概率),恢复正常:
- 偏移量归零,透明度归零——只显示主文字层
这个"大部分时间正常,偶尔故障"的节奏,模拟了老式显示器的信号干扰——不是持续花屏,而是偶尔抖动一下。
## 为什么是 120ms
120ms 不是拍脑袋定的。在 UI 动画里,100-150ms 是"人类能感知但不觉得烦"的区间。如果间隔太短(比如 20ms),文字会一直抖动,玩家看不清;如果间隔太长(比如 500ms),故障感会消失。
## 颜色选择:品红 + 青色
为什么是 `#ff003c`(品红)和 `#00f0ff`(青色)?这不是随机的,而是**互补色对**。
在 RGB 色彩空间里:
- 红色 + 青色 = 白色(互补)
- 品红 + 绿色 = 白色(互补)
- 黄色 + 蓝色 = 白色(互补)
品红和青色是一对互补色,它们在视觉上对比强烈,同时又不会像红绿那样"刺眼"。在故障效果里,互补色的偏移会产生"色彩溢出"的错觉——这是 CRT 显示器信号失真的经典表现。
## 组件接口
```ts
setText(text: string): void {
this.mainText.setText(text);
this.redShift.setText(text);
this.cyanShift.setText(text);
}
setScale(scale: number): this {
this.container.setScale(scale);
return this;
}
setAlpha(alpha: number): this {
this.container.setAlpha(alpha);
return this;
}
destroy(): void {
if (this.glitchTimer) this.glitchTimer.remove();
this.container.destroy();
}
```
`setText()` 同时更新三层文字,`setScale()` 和 `setAlpha()` 委托给 `Container`,`destroy()` 清理定时器。这个接口设计遵循了**单一职责**——GlitchText 只负责"故障文字",不负责布局、不负责交互。
## 在 MenuScene 中的使用
```ts
this.titleGlitch = new GlitchText(this, {
x: width / 2,
y: height * 0.28,
text: 'NEON RUNNER',
fontSize: 88,
color: COLORS.NEON_YELLOW,
});
```
`MenuScene` 里用 GlitchText 显示 "NEON RUNNER",88px Orbitron,霓虹黄色。这个标题在 1280×720 的画布上占据了顶部 28% 的高度,足够大,足够醒目。
## 下一章预告
GlitchText 讲完了,但 MenuScene 里还有一个视觉组件——`HoloPanel`。那个带扫描浮线的全息面板是怎么做的?下一章我会拆解这个 99 行的组件。
> 故障效果的核心原则:**随机性要有边界**。45% 的故障概率、±3px 的偏移量、0.7-1.0 的透明度——这些参数不是"调着玩的",而是经过视觉验证的。
- 点赞
- 收藏
- 关注作者
评论(0)