Skip to content

Viết hội thoại ​

Một conversation là danh sách câu thoại có tên. play() chạy một cái theo tên, đúng như AnimationPlayer.play() chạy một animation theo tên.

Chúng nằm ở đâu ​

Gõ thẳng vào node thì chúng được lưu trong scene — không file nào. Lưu thành .tres là lựa chọn về sau, không phải điều kiện để bắt đầu. Xem Kho dùng chung.

gd
extends Node

## Docs: /guide/quick-start — the whole surface needed to say two lines.

func _ready() -> void:
	_build_without_files()


#region play
# Nothing here needs a .tres file. The conversation was typed in the TalkKit
# workspace, and this is the entire runtime surface.
func greet() -> void:
	$NPCTalkKit.play(&"greeting")
#endregion


## Builds in code what the TalkKit workspace builds by hand, so this file runs.
func _build_without_files() -> void:
	var talk := NPCTalkKit.new()
	talk.name = "NPCTalkKit"
	var smith := TalkSpeaker.new()
	smith.display_name = "Torvald"
	talk.speakers = [smith]
	var box := _instant_box()
	talk.dialogue_boxes = [box]

	var conversation := TalkConversation.new()
	conversation.conversation_id = &"greeting"
	conversation.default_speaker = smith
	conversation.default_dialogue_box = box
	var first := TalkLine.new()
	first.text = "The forge runs hot today."
	var second := TalkLine.new()
	second.text = "Come back when you need steel."
	conversation.lines = [first, second]
	talk.conversations = [conversation]
	add_child(talk)


func _verify() -> Array[String]:
	var failures: Array[String] = []
	var spoken: Array[String] = []
	var talk: NPCTalkKit = $NPCTalkKit
	talk.line_started.connect(func(line: TalkLine) -> void: spoken.append(line.text))

	greet()
	if not talk.is_running():
		failures.append("quick_start: play() did not start the conversation")
	talk.advance()
	talk.advance()
	if spoken.size() != 2:
		failures.append("quick_start: expected two lines, got %s" % str(spoken))
	if talk.is_running():
		failures.append("quick_start: the conversation should have ended")
	return failures


## Typing speed lives on the dialogue box now, so turning it off for a scripted
## run means handing the node a box that types instantly.
func _instant_box() -> TalkBoxTemplate:
	var template := TalkBoxTemplate.new()
	template.typewriter_speed = 0.0
	return template

Tham số trong lời thoại ​

Viết trong câu rồi điền lúc phát:

gd
extends Node

## Docs: /guide/conversations — {{placeholders}} filled at playback.

func _ready() -> void:
	_build()


#region play
# {{player_name}} is replaced when the line is shown. TalkKit never reads game
# state: it renders what you hand it.
func greet(player_name: String) -> void:
	$NPCTalkKit.play(&"greeting", {"player_name": player_name})
#endregion


#region unknown
# A key with no value is left written as-is and warned about, rather than
# silently emptied — an authoring mistake you can see beats one you cannot.
func greet_without_a_name() -> void:
	$NPCTalkKit.play(&"greeting")  # renders: Welcome back, {{player_name}}.
#endregion


func _build() -> void:
	var box := _instant_box()
	var talk := NPCTalkKit.new()
	talk.name = "NPCTalkKit"
	talk.dialogue_boxes = [box]
	var line := TalkLine.new()
	line.text = "Welcome back, {{player_name}}."
	var conversation := TalkConversation.new()
	conversation.conversation_id = &"greeting"
	conversation.default_dialogue_box = box
	conversation.lines = [line]
	talk.conversations = [conversation]
	add_child(talk)


func _verify() -> Array[String]:
	var failures: Array[String] = []
	var talk: NPCTalkKit = $NPCTalkKit
	var spoken: Array[String] = []
	talk.line_started.connect(func(line: TalkLine) -> void: spoken.append(line.text))

	greet("Alex")
	if spoken.is_empty() or spoken[0] != "Welcome back, Alex.":
		failures.append("parameters: substitution failed, got %s" % str(spoken))
	talk.stop()

	spoken.clear()
	greet_without_a_name()
	if spoken.is_empty() or not spoken[0].contains("{{player_name}}"):
		failures.append("parameters: an unknown key must stay visible, got %s" % str(spoken))
	talk.stop()

	# The source line must never be mutated: it may be shared by other NPCs.
	if talk.conversations[0].lines[0].text != "Welcome back, {{player_name}}.":
		failures.append("parameters: the authored line was mutated by substitution")
	return failures


## Typing speed lives on the dialogue box now, so turning it off for a scripted
## run means handing the node a box that types instantly.
func _instant_box() -> TalkBoxTemplate:
	var template := TalkBoxTemplate.new()
	template.typewriter_speed = 0.0
	return template

Thứ tự ghép, cái sau thắng:

  1. default_params trên node
  2. dictionary truyền vào play()

