白日昏灯,且照人间路

剧情分支

制作历程

最开始我想的是先出来一个选项让你决定使用哪个技能进行检定,然后再给出检定后的选项让你选择……举个例子就是:

“你面前有一扇门,砸开,撬开,敲门。”
“我选择砸开。”
“检定成功,你的力量足够砸开它,你确定吗?确定,撬开,敲门。”

后面想了想……这不多此一举吗,我都选择这个技能检定了,肯定不会想做别的动作,所以删掉了这个设计。

删掉以后剧情功能就好实现多了。

表格编写

实现方面,依旧是使用csv来读取,并且给csv解析器写了点新东西,方便我拓展剧情功能。csv写成如下格式

id,speaker,dialog,checks
str,str,str,arr
#剧情编号,#剧情对象,#剧情内容,#检定选项
0,,"“%s,很高兴你能清醒。”\n你睁开眼,面前是位留着黑色长发的女性,她戴着宽大的巫师帽,帽檐微微垂下,遮住了她的目光。\n“我在海边遇到了遇难的你,那时候你已经昏迷了,是我救了你。”\n“而现在……如果你不介意的话,我得和你谈谈清醒之后的事。”","侦查:3,你感觉有点恶寒……她的目光让你联想到一头正在寻找地方进食的野兽。,她帽檐下微微弯起的嘴角让你感觉有些亲切,这可是救命之恩,你感激的恨不得把家底掏空……如果你有的话。|你是谁?:0,你可以称呼我为“丹”。\n她略微抬起宽大的帽檐,露出了藏在阴影下的双眸……黑得没有半分色彩,就像是小时候让人藏在被子里不敢冒头的黑夜一样暗淡。\n“为了避免我们之后的交流出现一些误会,提前向你说明,镇上的人都叫我魔女,恶魔的魔。”\n她停顿了一会,像是给你思考的时间,又像是在强调接下来的对话。\n“而我救你,自然也不是无偿的。”"

CSV解析器更新

详见下文。

此外,我还把CSV解析器换成了静态方法 static func load_csv(csv_path: String) 这样调用的时候比较方便,不用像以前那样还得使用 preload() 来读取:

match type_str:
    "int": row[title] = value.to_int()
    "str", "string": row[title] = value
    "flo", "float": row[title] = value.to_float()

    # 下面这两个是新增的类型
    "bool", "boolean": row[title] = value.lower() in ["true", "1"]
    "arr", "array":
        var elements = value.strip_edges().split("|")
        var parsed_elements = []
        var max_elements_count = 0
        for element in elements:
            var cleaned = element.split(",")
            parsed_elements.append(cleaned)
            max_elements_count = max(max_elements_count, cleaned.size())
        # 填充每个子数组到最大长度,ps:后来我在官方文档中找到了一个更高效的预制方法 resize,可以直接根据元素个数来调整数组内容,不再需要循环了
        for parsed_list in parsed_elements:
            while parsed_list.size() < max_elements_count:
                parsed_list.append(parsed_list[-1])
        row[title] = parsed_elements

    _: row[title] = value

自定义段间距

RichTextLabel节点居然没有段落间距的控制选项……就挺莫名其妙,可选方法有两个:一个是用容器来控制子节点的间距,然后往容器里一段一段的加节点;另一个是,凭一人之力打倒整个世界(其实也就是用代码硬写一个功能用来控制段落间距)

目前我用的方法是,检测是否有换行,然后把换行替换成 \n[font_size=5] [/font_size]\n 。其实也就是插入一个空格,并通过通过控制空格的字符大小,来间接控制段落间距。

关于替换时机的检测,有两种方法,一个是使用 _process ,每帧检查一次文本是否有变动,有变动就替换。另一个是依据接收到的信号来替换,很显然,按照我这种极限精简高效的美学,肯定是选择了第二种。

第一版:
extends RichTextLabel

var last_text := ""
var processed_text := ""
signal text_changed

