Can I switch blackboards at runtime?

The Context:

1. I want to make my enemies’ behavior completely designable through the Behavior Graph

  • Example:
    1. Play AnimationClip “Clip” (this would be variable “Clip”)
    2. Then, move to Vector3 “Position” (this would be variable “Position”)
    3. Then, Debug.Log string “Message” (this would be variable “Message”)

2. I want to add the Behavior Agent component at runtime

3. I want to override the Behavior Agent’s variables at runtime by making use of a separate Blackboard

The Issue:

In order to achieve the steps I’ve described above, I’ve tried creating a simple MonoBehaviour which takes a BehaviorGraph and a RuntimeBlackboardAsset as references. Then when it is instantiated, add the “BehaviourGraphAgent” component, assign the referenced “Graph” to it, and call “SetVariableValue” on the agent in order to change its variables.

public class RemoveMe_TestRuntimeBlackboard : MonoBehaviour {
    public BehaviorGraph Graph; // Reference of the Behavior Graph that my enemy uses
    public RuntimeBlackboardAsset OverrideBlackboard; // Reference of a separate Blackboard, containing the same
                                                      // variables as "Graph"'s Blackboard, but with different
                                                      // values

    private void Awake() {
        var agent = gameObject.AddComponent<BehaviorGraphAgent>();
        agent.Graph = Graph;

        foreach (var bbv in OverrideBlackboard.Blackboard.Variables) {
            if (bbv.Name == "Self") continue;
            agent.SetVariableValue(bbv.Name, bbv.ObjectValue);
        }
    }
}

However, the variable’s value doesn’t get changed.
Please note that if instead I’m using agent.SetVariableValue(bbv.Name, "Foo"); (assuming that the variable’s type is String) works.

The Question

I would really like to be able to override a Behavior Agent’s variables by making use of a separate Blackboard so that I don’t have to write custom code just to override variables.
Would this even be possible?

Or even better..

Having something like BehaviorGraphAgent.Graph.SetBlackboard(Blackboard blackboard); or BehaviorGraphAgent.Graph.CopyVariablesFrom(Blackboard blackboard); would be exceptionally helpful. In my case, enemies are spawned based on data from an enemy-specific ScriptableObject. This ScriptableObject contains data like: the enemy prefab, its max health, its damage, etc. Having a Blackboard as a sub-asset to this ScriptableObject would allow me to customize overridden variables through the already existing Editor tools for Behavior Graph and Blackboards.

Or is there any other way to do that? I wouldn’t like to have a separate Behavior Graph for each enemy, and I certainly wouldn’t want to have to have a MonoBehaviour which modifies variables in a hardcoded way.

Hi @Hyperion90 ,

This is a good idea! We currently don’t allow it but we want to expand the use of the Blackboards for that type of thing exactly.

I’m not yet sure when this will be available however. I’ll check if someone can take it before end of year, otherwise it’ll have to wait.

Thank you for your answer!

Regarding this line:

agent.SetVariableValue(bbv.Name, bbv.ObjectValue); // bbv is a BlackboardVariable with no generic argument

This won’t work - setting the variable won’t do anything and no exception is raised, instead I have to do the following:

if (bbv.ObjectValue is string stringValue) {
    agent.SetVariableValue(bbv.Name, stringValue);
} else if (bbv.ObjectValue is float floatValue) {
    agent.SetVariableValue(bbv.Name, floatValue);
} // etc..

This seems more like a bug - is there any better workaround for this?

Looks like we’re missing an API to assign something without a type. I wonder if adding this should sort out the issue? Although it still won’t let you simply set a blackboard, just override the values.

public bool SetVariableValue(string variableName, object boxedValue, Type objectType)

I understand it shouldn’t let me simply set a blackboard, but it’s a good workaround until that functionality is added.

However, I’m having issues implementing your suggestion:

I can’t find any override similar to the one you’ve specified which would take a System.Type as a parameter. I’ve tried BehaviorGraphAgent.SetVariableValue<> and BlackboardReference.SetVariableValue<>.

I can only see the following overrides in both classes:

bool SetVariableValue<TValue>(string variableName, TValue value)
bool SetVariableValue<TValue>(Unity.Behavior.GraphFramework.SerializableGUID guid, TValue value)

I’m using version 1.0.3 of the package.

Hi @Hyperion90,

Yes sorry to clarify, I meant we’re missing such an API and I asked if adding the example I gave above would give you a good enough workaround for now :slight_smile:

Oh, yes, sorry for the misunderstanding. That should work. Thanks!

Got it, thank you! I’ll look into it for 1.0.4 or 1.0.5 :slight_smile:

Hey @Hyperion90 ,

I just realised, you can probably use the following API in the BehaviorGraphAgent:

        /// <summary>
        /// Gets a variable associated with the specified GUID.
        /// </summary>
        /// <param name="guid">The GUID of the variable to get</param>
        /// <param name="variable">The variable associated with the specified GUID.</param>
        /// <returns>Returns true if a variable with a matching GUID was found and false otherwise.</returns>
        public bool GetVariable(SerializableGUID guid, out BlackboardVariable variable)
        {
            if (m_Graph.RootGraph.GetVariable(guid, out variable))
            {
                return true;
            }

            foreach (var behaviorGraphModule in m_Graph.Graphs)
            {
                if (behaviorGraphModule.GetVariable(guid, out variable))
                {
                    return true;
                }
            }
            
            return false;
        }

Then you can use the variable.ObjectValue to assign the new value. You should probably have the variables inside already if you’re just using an override of the same blackboard?

Hi @ShaneeNishry, at line #13 in the code snippet I’ve pasted in the first comment I’m already setting the agent’s variable’s value to its relevant value from the separate blackboard. However, the line has no effect unless I specifically cast the ObjectValue (which has an object type) to a string or a float or whatever. I would like to keep the behaviour as generic as possible, so I would need to use Reflection in order to hack my way around it (cast the ObjectValue to whichever type the variable’s value is). I don’t think this is the expected behavior - or is it?

Sorry if I misunderstood your reply.

Hi @Hyperion90 ,

My suggestion is to replace line 13 with something along the lines of:

if (agent.GetVariable(bbv.GUID / bbv.Name, out BlackboardVariable agentVariable) {
    agentVariable.ObjectValue = bbv.ObjectValue;
}

If the 2 blackboards are identical with just different values then this should work. It’ll only be an issue if you’re trying to get variables that don’t exist on the agent.

@ShaneeNishry thank you so much, that worked! I hope you guys will still consider making it work with “BehaviorAgent.SetVariableValue” as well when the ObjectValue is not explicitly casted. :slight_smile:

Just to recap:

This works: agent.SetVariableValue("Foo", "Bar");

This doesn’t work: agent.SetVariableValue("Foo", (object) "Bar");

This also doesn’t work:

// Assuming "_blackboard" is a RuntimeBlackboardAsset
BlackboardVariable overriddenVariable = _blackboard.Blackboard.Variables.First(bbv => bbv.Name == "Foo");
agent.SetVariableValue("Foo", overriddenVariable.ObjectValue);

This also doesn’t work:

// Assuming "otherBlackboard" is a BlackboardReference
otherBlackboard.GetVariable("Foo", out var overriddenVariable);
agent.SetVariableValue("Foo", overriddenVariable.ObjectValue);

Thanks a lot!!!

I’m glad this works! We’ll definitely look into adding an object SetVariableValue to make life easier :slight_smile:


@Hyperion90 ,

I have been testing and I can confirm this is not working in the API like you said.

Incase you need a workaround while we fix the API, the first method shown in my example does work.