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

如何在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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.14 00:45:18