Stack overflow errors with setting leaders in Formation Pack

Kokyalord

New member
Hello, I am new to Behaviour Designer and have been looking how to implement the formation pack into an RTS environment. I'm trying to set it up in a way that the first unit selected is the designated leader, while also setting this unit as the leader for the followers. So far, I have managed to get this set up in a way that allows me to get and set the leader for all of the following units; but no matter what I seem to do, it always ends with a sack overflow involving the "AddAgentToGroup" function. I have tried breaking everything down into separate pieces to try and single out what is causing the issue, but I can't seem to find any solution.

At the moment, I have the tree set up like the attached picture in my attempts to break down the process. The tree on the left is the one used by the leader unit, and has no gameobject set as the leader. The right tree is used by the followers, and uses a sharedgameobject variable for the leader, which is set as the unit running the left tree. I did a bunch of digging into this issue and I thought it might have been caused by the leader trying to insert itself into the agent group infinitely, but I don't see how that could be happening. Before the right tree moves to the swarm task, the task before it works like:

BT = manager.GetComponent<BehaviorTree>();
BT.SetVariableValue("Leader", leader);

And it does set the correct value for the variable in the end, and it only runs once before the swarm task is activated. I'm just absolutely stumped on this issue and I would love some help figuring this out, please and thank you!
 

Attachments

  • Screenshot 2023-02-21 03.28.19.png
    Screenshot 2023-02-21 03.28.19.png
    88.9 KB · Views: 3
StackOverflowException: The requested operation caused a stack overflow.
BehaviorDesigner.Runtime.Formations.Tasks.FormationGroup.AddAgentToGroup (BehaviorDesigner.Runtime.Behavior agent, System.Int32 index) (at Assets/Behavior Designer Formations/Scripts/Tasks/FormationGroup.cs:187)
BehaviorDesigner.Runtime.Formations.AstarPathfindingProject.Tasks.IAIAgentFormationGroup.AddAgentToGroup (BehaviorDesigner.Runtime.Behavior agent, System.Int32 index) (at Assets/Behavior Designer Formations/Integrations/Astar Pathfinding Project/Tasks/IAIAgentFormationGroup.cs:120)
BehaviorDesigner.Runtime.Formations.AstarPathfindingProject.Tasks.Swarm.AddAgentToGroup (BehaviorDesigner.Runtime.Behavior agent, System.Int32 index) (at Assets/Behavior Designer Formations/Integrations/Astar Pathfinding Project/Tasks/Swarm.cs:25)
BehaviorDesigner.Runtime.Behavior.SendEvent[T,U] (System.String name, T arg1, U arg2) (at <54e4b904ef9a4d4792c51489161280c3>:0)
BehaviorDesigner.Runtime.Formations.Tasks.FormationGroup.AddAgentToGroup (BehaviorDesigner.Runtime.Behavior agent, System.Int32 index) (at Assets/Behavior Designer Formations/Scripts/Tasks/FormationGroup.cs:196)
BehaviorDesigner.Runtime.Formations.AstarPathfindingProject.Tasks.IAIAgentFormationGroup.AddAgentToGroup (BehaviorDesigner.Runtime.Behavior agent, System.Int32 index) (at Assets/Behavior Designer Formations/Integrations/Astar Pathfinding Project/Tasks/IAIAgentFormationGroup.cs:120)
BehaviorDesigner.Runtime.Formations.AstarPathfindingProject.Tasks.Swarm.AddAgentToGroup (BehaviorDesigner.Runtime.Behavior agent, System.Int32 index) (at Assets/Behavior Designer Formations/Integrations/Astar Pathfinding Project/Tasks/Swarm.cs:25)
BehaviorDesigner.Runtime.Behavior.SendEvent[T,U] (System.String name, T arg1, U arg2) (at <54e4b904ef9a4d4792c51489161280c3>:0)
This is what I get, the second part of the trace repeats several times.
 
Last edited:
I tried something different by using the follower section for both the leader and the follower, and it ends up endlessly looking for a leader tree to receive orders from. I'm going to see what happens if I make a separate external behaviour tree for the followers branch, because maybe this could be caused by both seek tasks being present in the same tree used by both the leader and the follower at the same time.
 
