Using Behavior Tree in prefab only works if you don't change a thing?

SXtheOne

New member
Hi,

Just a quick but important question. I made a prefab from the AI agent which has a Behavior Tree. Everything works fine, I even can duplicate it without problems UNTIL I decide to change even one variable in a Behavior Tree (like detection distance or speed) in any of the AI gameobjects. When any variable gets changed the whole Behavior Tree gets copied into the scene. After this, that AI instance is completely disconnected from the original prefab and no changes in the prefab's Behavior Tree reflects there.

How can this be avoided? I need a test scene with different AI configurations (for example the patrol waypoints are different at minimum) and I need to be able to update the Tree as I develop the game. As I remember this copying happens with external Behavior Trees as well.
 

Justin

Administrator
Staff member
I generally recommend using external behavior trees as it works better with Unity's prefab system. With that said, when the prefab system notices that a field has changed (in this case the binary serialization) then it can easily corrupt the data when applying the overriding values. As a result of this if you use JSON serialization it works better, but I still recommend external behavior trees.
 

SXtheOne

New member
I've exported the tree and using it as an external behavior tree for the AI prefab. I've done a quick test and it looks like the variable changes are reflected in the AI instances even when that particular instance has changes (like waypoints set up) so it looks fine.
The scene file hasn't changed, all the json still gets serialized into it and I don't understand the reason. If it is an external behavior tree, why serialize the whole tree into the scene file, why not serialize only the variables?
 

Justin

Administrator
Staff member
If you set an external tree the behavior tree stored on the component does not get cleared out so you can revert to it. You can delete that tree though and then just the variables will be set.
 

SXtheOne

New member
I tried out your suggestion but unfortunately, it's still copying all the tree contents to modified instances just like before. Here are my steps:
- deleted the behavior tree from the prefab and added a new behavior tree component to it and set the external tree. This looked good, the previous tree contents were deleted from the prefab file
- added a prefab instance to the scene
- changed a value in the behavior tree's variable (in the inspector) in the instance and that immediately caused to copy all the behavior tree into the scene file

To reproduce:
- make an empty game object (no need for prefabs)
- add the Behavior Tree component, set an external tree
- save, check changes (I use source control and can easily check with a diff editor), nothing wrong
- change just one Tree variable's value in the inspector, save
- check changes again, everything is copied into the scene file

I use Unity 2021.2.13.
 

Justin

Administrator
Staff member
- change just one Tree variable's value in the inspector, save
- check changes again, everything is copied into the scene file
You're changing the variable value? All of the variables will be saved to the scene GameObject. This is so the variables can override the external tree values.
 

SXtheOne

New member
Let me give you an example. In the attached picture there is the Patrol Waypoints array.
behavtree.PNG

When I put the wp1 and wp2 gameobjects to that array, a new json gets serialized into the scene that starts like this (it is pretty lengthy, I just paste the start):

