Add Behavior Tree component with External BD at runtime

Haytam95

Active member
Hi !

I'm creating a custom inspector that allows to instance external BD at runtime, in this case it's working with Playmaker but could be in other script as well.

I've created the inspector of PM that allows me to select an External Behavior Tree, when I select one, then the Variables shows up like this:

1623035836660.png

1623035852918.png


I'm novice at developing inspectors, so I'm stuck in the part of serializing the data (I think). The trouble is that when the game start, the Variables that I've assigned doesn't show up in the game and the inspector gets cleaned.

Here is my Inspector:

C#:
public override bool OnGUI()
{
    DrawDefaultInspector();

    if (!(target is BehaviorTreeExtended action) || action.ExternalBehaviorTree == null) return true;

    // Dibujar datos del Behavior Tree
    if (action.SharedVariables == null || action.ExternalBehaviorTree != _externalBehavior)
    {
        _externalBehavior = action.ExternalBehaviorTree;
        action.SharedVariables = action.ExternalBehaviorTree.GetBehaviorSource().GetAllVariables();
    }

    List<SharedVariable> allVariables = action.ExternalBehaviorTree.GetBehaviorSource().GetAllVariables();;

    if (allVariables != null && allVariables.Count > 0)
    {
        if (VariableInspector.DrawAllVariables(false, action.ExternalBehaviorTree.GetBehaviorSource(), ref allVariables,
            false,
            ref variablePosition, ref selectedVariableIndex, ref selectedVariableName,
            ref selectedVariableTypeIndex,
            false,
            true))
        {
            // Si no se está jugando
            if (!EditorApplication.isPlayingOrWillChangePlaymode)
            {
                action.SharedVariables = allVariables;
            }
        }
    }
    else
    {
        EditorGUILayout.LabelField("There are no variables to display");
    }

    return true;
}


And here is my PM task:

C#:
public class BehaviorTreeExtended : FsmStateAction
    {
        public ExternalBehaviorTree ExternalBehaviorTree;

        private BehaviorSource _behaviorSource = new BehaviorSource();
       
       

        public BehaviorSource behaviorSource
        {
            get => _behaviorSource;
            set => _behaviorSource = value;
        }


        public override void OnEnter()
        {
            base.OnEnter();
            var behaviorTree = Owner.AddComponent<BehaviorTree>();

            behaviorTree.ExternalBehavior = ExternalBehaviorTree;
            behaviorTree.SetBehaviorSource(_behaviorSource);

            /*foreach (var variable in _behaviorSource.GetAllVariables())
            {
                behaviorTree.SetVariable(variable.Name, variable);
            }*/

            behaviorTree.EnableBehavior();

        }
    }

How can I fix this?
 
Last edited:
It does sound like you need to serialize the tree:

 
Thanks Justin, I tried that! It actually saves the changes, but overwrittes the External Tree default values.

1623074625542.png


What I would like is that the values aren't overwritten in the external Tree, but to be stored in the inspector variable:

1623074687363.png

So when I instance manually the Tree, I would assign those variables to it.

That will allow me to create the same Trees in different Gameobjects, but with different variables values.

How would you do it? (I'm sorry, as I said I'm a bit new programming inspectors) :)
 
When an external tree is set it will serialize that external tree by default. In order to prevent this from happening you can set the behavior source to null so then it'll serialize the tree directly.
 
After being fighting the last four hours, I'm still stuck, and I've tried everything that I had on my mind

State action

