GlitchText 组件:RGB 分离故障效果实现

举报
yd_296221794 发表于 2026/09/13 11:47:26 2026/09/13
【摘要】 > 105 行代码,让文字"抖动"、"偏移"、"闪烁"——这就是赛博朋克的味道。## 组件概览`GlitchText` 是 NEON RUNNER 2077 里最"有个性"的 UI 组件。它不是一个简单的文字对象,而是一个**三图层叠加系统**:红色偏移层、青色偏移层、主文字层。```ts// src/ui/GlitchText.tsexport class GlitchText {  re...

> 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 的透明度——这些参数不是"调着玩的",而是经过视觉验证的。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。