使用 Godot 的基于工具的应用程序的架构是什么
What architecture for a tool based application with Godot
我目前正在开发一个可以为我的 DnD(和其他 RPG)组创建和导出战斗地图的应用程序。我使用 Godot 作为基础,因为它带来了许多支持此用例的功能,例如精灵渲染、多边形、着色器、令人惊叹的 UI 框架等
我有一个场景树,大致如下:
- Window
- 菜单
- ...
- 工具Window
- 选择工具
- 多边形工具
- ...
- Canvas
- 工具特定节点
- 精灵预览
- 地图根
- 多边形 1
- 精灵1
每当另一个工具被 selected 时,我会丢弃“ToolSpecificNodes”下的所有节点,并让 selected 工具添加它自己的节点。每个工具都有一个 select() 和 deselect() 生命周期方法和对 canvas 的引用。每个工具还附带一个它自己管理的 属性 window。
我的问题是如何以允许它们松散耦合和可交换的方式集成不同的工具。计划的工具有“select工具”、“多边形工具”、“直线工具”、“精灵工具”。我尤其在使用“select 工具”,因为它依赖于其他工具来处理多边形变形、线条变形和精灵缩放等内容。
是否有涵盖此用例的众所周知的架构模式?或者也许是一个示例项目来展示这一点而不会增加太多复杂性? None-Godot 解决方案也很受欢迎,因为我应该能够将它们应用到我的上下文中。
一般来说,
- 如果您直接引用其他 类(或节点,在本例中),则代码紧密耦合。
- 如果您使用依赖注入(或代码相互查找的方式,例如反射或服务定位器)和接口(或使用鸭子类型),您的代码是松散耦合的。
- 如果您使用基础结构在对象之间传递消息(即信号),那么您已经解耦了代码。
因此,如果您希望您的工具解耦,请使用信号。
当工具可用时,它会连接到它需要的任何信号。当工具不再可用时,它会断开连接。
注意:释放对象会自动断开连接。
然后其他代码(例如,您处理输入的地方)可以发出信号,而无需担心目前可用的工具。
这与一个对象可以发出其他对象的信号这一事实相结合,也将允许工具之间进行通信。但是,它不允许一个工具专门与另一个特定工具通信(从一个工具到另一个工具的这种引用会耦合它们)。
附录:您可以使用信号总线进一步解耦代码,如我所述。
如果您希望您的工具松散耦合,但不分离……
在任何需要的地方,保留对当前工具的引用。所以你可以调用它。更改当前工具就是更改该参考。
附录:也许你会从堆栈中获益。堆栈顶部是当前工具。因此,当您 select 工具时,您可以将它们推入堆栈。当你 deselect 它们时弹出它们。哪些 return 用户访问了他们之前使用的工具。
您当然不希望工具相互引用,但您希望它们能够相互找到。一个简单的机制是查看场景树,如果所需的工具不存在,则加载它。所以,让我们把这个逻辑放入 autoload (sigleton). It will be our service locator.
为此,创建一个只有根节点的场景。并将其添加为自动加载,并命名为“ToolLocator”。给根节点一个脚本,我们需要添加一个方法,给我们一个工具的引用:
func get_tool(name:String) -> Node:
return null # TODO
它将检查是否加载了工具。为此,我们将使用场景树。如果加载了,return 它:
func get_tool(name:String) -> Node:
var result := get_node_or_null(name)
if result != null:
return result
return null # TODO
否则,它将尝试加载它。为此,它将尝试根据工具名称构建资源路径。而且,当然,它必须保留它(在场景树中)以供将来请求使用。
const tool_resource_root := "res://tools/"
const extension := ".tscn"
func get_tool(name:String) -> Node:
var result := get_node_or_null(name)
if result != null:
return result
var packed_scene := load(tool_resource_root + name + extension) as PackedScene
if packed_scene != null:
result = packed_scene.instance()
result.name = name
add_child(result)
return result
return null
本例中,我们在"res://tools/"
寻找工具场景,扩展名为".tscn"
。
由于这是自动加载,您可以在任何场景中使用它,如下所示:
var polygon_tool := ToolLocator.get_tool("polygon")
是的,这将包括工具场景。也就是说,工具场景将能够像那样找到彼此。因此,这是一种无需硬编码引用或类型即可进行通信的方式。换句话说,它们是松耦合的。
注意:您需要处理工具不可用的情况(get_tool
returns null)。考虑在 get_tool
的末尾添加 push_error
,然后再 return 为 null。
您可能还想看看 https://softwareengineering.stackexchange.com,这个问题会在那里成为主题。
我目前正在开发一个可以为我的 DnD(和其他 RPG)组创建和导出战斗地图的应用程序。我使用 Godot 作为基础,因为它带来了许多支持此用例的功能,例如精灵渲染、多边形、着色器、令人惊叹的 UI 框架等
我有一个场景树,大致如下:
- Window
- 菜单
- ...
- 工具Window
- 选择工具
- 多边形工具
- ...
- Canvas
- 工具特定节点
- 精灵预览
- 地图根
- 多边形 1
- 精灵1
- 工具特定节点
- 菜单
每当另一个工具被 selected 时,我会丢弃“ToolSpecificNodes”下的所有节点,并让 selected 工具添加它自己的节点。每个工具都有一个 select() 和 deselect() 生命周期方法和对 canvas 的引用。每个工具还附带一个它自己管理的 属性 window。
我的问题是如何以允许它们松散耦合和可交换的方式集成不同的工具。计划的工具有“select工具”、“多边形工具”、“直线工具”、“精灵工具”。我尤其在使用“select 工具”,因为它依赖于其他工具来处理多边形变形、线条变形和精灵缩放等内容。
是否有涵盖此用例的众所周知的架构模式?或者也许是一个示例项目来展示这一点而不会增加太多复杂性? None-Godot 解决方案也很受欢迎,因为我应该能够将它们应用到我的上下文中。
一般来说,
- 如果您直接引用其他 类(或节点,在本例中),则代码紧密耦合。
- 如果您使用依赖注入(或代码相互查找的方式,例如反射或服务定位器)和接口(或使用鸭子类型),您的代码是松散耦合的。
- 如果您使用基础结构在对象之间传递消息(即信号),那么您已经解耦了代码。
因此,如果您希望您的工具解耦,请使用信号。
当工具可用时,它会连接到它需要的任何信号。当工具不再可用时,它会断开连接。
注意:释放对象会自动断开连接。
然后其他代码(例如,您处理输入的地方)可以发出信号,而无需担心目前可用的工具。
这与一个对象可以发出其他对象的信号这一事实相结合,也将允许工具之间进行通信。但是,它不允许一个工具专门与另一个特定工具通信(从一个工具到另一个工具的这种引用会耦合它们)。
附录:您可以使用信号总线进一步解耦代码,如我所述
如果您希望您的工具松散耦合,但不分离……
在任何需要的地方,保留对当前工具的引用。所以你可以调用它。更改当前工具就是更改该参考。
附录:也许你会从堆栈中获益。堆栈顶部是当前工具。因此,当您 select 工具时,您可以将它们推入堆栈。当你 deselect 它们时弹出它们。哪些 return 用户访问了他们之前使用的工具。
您当然不希望工具相互引用,但您希望它们能够相互找到。一个简单的机制是查看场景树,如果所需的工具不存在,则加载它。所以,让我们把这个逻辑放入 autoload (sigleton). It will be our service locator.
为此,创建一个只有根节点的场景。并将其添加为自动加载,并命名为“ToolLocator”。给根节点一个脚本,我们需要添加一个方法,给我们一个工具的引用:
func get_tool(name:String) -> Node:
return null # TODO
它将检查是否加载了工具。为此,我们将使用场景树。如果加载了,return 它:
func get_tool(name:String) -> Node:
var result := get_node_or_null(name)
if result != null:
return result
return null # TODO
否则,它将尝试加载它。为此,它将尝试根据工具名称构建资源路径。而且,当然,它必须保留它(在场景树中)以供将来请求使用。
const tool_resource_root := "res://tools/"
const extension := ".tscn"
func get_tool(name:String) -> Node:
var result := get_node_or_null(name)
if result != null:
return result
var packed_scene := load(tool_resource_root + name + extension) as PackedScene
if packed_scene != null:
result = packed_scene.instance()
result.name = name
add_child(result)
return result
return null
本例中,我们在"res://tools/"
寻找工具场景,扩展名为".tscn"
。
由于这是自动加载,您可以在任何场景中使用它,如下所示:
var polygon_tool := ToolLocator.get_tool("polygon")
是的,这将包括工具场景。也就是说,工具场景将能够像那样找到彼此。因此,这是一种无需硬编码引用或类型即可进行通信的方式。换句话说,它们是松耦合的。
注意:您需要处理工具不可用的情况(get_tool
returns null)。考虑在 get_tool
的末尾添加 push_error
,然后再 return 为 null。
您可能还想看看 https://softwareengineering.stackexchange.com,这个问题会在那里成为主题。