C#:
public class BehaviorTreeExtended : FsmStateAction, IBehavior
    {
        [RequiredField]
        public int groupId;
        public ExternalBehaviorTree ExternalBehaviorTree;
        private BehaviorSource BehaviorSource;
     
     
        public override void OnEnter()
        {
            base.OnEnter();
            var behaviorTree = Owner.AddComponent<BehaviorTree>();
            behaviorTree.ExternalBehavior = ExternalBehaviorTree;
            // behaviorTree.ExternalBehavior.GetBehaviorSource().SetAllVariables(BehaviorSource.GetAllVariables());
            Debug.Log("Behavior source variables: " + BehaviorSource.TaskData);
            behaviorTree.SetBehaviorSource(behaviorTree.GetBehaviorSource());

            // ExternalBehaviorTree.SetBehaviorSource(BehaviorSource);
            // behaviorTree.SetBehaviorSource(BehaviorSource);
            /*foreach (var variable in BehaviorSource.GetAllVariables())
            {
                behaviorTree.SetVariable(variable.Name, variable);
            }*/
         

            /*foreach (var variable in _behaviorSource.GetAllVariables())
            {
                behaviorTree.SetVariable(variable.Name, variable);
            }*/

            // behaviorTree.EnableBehavior();

        }

        public string GetOwnerName()
        {
            return Owner.name;
        }

        public int GetInstanceID()
        {
            return Owner.gameObject.GetInstanceID();
        }

        public BehaviorSource GetBehaviorSource()
        {
            return BehaviorSource;
        }

        public void SetBehaviorSource(BehaviorSource behaviorSource)
        {
            BehaviorSource = behaviorSource;
        }

        public Object GetObject()
        {
            return Owner.gameObject;
        }

        public SharedVariable GetVariable(string name)
        {
            return BehaviorSource.GetVariable(name);
        }

        public void SetVariable(string name, SharedVariable item)
        {
            BehaviorSource.SetVariable(name, item);
        }

        public void SetVariableValue(string name, object value)
        {
            SharedVariable variable = this.GetVariable(name);
            if (variable != null)
            {
                if (value is SharedVariable)
                {
                    SharedVariable sharedVariable = value as SharedVariable;
                    if (!string.IsNullOrEmpty(sharedVariable.PropertyMapping))
                    {
                        variable.PropertyMapping = sharedVariable.PropertyMapping;
                        variable.PropertyMappingOwner = sharedVariable.PropertyMappingOwner;
                        variable.InitializePropertyMapping(BehaviorSource);
                    }
                    else
                        variable.SetValue(sharedVariable.GetValue());
                }
                else
                    variable.SetValue(value);
                variable.ValueChanged();
            }
            else if (value is SharedVariable)
            {
                SharedVariable sharedVariable = value as SharedVariable;
                SharedVariable instance = TaskUtility.CreateInstance(sharedVariable.GetType()) as SharedVariable;
                instance.Name = sharedVariable.Name;
                instance.IsShared = sharedVariable.IsShared;
                instance.IsGlobal = sharedVariable.IsGlobal;
                if (!string.IsNullOrEmpty(sharedVariable.PropertyMapping))
                {
                    instance.PropertyMapping = sharedVariable.PropertyMapping;
                    instance.PropertyMappingOwner = sharedVariable.PropertyMappingOwner;
                    instance.InitializePropertyMapping(this.BehaviorSource);
                }
                else
                    instance.SetValue(sharedVariable.GetValue());
                BehaviorSource.SetVariable(name, instance);
            }
            else
                Debug.LogError((object) ("Error: No variable exists with name " + name));
        }
    }


Inspector (Probably this is the interesting one)

C#:
[CustomActionEditor(typeof(BehaviorTreeExtended))]
public class CustomActionEditorTest : CustomActionEditor
{
    private ExternalBehavior _externalBehavior;
    public List<float> variablePosition;
    public int selectedVariableIndex;
    public string selectedVariableName;
    public int selectedVariableTypeIndex;

    public override void OnEnable()
    {
        variablePosition = new List<float>();
        selectedVariableIndex = 0;
        selectedVariableName = null;
        selectedVariableTypeIndex = 0;
        // Do any expensive initialization stuff here.
        // This is called when the editor is created.
    }

    public override bool OnGUI()
    {
        var hasChanged = false;
        DrawDefaultInspector();

        if (!(target is BehaviorTreeExtended action)) return false;

        // El Behavior nuevo es nulo, blanqueemos.
        if (action.ExternalBehaviorTree == null && _externalBehavior != null)
        {
            _externalBehavior = null;
            action.SetBehaviorSource(null);
            Debug.Log("He borrado Behavior Source");
            BehaviorDesignerUtility.SetObjectDirty(action.Owner);

            return true;
        }

        // ¿Tenemos actualizado en nuestra variable actual a External Behavior Tree?
        if (_externalBehavior != action.ExternalBehaviorTree)
        {
            if (action.GetBehaviorSource() == null || action.ExternalBehaviorTree.BehaviorSource.behaviorName !=
                action.GetBehaviorSource().behaviorName) // En realidad está actualizado, pero la referencia de este inspector no lo está.
            {
                hasChanged = true;
            }

            _externalBehavior = action.ExternalBehaviorTree;
        }

        if (hasChanged) // El Behavior no es nulo y encima es nuevo, inicializamos las variables.
        {
            action.SetBehaviorSource(new BehaviorSource(action)
            {
                behaviorDescription = action.ExternalBehaviorTree.GetBehaviorSource().behaviorDescription,
                behaviorName = action.ExternalBehaviorTree.GetBehaviorSource().behaviorName,
                Owner = action,
                Variables = action.ExternalBehaviorTree.GetBehaviorSource().Variables,
                DetachedTasks = action.ExternalBehaviorTree.GetBehaviorSource().DetachedTasks,
                EntryTask = action.ExternalBehaviorTree.GetBehaviorSource().EntryTask,
                HasSerialized = action.ExternalBehaviorTree.GetBehaviorSource().HasSerialized,
                RootTask = action.ExternalBehaviorTree.GetBehaviorSource().RootTask,
                TaskData = action.ExternalBehaviorTree.GetBehaviorSource().TaskData,
                BehaviorID = action.ExternalBehaviorTree.GetBehaviorSource().BehaviorID
            });
            SerializeBehaviorTree(action.GetBehaviorSource(), action.ExternalBehaviorTree.GetBehaviorSource().Variables);
        }

        if (DisplayVariables(action))
        {
            hasChanged = true;
        }

        /*if (hasChanged)
        {
            if (!EditorApplication.isPlayingOrWillChangePlaymode)
            {
                Debug.Log("He salvado Behavior Source");

                action.BehaviorSource.HasSerialized = false;

                if (BehaviorDesignerPreferences.GetBool(BDPreferences.BinarySerialization))
                {
                    BinarySerialization.Save(action.BehaviorSource);
                }
                else
                {
                    JSONSerialization.Save(action.BehaviorSource);
                }

                return true;
            }
        }
        */

        return hasChanged;
    }


