Kho dùng chung
Tuỳ chọn. Một NPC chỉ nói lời thoại của riêng nó thì không cần gì ở trang này.
TalkKitDatabase là resource thường, chứa hội thoại và speaker, dành cho nội dung dùng lại giữa nhiều NPC hoặc nhiều scene — ví dụ một câu chào mà mười hai người bán hàng cùng dùng.
Thứ tự tra cứu
extends Node
## Docs: /guide/library — reusing conversations across NPCs. Optional.
func _ready() -> void:
_build()
#region lookup
# play() checks the node first, then the library. So an NPC overrides a shared
# conversation simply by owning one with the same name — there is no alias
# concept to learn.
func who_wins() -> StringName:
return $NPCTalkKit.find_conversation(&"greeting").conversation_id
#endregion
#region list
# Everything this NPC can play, node content shadowing the library.
func every_name() -> Array[StringName]:
return $NPCTalkKit.list_conversations()
#endregion
func _build() -> void:
var box := _instant_box()
var shared := TalkConversation.new()
shared.conversation_id = &"greeting"
shared.default_dialogue_box = box
var shared_line := TalkLine.new()
shared_line.text = "Welcome to Oakhollow."
shared.lines = [shared_line]
var farewell := TalkConversation.new()
farewell.conversation_id = &"farewell"
farewell.default_dialogue_box = box
var bye := TalkLine.new()
bye.text = "Safe roads."
farewell.lines = [bye]
var library := TalkKitDatabase.new()
library.conversations = [shared, farewell]
var own := TalkConversation.new()
own.conversation_id = &"greeting"
own.default_dialogue_box = box
var own_line := TalkLine.new()
own_line.text = "Mind the anvil, it is still hot."
own.lines = [own_line]
var talk := NPCTalkKit.new()
talk.name = "NPCTalkKit"
talk.dialogue_boxes = [box]
talk.library = library
talk.conversations = [own]
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))
talk.play(&"greeting")
if spoken.is_empty() or not spoken[0].begins_with("Mind the anvil"):
failures.append("library: the node's own conversation must win, got %s" % str(spoken))
talk.stop()
spoken.clear()
talk.play(&"farewell")
if spoken.is_empty() or spoken[0] != "Safe roads.":
failures.append("library: a library-only conversation must still play")
talk.stop()
var names := every_name()
if not (names.has(&"greeting") and names.has(&"farewell")) or names.size() != 2:
failures.append("library: listing must merge both without duplicates, got %s" % str(names))
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 templateplay() tìm trên node trước, rồi mới tới kho. Nên một NPC ghi đè nội dung dùng chung đơn giản bằng cách tự sở hữu một hội thoại cùng tên. Không có khái niệm alias nào, vì không cần tới nó.
extends Node
## Docs: /guide/library — reusing conversations across NPCs. Optional.
func _ready() -> void:
_build()
#region lookup
# play() checks the node first, then the library. So an NPC overrides a shared
# conversation simply by owning one with the same name — there is no alias
# concept to learn.
func who_wins() -> StringName:
return $NPCTalkKit.find_conversation(&"greeting").conversation_id
#endregion
#region list
# Everything this NPC can play, node content shadowing the library.
func every_name() -> Array[StringName]:
return $NPCTalkKit.list_conversations()
#endregion
func _build() -> void:
var box := _instant_box()
var shared := TalkConversation.new()
shared.conversation_id = &"greeting"
shared.default_dialogue_box = box
var shared_line := TalkLine.new()
shared_line.text = "Welcome to Oakhollow."
shared.lines = [shared_line]
var farewell := TalkConversation.new()
farewell.conversation_id = &"farewell"
farewell.default_dialogue_box = box
var bye := TalkLine.new()
bye.text = "Safe roads."
farewell.lines = [bye]
var library := TalkKitDatabase.new()
library.conversations = [shared, farewell]
var own := TalkConversation.new()
own.conversation_id = &"greeting"
own.default_dialogue_box = box
var own_line := TalkLine.new()
own_line.text = "Mind the anvil, it is still hot."
own.lines = [own_line]
var talk := NPCTalkKit.new()
talk.name = "NPCTalkKit"
talk.dialogue_boxes = [box]
talk.library = library
talk.conversations = [own]
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))
talk.play(&"greeting")
if spoken.is_empty() or not spoken[0].begins_with("Mind the anvil"):
failures.append("library: the node's own conversation must win, got %s" % str(spoken))
talk.stop()
spoken.clear()
talk.play(&"farewell")
if spoken.is_empty() or spoken[0] != "Safe roads.":
failures.append("library: a library-only conversation must still play")
talk.stop()
var names := every_name()
if not (names.has(&"greeting") and names.has(&"farewell")) or names.size() != 2:
failures.append("library: listing must merge both without duplicates, got %s" % str(names))
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Đẩy một hội thoại lên kho
Soạn thảo bắt đầu từ nội dung nhúng. Chuyển một cái lên kho là hành động tường minh, không phải thứ xảy ra sau lưng bạn:
- Gán một kho cho node, và lưu kho đó thành file.
- Chọn conversation trong workspace TalkKit rồi chọn ⋮ → Save to Library.
Hội thoại được lưu thành .tres riêng, ô trên node được trỏ sang file đó, và kho có thêm một mục.
Không có đồng bộ tự động. Đồng bộ mỗi lần lưu scene sẽ làm bẩn một file dùng chung ở mọi lần chỉnh sửa — một tác dụng phụ vô hình và là cỗ máy sinh xung đột merge.
Id hội thoại có phạm vi theo node
play("greeting") được gọi trên một node cụ thể, nên hai mươi NPC đều có thể sở hữu một greeting. Chỉ kho mới là một không gian tên duy nhất. Validator theo đúng nguyên tắc đó:
| Tình huống | Báo là |
|---|---|
| kho khai một id hai lần | lỗi — mơ hồ thật sự |
| một node khai một id hai lần | lỗi — cái thứ hai không bao giờ chạy được |
| hai node khác nhau cùng một id | cảnh báo — bình thường, nhưng đẩy cả hai lên kho thì đụng nhau |
| node che id của kho | không báo gì — đó chính là ghi đè ở trên |
Kiểm tra cả một scene
TalkValidator và TalkKitSerializer nhận mảng thường chứ không nhận database, nên nội dung nhúng là công dân hạng nhất chứ không phải trường hợp đặc biệt:
var report := TalkKitSceneCollector.validate(scene_root, library)Nó duyệt mọi NPCTalkKit trong scene và kiểm tra cùng với kho.
JSON
TalkKitSerializer xuất và nhập hội thoại dưới dạng JSON, cho quy trình dịch thuật, công cụ bên ngoài, hoặc một AI agent viết nội dung. Trong addon không có LLM nào cả — đó chỉ là dữ liệu có cấu trúc mà tình cờ dễ cho AI tạo ra.