You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用GDScript构建规范的游戏对话系统?修复现有问题

GDScript对话系统问题修复与规范构建指南

现有实现代码

NPC触发对话逻辑

func _on_worker_talking_area_body_entered(body):
  if body.name == "Player":
    $BubbleAnimation.show()
    $BubbleAnimation.play("on")
    $DialogLayer/Dialog/Panel/DialogText.text = approach_text       
    $DialogLayer.show()
    
func _on_worker_talking_area_body_exited(body):
  if body.name == "Player":
    $BubbleAnimation.stop()
    $BubbleAnimation.hide()
    $DialogLayer.hide()

玩家交互输入处理

func _input(event: InputEvent):
  if event.is_action_pressed("interaction") and $BubbleAnimation.visible:
    if worker_name == "welcome_worker" and state == 0:
        await $DialogLayer.split_text(get_current_text())
        state = 1
        state_changed.emit()
    
    if (worker_name == "crane_worker" or worker_name == "milling_machine_worker") \
    and state == 1:
        await $DialogLayer.split_text(get_current_text())
        state = 2 
    
    if (worker_name == "crane_worker" or worker_name == "milling_machine_worker") \
    and state == 2:
        await $DialogLayer.split_text(get_current_text())       
    
  if event.is_action_pressed("no") and state == 2 and $BubbleAnimation.visible:
    state = 3
    await $DialogLayer.split_text(get_current_text())
    state_changed.emit()

  if event.is_action_pressed("yes") and state == 2 and $BubbleAnimation.visible:
    state = 4
    await $DialogLayer.split_text(get_current_text())
    state_changed.emit()

DialogLayer文本滚动逻辑

func split_text(input_text: String) -> void:
   $Dialog.show()
   var text_array = input_text.split("/")
   for text_piece in text_array:
      scroll_text(text_piece)
      await get_tree().create_timer(5.5).timeout
   $Dialog.hide()

func scroll_text(input_text: String) -> void:
   var text = input_text
   var visible_characters = 0
   for i in text.length():
      visible_characters += 1
      await get_tree().create_timer(0.1).timeout
      $Dialog/Panel/DialogText.text = text.left(visible_characters)

现有问题

  • 玩家在文本滚动时离开对话区域,文本停止显示但滚动仍在后台运行;
  • 短对话需等待固定5.5秒,无跳过功能,体验糟糕;
  • 对话偶尔会突然消失,整体机制不稳定。

问题修复方案

1. 终止后台残留滚动任务

当前body_exited仅隐藏UI,但异步滚动任务仍在后台执行,会导致后续状态混乱。

修复步骤:

  • 在DialogLayer中添加中断标志和Timer管理,所有异步循环中检查中断状态;
  • NPC离开时调用DialogLayer的中断方法,同时重置对话状态。

修改后的DialogLayer代码:

var is_interrupted = false
var current_timer: Timer = null

func split_text(input_text: String) -> void:
   is_interrupted = false
   $Dialog.show()
   var text_array = input_text.split("/")
   for text_piece in text_array:
      if is_interrupted:
          break
      await scroll_text(text_piece)
      if is_interrupted:
          break
      await wait_for_skip_or_timeout(5.5)
   $Dialog.hide()

func scroll_text(input_text: String) -> void:
   is_interrupted = false
   var text = input_text
   var visible_characters = 0
   $Dialog/Panel/DialogText.text = ""
   while visible_characters < text.length() and not is_interrupted:
       visible_characters += 1
       $Dialog/Panel/DialogText.text = text.left(visible_characters)
       await get_tree().create_timer(0.1).timeout

func wait_for_skip_or_timeout(timeout: float) -> void:
   current_timer = get_tree().create_timer(timeout)
   await current_timer.timeout
   current_timer = null

func interrupt_dialog():
   is_interrupted = true
   if current_timer:
       current_timer.stop()
       current_timer = null
   $Dialog.hide()

修改NPC的body_exited:

func _on_worker_talking_area_body_exited(body):
  if body.name == "Player":
    $BubbleAnimation.stop()
    $BubbleAnimation.hide()
    $DialogLayer.interrupt_dialog()
    # 重置对话状态,避免返回后状态错位
    state = 0

2. 添加对话跳过功能

允许玩家通过交互键跳过当前文本块和等待时间,提升短对话体验。

修复步骤:

  • 修改wait_for_skip_or_timeout支持输入中断;
  • 在scroll_text中添加中途跳过逻辑,直接显示全部文本。

更新后的DialogLayer代码:

func wait_for_skip_or_timeout(timeout: float) -> void:
   current_timer = get_tree().create_timer(timeout)
   while not current_timer.is_timeout():
       if Input.is_action_just_pressed("interaction"):
           current_timer.stop()
           break
       await get_tree().process_frame
   current_timer = null

func scroll_text(input_text: String) -> void:
   is_interrupted = false
   var text = input_text
   var visible_characters = 0
   $Dialog/Panel/DialogText.text = ""
   while visible_characters < text.length() and not is_interrupted:
       if Input.is_action_just_pressed("interaction"):
           # 一键显示全部文本
           $Dialog/Panel/DialogText.text = text
           visible_characters = text.length()
           break
       visible_characters += 1
       $Dialog/Panel/DialogText.text = text.left(visible_characters)
       await get_tree().create_timer(0.1).timeout

3. 解决对话突然消失问题

对话消失通常源于状态判断不严谨或UI时机冲突。

修复步骤:

  • 移除body_entered中直接设置对话文本的逻辑,仅显示气泡,等待玩家触发交互;
  • 给状态变量添加合法性检查,避免非法状态下执行逻辑。

修改NPC的body_entered:

func _on_worker_talking_area_body_entered(body):
  if body.name == "Player":
    $BubbleAnimation.show()
    $BubbleAnimation.play("on")
    # 仅显示交互气泡,对话文本由玩家触发交互时加载
    $DialogLayer.hide()

在NPC的_input中添加状态范围检查:

func _input(event: InputEvent):
  if not $BubbleAnimation.visible:
      return
  # 其他输入逻辑...

规范对话系统构建准则

1. 状态管理模块化

  • 将对话流程从NPC脚本中分离,用DialogueResource资源存储每个NPC的对话节点、选项和跳转逻辑,避免硬编码worker_name和state判断;
  • 示例:每个DialogueResource包含id、text、options、next_state字段,NPC脚本仅负责加载资源并驱动流程。

2. UI与逻辑解耦

  • DialogLayer只负责文本渲染、滚动、跳过等UI操作,不处理对话内容和状态流转;
  • 使用信号传递事件,比如DialogLayer完成文本滚动后发出text_finished信号,NPC脚本监听后更新状态。

3. 异步任务统一管理

  • 所有await操作必须支持中断,避免后台残留任务导致内存泄漏或状态混乱;
  • 统一管理Timer实例,避免创建匿名Timer后无法终止。

4. 输入处理集中化

  • 避免在多个脚本中分散处理输入,可创建全局DialogueManager统一处理交互、跳过、选项选择等输入逻辑。

内容的提问来源于stack exchange,提问作者Axel

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.01 22:50:56