{ "EntryTask": { "Type": "BehaviorDesigner.Runtime.Tasks.EntryTask", "NodeData": { "Offset": "(409,30)" }, "ID": 0, "Name": "Entry", "Instant": true }, "RootTask": { "Type": "BehaviorDesigner.Runtime.Tasks.Sequence", "NodeData": { "Offset": "(0.9088955,124.999847)" }, "ID": 1, "Name": "Sequence", "Instant": true, "AbortTypeabortType": "None", "Children": [ { "Type": "BehaviorDesigner.Runtime.Tasks.UltimateCharacterController.StartStopAbility", "NodeData": { "Offset": "(-605,142.916656)", "Comment": "Start Speed Change" }, "ID": 2, "Name": "Start Stop Ability", "Instant": true, "SharedGameObjectm_TargetGameObject": { "Type": "BehaviorDesigner.Runtime.SharedGameObject", "Name": "" }, "SharedStringm_AbilityType": { "Type": "BehaviorDesigner.Runtime.SharedString", "Name": "", "StringmValue": "Opsive.UltimateCharacterController.Character.Abilities.SpeedChange" }, "SharedIntm_PriorityIndex": { "Type": "BehaviorDesigner.Runtime.SharedInt", "Name": "", "Int32mValue": -1 },

I formatted the json to make it easier to read but the point is it contains the whole behavior tree even when the tree is an external tree and I don't understand why. In case it's an external tree it should only contain the variables as you've said. I only changed the mentioned Patrol Waypoints array from the inspector, nothing else and if I revert changes from that gameobject (I use prefabs) this whole json is gone.
 

Justin

Administrator
Staff member
Hmm, yeah, I'm not sure the reasoning for that. I'll look into it. It doesn't hurt anything in the long run, but without digging deeper I'm not sure why it's serializing that data.
 

SXtheOne

New member
Hmm, yeah, I'm not sure the reasoning for that. I'll look into it. It doesn't hurt anything in the long run, but without digging deeper I'm not sure why it's serializing that data.
My problem with this is that if I change any variable in a prefab instance and later change another prefab variable value, nothing gets into the instances only when I revert changes in the instances and this is tiresome. If only the changed variables would be saved (as Unity does) the others could be read from the prefab.
 

Justin

Administrator
Staff member
Here's what I tried:

1. Added a new behavior tree to a new GameObject in a new scene.
2. Made a prefab out of that behavior tree.
3. On the prefab added a new float called "Test".
4. Verified that "Test" exists on the prefab instance
5. Changed the value of "Test" to 5 on the prefab.
6. The value of "Test" changed to 5 on the prefab instance.
7. Added a new float to the prefab called "AnotherTest".
8. Verified that "AnotherTest" appeared on the prefab instance.
9. Changed the value of "AnotherTest" to 8 on the prefab instance.
10. Changed the value of "AnotherTest" to 3 on the prefab. The prefab instance value did not change.
11. Changed the value of "Test" on the prefab to 2.
12. The prefab instance value of "Test" did not change from 5.

The reason for this is because Unity marks the entire JSONSerialization field as dirty and it does not update the prefab instance value when it changes. Unfortunately there aren't many prefab callbacks so I am not sure if there is a proper solution for this as it's how the prefab system works.

As a test I also added a new behavior tree to the external behavior tree and only the variables were copied over to the scene instance. Can you list the steps to reproduce the issue of the external behavior tree tasks being copied over to the scene instance?
 
Last edited:

SXtheOne

New member
Here's what I tried:

1. Added a new behavior tree to a new GameObject in a new scene.
2. Made a prefab out of that behavior tree.
3. On the prefab added a new float called "Test".
4. Verified that "Test" exists on the prefab instance
5. Changed the value of "Test" to 5 on the prefab.
6. The value of "Test" changed to 5 on the prefab instance.
7. Added a new float to the prefab called "AnotherTest".
8. Verified that "AnotherTest" appeared on the prefab instance.
9. Changed the value of "AnotherTest" to 8 on the prefab instance.
10. Changed the value of "AnotherTest" to 3 on the prefab. The prefab instance value did not change.
11. Changed the value of "Test" on the prefab to 2.
12. The prefab instance value of "Test" did not change from 5.

The reason for this is because Unity marks the entire JSONSerialization field as dirty and it does not update the prefab instance value when it changes. Unfortunately there aren't many prefab callbacks so I am not sure if there is a proper solution for this as it's how the prefab system works.

As a test I also added a new behavior tree to the external behavior tree and only the variables were copied over to the scene instance. Can you list the steps to reproduce the issue of the external behavior tree tasks being copied over to the scene instance?

I've made a video to make it easier to reproduce the issue:

What was left out of the video: if I make the GameObject a prefab and change testint variable in the instance, the whole behavior tree gets copied into the scene so making it a prefab doesn't change how it works.

About the serialization issue. I haven't created custom serialization yet but is there a way to serialize variables one by one? I mean by writing separate JSON entries for each.
If that's not possible, I guess there should be a serialization step between the prefab and the instances when the prefab is changed, right? If you could serialize the status (modified by the user or contains prefab value) of each field into the instance's JSON, you would be able to decide which field needs an update from the prefab and which does not.
 

SXtheOne

New member
Thanks, I am looking into it.
Hi,
Any update on this? As we are putting AI instances to the game and parallel tweaking the AI we need to revert instance changes of Behavior Tree every single time and set the instances again so the variables default to the new default values. It makes work exceptionally frustrating so it would be great to hear there will be an update on this issue. :)
 

Justin

Administrator
Staff member
I have spent some time working with it but will need some more time. Prefabs are inherently a black box so I am having to work around some of those issues and it's not a straight forward change. I'll let you know.
 

Justin

Administrator
Staff member
I believe that I have this fixed. I'll send you a PM with the new version to test out.
 

SXtheOne

New member
Hi,

I've tested it and the original problem is fixed so thanks for that! I do have several other problems that still exist, here they are:

#1 - Values in the prefab instance's inspector only get updated after pressing play:

#2 - It's like the previous one but in a different scenario:

#3 - Adding a new variable to the External Tree and setting the variable in the prefab doesn't reflect in prefab instances, even when starting the game. This one not only affects editing but introduces more serious bugs:

#4 - reverting prefab instance changes doesn't reflect in the inspector

#5 - deleting a variable from the External Tree doesn't reflect in the prefab's inspector

That's all for now. These problems together make our life really hard. I've made a workaround in our project and not using the Behavior Tree inspector at all to be able to make progress (I'm auto-generating classes for external trees to be able to do setup) but it shouldn't be the way of working so I'm really looking forward to these fixes.

Thanks!
 

Justin

Administrator
Staff member
I just tried to reproduce your first video and it worked correctly. It looks like you are running an older Unity version - can you try updating to the LTS? But besides that the prefab system is a black box without many callbacks so there's only so much flexibility from an end user perspective. When you update the variable value that marks the entire field as dirty. So the JSONSerialization field gets marked as dirty and that prefab instance will no longer be able to be overridden by the prefab root.

A workaround is to set the variable values at runtime on the prefab instance. This will prevent the JSONSerialization field from being marked as dirty and you shouldn't hit any issues since Unity doesn't need to try to merge the fields.
 

SXtheOne

New member
I've reproduced the error in 2021.3.3f1. The easiest is to just make two float variables and change one in the instance, than change the other one in the prefab.

"A workaround is to set the variable values at runtime on the prefab instance. This will prevent the JSONSerialization field from being marked as dirty and you shouldn't hit any issues since Unity doesn't need to try to merge the fields."
You mean I should write a script just to set up the AI instances instead of using the Behavior Tree component that is already in the instance? That means the Behavior Tree component is not usable, even worse. It looks usable but it's misleading the user because it do not display the real values. Check videos #3 and #4 to see how big the problems are.

I have an idea of how the one-field JSON issue can be fixed:
You have the custom editor window for the Behavior Tree component, which means you know when those fields are changed. If an "overridden" field gets introduced for every field, that can be used as an indicator. The problem can be to find out if you are currently modifying a prefab or an instance (because the overridden field should not be true for the prefab ideally). Unity's PrefabUtility may provide help with this. If the user modifies a prefab instance and sets the same value as in the prefab, overridden should be false. Only a reference to the prefab is needed to check this and the PrefabUtility can give you that.

If this is not feasible the ability to set the Behavior Tree values from a prefab instance should be disabled and another way should be provided because currently this part of the otherwise well-working system is broken and makes the asset unusable even for prototyping. Video #1 show a very annoying problem but videos #3, #4 and #5 shows issues that are not simply annoying but makes work nearly impossible.
 

Justin

Administrator
Staff member
Video #1 show a very annoying problem but videos #3, #4 and #5 shows issues that are not simply annoying but makes work nearly impossible.
Did you have the behavior tree editor open? When I tried it with the editor open I wasn't able to reproduce the errors, but was able to when it was closed. I have the use case fixed when the editor is closed.

You mean I should write a script just to set up the AI instances instead of using the Behavior Tree component that is already in the instance? That means the Behavior Tree component is not usable, even worse. It looks usable but it's misleading the user because it do not display the real values. Check videos #3 and #4 to see how big the problems are.
No, you can still use the behavior tree component. Your script would just set the values at runtime. This way the values are not serialized in the single field and it'll work better with the prefab system.

If an "overridden" field gets introduced for every field, that can be used as an indicator.
The problem is that the JSONSerialization field is a single string so when the prefab goes to override it, it overrides the entire field. A single "overridden" field stored within the task data would get set within the same JSONSerialization field so it wouldn't help too much.

The true solution to this is to use Unity's relatively new [SerializeReference] attribute. This would then allow Unity to do the serialization and it can split all of the fields using their serialization format. However, this is a major change and with so many existing projects I do not want to make it unless it's a major update.
 

SXtheOne

New member
I haven't got the editor open in that case, so that was the difference. I'm glad you've fixed it. :)

I understand that I could set the variables at runtime but if I have an AI with defaults, I want to configure the variables based on the position where I put the AI in. Also, for example, the waypoints are needed to be set for every AI.

I meant the overridden field to be put into every variable field, like this:
{
"Type": "BehaviorDesigner.Runtime.SharedTransform",
"Name": "_TurretTransform",
"IsShared": true,
"TransformmValue": 49,
"Overridden": true
},
{
"Type": "BehaviorDesigner.Runtime.SharedGameObject",
"Name": "_TurretGameObject",
"IsShared": true,
"GameObjectmValue": 50,
"Overridden": false
},

but I don't force this solution because I haven't tried it. I've made a class generator that gets all the variables from the tree and creates a class that does the set up at game start. This works but is not ideal because I would like to use the asset as is.
Another alternative solution could be to make an array list out of the variables and serialize that. Unity is good in that. I used the SharedVariable (SharedFloat, SharedGameObjectList, etc.) in my class generator and those serialized very well.

I could accept a solution with SerializeReference as well if it works fine. I would stress that currently, it's really hard to use this asset in production so it would be very welcome. You may create a merge tool as well to make the update easier.
 
Top