Skip to content

标题打字机

文档版本:基于 2026-06-01 代码分析

概述

标题打字机系统是一个全屏文字动画播放器,通过 JSON 配置文件定义分组动画序列,在目标玩家屏幕上以逐字打字效果展示标题文字。系统采用服务端配置加载 → 网络包发送 → 客户端状态机渲染的架构,每个分组可独立配置文字样式、速度、停留时间和跳转逻辑,支持循环和分支。


核心概念

分组动画序列

每个 JSON 文件包含多个 group,每个 group 由 index(>=1)唯一标识,播放完毕后通过 next 字段跳转到下一组。next=0 表示结束,next 可跳转到任意 index 形成有向图(可循环/分支),而非简单链表。

状态机五阶段

PRE_BLINK → LINE1_TYPING → LINE2_TYPING → STAY → BLANK → (next>0? 跳转下一组 : stop)
阶段屏幕显示作用
PRE_BLINK闪烁下划线 _预备阶段,给玩家注意力缓冲
LINE1_TYPING第一行逐字出现 + 闪烁光标打字机效果(第一行)
LINE2_TYPING第二行逐字出现 + 闪烁光标打字机效果(第二行)
STAY完整文字持续显示玩家阅读时间
BLANK清屏过渡间隔

跳转策略

  • 第一行 text 为空 → 直接跳到 LINE2_TYPING(若第二行也为空则跳到 STAY
  • 第二行 text 为空 → LINE1_TYPING 完成后跳过 LINE2_TYPING 直接进入 STAY
  • BLANK 阶段不渲染任何内容

架构设计

服务端→客户端数据流时序

mermaid
sequenceDiagram
    participant Admin as 管理员
    participant Cmd as RfmTitleWriterStartCommand
    participant Config as RfmTitleWriterConfigManager
    participant Manager as RfmTitleWriterManager
    participant Net as PacketDistributor
    participant Client as RfmTitleWriterClientState
    participant Render as ClientRfmTitleWriterEvents

    Admin->>Cmd: /nfa rfm titlewriter start targets fileId group
    Cmd->>Config: getFile(server, fileId)
    Config-->>Cmd: TitleWriterFileConfig(groups)
    Cmd->>Manager: start(server, targets, fileId, groupIndex)
    Manager->>Manager: 遍历groups,构建RfmTitleWriterGroupData列表
    alt 文件缺失
        Manager-->>Cmd: StartResult.fileMissing()
    else 组编号不存在
        Manager-->>Cmd: StartResult.groupMissing()
    else 成功
        Manager->>Net: PlayRfmTitleWriterPayload(fileId, startGroup, allGroups)
        Net->>Client: 发送到每个目标玩家
    end

    Client->>Client: start(payload) 清空GROUPS,填充数据
    Client->>Client: beginGroup(initGroupIndex)
    Note over Client: 状态机进入 PRE_BLINK

    loop 每tick
        Client->>Client: tick() 推进状态机
        Client->>Render: render() 渲染当前帧
    end

    Note over Admin: /nfa rfm titlewriter stop targets
    Admin->>Client: StopRfmTitleWriterPayload → 清空所有状态

客户端状态机

mermaid
stateDiagram-v2
    [*] --> PRE_BLINK : beginGroup

    PRE_BLINK --> LINE1_TYPING : phaseTicks >= preBlinkTicks<br/>且 line1非空
    PRE_BLINK --> LINE2_TYPING : phaseTicks >= preBlinkTicks<br/>且 line1为空 line2非空
    PRE_BLINK --> STAY : phaseTicks >= preBlinkTicks<br/>且两行都为空

    LINE1_TYPING --> LINE1_TYPING : 每charIntervalTicks<br/>visibleLine1CodePoints+1
    LINE1_TYPING --> LINE2_TYPING : 第一行打字完成<br/>且 line2非空
    LINE1_TYPING --> STAY : 第一行打字完成<br/>且 line2为空

    LINE2_TYPING --> LINE2_TYPING : 每charIntervalTicks<br/>visibleLine2CodePoints+1
    LINE2_TYPING --> STAY : 第二行打字完成

    STAY --> BLANK : phaseTicks >= stayTicks

    BLANK --> PRE_BLINK : phaseTicks >= blankAfterTicks<br/>且 next>0 → beginGroup(next)
    BLANK --> [*] : next=0 → stop()

    note right of PRE_BLINK : 显示闪烁下划线_
    note right of LINE1_TYPING : 逐字显示+光标闪烁
    note right of STAY : 完整文字停留
    note right of BLANK : 清屏不渲染

关键文件

服务端

文件说明
com/chenxi/chenxi_rfm/server/config/rfm/RfmTitleWriterConfigManager.javaJSON 配置加载、解析、缓存(WeakHashMap 以 server 为 key)
com/chenxi/chenxi_rfm/server/rfm/RfmTitleWriterManager.java将配置转化为网络 Payload 并发送
com/chenxi/chenxi_rfm/server/command/rfm/RfmTitleWriterCommand.java/nfa rfm titlewriter 节点注册
com/chenxi/chenxi_rfm/server/command/rfm/RfmTitleWriterStartCommand.javatitlewriter start 指令实现(含 /suggest 自动补全)
com/chenxi/chenxi_rfm/server/command/rfm/RfmTitleWriterStopCommand.javatitlewriter stop 指令实现

客户端

文件说明
com/chenxi/chenxi_rfm/client/rfm/RfmTitleWriterClientState.java五阶段状态机核心(tick + render)
com/chenxi/chenxi_rfm/client/event/ClientRfmTitleWriterEvents.javatick 驱动 + GUI 渲染事件入口

网络包

文件说明
com/chenxi/chenxi_rfm/common/network/payload/rfm/PlayRfmTitleWriterPayload.java启动播放包(含全部 groups 数据 + 起始 group)
com/chenxi/chenxi_rfm/common/network/payload/rfm/RfmTitleWriterGroupData.java单个 group 数据 record(网络传输用)
com/chenxi/chenxi_rfm/common/network/payload/rfm/StopRfmTitleWriterPayload.java停止播放包

关键流程

JSON 配置文件

存放于 <server>/nfa_rfm/typewriter_titles/*.json,文件 ID 取自文件名(去 .json 后缀并转小写)。

json
{
  "id": "demo",
  "groups": [
    {
      "index": 1,
      "line1": { "text": "欢迎各位逃走者", "bold": true, "color": "gold" },
      "line2": { "text": "欢迎来到第四领域", "bold": true, "color": "white" },
      "next": 2,
      "pre_blink_ticks": 16,
      "char_interval_ticks": 10,
      "cursor_blink_ticks": 6,
      "stay_ticks": 40,
      "blank_after_ticks": 20
    },
    {
      "index": 2,
      "line1": { "text": "祝你好运", "color": "green" },
      "line2": { "text": "" },
      "next": 0,
      "pre_blink_ticks": 16,
      "char_interval_ticks": 8,
      "cursor_blink_ticks": 6,
      "stay_ticks": 60,
      "blank_after_ticks": 20
    }
  ]
}

配置字段说明

字段类型范围默认值说明
indexint>=1组编号(必填,用于识别和跳转)
line1 / line2StyledText""文字内容及样式
nextint>=00播完后跳转的组 index;0=结束
pre_blink_ticksint>=00光标闪烁预备 tick
char_interval_ticksint>=12每字间隔 tick(越小越快)
cursor_blink_ticksint>=16光标闪烁切换间隔
stay_ticksint>=00完全显示后停留 tick
blank_after_ticksint>=00清屏后空屏 tick

StyledText 样式支持

每行文字支持以下样式属性(全部可选):

属性类型说明
textString文字内容
colorString颜色:white/gold/green/red 等 ChatFormatting 名称,或 #RRGGBB 十六进制
boldbool加粗
italicbool斜体
underlinedbool下划线
strikethroughbool删除线
obfuscatedbool乱码效果

颜色解析优先级:先尝试 ChatFormatting.getByName()(如 "gold"→金色,仅限颜色类),再尝试 #RRGGBB 格式(如 "#FF6600")。非颜色类 Formatting(如 "bold" 写在 color 字段)无效。

字体渲染

  • 缩放:第一行放大 4 倍(FIRST_LINE_SCALE=4.0),第二行放大 2 倍(SECOND_LINE_SCALE=2.0
  • 溢出自适应scaledWidth > maxWidth 时等比缩小:scale *= maxWidth / scaledWidth
  • 居中:计算缩放后的总宽度,水平 + 垂直居中
  • BLANK 阶段不渲染render()Phase.BLANK 直接 return

光标闪烁

shouldShowCursor() 计算逻辑:(phaseTicks / cursorBlinkTicks) % 2 == 0。仅在 PRE_BLINKLINE1_TYPINGLINE2_TYPING 阶段显示(打字时附加在当前行末尾,预备时独立显示)。

JSON 配置加载时机

配置仅在服务器启动时加载一次ServerStartedEvent 触发 reload())。如需加载新配置,需重启服务器或执行 /nfa reload(若 reload 命令已集成)。缓存使用 WeakHashMap<MinecraftServer, Map<String, TitleWriterFileConfig>>,停止服务器后自动释放。

指令

指令权限说明
/nfa rfm titlewriter start <targets> <fileId> <group>MASTER_ADMIN向目标玩家播放指定文件的指定起始组
/nfa rfm titlewriter stop <targets>MASTER_ADMIN立即停止目标玩家的打字机动画并清屏

start 指令会自动提供文件 ID 和组 index 的 Tab 补全。


注意事项 / 限制

  1. JSON 仅在启动时加载RfmTitleWriterConfigManagerServerStartedEvent 时执行 reload(),无手动热重载接口。修改或新增 JSON 文件后需重启服务器。
  2. fileId 规范化为小写normalizeFileId() 将文件名转小写并去首尾空格,因此 Demo.jsondemo.json 视为同一文件。
  3. 全部 groups 一次性打包PlayRfmTitleWriterPayload 包含文件中的所有 groups,而非仅起始组。next 跳转在客户端本地执行,无需再向服务端请求。
  4. 停止包立即生效StopRfmTitleWriterPayload 到达客户端后 stop() 方法清空所有状态和缓存数据,清屏。
  5. 各玩家独立渲染:每个玩家独立运行 RfmTitleWriterClientState,可同时向不同玩家播放不同文件。
  6. BLANK 阶段不渲染:进入 BLANK 后屏幕完全清空,不显示任何文字,仅等待 blankAfterTicks 到期。
  7. 预览期间其他 GUI 仍正常渲染:打字机文字渲染在 RenderGuiLayerEvent.Post,叠加在已有 GUI 之上。
  8. style 可能为 Style.EMPTY:JSON 中未指定样式时使用空样式(白色、无格式),而非默认加粗。

相关文档