StackOverflowException: The requested operation caused a stack overflow.
System.Collections.Generic.Dictionary`2[TKey,TValue].TryGetValue (TKey key, TValue& value) <0x13a466bc5d0 + 0x00008> in <e40e5a8f982c4b618a930d29f9bd091c>:0
BehaviorDesigner.Runtime.Behavior.GetDelegate (System.String name, System.Type type) (at <54e4b904ef9a4d4792c51489161280c3>:0)
BehaviorDesigner.Runtime.Behavior.SendEvent[T,U] (System.String name, T arg1, U arg2) (at <54e4b904ef9a4d4792c51489161280c3>:0)
BehaviorDesigner.Runtime.Formations.Tasks.FormationGroup.AddAgentToGroup (BehaviorDesigner.Runtime.Behavior agent, System.Int32 index) (at Assets/Behavior Designer Formations/Scripts/Tasks/FormationGroup.cs:196)
BehaviorDesigner.Runtime.Formations.AstarPathfindingProject.Tasks.IAIAgentFormationGroup.AddAgentToGroup (BehaviorDesigner.Runtime.Behavior agent, System.Int32 index) (at Assets/Behavior Designer Formations/Integrations/Astar Pathfinding Project/Tasks/IAIAgentFormationGroup.cs:120)
BehaviorDesigner.Runtime.Formations.AstarPathfindingProject.Tasks.Swarm.AddAgentToGroup (BehaviorDesigner.Runtime.Behavior agent, System.Int32 index) (at Assets/Behavior Designer Formations/Integrations/Astar Pathfinding Project/Tasks/Swarm.cs:25)
BehaviorDesigner.Runtime.Behavior.SendEvent[T,U] (System.String name, T arg1, U arg2) (at <54e4b904ef9a4d4792c51489161280c3>:0)

Unfortunately, almost same result, but a bit different.
I did a bit more digging and for some reason, all of the followers, except one, isn't having the leader variable set for them, causing the whole grouping process to loop endlessly and thus fail. I'm thinking maybe it has something to do with the task I use to set the leader. It looks like this in total:

public class SetSelectedUnitOrders : Action
{
public SharedCharacterManager _manager;
private CharacterManager manager;
private BehaviorTree BT;
private GameObject leader;

public override void OnStart()
{
manager = _manager.Value;
foreach (UnitManager um in Globals.SELECTED_UNITS)
{
if (um.SelectIndex == 0)
{
leader = um.gameObject;
break;
}
}
BT = manager.GetComponent<BehaviorTree>();
BT.SetVariableValue("Leader", leader);
}


public override TaskStatus OnUpdate()
{
return TaskStatus.Success;
}
}
I'm going to try using a shared gameobject for the leader variable instead, maybe i've been doing it wrong this whole time!
 
Last edited:
After changing the task to set the sharedgameobject instead, the same result occurs where all but one don't get their leader variable set. Hmm. I'm thinking the problem is with how the leader is being set, because it gets set for one follower and one only. Because looking at how the tree goes while this stackoverflow occurs, every follower besides the first doesn't get past the task that sets the leader variable AKA. "Set Selected Unit Orders", which is detailed in the previous post. I'm still relatively new to this so I might be doing something wrong.
 
Last edited:
I did more testing and pretty much anything I try ends up with the same stack overflow, this includes using a single preset leader instead of setting one dynamically.

EDIT:
I've gone and done some more testing and made an isolated testing scene where I can simply muck around and figure out what's going on. So far, I have managed to get the system partially working by using a completely external script from the behaviour tree to set the leaders for all the units. It still has a lot of kinks I need to iron out though, but I'll post an update if I manage to fully fix the problem.
 
Last edited:
This does look leader related. To confirm:

- Only a single behavior tree is the leader. This leader tree does not have anything filled in for the leader variable.
- All other behavior trees reference the leader behavior tree.
 
You are right! After hours of stress testing and tinkering, I finally managed to create a format inside of my test scene that works exactly how I want it to! For reference to anyone else who might find this issue and wants to do something similar, here is a reference to both the code and the behaviour tree:
Screenshot 2023-02-22 15.50.50.png
public class SelectionTest : MonoBehaviour
{
public List<GameObject> selectedUnits;
public List<GameObject> movingUnits;
public GameObject newLeader;
public bool hasLeader = false;
private Ray ray;
private unitTest unit;
private BehaviorTree BT;

private void Update()
{
ray = Camera.main.ScreenPointToRay(Input.mousePosition);
RaycastHit _raycastHit;

// set the selected units when clicking on a selectable unit, or otherwise clear all currently selected units
if (Input.GetMouseButtonDown(0))
{
if (Physics.Raycast(ray, out _raycastHit, 1000f, Globals.UNIT_MASK))
{
if (selectedUnits.Contains(_raycastHit.transform.gameObject)) return;
selectedUnits.Add(_raycastHit.transform.gameObject);
BT = _raycastHit.transform.GetComponent<BehaviorTree>();
BT.SetVariableValue("isSelected", true);
}
else if (Physics.Raycast(ray, out _raycastHit, 1000f, Globals.TERRAIN_LAYER_MASK))
{
for (int i = 0; i < selectedUnits.Count; i++)
{
BT = selectedUnits.GetComponent<BehaviorTree>();
BT.SetVariableValue("isSelected", false);
}
selectedUnits.Clear();
}
}
if (Input.GetMouseButtonDown(1))
{
// if movingunits is empty, continue
// if movingunits is not empty, check through selected units and see if they are already set to move
// if they are, check if the leader of the moving units is currently selected
// if so, change the leader of the moving units to the next available one, before setting the leader of the selected units
if (movingUnits.Count == 0)
{
for (int i = 0; i < selectedUnits.Count; i++)
{
BT = selectedUnits.GetComponent<BehaviorTree>();
movingUnits.Add(selectedUnits);
if (i == 0)
BT.SetVariableValue("Leader", null);
else
BT.SetVariableValue("Leader", selectedUnits[0].gameObject);
}
}
else
{
if (selectedUnits.Contains(movingUnits[0]))
// remove selected units first
hasLeader = false;
for (int i = 0; i < selectedUnits.Count; i++)
{
if (movingUnits.Contains(selectedUnits))
movingUnits.Remove(selectedUnits);
}
// reset the leader of moving units
for (int i = 0; i < movingUnits.Count; i++)
{
BT = movingUnits.GetComponent<BehaviorTree>();
if (hasLeader == false)
{
Debug.Log("setting as leader:" + movingUnits.name);
BT.SetVariableValue("Leader", null);
newLeader = movingUnits.gameObject;
hasLeader = true;
}
else if (hasLeader == true)
{
Debug.Log("following leader: " + newLeader.name);
BT.SetVariableValue("Leader", newLeader);
}
}
// add back all selected units
for (int i = 0; i < selectedUnits.Count; i++)
{
BT = selectedUnits.GetComponent<BehaviorTree>();
if (i == 0)
BT.SetVariableValue("Leader", null);
else
BT.SetVariableValue("Leader", selectedUnits[0].gameObject);
}
}
}
}
}

Basically, I put this on an empty gameobject in the scene and set all the units up with this behaviour tree. The left tree constantly checks for if the unit is actually selected, and when your right mouse button is pressed; if it is, then sets a position using a ScreenPointToRay cast. The right tree constantly checks a HasDestination bool, which is set by the custom raycast task when it hits a movable space, and then enacts the swarm task. The swarm tasks uses the shared gameobject Leader variable while having its target position set to the position variable. Once the swarm task is complete, it will set the HasDestination bool to false, set the Leader value to none, and get the SelectionTest script from the game scene to remove itself from the movingUnits list. If at any point RMB is pressed while the swarm task is running, I have it set so the HasDestination bool will be set to false just before the raycast task runs, which will abort the current swarm task through a conditional abort so it can reevaluate the new destination.

For the script, it simply sets the selected units into a list when clicking on them, and clears the list when clicking anywhere else. When RMB is pressed (you can add a check for if units are selected here!), it iterates through the selected units and sets the leader as the first unit in the list while keeping the leader's variable null. It also adds all those units to a secondary list, called movingUnits. If you select a new batch of units, and said new batch happens to contain a leader for a group of moving units, it will do the following:
1. Remove all the selected units from the movingUnits list
2. Iterate through the remainder of the movingUnits list and set the first available unit as the new leader
3. Set the new leader for the currently selected units

It could probably be cleaned up by someone far more experienced than me, but this method works for commanding units to move in formation, and interrupting said moving units with another movement order, without causing any stack overflow errors!
 
Top