Handling many mapped variables

gumboots

Member
Hi there!

Is there a way to assign mapped variables programatically? I have a large amount (~50), and also want to interchange behavior trees.

I feel like there might be a smarter way to do it, as creating and mapping shared variables is somewhat cumbersome. But I can't figure one out. The main one I tried was making a Flag task, with an enum value. So I didn't need to keep making and mapping variables in the BD Editor, I'd just make a new enum value and could check it. However this had its own set of problems. So, I am hoping I can programatically assign the mapped variables myself, and at least can partially automate it?

Edit: A little digging and I figured out the below. It seems to be working fine, is there any issues I'm overlooking?

C#:
void MapAllBehaviorTreeSharedVariables() {
    Type type = GetType();
    string propertyFormat = type.FullName + "/{0}";

    foreach (BehaviorTree behaviorTree in GetComponents<BehaviorTree>()) {
        List<SharedVariable> variables = behaviorTree.GetAllVariables();

        foreach (SharedVariable variable in variables) {
            if (type.GetProperty(variable.Name) == null) { continue; }

            variable.PropertyMapping = string.Format(propertyFormat, variable.Name);
            variable.PropertyMappingOwner = gameObject;
        }
    }
}

Edit 2: So a follow up question, is it possible to create SharedVariables programatically? I had the thought that I could use Attributes to tag properties in my class, and then when I press a button in the Editor, clear the BehaviorTree's SharedVariables and recreate them from my Attributes? Though this could possibly be problematic for Tasks in the tree, which might be references?

Edit 3: I've made some progress, but I believe I'm now blocked by private fields (and a possible misunderstanding of Behavior Designer). Here's the code I've got so far:

C#:
[AttributeUsage(AttributeTargets.Property)]
public class MappedPropertyAttribute : Attribute {
    public readonly Type Type;

    public MappedPropertyAttribute(Type type) {
        Type = type;
    }
}

void UpdateBehaviorTreeMappedProperties() {
    Type type = GetType();
    PropertyInfo[] properties = type.GetProperties();
    string propertyFormat = type.FullName + "/{0}";

    List<SharedVariable> variables = BehaviorTree.GetAllVariables();
    variables.Clear();

    foreach (PropertyInfo property in properties) {
        object[] attributes = property.GetCustomAttributes(typeof(MappedPropertyAttribute), false);

        foreach (MappedPropertyAttribute attribute in attributes) {
            SharedVariable mappedProperty = (SharedVariable)Activator.CreateInstance(attribute.Type);
            mappedProperty.IsShared = true;
            mappedProperty.IsGlobal = false;
            mappedProperty.IsDynamic = false;
            mappedProperty.Name = property.Name;
            mappedProperty.PropertyMapping = string.Format(propertyFormat, property.Name);
            mappedProperty.PropertyMappingOwner = gameObject;

            variables.Add(mappedProperty);
        }
    }

    BehaviorTree.GetBehaviorSource().SetAllVariables(variables);
}

It seems to work up to a point: the inspector updates with the new shared variables. However it eventually reverts to what has been serialized. So, two questions: Is there a way to force reserialization, and secondly: Is this even going to work with trees already referencing existing shared variables?
 
Last edited:
You can force a serialization with:

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

I wouldn't create a new shared variable, rather just modify an existing one. If you create a new one you have to hook up the references within the tasks.
 
I wouldn't create a new shared variable, rather just modify an existing one. If you create a new one you have to hook up the references within the tasks.

Thanks Justin! So just to confirm, you think I should iterate through all of the existing SharedVariables and see if one exists first, if it does, then simply update it, if not, create a new one?

Is there any issue in reordering the existing SharedVariables list to match the order of my properties?
 
you think I should iterate through all of the existing SharedVariables and see if one exists first, if it does, then simply update it, if not, create a new one?
I would create all of the shared variables that you will need in the editor, assign them to the tasks, and then run your property mapping script. This will prevent you from needing to create a new variable so all of the correct references will be assigned.

Is there any issue in reordering the existing SharedVariables list to match the order of my properties?
No, the variables can be in any order.
 
Thanks Justin, it seems the serialization still isn't taking. (Or I'm misdiagnosing the issue.)

Here's the complete Editor class: https://pastebin.com/PtGvRxeQ I'm using it on a prefab, incase it's relevant. And for completeness, here is the Attribute and usage: https://pastebin.com/HtNj3SeY

It updates the Unity Inspector exactly as I'd expect it to. I semi-circumvented your recommendation of not creating any new SharedVariables. If one exists, I use that. However where one doesn't, I create new SharedVariables as needed (Side note: I removed the need to manually define the SharedVariable type in the attribute, now it finds that itself.)

However as soon as I click into the BD Editor, I get one of two results:
1) If I've programatically reordered the variables list, then a number of the mapped properties lose their mapping. Any newly created fields are removed.
2) If I haven't reordered the variables list, it simply reverts back to (what I believe) is the previously serialized version.

This only happens when I view the tree with an active BD Editor. I have two Behavior Trees on the GameObject, and only the one that I view in the Editor is reverted. Selecting the other then reverts that one. In fact, I can even see the added properties in the BD Editor before I click into it.

Specifically, here is how I'm applying/serializing the modified variables:

C#:
static void SerializeBehaviorTree(BehaviorTree behaviorTree, List<SharedVariable> variables) {
    BehaviorSource behaviorSource = behaviorTree.GetBehaviorSource();
    behaviorSource.SetAllVariables(variables);

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

Is there something I'm missing? Perhaps another cache the Behavior Tree retains? Or something to do with the fact that these trees use External Behaviors?
 
Last edited:
When you place focus back on the editor it'll deserialize the tree completely so the editor doesn't store any cache in that regard. The only thing that I can think of is that you aren't marking the behavior tree component / external tree as dirty so Unity won't necessarily know to keep the serialization changes.

 
Edit: Bah, nope, still not working properly. Deletes the tree sometimes, or just runs into previous issues.

Edit 2: Or maybe it is? Restarting Unity seems to have helped in some way. It could be that my method for testing (rolling back the changes via source control) might have been interfering with the results? Perhaps something in Unity or a BD components's cache wasn't being rolled back along with everything else?

So I think I've got it!

https://pastebin.com/airNDVVs

I was updating the BehaviorTree, when I should have been updating the BehaviorSource(s). Here is how I believe the relationship works, please correct me if I'm wrong:
  • If using an ExternalBehavior, a BehaviorTree loads the external BehaviorSource to see what variables it should have, and creates new (important) variables to match.
  • If not using an ExternalBehavior, the BehaviorTree just manages its own variables.
So instead of using the BehaviorTree directly, I load the BehaviorSources of both the BehaviorTree and the ExternalBehavior, and update both lists with new variables/order. However I only apply mapping to the BehaviorTree (not the ExternalBehavior).

The final, crucial part (I believe) is serializing the ExternalBehavior first (this includes marking it as dirty, I believe), as otherwise when I apply the variables to the BehaviorTree, it loads from the ExternalBehavior and loses everything.

Does this appear sound to you? I understand it's a little out of what you actually intend with the asset, but being able to essentially drive the BehaviorTree variables with code removes one of my biggest frustrations!
 
Last edited:
Top