VisualScriptAction 调试器,改进提案

我对 AGM API 的首次“黑客”尝试。

视觉脚本调试看起来已经会很混乱,而且添加 PrintMessageToConsole 动作需要花费大量繁琐的时间 https://guild.rpgmakerofficial.com/t/topic/197

更好的方案是在 VisualScriptAction 上直接提供一个选项来启用该调试功能。

我并不是要求将其添加到 1.0.6 或 1.0.7 版本中。这个想法仍需完善。我正在研究可以为视觉脚本实现的其他调试功能。

这原本只是一张展示选项应出现位置的图片,但工具开发者接手并制作了一个演示脚本。

GitHub Gist: Action Game Marker, Visual Script debugging, GDScript tools

原始脚本

# debug_vsa.gd
extends VisualScriptAction

@export var print_message_to_console : bool = true :
	set(v):
		if v:
			_add_print_action.call_deferred()
		else:
			_remove_print_action.call_deferred()
		print_message_to_console = v

var _print_action : Actions = PrintMessageToConsole.new()
var _print_action_at_index : int

func _init() -> void:
	_print_action.print_message = visual_script_node_title + " entered"

func _add_print_action() -> void:
	other_actions.append(_print_action)
	_print_action_at_index = other_actions.size() - 1
	
func _remove_print_action() -> void:
	if  other_actions.size() > 0 and other_actions[_print_action_at_index] == _print_action:
		other_actions.remove_at(_print_action_at_index)

(目前无法将 .gd 脚本作为文件类型上传)

此脚本运行时的 _remove_print_action() 尚未经过测试。

对于熟悉 Godot 脚本的高级用户,要将此脚本附加到 VisualScript (VS) 中现有的 State:

  1. 在 FileSystem 中创建 debug_vsa.gd
  2. 它应继承自 VisualScriptAction,否则…
  3. 复制上面的整个脚本
  4. 创建或使用现有的 VS
  5. 选择 Initial State
  6. 在 Inspector 中滚动到底部的’RefCounted’标题处
  7. 通过加载、快速加载或拖拽方式分配 debug_vsa.gd

现在您应该有一个已勾选的布尔值选项“Print Message to Console”。在测试运行时,您应该会收到一条包含 State 名称和"entered"的输出消息。

对于调试来说,真正有帮助的是像 Godot 的 SignalVisualizer 插件那样的工具,以便直观地追踪哪些 Links 和 States 正在触发。

除了调试之外,请记住,扩展 VisualScriptActionVisualScriptActionLink 并不能提供进入实际视觉脚本解释器的入口点。它们只是 AGM 用于执行预编码条件检查和运行预编写代码的 Resource

它们可以允许自定义 GDScript 轻松修改选定的 Links 和 States 及其 ConditionsActions。我不清楚当前的进程安全性,因此请考虑在 _physics_process 中使用 call_deferredset_deferred,以便在修改之前让 AGM 能够引用和修改所有 VS 资源。

3 个赞

已添加一个 GitHub Gist 链接,随着我测试各种选项,该链接将接收更新。

使用 PrintMessageToConsole 并不优雅。它无法提供严肃 追踪 所需的全部信息。

调试输出应不仅能告知是否出现问题,还应能明确指出问题发生的具体位置(GameObject 实例 及 Link/State)。或者至少能定位到问题附近的区域。

更好的解决方案可能是让 Links 向中央调试器注册自身及其 Conditions。这将使调试器能够了解需要监控的内容。至少可以创建一个 GUI,用于调查哪些 Links 应该被触发。这完全基于其他可观测的参数。

1 个赞

有效版本 1.0.2 – 1.0.4

