How to save and load behavior trees?

caleidon

Member
I am making an RTS-like simulation game where the player may choose to save and leave at any point.

This will end the game and leave characters doing in-progress tasks such as walking towards a designated job position (towards a tree) or chopping a tree (e.g. tree-cutting at 66% progress).

I've searched the forums for solutions but couldn't find any concise answers, although I have seen that a save/load feature was planned for version 2. If so, I think this should be near the top of the priority lists, because any slightly more complex game will be in need to save BT states at runtime, and many, including me, cannot publish a game without this feature.

Keep that in mind, are there currently any built-in ways to easily save trees and their current state/variables?

If not, are there any recommendations on how to do this manually? Is there perhaps a way to save with Easy Save 3, which I'm using?

This is CRUCIAL for my game, and any solution would be great. I'm sure almost all people who use BD have a similar need, so this topic might be useful for others later on.
 
Last edited:

caleidon

Member
If you haven't seen it this is a good post for accomplishing save/load: https://opsive.com/forum/index.php?...aviorsource-json-in-playmode.2577/#post-12652

This is not currently a feature built in and is still planned for version 2.
I have seen that post, but I'm uncertain about a couple of things:

1. At the end of the post, you say that "Right now it is not possible to save/load the tree at runtime.". Does that mean the solution from that user works at runtime (what I need for my purposes), or it doesn't?

2. If I were to follow that post to achieve save/load functionality, is it enough to just follow his first post, and then his third post and merge all that code together? It's very hard to read and understand what's happening because there is no formatting. There's also a dead link leading to a solution on his second post, but I'm assuming this post now combines all of it because you said you're merging his threads?

3. If the said code works, would you be so kind to put together a super short example, maybe as a code snippet or as a Unity project, where you can demonstrate this current workaround?

Finally, I haven't seen this anywhere, what is a rough estimate of when version 2 would be released? I'm uncertain if I should invest my time into a workaround, or wait for the final polished version of saving/loading.
 

Justin

Administrator
Staff member
Does that mean the solution from that user works at runtime (what I need for my purposes), or it doesn't?
Yes, it does.

2. If I were to follow that post to achieve save/load functionality, is it enough to just follow his first post, and then his third post and merge all that code together? It's very hard to read and understand what's happening because there is no formatting. There's also a dead link leading to a solution on his second post, but I'm assuming this post now combines all of it because you said you're merging his threads?
The solution that I heard of wasn't from that user. I just have heard from multiple users on how they got it working. You'll need to serialize the task variables, as well as the active state of the BehaviorTree object within the BehaviorManger.

Finally, I haven't seen this anywhere, what is a rough estimate of when version 2 would be released? I'm uncertain if I should invest my time into a workaround, or wait for the final polished version of saving/loading.
Unfortunately no rough estimate. I am trying to use DOTS for a hybrid approach for version 2 and right now we don't have any direction for where DOTS is headed:

 

caleidon

Member
I've managed to save/load basic tree information, but the current state of the tree either isn't saved or is but still starts from the beginning.

This is the test tree I'm trying to save:

Screenshot_1.png

The logs output "loggin1", "logging2", and so on... The wait timers are all set to 1 second.

I'm trying to save the tree when it says "logging2" and resume it from that position.

Here's the saving code, as seen in that post:


C#:
using System.Collections;
using System.Collections.Generic;
using BehaviorDesigner.Runtime;
using UnityEngine;

public class TestBehaviorTreeSerialize : MonoBehaviour
{
    public string Json;

    public BehaviorTree behaviorTree;

    private void Awake()
    {
        behaviorTree = GetComponent<BehaviorTree>();
    }

    [ContextMenu("Save")]
    private void Save()
    {
        BehaviorSource source = behaviorTree.GetBehaviorSource();
        Json = JsonUtility.ToJson(source);
        Debug.Log(Json);
    }

    [ContextMenu("Load")]
    private void Load()
    {
        var source = JsonUtility.FromJson<BehaviorSource>(Json);
        behaviorTree.enabled = false;
        source.Initialize(behaviorTree);
        if (source.CheckForSerialization(true))
        {
            behaviorTree.SetBehaviorSource(source);
        }

        behaviorTree.enabled = true;
    }
}


