NodeCanvas Forums › Support › Switch SubTree asset at runtime and connect its’ exposed parameters to parent?
First of all – thanks for the Node Canvas – a great tool to have ๐
I have several zombie prefabs: each with a unique aggressive behaviour; the rest is the same.
As I see it – I want a SubTree node where the Behaviour Tree (BT) asset is set at runtime, and each zombie gets his own personal Tree asset.
Node canvas allows to set this up through an exposed public param, so I sort of can exchange BT assets in a certain SubTree node without a problem, HOWEVER!
I’ve found no way to set exposed parameters from the SubTree if I change BT asset this way.. I can only setup exposed parameters connections to the default SubTree BT asset.
Is there any way to setup exposed parameters to a BT inside SubTree node set at runtime/prefab creation?
After some experiments I’ve come up with this code solution. Sudo-code:
var varDriven = keyValuePair.Value;
var bbParam = new BBMappingParameter(varDriven);
bbParam.canRead = true;
bbParam.canWrite = true;
bbParam.useBlackboard = true;
bbParam.bb = graphParent.blackboard;
bbParam.SetTargetVariable(graphParent.blackboard, varDriver);
subtree.variablesMap.Add(bbParam);
And it doesn’t work ๐
The links are shown, but the value does not get updated even when changed.
Thus far I’m unable to identify the method that will “awaken” the link.
This is what I’ve got thus far:
using System;
using System.Collections.Generic;
using NodeCanvas.Framework;
using NodeCanvas.Framework.Internal;
using NodeCanvas.StateMachines;
using ParadoxNotion.Design;
using UnityEngine;
namespace AP.Scripts.Utils
{
/// <summary>
/// Auto-connects subgraph blackboard vars to their master-graph counterparts;
/// In case several master-graph vars available: “Var” > “var” > “_var”;
/// Subgraph var name sets up the connection rule: “Var”=in+out, “var”=in, “_var”=ignored;
/// </summary>
[Name(“Prepare Subgraph Variables (FSM)”)]
[Description(“Auto-fills exposed vars of all subGraphs to their parentGraph counterparts.” +
“Auto-find and fill; uses naming conventions as basis:” +
“‘CamelCase’ means read+write, ‘camelCase’ means read, ‘_camelCase’ are ignored” +
“parent graph variables are prioritized:” +
“‘CamelCase’ = top priority, ‘camelCase’ = second priority, ‘_camelCase’ = last”)]
[Category(“Auto”)]
public class PrepareSubgraphVariables : ActionTask<FSMOwner>
{
protected override void OnExecute()
{
//first we standardize names and fill in the list of available values from the parent graph:
var varsByStandardizedNames = new Dictionary<string, Variable>();
var priorityByStandardizedNames = new Dictionary<string, int>();
var graphParent = agent.graph;
foreach (var keyValuePair in graphParent.blackboard.variables)
{
var standardizedName = StandardizeName(keyValuePair.Key, out var priority);
if (varsByStandardizedNames.TryGetValue(standardizedName, out var variable))
{
if (priority < priorityByStandardizedNames[standardizedName])
{
varsByStandardizedNames[standardizedName] = variable;
priorityByStandardizedNames[standardizedName] = priority;
}
continue;
}
varsByStandardizedNames.Add(standardizedName, keyValuePair.Value);
priorityByStandardizedNames.Add(standardizedName, priority);
}
//then we go through all the subGraphs and set their appropriate connections:
foreach (var node in graphParent.allNodes)
{
// List<BBMappingParameter> mapping = null;
var subNode = node as IGraphAssignable;
if (subNode == null)
continue;
var mapping = subNode.variablesMap;
//just in case, it’s probably a good idea to erase all of the existing connections.
mapping.Clear();
var subGraph = subNode.subGraph;
foreach (var keyValuePair in subGraph.blackboard.variables)
{
var varDriven = keyValuePair.Value;
if (!varDriven.isExposedPublic)
continue;
var name = StandardizeName(keyValuePair.Key, out var type);
if (type == 2) //if “_camelCase” = ignore
{
Debug.LogError($”‘_camelCase’ var exposed public; vars like these should be ignored!\n{agent.transform.name} :: {node.name} :: {keyValuePair.Key}”);
continue;
}
if (!varsByStandardizedNames.TryGetValue(name, out var varDriver))
{
Debug.LogWarning($”Exposed var counterpart not found in parentGraph!\n{agent.transform.name} :: {node.name} :: {keyValuePair.Key}”);
continue;
}
#if UNITY_EDITOR
//is this check neccessary for release? probably not.
if (varDriver.GetType() != varDriven.GetType())
{
Debug.LogWarning(
$”Exposed var has different type from parentGraph!\n” +
$”{agent.transform.name} :: {node.name} :: {keyValuePair.Key};\n” +
$”{varDriver.GetType()} :: {varDriven.GetType()}”);
continue;
}
#endif
// varDriven.value = varDriver.value;
var bbMappingParam = new BBMappingParameter(varDriven);
bbMappingParam.canRead = type < 1;
bbMappingParam.canWrite = true;
bbMappingParam.useBlackboard = true;
bbMappingParam.bb = graphParent.blackboard;
bbMappingParam.SetTargetVariable(graphParent.blackboard, varDriver);
mapping.Add(bbMappingParam);
}
//perhaps some method inside subgraph should awaken the linked params?
// subGraph.Restart();//.UpdateReferences(subGraph.agent, subGraph.parentBlackboard, true);
}
EndAction();
}
private static string StandardizeName(string key, out int priority)
{
var result = key;
var firstLetter = key[0];
if (firstLetter == ‘_’)
{
result = result.Remove(0, 1);
priority = 2;
}
else if (char.IsLower(firstLetter))
{
priority = 1;
}
else
{
result = Char.ToLowerInvariant(result[0]) + result.Substring(1);
priority = 0;
}
return result;
}
}
}
My mistake. This works fine.
I just thought it was keeping the value up-to-date all the time. But instead it writes-in the value only on graph start.