如何在Godot行为树中实现节点行为重置?
Godot行为树节点状态重置问题解决
问题核心
切换Sequence节点时,前一个Sequence的子节点(如PatrolAction)会保留旧状态数据,切回时无法重置;尝试在Sequence成功/失败时调用ResetBehaviour导致Wait节点等出现异常。
解决方案思路
复合节点(Selector/Sequence)需要跟踪上一次执行的子节点,当本次执行跳过了之前处于RUNNING状态的子节点时,对其调用ResetBehaviour,确保节点状态被重置。同时修正Sequence的执行逻辑,保证节点状态流转正确。
1. 修改SelectorBT节点
添加子节点执行跟踪,在切换执行分支时重置被放弃的节点:
class_name SelectorBT extends NodeBT var SelectorChildren : Array var last_running_index : int = -1 # 记录上一次运行的子节点索引 func _ready() -> void: SelectorChildren = get_children() func Evaluate(Data : DataTreeClass) -> NodeState: var current_running_index : int = -1 var result_state : NodeState = NodeState.FAILURE for i in range(SelectorChildren.size()): var child = SelectorChildren[i] var childState = child.Evaluate(Data) match childState: NodeState.FAILURE: continue NodeState.SUCCESS: result_state = NodeState.SUCCESS # 如果之前有运行的节点,重置它 if last_running_index != -1 and last_running_index != i: SelectorChildren[last_running_index].ResetBehaviour() last_running_index = -1 return result_state NodeState.RUNNING: result_state = NodeState.RUNNING current_running_index = i # 只保留第一个RUNNING节点,符合Selector特性 break _: continue # 处理状态切换:如果之前有运行节点但本次没有,重置它 if last_running_index != -1 and current_running_index == -1: SelectorChildren[last_running_index].ResetBehaviour() elif current_running_index != -1 and last_running_index != current_running_index: if last_running_index != -1: SelectorChildren[last_running_index].ResetBehaviour() last_running_index = current_running_index return result_state
2. 修改SequenceBT节点
添加子节点执行跟踪,修正执行逻辑(完成所有子节点应返回SUCCESS),并重置被中断的节点:
class_name SequenceBT extends NodeBT var SequenceChildren : Array var current_child_index : int = 0 # 记录当前执行到的子节点索引 func _ready() -> void: SequenceChildren = get_children() func Evaluate(Data : DataTreeClass) -> NodeState: while current_child_index < SequenceChildren.size(): var child = SequenceChildren[current_child_index] var childState = child.Evaluate(Data) match childState: NodeState.FAILURE: # 序列失败,重置当前节点及后续节点 for i in range(current_child_index, SequenceChildren.size()): SequenceChildren[i].ResetBehaviour() current_child_index = 0 return NodeState.FAILURE NodeState.SUCCESS: current_child_index += 1 NodeState.RUNNING: return NodeState.RUNNING _: current_child_index += 1 # 所有子节点执行完成,返回SUCCESS并重置序列 var result = NodeState.SUCCESS for child in SequenceChildren: child.ResetBehaviour() current_child_index = 0 return result # 外部调用重置整个序列 func ResetBehaviour() -> void: for child in SequenceChildren: child.ResetBehaviour() current_child_index = 0
3. 验证PatrolAction节点
你的PatrolAction已经正确实现了ResetBehaviour,无需修改:
class_name PatrolAction extends NodeBT @export var Speed : int var TargetPosition : Vector2 func _ready() -> void: NewTarget() func Evaluate(Data : DataTreeClass) -> NodeState: var Actor = Data.Get("Actor") # Move towards the target position Actor.velocity = (TargetPosition - Actor.position).normalized() * Speed Actor.move_and_slide() # Check if the Actor has reached the target position if Actor.position.distance_to(TargetPosition) < 5.0: Actor.position = TargetPosition NewTarget() # Return success when the patrol is completed return NodeState.SUCCESS # Return RUNNING while the patrol is in progress return NodeState.RUNNING func NewTarget(): TargetPosition = Vector2(randi_range(100,900), randi_range(100,450)) func ResetBehaviour() -> void: print("Restarted") NewTarget()
关键说明
- Selector会在切换到新分支时,自动重置之前处于RUNNING状态的旧分支节点
- Sequence会在执行失败、完成所有节点,或者被外部重置时,重置所有子节点状态
- 这种方式避免了无差别重置,只针对被中断的节点执行重置,不会影响正常运行的节点(如Wait节点不会被误重置)
内容的提问来源于stack exchange,提问作者Dragon20C
相关产品推荐
相关产品推荐