And here is the JSON output (too long for this post): https://hastebin.com/kakawidija.json

When Load is pressed, it does seem to load the tree and start it, but it always starts from "logging1". What am I missing?
 

Justin

Administrator
Staff member
It doesn't look like you are saving anything within BehaviorManager which is where the execution of the tree occurs.
 

caleidon

Member
It doesn't look like you are saving anything within BehaviorManager which is where the execution of the tree occurs.
Could you at least give me a clue on what to save...? Do I have to save something from BehaviorManager.instance.something, or something else? I don't understand.
 

Justin

Administrator
Staff member
There is a BehaviorTree object within the Behavior manager that saves the state of the behavior tree at runtime. Unfortunately without trying it myself I don't know too much more if anything else needs to be saved. The BehaviorTree object is the big one though
 

caleidon

Member
Assuming this is the BehaviorTree you're referring to under BehaviorManager, this is what is looks like:

Screenshot_1.png

It would be very useful if you could help me figure out this runtime saving workaround for now because of DOTS uncertainty, official saving might as well be half a year to a year away. Also, the lack of the saving feature is the only thing preventing me from leaving a 5-star review on an otherwise amazing asset.

Now, onto the problem.

I assume that to be able to save this tree, I should run:

Code:
var treeSave = Behaviormanager.instance.BehaviorTrees[0];
var json = JsonUtility.ToJson(savingSource);

However, by default, this seems to only save the following things:
Screenshot_3.png

Now, I assume that to save other things, we need to serialize them. Correct me if I'm wrong, but isn't it just enough to add [System.Serializable] in front of those missing fields we need to save?

As an example, we likely need to save these tasks among other things (inside of the BehaviorTree object):

Screenshot_2.png

To save them, should I add [Serializable] in front of:
1. The concrete "Log" and "Wait" classes
2. The "Task" class they inherit from
3. All the properties of the "Log", "Wait" and "Task" classes
4. All of the above?

I know this is a pain in the butt to deal with, but it is absolutely crucial for me and I assume many other people until you can develop a stable, official version of saving/loading.
 

Justin

Administrator
Staff member
I recommend that you download the runtime source so you can see what the Behavior Manager contains:


The entire Behavior Tree object needs to be saved. This object contains a dictionary and I'm not sure if Unity has added support for dictionaries within JsonUtility so you may need a workaround there.
 

caleidon

Member
I am using Easy Save 3 which has built-in support for dictionaries, so that likely wouldn't be a problem.

I use Rider which can decompile the source code and allows me to edit it (I think).

Here you can see that ES3 is able to serialize all 17 of the properties/fields that we need to save:

Screenshot_1.png

However, conditionalReevaluate is a list of those types, so those probably have to be serialized as well. We can look them up too:
Screenshot_2.png

We can also serialize and be able to save all 4 of those public properties. If there's any properties that are private or protected, ES3 can't serialize them, so I assume I'd have to make changes to your code to make them public?

Finally, a piece of data we need to save within Behavior Tree is List<Task>. I see that the Task class is Abstract, so I likely need to make the actual tasks serializable. In my case, I'd need to make every single property/field in the tasks "Log" and "Wait" public, and then I could probably serialize them.

I feel like I understand what I need to do better now. Am I on the right path? If I successfully complete this, I might share it with other people on the forum who have the same problem, saving you and them plenty of valuable time.
 

Justin

Administrator
Staff member
It does sound like you are on the right path. Good luck with the implementation and looking forward to seeing your results!
 

caleidon

Member
So, for the above screenshot (saving a sequence with log, wait, log wait etc...), I've serialized all the public fields without modifying code, and saved that tree.

Here's the saved data: https://hastebin.com/xifoxihife.json

However, when loading this, I get the following type back: BehaviorManager.BehaviorTree
The BehaviorTree monobehavior sits on a GameObject and is of type BehaviorDesigner.Runtime.Behavior tree, and inherits from Behavior.
Since I'm relatively new to Behavior Designer, how can I assign my newly loaded tree to this object? This is the error I get:


Screenshot_1.png
 

Justin