可以使用 GDScript 修改 VisualScript (.vs) 文件。可以通过直接加载 load(res://path/to/visual_script.vs) 或在现有 GameObject 上使用 Objectget_script 方法来访问 VisualScript

可以通过 VisualScript.get_node(id:int) 获取链接 (VisualScriptActionLink) 和状态 (VisualScriptAction) 的引用。

类继承

  • Resource // Godot 类
    • VisualScriptNode // AGM 类
      • VisualScriptAction // 状态
      • VisualScriptActionLink // 链接

VisualScript
方法描述

  • VisualScriptNode get_node(id: int)

返回 VisualScript 数据中包含的 VisualScriptNode 的引用。这可以是 VisualScriptAction(状态)或 VisualScriptActionLink(链接)。在 GDScript 中不需要进行类型转换。这与 Godot 方法 Node.get_node() 不同。

  • bool has_node(id: int)

如果 VisualScript 在此索引处拥有有效的 VisualScriptNode,则返回 true。否则返回 false

数组或其他集合无法直接访问。值可能不是连续存储的。某些索引值可能为 null。要获取 VisualScript 中所有 VisualScriptNode 的完整列表,必须循环使用 get_node 方法。

#GDScript

var vscript #game_object.get_script() 或 load("res://path/to/visual_script.vs")

# 类型化字典直到 Godot 4.4+ 引擎更新才可用
var states: Dictionary #[int,VisualScriptAction]
var links : Dictionary #[int,VisualScriptActionLink]

# 存储的 VisualScriptNode 的最大限制未知,
# 单个脚本中包含 1000 个链接和状态似乎过多。
# 我假设它是一个数组,`id` 不低于 0
for i in range(0, 1000):
		if !vscript.has_node(i):
			continue # 跳过下一个索引
		var n = vscript.get_node(i)
		if is_instance_valid(n):
			if n is VisualScriptAction:
				states[n.id] = n # 键 id : 值 VisualScriptAction 引用
			if n is VisualScriptActionLink:
				links[n.id] = n # 键 id : 值 VisualScriptActionLink 引用

状态和链接具有可访问的属性。这些属性可以在检查器中使用 Filter Properties :hammer_and_wrench: 按钮(右侧)查看。

对于 VisualScriptActionother_actions 数组包含 Actions 资源。

出于调试用途,我目前正在研究 PrintMessageToConsoleEmitSignalInitializeObjectChangeAction 操作。

目前,VisualScript 的逻辑和执行方式在某种形式的编译中被锁定。这发生在 GameObject 初始化的某个时刻(仍在调查确切时间)。在初始化后对附加的 VisualScript 所做的任何更改都不会应用于已实例化的 GameObject,但会应用于任何新实例。

这使得在运行时激活调试系统变得复杂。

InitializeObject 操作将更新现有 GameObject 的逻辑,添加新的 ActionsInitializeObject 在某些 GameObject 上使用目前不稳定(运行时崩溃)(正在调查中)。

PrintMessageToConsole 将在 GameObject 经过 InitializeObject 后开始工作。对于调试,print_message 应设置为包含 TimeNodeAction 的信息。如果有一个使用 @GlobalScope.print_rich()PrintRichToConsole 将会很有帮助。

EmitSignal 操作目前无法正常工作。当通过 GDScript 将 EmitSignal 添加到 other_actions 时,GameObject 将添加一个新信号。这可以通过 Object.has_signal("signal_name") 确认。它可以通过 GDScript 进行 connect()emit()。遗憾的是,EmitSignal 操作不会触发 emit(),即使在 InitializeObject 之后也是如此。这目前对于调试是不可用的。

ChangeActionChangeObjectProperty 是替代不可用的 EmitSignal 的可能方法。通过将“调试器”GameObject 的状态强制更改为具有预制的 EmitSignal 的状态,该信号发送包含所需调试信息的 Project Database record。它应该连接到更强大的基于 GDScript 的调试器。这目前尚未测试。

1 个赞

我目前最好的想法是使用 Godot 的自动加载(Autoload)来预加载需要调试的视觉脚本。在 CoreScene.tscn 加载之前修改它们。并希望 PrintMessageToConsole 以及 ChangeAction+ChangeObjectProperty 的添加能够保留下来。

如果在 GameManager 处有一种调试器会非常有用。由于我不了解视觉脚本的执行设计,我只能给出一个双重 bool 检查的粗略建议。这可以配合在 VisualScriptNode 中添加一个 debug 属性的复选框来实现。

if GameManager.debugger_on && current_node.debugger_on:
    GameManager.debugger_signaled.emit(current_game_object, current_node)

在 C++ 中肯定有更好的方式来设置这个机制,这样当调试器不活跃时,就不需要在视觉脚本的每一步都进行检查。

一个带有传递的 GameObject 和当前 VisualScriptNode 的调试器信号,应该足以让任何第三方调试器围绕其构建图形用户界面(GUI)。

我觉得允许用户在检查器中启用某些选项,以便向控制台打印更详细的信息,这是一个很好的想法。也许我们可以更清晰地阐明这一点,并获得官方对此功能的支持。

关于应提供哪些字段选项以及应打印哪些内容,是否有任何建议?
如果有相对清晰且可行的建议,我认为现在是他们实现这些功能的好机会。因为这些功能似乎不会引入过于复杂的交互,它们在开发过程中非常方便。

在非可视化代码中,您可以设置断点。代码执行将在断点处暂停,让调试人员检查当前的执行位置,并查看被监控的变量。

  • 监控 LinksStatesActions 的执行情况
  • LinksStatesActions 上设置断点
  • 监控特定 GameObject 属性和数据库记录
    • Baz 在 Discord 英文展示区提供了一个示例
  • 监控 GameObjects 的当前 State

面向设计人员的友好工具是创建一个 GUI,将此类信息集中展示。

  • 调试 Links 可以提供其 Conditions
  • Conditions 提供需要监控的属性和用户输入
  • 调试 States 应报告其执行时机以及它们计划执行的操作
  • Actions 报告其参数设置,格式应更易于阅读和共享

这些功能将极大地帮助解答一些最常见的问题,例如:

为什么这不起作用?

当设计师缺乏知识工具来提出更直接的问题时,这个问题会被公开提出。

  1. 为什么没有进入某个状态或代码段?
  2. 为什么这个 StateAction 没有按我期望的那样工作?它做了别的事情。

第一个问题可以通过监控所需的条件以及它们是否(曾经)被满足来回答。

第二个问题通常是由于参数设置错误、对如何使用特定 Action 的误解,或者(希望更罕见)AGM 中 Action 本身的 bug 导致的。

printprint_debug(脚本和行号)以及 print_rich(颜色和视觉特效高亮)只是在文本日志中设置监控的粗糙方式。它们适用于短期代码设计缺陷的调试,但在复杂且高度条件化的设计中作用有限。

1 个赞