func _ready() -> void:
    text_changed.connect(_on_text_changed)
    _format_text()

func _on_text_changed() -> void:
    var current_text = get_text()
    if current_text != last_text:
        _format_text()
        last_text = current_text

func _format_text() -> void:
    var current_text = get_text()
    if current_text != processed_text:
        set_text(current_text.replace("\n", "\n[font_size=5] [/font_size]\n"))
        processed_text = get_text()  # 更新已处理文本

第二版:
extends RichTextLabel

signal text_changed  # 创建自定义信号

func _ready() -> void:
    text_changed.connect(_format_text)  # 连接信号到函数
    _format_text()  # 初始化文本

func _format_text() -> void:
    var current_text = get_text()  # 获取当前文本
    # 替换每一段的换行符,以改变文本格式。哈哈哈,我真他妈添材!叉腰!连续替换两次,先把更新后的换回原本的换行符,再统一把所有换行符换成分段格式,这样就不会反复添加换行符,也不需要搞这么多判断了。
    set_text(current_text.replace("\n[font_size=20] [/font_size]\n", "\n").replace("\n", "\n[font_size=20] [/font_size]\n"))

这里顺带也记录一下信号的用法,其实挺简单的:

# 定义信号
signal text_changed

# 连接信号(下面四种任选一种都可以,不懂选哪个方法的请去看官方文档,不过大部分情况下都没什么差别啦。)
  ## 方法 1:Object.connect() 并使用已定义的函数的隐式 Callable。
    connect("text_changed", _on_text_changed)
  ## 选项 2:Object.connect() 并使用由目标对象和方法名称构造的 Callable。
    connect("text_changed", Callable(self, "_on_text_changed"))
  ## 选项 3:官方最推荐的用法,也是最快的用法,Signal.connect() 并使用已定义的函数的隐式 Callable。
    text_changed.connect(_on_text_changed)
  ## 选项 4:Signal.connect() 并使用由目标对象和方法名称构造的 Callable。
    text_changed.connect(Callable(self, "_on_button_down"))

# 发送信号(在哪个脚本发送都可以,信号是全局的。但需要注意,自定义信号需要由节点定义并发射,不能直接发送。)
节点地址.emit_signal("text_changed")

主界面剧情功能的实现(暂未完成)

extends Node

#@onready var CSVParser = preload("res://scripts/CSVparse.gd").new()  # 我把CSV解析器改成了静态函数,所以这个可以省略
@onready var Polt = $VBoxContainer/Plot
var plot_data = CSVTool.load("res://scripts/csv/Plot.csv")

func _ready():
    Polt.text = plot_data[0]["dialog"]
    Polt.emit_signal("text_changed")

    new_button()

func new_button():
    var data = plot_data[0]["checks"]

    # 遍历数据数组,创建按钮
    for item in data:
        var split_data = item[0].split(":")
        var button_text = split_data[0]
        var success_threshold = int(split_data[1])

        var button = Button.new()
        button.text = button_text
        # 连接按钮的 pressed 信号到一个新定义的槽函数,并传递参数
        button.pressed.connect(_on_button_pressed.bind(success_threshold, item[1], item[2]))
        $VBoxContainer.add_child(button)

func _on_button_pressed(success_threshold, text1, text2):
    emit_signal("text_changed")
    # 生成随机数并进行检定
    var random_value = randi() % 21  # 生成0到20之间的随机数
    # 获取当前文本内容
    var current_text = Polt.text

    # 追加新文本
    if random_value > success_threshold:
        Polt.append_text("\n\n" + text1)
        Polt.emit_signal("text_changed")
    else:
        Polt.append_text("\n\n" + text2)
        Polt.emit_signal("text_changed")
本文作者:Mansifield

版权声明:本文采用 CC BY-NC 4.0 协议授权

转载需注明作者及原文链接: https://glyphite.github.io/SCXWNI/