Sự kiện và rẽ nhánh
TalkKit không có ngôn ngữ điều kiện, không có kho biến, không có trình tính biểu thức — vì game của bạn đã có đủ cả ba. Thứ nó có là một điểm nhường.
Sự kiện không chặn
Gán event_id cho một câu và game sẽ được báo khi câu đó kết thúc. Hội thoại chạy tiếp bình thường.
extends Node
## Docs: /guide/quick-start, /guides/use-with-existing-interaction-system
##
## TalkKit asks; it never takes. Every hook below is opt-in — ignore them all
## and the addon still works, it just will not know about your player.
func _ready() -> void:
_build()
#region signals
func _connect_talk() -> void:
var talk: NPCTalkKit = $NPCTalkKit
talk.request_player_lock.connect(_on_player_lock)
talk.event_triggered.connect(_on_talk_event)
talk.conversation_finished.connect(_on_finished)
#endregion
#region lock
# TalkKit does not own your player controller. It asks, you decide.
func _on_player_lock(locked: bool) -> void:
_player_accepts_input = not locked
#endregion
#region event
# A non-blocking event: fire and forget, playback carries on.
func _on_talk_event(event_id: StringName, payload: Variant) -> void:
match event_id:
&"give_quest":
_quests.append(payload)
&"play_sound":
pass
#endregion
#region existing-interaction
# Already have an interaction system? Then skip TalkInteractionArea2D entirely
# and call play() from the code you already have. That is the preferred path.
func _on_my_own_interact_pressed(npc: Node) -> void:
var talk := npc.get_node_or_null("NPCTalkKit") as NPCTalkKit
if talk != null and not talk.is_running():
talk.play(&"greeting", {"player_name": _player_name})
#endregion
var _player_accepts_input := true
var _quests: Array = []
var _player_name := "Alex"
func _on_finished(_id: StringName) -> void:
pass
func _build() -> void:
var box := _instant_box()
var talk := NPCTalkKit.new()
talk.name = "NPCTalkKit"
talk.dialogue_boxes = [box]
var line := TalkLine.new()
line.text = "Good to see you, {{player_name}}."
line.event_id = &"give_quest"
line.event_payload = "deliver_the_letter"
var conversation := TalkConversation.new()
conversation.conversation_id = &"greeting"
conversation.default_dialogue_box = box
conversation.lines = [line]
talk.conversations = [conversation]
add_child(talk)
_connect_talk()
func _verify() -> Array[String]:
var failures: Array[String] = []
var talk: NPCTalkKit = $NPCTalkKit
_on_my_own_interact_pressed(self)
if not talk.is_running():
failures.append("integration: play() from host code did not start")
if _player_accepts_input:
failures.append("integration: the lock request never reached the host")
# A second press must not restart what the player is reading.
_on_my_own_interact_pressed(self)
talk.advance()
if not _quests.has("deliver_the_letter"):
failures.append("integration: the non-blocking event never arrived")
if not _player_accepts_input:
failures.append("integration: the lock was never released at the end")
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 templateDùng cho hiệu ứng âm thanh, rung camera, trao nhiệm vụ — bất cứ thứ gì không cần câu trả lời.
Blocking event: chỗ rẽ nhánh
Tích event_blocking cho một câu thì hội thoại dừng lại cho tới khi game trả lời. Dùng signal riêng, cố ý như vậy, để không thể bỏ sót việc cần phản hồi.
extends Node
## Docs: /guide/events — player choice without a branching engine.
func _ready() -> void:
_build()
#region listen
func _connect_talk() -> void:
$NPCTalkKit.event_blocked.connect(_on_blocked)
#endregion
#region respond
# Playback is paused. The game decides what happens next, reading its own quest
# and inventory state — TalkKit never learns what a quest is.
func _on_blocked(event_id: StringName, payload: Variant) -> void:
if event_id != &"offer_job":
return
var accepted: bool = _player_has_room_for(payload)
$NPCTalkKit.play(&"accepted" if accepted else &"declined")
#endregion
#region resume
# The other two answers: carry on where it paused, or end the conversation.
func _keep_going() -> void:
$NPCTalkKit.resume()
func _walk_away() -> void:
$NPCTalkKit.stop()
#endregion
func _player_has_room_for(_payload: Variant) -> bool:
return true
func _build() -> void:
var box := _instant_box()
var talk := NPCTalkKit.new()
talk.name = "NPCTalkKit"
talk.dialogue_boxes = [box]
var ask := TalkLine.new()
ask.text = "Carrying a message to the mill pays three silver. Take it?"
ask.event_id = &"offer_job"
ask.event_blocking = true
var offer := TalkConversation.new()
offer.conversation_id = &"offer"
offer.default_dialogue_box = box
offer.lines = [ask]
var yes := TalkLine.new()
yes.text = "Good. The mill before dusk."
var accepted := TalkConversation.new()
accepted.conversation_id = &"accepted"
accepted.default_dialogue_box = box
accepted.lines = [yes]
var no := TalkLine.new()
no.text = "Suit yourself."
var declined := TalkConversation.new()
declined.conversation_id = &"declined"
declined.default_dialogue_box = box
declined.lines = [no]
talk.conversations = [offer, accepted, declined]
add_child(talk)
_connect_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(&"offer")
talk.advance()
if spoken.size() != 2 or not spoken[1].begins_with("Good."):
failures.append("blocking: the branch did not play, got %s" % str(spoken))
if talk.is_blocked():
failures.append("blocking: still blocked after branching")
talk.stop()
# resume() continues the conversation that paused.
var second := TalkLine.new()
second.text = "Dusk, remember."
talk.conversations[0].lines.append(second)
talk.event_blocked.disconnect(_on_blocked)
spoken.clear()
talk.play(&"offer")
talk.advance()
if not talk.is_blocked():
failures.append("blocking: advancing past a blocking line must pause")
talk.resume()
if spoken.size() != 2:
failures.append("blocking: resume() did not continue, got %s" % str(spoken))
talk.stop()
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 templateextends Node
## Docs: /guide/events — player choice without a branching engine.
func _ready() -> void:
_build()
#region listen
func _connect_talk() -> void:
$NPCTalkKit.event_blocked.connect(_on_blocked)
#endregion
#region respond
# Playback is paused. The game decides what happens next, reading its own quest
# and inventory state — TalkKit never learns what a quest is.
func _on_blocked(event_id: StringName, payload: Variant) -> void:
if event_id != &"offer_job":
return
var accepted: bool = _player_has_room_for(payload)
$NPCTalkKit.play(&"accepted" if accepted else &"declined")
#endregion
#region resume
# The other two answers: carry on where it paused, or end the conversation.
func _keep_going() -> void:
$NPCTalkKit.resume()
func _walk_away() -> void:
$NPCTalkKit.stop()
#endregion
func _player_has_room_for(_payload: Variant) -> bool:
return true
func _build() -> void:
var box := _instant_box()
var talk := NPCTalkKit.new()
talk.name = "NPCTalkKit"
talk.dialogue_boxes = [box]
var ask := TalkLine.new()
ask.text = "Carrying a message to the mill pays three silver. Take it?"
ask.event_id = &"offer_job"
ask.event_blocking = true
var offer := TalkConversation.new()
offer.conversation_id = &"offer"
offer.default_dialogue_box = box
offer.lines = [ask]
var yes := TalkLine.new()
yes.text = "Good. The mill before dusk."
var accepted := TalkConversation.new()
accepted.conversation_id = &"accepted"
accepted.default_dialogue_box = box
accepted.lines = [yes]
var no := TalkLine.new()
no.text = "Suit yourself."
var declined := TalkConversation.new()
declined.conversation_id = &"declined"
declined.default_dialogue_box = box
declined.lines = [no]
talk.conversations = [offer, accepted, declined]
add_child(talk)
_connect_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(&"offer")
talk.advance()
if spoken.size() != 2 or not spoken[1].begins_with("Good."):
failures.append("blocking: the branch did not play, got %s" % str(spoken))
if talk.is_blocked():
failures.append("blocking: still blocked after branching")
talk.stop()
# resume() continues the conversation that paused.
var second := TalkLine.new()
second.text = "Dusk, remember."
talk.conversations[0].lines.append(second)
talk.event_blocked.disconnect(_on_blocked)
spoken.clear()
talk.play(&"offer")
talk.advance()
if not talk.is_blocked():
failures.append("blocking: advancing past a blocking line must pause")
talk.resume()
if spoken.size() != 2:
failures.append("blocking: resume() did not continue, got %s" % str(spoken))
talk.stop()
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 templateTrong lúc dừng, advance() không làm gì — hộp vẫn trên màn hình, đứng yên. Game của bạn phải chọn một trong ba:
| Gọi | Kết quả |
|---|---|
play("other") | Bỏ phần còn lại, bắt đầu hội thoại khác. Hộp được tái dùng nếu cùng renderer nên không bị nháy. |
resume() | Đi tiếp từ câu kế của hội thoại đang dừng. |
stop() | Huỷ. Phát conversation_cancelled và mở khoá người chơi. |
extends Node
## Docs: /guide/events — player choice without a branching engine.
func _ready() -> void:
_build()
#region listen
func _connect_talk() -> void:
$NPCTalkKit.event_blocked.connect(_on_blocked)
#endregion
#region respond
# Playback is paused. The game decides what happens next, reading its own quest
# and inventory state — TalkKit never learns what a quest is.
func _on_blocked(event_id: StringName, payload: Variant) -> void:
if event_id != &"offer_job":
return
var accepted: bool = _player_has_room_for(payload)
$NPCTalkKit.play(&"accepted" if accepted else &"declined")
#endregion
#region resume
# The other two answers: carry on where it paused, or end the conversation.
func _keep_going() -> void:
$NPCTalkKit.resume()
func _walk_away() -> void:
$NPCTalkKit.stop()
#endregion
func _player_has_room_for(_payload: Variant) -> bool:
return true
func _build() -> void:
var box := _instant_box()
var talk := NPCTalkKit.new()
talk.name = "NPCTalkKit"
talk.dialogue_boxes = [box]
var ask := TalkLine.new()
ask.text = "Carrying a message to the mill pays three silver. Take it?"
ask.event_id = &"offer_job"
ask.event_blocking = true
var offer := TalkConversation.new()
offer.conversation_id = &"offer"
offer.default_dialogue_box = box
offer.lines = [ask]
var yes := TalkLine.new()
yes.text = "Good. The mill before dusk."
var accepted := TalkConversation.new()
accepted.conversation_id = &"accepted"
accepted.default_dialogue_box = box
accepted.lines = [yes]
var no := TalkLine.new()
no.text = "Suit yourself."
var declined := TalkConversation.new()
declined.conversation_id = &"declined"
declined.default_dialogue_box = box
declined.lines = [no]
talk.conversations = [offer, accepted, declined]
add_child(talk)
_connect_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(&"offer")
talk.advance()
if spoken.size() != 2 or not spoken[1].begins_with("Good."):
failures.append("blocking: the branch did not play, got %s" % str(spoken))
if talk.is_blocked():
failures.append("blocking: still blocked after branching")
talk.stop()
# resume() continues the conversation that paused.
var second := TalkLine.new()
second.text = "Dusk, remember."
talk.conversations[0].lines.append(second)
talk.event_blocked.disconnect(_on_blocked)
spoken.clear()
talk.play(&"offer")
talk.advance()
if not talk.is_blocked():
failures.append("blocking: advancing past a blocking line must pause")
talk.resume()
if spoken.size() != 2:
failures.append("blocking: resume() did not continue, got %s" % str(spoken))
talk.stop()
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 templateLogic rẽ nhánh là GDScript bình thường trong game của bạn, đọc trạng thái nhiệm vụ và túi đồ của chính bạn. TalkKit không bao giờ biết nhiệm vụ là gì.
Giao diện lựa chọn không có sẵn
TalkKit dừng lại và báo cho bạn. Vẽ nút là việc của game — game đã biết menu của nó trông ra sao.
Sự kiện cấp hội thoại
Một TalkConversation có thể mang event_id riêng, phát khi hội thoại kết thúc, ngay trước conversation_finished.
Chỉ không chặn. Tới lúc đó không còn gì để dừng; một blocking event ở cuối hội thoại chính là blocking event trên câu cuối.
play() làm gì khi đang bận
| Gọi lúc | Kết quả |
|---|---|
| rảnh | bắt đầu |
| đang mở hoặc đang đọc | từ chối, trả false — lần bấm tương tác thứ hai không thể khởi động lại câu người chơi đang đọc dở |
| đang bị chặn | cho phép — chính là chỗ rẽ nhánh ở trên |
Nó trả về bool và không bao giờ ném lỗi, nên một lời gọi bị từ chối là thứ bạn kiểm tra được chứ không phải thứ làm game sập.