Administrator
Staff member
You will likely need to import the runtime source and create a method which adds the BehaviorTree instance. The BehaviorManager does not have a method which allows for an BehaviorManager.BehaviorTree instance to be added.
 

caleidon

Member
I thought if I got the tree data like this:

C#:
BehaviorManager.BehaviorTree tree = BehaviorManager.instance.BehaviorTrees[0];
SaveManager.SaveTree(tree);

Then, why wouldn't I just be able to replace it with what I load, like this:

Screenshot_2.png

However, when the tree starts running, I immediately get this error:

Screenshot_3.png
pointing to a null reference exception here
Screenshot_4.png

This isn't descriptive and it's hard to say what the problem is, so this doesn't work.

As for what you mentioned, I downloaded the runtime source and examined the source code, but I don't understand what I need to add exactly and how to do it.

From what I understand, I have to go to BehaviorManager.cs and add a method that takes in a parameter of type BehaviorManager.BehaviorTree and returns a type of BehaviorTree, so that I can assign it as I tried before?

I would really appreciate some more detailed help, as this is getting quite advanced and general advice doesn't seem to be enough for me to figure out a solution.
 

Justin

Administrator
Staff member
From what I understand, I have to go to BehaviorManager.cs and add a method that takes in a parameter of type BehaviorManager.BehaviorTree and returns a type of BehaviorTree, so that I can assign it as I tried before?
Yes, that's correct.

I would really appreciate some more detailed help, as this is getting quite advanced and general advice doesn't seem to be enough for me to figure out a solution.
Unfortunately save/load isn't an easy topic. I will try to add it to one of the next feature releases but I don't have an ETA on that.
 

caleidon

Member
Yes, that's correct.


Unfortunately save/load isn't an easy topic. I will try to add it to one of the next feature releases but I don't have an ETA on that.
I've managed to get saving/loading working, but only with a simple tree with two tasks. I had to serialize all the SharedVariables being used, the Behavior Tree and the sequence Composite.

As soon as I tried this method on one of my actual behavior trees, it immediately failed because of the reference loops.
This is because the BehaviorTree I saved has a TaskList, where it references all types of tasks. But also, in that task list, I have tasks that reference those same tasks again. Also what I did felt like a huge workaround and not worth continuing.

I've concluded this isn't doable for me or really anyone who doesn't have a full understanding of how Behavior Designer works, AKA, someone like yourself.

All I can do now is beg you to consider making saving/load as high of a priority as possible. If I could get simple save/load working in a couple of hours, I have no doubt you can. I know it isn't easy, but it's a core feature and a make-or-break feature for me and many other people. An example of this is how many views this question is getting compared to other questions. I'd advise not getting too caught up in making it work with DOTS because DOTS' future is about as uncertain as it can get right now.

Seeing how Unity doesn't have a proper AI solution (like Unreal Engine has built-in BTs), adding save/load would make this asset a complete package and the number one go-to for anyone looking for an AI solution.

I'm still holding on to post my review on this asset as soon as save/load comes out.
Until then, I'm marking this question as solved.

Thank you for all the hard work you put in Justin, it is highly appreciated.
 
Last edited:

caleidon

Member
As a bonus side question:

I will remain developing my game like I would, only without the ability to save/load BTs.

For future reference, I'd like to know what sort of structure I should implement and maintain so that when the saving system is added, I can easily implement it.

For example: In an Action called "ChopWood" that has a reference to some other data structure, will that reference be saved directly, or should I only include an ID of that data structure, so that upon loading, the ID is used to get that data structure?

So, should it be:

DataStructure someData;

OR

string dataStructureId;

public void LoadData() {
DataStructure someData = Map.LoadById(dataStructureId);
}

Generally speaking, I'd like to know what sort of structure my Tasks, Actions, and Composites should have to allow for easy implementation of the save system when it arrives. Thank you.
 

Justin

Administrator
Staff member
I will need to get into the nitty-gritty of save/load in order to be able to tell you, but the goal will be to serialize all serializable objects. So you could do either in some sense.
 

caleidon

Member
Since you said there is no rough ETA, I'd appreciate it if you could inform me when there's any news of a saving system. Also, all the people looking at how to save may also be redirected here, so it would be beneficial.

Thank you again.
 
Top