    private bool DisplayVariables(BehaviorTreeExtended action)
    {
        List<SharedVariable> allVariables = action.GetBehaviorSource().GetAllVariables();

        if (allVariables != null && allVariables.Count > 0)
        {
            if (VariableInspector.DrawAllVariables(false, action.GetBehaviorSource(),
                ref allVariables,
                false,
                ref variablePosition, ref selectedVariableIndex, ref selectedVariableName,
                ref selectedVariableTypeIndex,
                false,
                true))
            {
                // Si no se está jugando
                if (!EditorApplication.isPlayingOrWillChangePlaymode)
                {
                    SerializeBehaviorTree(action.GetBehaviorSource(), allVariables);
                    return true;
                }
            }
        }
        else
        {
            EditorGUILayout.LabelField("There are no variables to display");
        }

        return false;
    }
 
    static void SerializeBehaviorTree(BehaviorSource behaviorSource, List<SharedVariable> variables) {
        behaviorSource.SetAllVariables(variables);
        behaviorSource.HasSerialized = false;
 
        if (BehaviorDesignerPreferences.GetBool(BDPreferences.BinarySerialization)) {
            BinarySerialization.Save(behaviorSource);
        } else {
            JSONSerialization.Save(behaviorSource);
        }
        Debug.Log("Árbol salvado, variables required to be saved: " + variables + " | Variables saved: " + behaviorSource.Variables);
    }
}

