Skip to content
extends Control

# 加上的炫酷的打字机效果


<NolebasePageProperties />




const TYPING_SPEED: float = 0.05        # 每个字符显示间隔(秒)
var typewriter_timer: float = 0.0        # 计时器
var typewriter_char_index: int = 0       # 当前显示到第几个字符
var typewriter_completed: bool = true    # 当前文字是否已完全显示
var full_text: String = ""               # 当前行的完整文本(用于跳过)

# 从 JSON 加载对话数据
var dialogues: Dictionary = {}        # 这是一个字典,存储整个 JSON 的字典
var current_section: String = "start" # 当前节名
var line_index: int = 0               # 当前节内的行号

# UI 节点引用
@onready var background: TextureRect = $Background
@onready var left_char: TextureRect = $Characters/LeftChar
@onready var right_char: TextureRect = $Characters/RightChar
@onready var name_label: Label = $NameLabel
@onready var text_label: Label = $TextLabel
@onready var choice_container: VBoxContainer = $ChoiceContainer

var waiting_for_choice := false

func _ready():
	# 加载第一篇章的json文件
	dialogues = load_dialogue("res://dialogues/chapter1.json")
	if dialogues.is_empty():
		return
	start_section("start")

func load_dialogue(path: String) -> Dictionary:
	var file = FileAccess.open(path, FileAccess.READ)
	if file == null:
		print("无法打开文件:", path)
		return {}
	var json_str = file.get_as_text()
	var json = JSON.new()
	var error = json.parse(json_str)
	if error != OK:
		print("JSON 解析错误:", json.get_error_message())
		return {}
	return json.get_data()

func start_section(section_name: String):
	current_section = section_name
	line_index = 0
	waiting_for_choice = false
	show_line()

func show_line():
	var section = dialogues.get(current_section)
	if section == null or line_index >= section.size():
	# ---- 重置打字机状态 ----
		typewriter_completed = true
		full_text = ""
		typewriter_char_index = 0
		typewriter_timer = 0.0
		name_label.text = ""
		text_label.text = "(对话结束)"
		return

	var line = section[line_index]

	# 突变(不变)
	if line.has("mutation"):
		handle_mutation(line.mutation)

	if line.type == "dialogue":
		name_label.text = line.speaker
		# ---- 打字机初始化 ----
		full_text = line.text
		text_label.text = ""                     # 清空,准备逐字显示
		typewriter_char_index = 0
		typewriter_timer = 0.0
		typewriter_completed = false
		# --------------------
		waiting_for_choice = false
		choice_container.visible = false
	elif line.type == "choice":
		name_label.text = ""
		text_label.text = ""
		# 选项不涉及打字机,直接标记完成
		typewriter_completed = true
		show_choices(line.choices)


# 处理突变
func handle_mutation(mut: Dictionary):
	if mut.has("background"):
		var tex = load(mut.background)
		if tex: background.texture = tex
	if mut.has("show_left"):
		var tex = load(mut.show_left)
		if tex: left_char.texture = tex
		left_char.visible = true
	if mut.has("show_right"):
		var tex = load(mut.show_right)
		if tex: right_char.texture = tex
		right_char.visible = true
	if mut.has("hide_left"):
		left_char.visible = false
	if mut.has("hide_right"):
		right_char.visible = false

# 显示选项
func show_choices(choices: Array):
	waiting_for_choice = true
	choice_container.visible = true
	for child in choice_container.get_children():
		child.queue_free()
	for choice in choices:
		var btn := Button.new()
		btn.text = choice.text
		choice_container.add_child(btn)
		btn.pressed.connect(func():
			# 清除按钮
			for c in choice_container.get_children():
				c.queue_free()
			choice_container.visible = false
			waiting_for_choice = false
			typewriter_completed = true    # 标记为已完成,避免 _process 继续驱动旧文本
			full_text = ""
			text_label.text = ""
			typewriter_char_index = 0
			typewriter_timer = 0.0
			# 如果这个选项有突变,也执行
			if choice.has("mutation"):
				handle_mutation(choice.mutation)
			# 修正:用 start_section 跳转到新节(choice.next 是节名)
			start_section(choice.next)
		)

func _process(delta):
	# 只在未完成且不是选择状态时驱动打字机
	if typewriter_completed or waiting_for_choice:
		return

	typewriter_timer += delta
	while typewriter_timer >= TYPING_SPEED and typewriter_char_index < len(full_text):
		typewriter_timer -= TYPING_SPEED
		typewriter_char_index += 1
		text_label.text = full_text.substr(0, typewriter_char_index)

	# 字符全部显示完毕,标记完成
	if typewriter_char_index >= len(full_text):
		typewriter_completed = true
		# 显示继续提示(可选)
		# next_indicator.visible = true


# 非选择状态时翻页、玩家输入
func _input(event):
	if not (event.is_action_pressed("ui_accept") or event.is_action_pressed("ui_left_mouse")):
		return

	# 有选项时,不处理翻页(选项按钮自己处理)
	if waiting_for_choice:
		return

	# 打字机未完成 → 立即显示全文
	if not typewriter_completed:
		text_label.text = full_text
		typewriter_char_index = len(full_text)
		typewriter_completed = true
		# 并显示继续提示(可选)
		# next_indicator.visible = true
		return

	# 打字已完成 → 翻页到下一句
	line_index += 1
	# 重置打字机状态(下一句会重新初始化)
	typewriter_completed = true
	show_line()

贡献者

The avatar of contributor named as G4OM-ES G4OM-ES

文件历史

撰写