Godot教程——GraphEdit和GraphNode
上文:
Godot游戏引擎:GraphEdit和GraphNode教程(一)—— GraphNode
Godot游戏引擎:GraphEdit和GraphNode教程(二)——GraphEdit
这期开始,会稍微困难一些。
承接上文,我们在做完连接槽和删除槽的功能之后,接着着手实现其他功能。
GraphEdit事件处理
节点被选中事件
顾名思义,当一个节点被选中时触发的事件。
extends GraphEdit
var selected_node : GraphNode
func _on_GraphEdit_connection_request(from, from_slot, to, to_slot):
connect_node(from, from_slot, to, to_slot)
func _on_GraphEdit_disconnection_request(from, from_slot, to, to_slot):
disconnect_node(from, from_slot, to, to_slot)
func _on_GraphEdit_node_selected(node):
selected_node = node
print(selected_node.name)
创建一个变量来储存被选中的节点。为了试验,让我们把它的名字print出来。
让我们试着运行一下,结果是我们想要的样子。
拷贝节点事件
这个事件只在拷贝节点的时候被触发(按下Ctrl + D
),让我们来处理这个事件。
func _on_GraphEdit_duplicate_nodes_request():
var i = selected_node.duplicate()
self.add_child(i)
上面的代码能够做的事情是把选中的节点拷贝一份,然后添加到自己下面。接下来让我们试一试。
可以发现,我选中GraphNode2拷贝了多次,明显比右边的要更实,这说明确实拷贝出来了。但是他们都叠在一起了,当你移动它的时候,所有叠在一起的节点都被你移动了。这该怎么办呢?
让我们思考一会。
当你尝试取消选中之后再拖动节点的时候,你发现它被拖出来了。这说明了它们被一起拖动的原因是它们都被选中了,被选中的节点会一起移动。
那么我们对上面的代码进行一下修改。
func _on_GraphEdit_duplicate_nodes_request():
var i = selected_node.duplicate()
selected_node.selected = false # 取消原件的选中
self.add_child(i)
set_selected(i) # 对拷贝后的节点进行选中
添加了两行代码,让我们试试效果。
拷贝之后可以直接拖出来,不用再点空位了。
但是还有一个问题。当我们什么也没有选中的时候,按下Ctrl + D
会发生什么?
很显然,我们会报错。因为这个时候,我们的selected_node
的值是null
,不能调用duplicate()
方法。所以我们再次修改一下:
func _on_GraphEdit_duplicate_nodes_request():
if selected_node != null: # 如果有选中的GraphNode
var i = selected_node.duplicate()
selected_node.selected = false # 取消原件的选中
self.add_child(i)
set_selected(i) # 对拷贝后的节点进行选中
删除节点事件
这个事件其实并不应该在这里提到,因为它是GraphNode的部分,但是还是在这里讲了吧。
这些GraphNode的属性中,当我们打开Show_Close
属性之后,这个节点的头上多了一个×。
就像这样。但是我们按下×的时候并没有任何反应,这是因为我们没有对关闭的事件进行处理。
在GraphNode的下面补充一个信号Close_request()
extends GraphNode
func _ready():
set_slot(0,true, TYPE_INT, Color.wheat, true, TYPE_INT, Color.wheat)
func _on_GraphNode_close_request():
self.queue_free()
添加了一行代码。这样我们按下×时,这个节点就可以被删除了。这时你可能就要问了:那么GraphEdit的信号delete_nodes_request()
有什么用呢?让我们来查看它的帮助文档。文档里说,当GraphEdit的所有子节点中任意一个GraphNode被试图删除的时候会触发这个信号。那么我们需要用它来处理什么呢?
让我们思考一下这个问题:如果一个GraphNode被选中之后,我把它删掉了,之后按下拷贝会发生什么?
答案很显然,我们不能拷贝一个刚刚被释放的物体。那么我们怎么办呢?我们把GraphEdit的delete_nodes_request()
连接上。
func _on_GraphEdit_delete_nodes_request():
selected_node = null
就这么简单,我们完成了。只要我们一有节点被删除,就不选中任何节点。这样就不会出现…问题???可是结果仍然报错。那么我在这个方法上添加一个print()
函数,看看它什么时候被调用。
func _on_GraphEdit_delete_nodes_request():
print("Delete_nodes_request")
selected_node = null
我们发现,当GraphNode拥有焦点并且按下Delete键的时候,会触发这个信号。那么我们想要的单个选择的删除应该怎么做呢?答案是:——当它的×被按下的时候,手动触发它父级的delete_nodes_request()
来清除目前的选择。
func _on_GraphNode_close_request():
if get_parent() is GraphEdit:
get_parent().emit_signal("delete_nodes_request")
self.queue_free()
这样。就避免了删除之后拷贝了个寂寞的错误。什么?你问按下Delete
键之后却没有反应?为了一劳永逸,我们把GraphNode的queue_free()
挪到它的GraphEdit上去,让GraphNode只管发送请求。这个时候,我们发现:delete_nodes_request()
这个信号没有提供参数,那么我们就不知道是那些GraphNode节点被选中了。这可怎么办呢?
先思考一会。
我们再次来到GraphNode,看看它的属性列表。
我们看到了Selected
属性,很明显这个就是我们要找的属性。我们要做的事情就变成了把所有selected属性为true的节点全部收集起来。
func _on_GraphEdit_delete_nodes_request():
print("Delete_nodes_request")
for i in range(get_child_count()):
if get_child(i) is GraphNode:
if get_child(i).selected:
get_child(i).queue_free()
selected_node = null
func _on_GraphNode_close_request():
if get_parent() is GraphEdit:
get_parent().emit_signal("delete_nodes_request")
我们再次重写了GraphEdit的删除节点方法和GraphEdit的关闭方法。现在,所有的删除GraphNode的请求全部移交到GraphEdit的这个方法来解决了。
当我们再次运行的时候,删除一个没有问题,框选删除也没有问题…等等!
当我们选中的和要删除的不是同一个节点的时候,会出现问题。
按下×之后,选中的节点被删掉了,而想删掉的节点却还在。这怎么办呢?于是我们对GraphNode的关闭事件又做出了些更改。
func _on_GraphNode_close_request():
if get_parent() is GraphEdit:
get_parent().set_selected(self) # 选中自己
get_parent().emit_signal("delete_nodes_request")
这样就完美了。当按下×的时候,其他的节点不会被选中,只有自己会被安安心心的选中删除;当按下delete的时候,GraphNode的close_request()
不会被调用,被选中的东西统统会被删除。
接下来,让我们整理一下之前的代码。我们使用了一个for循环来获取所有被多选的节点。那么为了方便复用,我们把这个步骤剥离出来,单独封装成一个方法。
func get_week_selected_nodes():
var will_return : Array
for i in range(get_child_count()):
if get_child(i) is GraphNode:
if get_child(i).selected:
will_return.append(get_child(i))
return will_return
注解:这里我管它叫做week_selected的原因是多选并不触发GraphEdit的
node_selected()
事件。
现在GraphEdit整体的代码就是这样子了:
extends GraphEdit
var selected_node : GraphNode
func _on_GraphEdit_connection_request(from, from_slot, to, to_slot):
connect_node(from, from_slot, to, to_slot)
func _on_GraphEdit_disconnection_request(from, from_slot, to, to_slot):
disconnect_node(from, from_slot, to, to_slot)
func _on_GraphEdit_node_selected(node):
selected_node = node
print(selected_node.name)
func _on_GraphEdit_duplicate_nodes_request():
if selected_node != null:
var i = selected_node.duplicate()
selected_node.selected = false # 取消原件的选中
self.add_child(i)
set_selected(i) # 对拷贝后的节点进行选中
func _on_GraphEdit_delete_nodes_request():
print("Delete_nodes_request")
var week_selected_nodes = get_week_selected_nodes()
for i in range(week_selected_nodes.size()):
week_selected_nodes[i].queue_free()
selected_node = null
func get_week_selected_nodes():
var will_return : Array
for i in range(get_child_count()):
if get_child(i) is GraphNode:
if get_child(i).selected:
will_return.append(get_child(i))
return will_return
小憩一下
休息与工作的关系,正如眼睑与眼睛的关系。——泰戈尔
让我们站起来活动一会,看看周围的世界。喝口水,放松下心情。
进行修改
休息完了,我们继续。先来修改一下之前拷贝节点事件的处理,我们想要既可以框选多个节点并拷贝,又可以仅仅选择一个节点拷贝。那么,我们开工。
func _on_GraphEdit_duplicate_nodes_request():
var week_selected_nodes = get_week_selected_nodes()
for i in range(week_selected_nodes.size()):
var p = week_selected_nodes[i].duplicate()
week_selected_nodes[i].selected = false # 这步的原因和之前需要取消原件选中的原因一样。
self.add_child(p)
注解:这里不需要像之前那样先判断“是否一个都没有选中”是因为如果一个节点都没有被选中,那么
week_selected_node.size()
的返回值就是0,那么for循环会执行0次,也就是不执行。
可以多选拷贝了。
复制节点事件
这个事件是在按下Ctrl + C
之后触发的事件。我们分析一下需求:当我们按下Ctrl + C
复制的时候,我们需要有一个变量来存储所有选中的节点。等到按下Ctrl + V
粘贴的时候,再把这个存储的东西全部粘贴出来,这个机制就叫做剪贴板。
注解:复制粘贴和我这里提到的“拷贝”的区别在于复制粘贴可以复制一次粘贴N次,而拷贝只是生成了一份,再次按下Ctrl + D的时候生成的是新选中的那些(如果没做其他操作的话是刚刚拷贝出来的东西)的复制品。
既然分析完了需求,让我们开始着手实现。先创建一个Array,叫做copied_nodes
,用来存储复制的节点们。然后只有一句话:
func _on_GraphEdit_copy_nodes_request():
copied_nodes = get_week_selected_nodes()
这个复制功能就完成了。
粘贴节点事件
接着让我们来处理粘贴节点事件。话不多说,上代码。
func _on_GraphEdit_paste_nodes_request():
for i in range(copied_nodes.size()):
var p = copied_nodes[i].duplicate()
copied_nodes[i].selected = false # 与之前同理,取消原来(被复制)那些节点的选择。
self.add_child(p)
完成了。
小结
让我们回顾一下,这期我们完善了GraphEdit的主要事件处理,现在我把成品的代码放在这里。
GraphEdit
extends GraphEdit
var selected_node : GraphNode
var copied_nodes : Array
func _on_GraphEdit_connection_request(from, from_slot, to, to_slot):
connect_node(from, from_slot, to, to_slot)
func _on_GraphEdit_disconnection_request(from, from_slot, to, to_slot):
disconnect_node(from, from_slot, to, to_slot)
func _on_GraphEdit_node_selected(node):
selected_node = node
print(selected_node.name)
func _on_GraphEdit_duplicate_nodes_request():
# if selected_node != null:
# var i = selected_node.duplicate()
# selected_node.selected = false # 取消原件的选中
# self.add_child(i)
# set_selected(i) # 对拷贝后的节点进行选中
var week_selected_nodes = get_week_selected_nodes()
for i in range(week_selected_nodes.size()):
var p = week_selected_nodes[i].duplicate()
week_selected_nodes[i].selected = false # 这步的原因和之前需要取消原件选中的原因一样。
self.add_child(p)
func _on_GraphEdit_delete_nodes_request():
print("Delete_nodes_request")
var week_selected_nodes = get_week_selected_nodes()
for i in range(week_selected_nodes.size()):
week_selected_nodes[i].queue_free()
selected_node = null
func get_week_selected_nodes():
var will_return : Array
for i in range(get_child_count()):
if get_child(i) is GraphNode:
if get_child(i).selected:
will_return.append(get_child(i))
return will_return
func _on_GraphEdit_copy_nodes_request():
copied_nodes = get_week_selected_nodes()
func _on_GraphEdit_paste_nodes_request():
for i in range(copied_nodes.size()):
var p = copied_nodes[i].duplicate()
copied_nodes[i].selected = false # 与之前同理
self.add_child(p)
GraphNode
extends GraphNode
func _ready():
set_slot(0,true, TYPE_INT, Color.wheat, true, TYPE_INT, Color.wheat)
func _on_GraphNode_close_request():
if get_parent() is GraphEdit:
get_parent().set_selected(self)
get_parent().emit_signal("delete_nodes_request")
各位下期再见。