剧情分支

制作历程

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

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

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

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

表格编写

实现方面,依旧是使用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")
本文作者:晝行燈

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

转载需注明作者及原文链接: https://mansifield.pages.dev/s0s83r/