Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

文本与事件:分离的艺术

这是 Mortar 最与众不同的地方:文本和事件分开写,但精确关联

为什么要分离?

想象你在写带事件的游戏对话,传统方式可能是:

"你好<sound=hi.wav>,欢迎<anim=wave>来到<color=red>这里</color>!"

问题来了:

  • 😰 写手看到一堆“标记”,难以专注于文字本身
  • 😰 程序员要解析复杂的标记,容易出错
  • 😰 事件增减参数相当麻烦

Mortar 的方式:

text: "你好,欢迎来到这里!"
with events: [
    0, play_sound("hi.wav")
    3, show_animation("wave")
    8, set_color("red")
    9, set_color("normal")
]

干净!清晰!好维护!

文本块基础

最简单的文本

node Example {
    text: "这是一段文本。"
}

多段文本

node Dialogue {
    text: "第一句话。"
    text: "第二句话。"
    text: "第三句话。"
}

它们会按顺序显示。

引号的使用

单引号和双引号都可以:

text: "双引号"
text: '单引号'

事件系统

基本语法

with events: [
    索引, 函数调用
    索引, 函数调用
]

索引,从 0 开始计数,类型为 Number,也就是支持整数和小数(浮点数)。

索引具体的使用方式取决于程序方的实现。

简单示例

以字符索引为例:

text: "你好世界!"
with events: [
    0, sound_a()  // 在"你"字
    2, sound_b()  // 在"世"字  
    4, sound_c()  // 在"!"
]

字符索引

  • “你” = 位置 0
  • “好” = 位置 1
  • “世” = 位置 2
  • “界” = 位置 3
  • “!” = 位置 4

链式调用

可以在同一个位置调用多个函数:

with events: [
    0, play_sound("boom.wav").shake_screen().flash_white()
]

或者分开写:

with events: [
    0, play_sound("boom.wav")
    0, shake_screen()
    0, flash_white()
]

两种方式效果一样。

小数索引

索引可以是小数,这在语音同步时特别有用:

text: "你好,世界!"
with events: [
    0.0, start_voice("hello.wav")   // 开始播放语音
    1.5, blink_eyes()               // 1.5秒时眨眼
    3.2, show_smile()               // 3.2秒时微笑
    5.0, stop_voice()               // 5秒时结束
]

什么时候用小数?

我们的建议是:

  • 打字机效果:用整数(一个字一个触发)
  • 语音同步:用小数(按时间轴触发)
  • 视频同步:用小数(精确到帧)

字符串插值

想在文本中插入变量或函数返回值?用 ${}

text: $"你好,{get_player_name()}!"
text: $"你有 {get_gold()} 金币。"
text: $"今天是 {get_date()}。"

注意

  • 字符串前面要加 $,来声明“可插值字符串”
  • 变量/函数放在 {}
  • 函数要提前声明

实战示例

打字机效果配音效

node 打字机 {
    text: "叮!叮!叮!"
    with events: [
        0, play_sound("ding.wav")  // 第一个"叮"
        2, play_sound("ding.wav")  // 第二个"叮"
        4, play_sound("ding.wav")  // 第三个"叮"
    ]
}

旁白配背景音乐

node 旁白 {
    text: "在一个遥远的王国..."
    with events: [
        0, fade_in_bgm("story_theme.mp3")
        0, dim_lights()
    ]
    
    text: "住着一位勇敢的骑士。"
}

语音同步动画

node Dialogue {
    text: "我要告诉你一个秘密..."
    with events: [
        0.0, play_voice("secret.wav")
        0.0, set_expression("serious")
        2.5, lean_closer()
        4.0, whisper_effect()
        6.0, set_expression("normal")
    ]
}

事件函数声明

用到的所有函数都要先声明:

// 在文件末尾声明
fn play_sound(file: String)
fn shake_screen()
fn flash_white()
fn set_expression(expr: String)
fn get_player_name() -> String
fn get_gold() -> Number

详见函数:连接游戏世界

最佳实践

✅ 好的做法

// 清晰的结构
text: "你好,世界!"
with events: [
    0, greeting_sound()
    2, sparkle_effect()
]

❌ 错误的做法

text: "你好"
text: "世界"
with events: [
    0, say_hello()  // 关联的文本不对!
]

建议

  1. 紧跟原则:events 紧跟在对应的 text 后面
  2. 适度使用:不是每句话都需要事件
  3. 有序排列:事件按位置从小到大写(虽然不强制)
  4. 合理命名:函数名要见名知意

常见问题

Q: 位置超出文本长度会怎样?

编译器会警告,但不会报错。运行时行为由你的游戏决定。

Q: 可以没有 events 吗?

当然可以!不是每段文本都需要事件。但是事件都需要依附于文本。

text: "这是纯文本。"
// 没有 events,完全没问题

Q: 多个事件在同一位置的执行顺序?

按写的顺序执行:

with events: [
    0, first()   // 先执行
    0, second()  // 再执行
    0, third()   // 最后执行
]

下一步