Ba luật đáng nhớ:

  • Key không có giá trị thì giữ nguyên hiện ra. hiện đúng như đã viết kèm một cảnh báo, thay vì bị xoá trắng âm thầm — lỗi nhìn thấy được vẫn hơn lỗi không nhìn thấy.
  • Một lượt, không đệ quy. Giá trị chứa không được khai triển tiếp. Đây không phải ngôn ngữ template.
  • Câu gốc không bao giờ bị sửa. Thay thế diễn ra trên một bản sao, vì câu đó có thể là resource dùng chung mà NPC khác đang dùng.
gd
extends Node

## Docs: /guide/conversations — {{placeholders}} filled at playback.

func _ready() -> void:
	_build()


#region play
# {{player_name}} is replaced when the line is shown. TalkKit never reads game
# state: it renders what you hand it.
func greet(player_name: String) -> void:
	$NPCTalkKit.play(&"greeting", {"player_name": player_name})
#endregion


#region unknown
# A key with no value is left written as-is and warned about, rather than
# silently emptied — an authoring mistake you can see beats one you cannot.
func greet_without_a_name() -> void:
	$NPCTalkKit.play(&"greeting")  # renders: Welcome back, {{player_name}}.
#endregion


func _build() -> void:
	var box := _instant_box()
	var talk := NPCTalkKit.new()
	talk.name = "NPCTalkKit"
	talk.dialogue_boxes = [box]
	var line := TalkLine.new()
	line.text = "Welcome back, {{player_name}}."
	var conversation := TalkConversation.new()
	conversation.conversation_id = &"greeting"
	conversation.default_dialogue_box = box
	conversation.lines = [line]
	talk.conversations = [conversation]
	add_child(talk)


func _verify() -> Array[String]:
	var failures: Array[String] = []
	var talk: NPCTalkKit = $NPCTalkKit
	var spoken: Array[String] = []
	talk.line_started.connect(func(line: TalkLine) -> void: spoken.append(line.text))

	greet("Alex")
	if spoken.is_empty() or spoken[0] != "Welcome back, Alex.":
		failures.append("parameters: substitution failed, got %s" % str(spoken))
	talk.stop()

	spoken.clear()
	greet_without_a_name()
	if spoken.is_empty() or not spoken[0].contains("{{player_name}}"):
		failures.append("parameters: an unknown key must stay visible, got %s" % str(spoken))
	talk.stop()

	# The source line must never be mutated: it may be shared by other NPCs.
	if talk.conversations[0].lines[0].text != "Welcome back, {{player_name}}.":
		failures.append("parameters: the authored line was mutated by substitution")
	return failures


## Typing speed lives on the dialogue box now, so turning it off for a scripted
## run means handing the node a box that types instantly.
func _instant_box() -> TalkBoxTemplate:
	var template := TalkBoxTemplate.new()
	template.typewriter_speed = 0.0
	return template

TalkKit không bao giờ đọc trạng thái game. Nó hiển thị thứ bạn đưa cho nó.

Nói một câu mà không soạn gì cả ​

Với câu dùng một lần — gợi ý hướng dẫn, thông báo debug — bỏ qua phần soạn thảo:

gdscript
$NPCTalkKit.say("The gate is barred.")
$NPCTalkKit.say(["Line one.", "Line two."], {"name": "Alex"})

Hội thoại được dựng tại chỗ rồi bỏ đi; không có gì được thêm vào node.

Tuỳ chọn từng câu ​

Phần lớn line chỉ cần chữ. Chọn line ngoại lệ rồi dùng detail pane bên phải. Add override… mở các nhóm theo tác vụ; override đang dùng luôn hiện và mỗi cái có nút Reset riêng:

TrườngDùng khi
speaker_mode, speakerInherit speaker của conversation, None cho câu người kể chuyện, hoặc nhân vật khác nói câu này — tên, mặt và giọng đi cùng nhau
display_name_overridechỉ đổi tên: "???" trước lúc lộ diện. Mặt và giọng vẫn là của người đang nói
visual_variantchọn một gương mặt có tên của speaker, ví dụ angry
characters_per_secondmuốn một câu cao trào chạy chậm lại; 0 là kế thừa
event_id, event_payloadbáo cho game biết điều gì đó — sự kiện
event_blockingdừng lại chờ câu trả lời — rẽ nhánh
dialogue_boxmột câu hiện trong hộp khác
placementmột câu hiện ở chỗ khác — bubble trên đầu người chen ngang
voice_clipmột câu thoại thu âm, phát khi câu bắt đầu
line_idcode của bạn cần nhận diện đúng câu đó

line_id không bắt buộc. Nó tồn tại cho code của bạn và cho khoá dịch thuật, không phải vì TalkKit cần.

Pane bên phải cũng vẽ line đang chọn bằng renderer runtime thật. ▶ Play phát conversation ngay tại đó từ line đang chọn — gõ chữ, transition và giọng, hết câu này sang câu khác — còn ■ Stop hoặc ↓ Next giữ nó đứng yên lại. Ở chiều rộng trung bình, pane này thành drawer để vùng viết vẫn rộng. Checkbox trên line bật bulk edit Speaker, Dialogue Box, Placement, duplicate và delete; drag handle hoặc Alt+↑/↓ đều sắp xếp lại được.

Ưu tiên GDScript. Addon phát hành không có telemetry, không gọi mạng, không phụ thuộc dịch vụ AI.