Search Unity

Discussion Creating a custom Sequence node for Behaviour Tree

Discussion in 'Visual Scripting' started by BorpaBoatski, Dec 16, 2022.

  1. BorpaBoatski

    BorpaBoatski

    Joined:
    Aug 3, 2018
    Posts:
    54
    I'm trying to create a custom node that would behave similarly to a sequence node in a Behaviour Tree. How it's different from the built-in Sequence node is that it expects the completion of a task before continuing to subsequent tasks whereas the built-in Sequence node would carry out all tasks immediately in sequence.

    Below is the script I have created for the custom node. It's a copy of the built-in Sequence node but with some edits to make it function like the Sequence node in a Behaviour Tree. There is however a problem I'm running into. With multiple prefabs running the same state graph, a problem occurs where the prefabs after the first would seem to skip the natural sequence and jump to whatever step the first prefab is on. Any idea what could be causing this problem?

    Code (CSharp):
    1. public class BehaviourSequence : Unit
    2. {
    3.     [SerializeAs(nameof(outputCount))]
    4.     private int _outputCount = 2;
    5.  
    6.     [DoNotSerialize]
    7.     public ControlInput enter { get; private set; }
    8.  
    9.     [DoNotSerialize]
    10.     public ControlInput reset;
    11.  
    12.     [DoNotSerialize]
    13.     public ControlInput nextStep;
    14.  
    15.     [DoNotSerialize]
    16.     [Inspectable, InspectorLabel("Steps"), UnitHeaderInspectable("Steps")]
    17.     public int outputCount
    18.     {
    19.         get => _outputCount;
    20.         set => _outputCount = Mathf.Clamp(value, 1, 10);
    21.     }
    22.  
    23.     [DoNotSerialize]
    24.     public ReadOnlyCollection<ControlOutput> multiOutputs { get; private set; }
    25.  
    26.     int SequenceStep;
    27.  
    28.     protected override void Definition()
    29.     {
    30.         enter = ControlInput("Start", Enter);
    31.         reset = ControlInput("Reset", Reset);
    32.         nextStep = ControlInput("Next", Next);
    33.  
    34.         var _multiOutputs = new List<ControlOutput>();
    35.  
    36.         multiOutputs = _multiOutputs.AsReadOnly();
    37.  
    38.         for (var i = 0; i < outputCount; i++)
    39.         {
    40.             var output = ControlOutput(i.ToString());
    41.             Succession(enter, output);
    42.             _multiOutputs.Add(output);
    43.         }
    44.     }
    45.  
    46.     private ControlOutput Next(Flow flow)
    47.     {
    48.         SequenceStep++;
    49.  
    50.         if(SequenceStep > outputCount)
    51.         {
    52.             SequenceStep = 0;
    53.         }
    54.  
    55.         return null;
    56.     }
    57.  
    58.     private ControlOutput Reset(Flow flow)
    59.     {
    60.         SequenceStep = 0;
    61.         return null;
    62.     }
    63.  
    64.     private ControlOutput Enter(Flow flow)
    65.     {
    66.         flow.Invoke(multiOutputs[SequenceStep]);
    67.         return null;
    68.     }
    69.  
    70.     public void CopyFrom(Sequence source)
    71.     {
    72.         base.CopyFrom(source);
    73.         outputCount = source.outputCount;
    74.     }
    75. }
     
  2. Trindenberg

    Trindenberg

    Joined:
    Dec 3, 2017
    Posts:
    396
    A guess here, but if you have the sequence in a macro that you attach to many prefabs, then all prefabs are sharing the same instance; 1 sequence processor (ok) 1 variable (not ok). Solution is either to have an outer variable used by the Sequencer per prefab ie. SequenceStep, or to embed a Sequencer into each prefab.
     
  3. BorpaBoatski

    BorpaBoatski

    Joined:
    Aug 3, 2018
    Posts:
    54
    When you mention "macro" you're referring to the source type for the state machine component? If so, I have that set as graph. I don't see a macro option.

    I'm not sure what you mean by this. Are you saying that using 1 variable inside the node is what's causing it to be shared between all prefabs?

    Thanks for the workaround. I'm using a graph variable to store the current step of the sequence. If the node's inner step variable is being shared between all prefab instances, how do I make it unique per prefab? Does the built-in sequence node also have the same problem?
     
  4. Trindenberg

    Trindenberg

    Joined:
    Dec 3, 2017
    Posts:
    396
    By macro I mean the graph is held in a file.
     
  5. BorpaBoatski

    BorpaBoatski

    Joined:
    Aug 3, 2018
    Posts:
    54
    I see. Yes, I'm using a graph source which would be a macro.
     
  6. BorpaBoatski

    BorpaBoatski

    Joined:
    Aug 3, 2018
    Posts:
    54
    Well, took some time but I finally made a node that combines the Set Flow, Get Flow, and Sequence Flow to make this Sequence that I want. If anyone is interested, I'm putting it below. I've only been testing this out with Graph variables so do take note of that. If there's room for improvement on, do let me know!

    Code (CSharp):
    1. public class BehaviourSequence : Unit
    2. {
    3.     [SerializeAs(nameof(outputCount))]
    4.     private int _outputCount = 2;
    5.  
    6.     [DoNotSerialize]
    7.     public ControlInput enter { get; private set; }
    8.  
    9.     [DoNotSerialize]
    10.     public ControlInput reset;
    11.  
    12.     [DoNotSerialize]
    13.     public ControlInput nextStep;
    14.  
    15.     [DoNotSerialize]
    16.     [PortLabelHidden]
    17.     public ValueInput VariableName;
    18.  
    19.     [Serialize, Inspectable, UnitHeaderInspectable]
    20.     public VariableKind Kind;
    21.  
    22.     [DoNotSerialize]
    23.     [PortLabelHidden]
    24.     [NullMeansSelf]
    25.     public ValueInput Object { get; private set; }
    26.  
    27.     [DoNotSerialize]
    28.     [Inspectable, InspectorLabel("Steps"), UnitHeaderInspectable("Steps")]
    29.     public int outputCount
    30.     {
    31.         get => _outputCount;
    32.         set => _outputCount = Mathf.Clamp(value, 1, 10);
    33.     }
    34.  
    35.     [DoNotSerialize]
    36.     public ReadOnlyCollection<ControlOutput> multiOutputs { get; private set; }
    37.  
    38.     protected override void Definition()
    39.     {
    40.         enter = ControlInput("Enter", Enter);
    41.         reset = ControlInput("Reset", Reset);
    42.         nextStep = ControlInput("Next", Next);
    43.         VariableName = ValueInput("", string.Empty);
    44.  
    45.         var _multiOutputs = new List<ControlOutput>();
    46.  
    47.         multiOutputs = _multiOutputs.AsReadOnly();
    48.  
    49.         for (var i = 0; i < outputCount; i++)
    50.         {
    51.             var output = ControlOutput(i.ToString());
    52.             Succession(enter, output);
    53.             _multiOutputs.Add(output);
    54.         }
    55.     }
    56.  
    57.     private ControlOutput Next(Flow flow)
    58.     {
    59.         int input = Get(flow);
    60.         var name = flow.GetValue<string>(VariableName);
    61.  
    62.         input = (input + 1) % outputCount;
    63.  
    64.         switch (Kind)
    65.         {
    66.             case VariableKind.Flow:
    67.                 flow.variables.Set(name, input);
    68.                 break;
    69.             case VariableKind.Graph:
    70.                 Variables.Graph(flow.stack).Set(name, input);
    71.                 break;
    72.             case VariableKind.Object:
    73.                 Variables.Object(flow.GetValue<GameObject>(Object)).Set(name, input);
    74.                 break;
    75.             case VariableKind.Scene:
    76.                 Variables.Scene(flow.stack.scene).Set(name, input);
    77.                 break;
    78.             case VariableKind.Application:
    79.                 Variables.Application.Set(name, input);
    80.                 break;
    81.             case VariableKind.Saved:
    82.                 Variables.Saved.Set(name, input);
    83.                 break;
    84.             default:
    85.                 throw new UnexpectedEnumValueException<VariableKind>(Kind);
    86.         }
    87.  
    88.         return null;
    89.     }
    90.  
    91.     private int Get(Flow flow)
    92.     {
    93.         var name = flow.GetValue<string>(VariableName);
    94.         VariableDeclarations variables;
    95.  
    96.         switch (Kind)
    97.         {
    98.             case VariableKind.Flow:
    99.                 variables = flow.variables;
    100.                 break;
    101.             case VariableKind.Graph:
    102.                 variables = Variables.Graph(flow.stack);
    103.                 break;
    104.             case VariableKind.Object:
    105.                 variables = Variables.Object(flow.GetValue<GameObject>(Object));
    106.                 break;
    107.             case VariableKind.Scene:
    108.                 variables = Variables.Scene(flow.stack.scene);
    109.                 break;
    110.             case VariableKind.Application:
    111.                 variables = Variables.Application;
    112.                 break;
    113.             case VariableKind.Saved:
    114.                 variables = Variables.Saved;
    115.                 break;
    116.             default:
    117.                 throw new UnexpectedEnumValueException<VariableKind>(Kind);
    118.         }
    119.  
    120.         return (int)variables.Get(name);
    121.     }
    122.  
    123.     private ControlOutput Reset(Flow flow)
    124.     {
    125.         var name = flow.GetValue<string>(VariableName);
    126.  
    127.         switch (Kind)
    128.         {
    129.             case VariableKind.Flow:
    130.                 flow.variables.Set(name, 0);
    131.                 break;
    132.             case VariableKind.Graph:
    133.                 Variables.Graph(flow.stack).Set(name, 0);
    134.                 break;
    135.             case VariableKind.Object:
    136.                 Variables.Object(flow.GetValue<GameObject>(Object)).Set(name, 0);
    137.                 break;
    138.             case VariableKind.Scene:
    139.                 Variables.Scene(flow.stack.scene).Set(name, 0);
    140.                 break;
    141.             case VariableKind.Application:
    142.                 Variables.Application.Set(name, 0);
    143.                 break;
    144.             case VariableKind.Saved:
    145.                 Variables.Saved.Set(name, 0);
    146.                 break;
    147.             default:
    148.                 throw new UnexpectedEnumValueException<VariableKind>(Kind);
    149.         }
    150.  
    151.         return null;
    152.     }
    153.  
    154.     private ControlOutput Enter(Flow flow)
    155.     {
    156.         flow.Invoke(multiOutputs[Get(flow)]);
    157.         return null;
    158.     }
    159.  
    160.     public void CopyFrom(Sequence source)
    161.     {
    162.         base.CopyFrom(source);
    163.         outputCount = source.outputCount;
    164.     }
    165. }
     
  7. BorpaBoatski

    BorpaBoatski

    Joined:
    Aug 3, 2018
    Posts:
    54
    An example of how to use it.
     

    Attached Files: