#dialog #gamedev #narrative #parser

chatter

将可读性对话脚本翻译成Rust

1个不稳定版本

0.1.0 2023年6月18日

#964游戏开发

MIT 许可证

19KB
129

Chatter

Chatter提供了一个宏,可以将可读性脚本翻译成可以在Rust中轻松使用的格式。

基本用法

以下是一个基本的chatter脚本示例

This is an example line
This line is followed by a choice:
- continue to branch 1
- switch to branch 2 -> branch_2

# branch_1 
This line is in branch 1
-> end

# branch_2
This line is in branch 2

# end

Chatter将上述脚本解析成有用的格式,即一个Chat。然而,如何运行这个对话由用户决定。《Chatterbox》是一个命令行运行器的示例。

运行对话的基本流程如下

use chatter::{chatter, Chat, ChatContent, ChatPosition, Choice, Line};

fn main() {
    let example_chat = chatter! {r#"
    INSERT CHATTER SCRIPT HERE
    "#};

    run(&example_chat);
}

fn run(chat: &Chat) {
    let mut current_position = ChatPosition::start();

    while current_position != ChatPosition::end() {
        current_position = step(chat, current_position);
    }
}

fn step(chat: &Chat, position: ChatPosition) -> ChatPosition {
    let content = match chat.get(position) {
        Some(content) => content,
        None => return ChatPosition::end(),
    };

    match content {
        ChatContent::Line(line) => handle_line(line),
        ChatContent::Choices(choices) => handle_choices(choices),
    }
}

fn handle_line(line: &Line) -> ChatPosition {
    // Present the line to the player
    // Wait for timer/player to skip
    // Return line.next()

    todo!()
}

fn handle_choices(choices: &Vec<Choice>) -> ChatPosition {
    // Present the choices to the player
    // Wait for player to make a selection
    // Return the selection choice's next()

    todo!()
}

标签

为了定义更复杂的行为,可以使用标签。

标签可以应用于行和选择。下面提供了一个chatter脚本示例。

[Narrator] You attempt to open the door, but it doesn't budge.
- [Player, strength_over_14] Kick down the door.
- [Player, thievery_atleast_expert inventory_includes_lockpick] Pick the lock.
- [Player] Call out "Is anyone in there?"
- [Player] Walk way.

行或选择可以获取标签列表作为字符串。更有用的一点是,Chatter提供了一个Tag trait。上述选择中的一些包含要求。它们可以表示为以下类型

use chatter::Tag;

enum Requirement {
  Ability{ability: Ability, min: u32},
  Skill{skill: Skill, level: SkillLevel},
  Inventory{item: Item, count: u32}
}

impl Requirement {
  fn fufilled(&self, player: &Player) -> bool { todo!() }
}

impl Tag for Requirement {
  fn from(string: &str) -> Option<Self> { todo!() }
}

可以很容易地从给定的Choice中获取要求列表(忽略其他标签)。然后,只有当所有要求都满足时,该选择才会提供给玩家。

Chatter没有定义这种行为——标签的解释和使用留给使用库的程序。

以下是一些可能的使用列表(不完整)

  • 角色名称
  • 艺术选择(例如,一个角色可能对不同面部表情有不同的艺术作品)
  • 文本装饰(字体、大小、颜色、粗体、斜体)
  • 选择要求(如上所述,但也可以用于NPC对话;如果玩家没有金币,则说A,否则说B)
  • 选择随机化(可以使用标签来分配权重)
  • 游戏状态更改(进入战斗、库存更改、关系/定位更改)

Chatter定义

是任何不以-#开头的行。如果以方括号开头,则方括号内的内容是标签。然后,它必须包含文本。[tag] -> branch是无效的。

选择-开头,但其他方面与遵循相同的规则。选择必须存在于至少两个组中。一个空行将两个不同的选择组分开。任何没有Goto选择将跟随该组后面的下一行。

分支是任何以#开头的行,它作为Goto的标记。

跳转标记用 -> 表示,其后必须跟一个存在的分支标签。它们可以附加到 选择 的末尾,表示对话在之后的 或选择该 选择 后移动到相关分支。它们也可以放在新的一行,应用于其前的内容。

标签 由逗号和空格分隔,并位于 选择 的开头,包含在方括号内。

标签分支 名称只能包含字母数字字符和下划线。

选择 中的文本可以包含除换行符或 -> 之外的所有内容。它也不能以 -# 开头。

注释以 /* 开始,以 */ 结束。

选择 不会溢出;换行符表示新的 选择

更复杂的例子

以下是一个更复杂的 chatter 脚本示例

[Guard] By order of the Jarl, stop right there!
[Guard] You have commited crimes against Skyrim and her people. What say you in your defense?

- [Player] You caught me. I'll pay off my bounty. -> bounty
- [Player] I submit. Take me to jail. -> jail
- [Player, starts_combat] I'd rather die than go to prison!

- [Guard, random_weight_1.0 requires_imperial] Then suffer the Emperor's wrath.
- [Guard, random_weight_1.0 requires_stormcloak] Skyrim has no use for your kind.
- [Guard, random_weight_1.0] Then pay with your blood.
- [Guard, random_weight_2.0] That can be arranged.
- [Guard, random_weight_1.0] So be it.

-> end

# jail
[Guard, imprison_player] I guess you're smarter than you look.
-> end

# bounty
- [Guard, requires_player_male] Smart man.
- [Guard, requires_player_female] Smart woman.
[Guard] Now come along with us. We'll take any stolen goods, and you'll be free to go.
[Guard] After you pay the fine, of course.

# end

依赖

~2.2–2.9MB
~58K SLoC