FindTasks and BehaviorTreeReference behave inconsistently between build and editor

rhys_vdw

New member
This is the script I use to initialize nodes in my BT.

Code:
public void Initialize(
  ExternalBehaviorTree behavior,
  int entityId,
  GameState gameState,
  string name
) {
  gameObject.name = $"{entityId}:{name}:{nameof(BehaviorTree)}";
  _behaviorTree.ExternalBehavior = behavior;
  var tasks = _behaviorTree.FindTasks<Task>();
  Debug.Log($"Initializing {tasks.Count} tasks for {name}");
  foreach (var task in tasks) {
    if (task is ITaskInitialize init) {
      init.Initialize(entityId, gameState);
      Debug.Log($"Initialized {task.GetType().Name}");
    } else {
      Debug.Log($"{task.GetType().Name} is not ITaskInitialize");
    }
  }
}

This is the script that causes a problem:
1710310439471.png

In editor this works as expected, iterating through all the nodes in the referenced behavior:
1710310601046.png

But in build I only get one node (the reference node):

Code:
Initializing 1 tasks for Purifier
UnityEngine.DebugLogHandler:LogFormat(LogType, Object, String, Object[])
UnityEngine.Logger:Log(LogType, Object)
UnityEngine.Debug:Log(Object)
EffortStar.ActorBehavior:Initialize(ExternalBehaviorTree, Int32, GameState, String) (at C:\Users\rhysv\Projects\enter-the-chronosphere\Assets\_Game\Scripts\ActorBehavior.cs:28)
...

BehaviorTreeReference is not ITaskInitialize
UnityEngine.DebugLogHandler:LogFormat(LogType, Object, String, Object[])
UnityEngine.Logger:Log(LogType, Object)
UnityEngine.Debug:Log(Object)
EffortStar.ActorBehavior:Initialize(ExternalBehaviorTree, Int32, GameState, String) (at C:\Users\rhysv\Projects\enter-the-chronosphere\Assets\_Game\Scripts\ActorBehavior.cs:34)
...

I have yet to work out which part of the code that is causing this.
 
Here is the BT above
YAML:
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &11400000
MonoBehaviour:
  m_ObjectHideFlags: 0
  m_CorrespondingSourceObject: {fileID: 0}
  m_PrefabInstance: {fileID: 0}
  m_PrefabAsset: {fileID: 0}
  m_GameObject: {fileID: 0}
  m_Enabled: 1
  m_EditorHideFlags: 0
  m_Script: {fileID: 11500000, guid: b23f08d2ae4cba14087c1ed36193d82b, type: 3}
  m_Name: PurifierBehaviorTree
  m_EditorClassIdentifier:
  mBehaviorSource:
    behaviorName: Behavior
    behaviorDescription:
    mTaskData:
      types: []
      parentIndex:
      startIndex:
      variableStartIndex:
      JSONSerialization: '{"EntryTask":{"Type":"BehaviorDesigner.Runtime.Tasks.EntryTask","NodeData":{"Offset":"(579.5,30)"},"ID":0,"Name":"Entry","Instant":true},"RootTask":{"Type":"BehaviorDesigner.Runtime.Tasks.BehaviorTreeReference","NodeData":{"Offset":"(-0.5,120)"},"ID":1,"Name":"Behavior
        Tree Reference","Instant":true,"ExternalBehavior[]externalBehaviors":[0],"SharedNamedVariable[]variables":[{"Type":"BehaviorDesigner.Runtime.SharedNamedVariable","Name":null,"NamedVariablemValue":{"Stringname":"SteadyCount","Stringtype":"SharedInt","SharedVariablevalue":{"Type":"BehaviorDesigner.Runtime.SharedInt","Name":null,"Int32mValue":0}}},{"Type":"BehaviorDesigner.Runtime.SharedNamedVariable","Name":null,"NamedVariablemValue":{"Stringname":"FireCount","Stringtype":"SharedInt","SharedVariablevalue":{"Type":"BehaviorDesigner.Runtime.SharedInt","Name":null,"Int32mValue":5}}},{"Type":"BehaviorDesigner.Runtime.SharedNamedVariable","Name":null,"NamedVariablemValue":{"Stringname":"RecoilCount","Stringtype":"SharedInt","SharedVariablevalue":{"Type":"BehaviorDesigner.Runtime.SharedInt","Name":null,"Int32mValue":0}}}],"Booleancollapsed":false}}'
      fieldSerializationData:
        typeName: []
        fieldNameHash:
        startIndex:
        dataPosition:
        unityObjects:
        - {fileID: 11400000, guid: 4f5d562712777e84ba8e0c434b9c8618, type: 2}
        byteData:
        byteDataArray:
      Version: 1.7.7

That `unityObjects` field is correctly referencing the nested object, so it should be available in build.
 
This sounds like a script execution order issue. Unity can change the order within a build. If you only get the reference node then the behavior tree hasn't been initialized yet. You can either manually initialize it or ensure the behavior tree starts before you call your own code.
 
That is for the external tree, but not the Behavior Tree component which consumes the external tree. One way to ensure the tree is initialized first is to enable and then immediately disable the behavior:

Code:
behaviorTree.EnableBehavior();
behaviorTree.DisableBehavior();
 
Right, so I should call this every time I assign BehaviorTree.ExternalBehavior?

It's hard to imagine how there could be a script execution order issue here. The code does this:
  1. Instantiate prefab with BT component
  2. Set external behavior tree.
  3. Calls FindTasks
Code:
// Create ai.
var actorBehavior = _gameState.Instantiate(
  _settings.ActorBehaviorPrefab,
  Vector2.zero,
  0
);
actorBehavior.Initialize(
  config.ExternalBehaviorTree,
  actorId,
  _gameState,
  config.Name
);

You can see the body of ActorBehavior.Initialize in my first post.

Wouldn't a script execution order assume that there is some race condition due to use of Start/Awake in my code? This is not the case, the above is being called from an update in a long-running game manager. I don't think there is any scope for script execution order to be a factor in this code.
 
You should call EnableBehavior before FindTasks. If the behavior tree hasn't been enabled FindTasks will return the reference task since it hasn't been consumed yet with the external tree.

My guess is that within the editor the tree is initialized before FindTasks is called which is why it works there.
 
Top