Here is were I got, now I'm not overwritting the variables in the external tree, but the behavior source that I saved isn't being loaded at all (And I can't even debug because Unity would crash, something crazy with the memory usage)


A little hand? :/
 
Are you getting any errors? If unity crashes from a memory usage that generally indicates that the binary serialization data is corrupt. Are you using binary? Just as a test try switching to JSON.
 
Are you getting any errors?
Not really, I'm currently using JSON binary serialization :)

Unity just crashes when trying debugging the FSM at the start of the game (I think it's not related to this, maybe a Rider bug or Playmaker bug or just Unity bug)



Probably the inspector isn't saving the data because of something that I did wrong, but I can't really see it
 
@Justin I'm still stuck with this one. Could you show me how would you serialize the tree in the inspector, so it works? :/

This is my current inspector:


C#:
[CustomActionEditor(typeof(BehaviorTreeExtended))]
public class CustomActionEditorTest : CustomActionEditor
{
    private ExternalBehavior _externalBehavior;
    public List<float> variablePosition;
    public int selectedVariableIndex;
    public string selectedVariableName;
    public int selectedVariableTypeIndex;

    public override void OnEnable()
    {
        variablePosition = new List<float>();
        selectedVariableIndex = 0;
        selectedVariableName = null;
        selectedVariableTypeIndex = 0;
        // Do any expensive initialization stuff here.
        // This is called when the editor is created.
    }

    public override bool OnGUI()
    {
        var hasChanged = false;
        DrawDefaultInspector();

        if (!(target is BehaviorTreeExtended action)) return false;

        // El Behavior nuevo es nulo, blanqueemos.
        if (action.ExternalBehaviorTree == null && _externalBehavior != null)
        {
            _externalBehavior = null;
            action.SetBehaviorSource(null);
            Debug.Log("He borrado Behavior Source");
            BehaviorDesignerUtility.SetObjectDirty(action.Owner);

            return true;
        }

        // ¿Tenemos actualizado en nuestra variable actual a External Behavior Tree?
        if (_externalBehavior != action.ExternalBehaviorTree)
        {
            if (action.GetBehaviorSource() == null || action.ExternalBehaviorTree.BehaviorSource.behaviorName !=
                action.GetBehaviorSource().behaviorName) // En realidad está actualizado, pero la referencia de este inspector no lo está.
            {
                hasChanged = true;
            }

            _externalBehavior = action.ExternalBehaviorTree;
        }

        if (hasChanged) // El Behavior no es nulo y encima es nuevo, inicializamos las variables.
        {
            action.SetBehaviorSource(new BehaviorSource(action)
            {
                behaviorDescription = action.ExternalBehaviorTree.GetBehaviorSource().behaviorDescription,
                behaviorName = action.ExternalBehaviorTree.GetBehaviorSource().behaviorName,
                Owner = action,
                Variables = action.ExternalBehaviorTree.GetBehaviorSource().Variables,
                DetachedTasks = action.ExternalBehaviorTree.GetBehaviorSource().DetachedTasks,
                EntryTask = action.ExternalBehaviorTree.GetBehaviorSource().EntryTask,
                HasSerialized = action.ExternalBehaviorTree.GetBehaviorSource().HasSerialized,
                RootTask = action.ExternalBehaviorTree.GetBehaviorSource().RootTask,
                TaskData = action.ExternalBehaviorTree.GetBehaviorSource().TaskData,
                BehaviorID = action.ExternalBehaviorTree.GetBehaviorSource().BehaviorID
            });
            SerializeBehaviorTree(action.GetBehaviorSource(), action.ExternalBehaviorTree.GetBehaviorSource().Variables);
        }

        if (DisplayVariables(action))
        {
            hasChanged = true;
        }

        /*if (hasChanged)
        {
            if (!EditorApplication.isPlayingOrWillChangePlaymode)
            {
                Debug.Log("He salvado Behavior Source");

                action.BehaviorSource.HasSerialized = false;

                if (BehaviorDesignerPreferences.GetBool(BDPreferences.BinarySerialization))
                {
                    BinarySerialization.Save(action.BehaviorSource);
                }
                else
                {
                    JSONSerialization.Save(action.BehaviorSource);
                }

                return true;
            }
        }
        */

        return hasChanged;
    }


    private bool DisplayVariables(BehaviorTreeExtended action)
    {
        List<SharedVariable> allVariables = action.GetBehaviorSource().GetAllVariables();

        if (allVariables != null && allVariables.Count > 0)
        {
            if (VariableInspector.DrawAllVariables(false, action.GetBehaviorSource(),
                ref allVariables,
                false,
                ref variablePosition, ref selectedVariableIndex, ref selectedVariableName,
                ref selectedVariableTypeIndex,
                false,
                true))
            {
                // Si no se está jugando
                if (!EditorApplication.isPlayingOrWillChangePlaymode)
                {
                    SerializeBehaviorTree(action.GetBehaviorSource(), allVariables);
                    return true;
                }
            }
        }
        else
        {
            EditorGUILayout.LabelField("There are no variables to display");
        }

        return false;
    }
   
    static void SerializeBehaviorTree(BehaviorSource behaviorSource, List<SharedVariable> variables) {
        behaviorSource.SetAllVariables(variables);
        behaviorSource.HasSerialized = false;
 
        if (BehaviorDesignerPreferences.GetBool(BDPreferences.BinarySerialization)) {
            BinarySerialization.Save(behaviorSource);
        } else {
            JSONSerialization.Save(behaviorSource);
        }
        Debug.Log("Árbol salvado, variables enviadas para guardar: " + variables + " | Variables guardadas: " + behaviorSource.Variables);
    }
}
 
Unfortunately there is a lot going on in that script and I haven't had the time to go through it. At a high level though to serialize you just need to call what you have:

Code:
        if (BehaviorDesignerPreferences.GetBool(BDPreferences.BinarySerialization)) {
            BinarySerialization.Save(behaviorSource);
        } else {
            JSONSerialization.Save(behaviorSource);
        }

Before the tree is serialized you should verify that the values are what you'd expect.
 
I couldn't make it work... :(

Anyway I got a different approach!

I've created a list of empty gameobjects as prefabs and each one contains a Behavior Tree. Each one of those BT has a custom task that initializes the SharedVariables, so the Tree knows who is the Host AI agent.

Finally my state machine just adds or removes those gameobjects from my character. It works pretty well actually and I think it's also more modular than the original idea.
 
Top