This is a key algorithm for creating flocks or a group of military agents. It is designed to be flexible enough to give you the chance to create your own formations.
The end result from this recipe will be a set of target positions and rotations for each agent in the formation. Then, it is up to you to create the necessary algorithms to move the agent to the previous targets.
We can use the movement algorithms learnt in Chapter 1, Movement, in order to target those positions.
We will need to create three base classes that are the data types to be used by the high-level classes and algorithms. The Location
class is very similar to the Steering
class and is used to define a target position and rotation given the formation's anchor point and rotation. The SlogAssignment
class is a data type to match a list's indices and agents. Finally, the Character
class component holds the target Location
class.
The following is the code for the Location
class:
using UnityEngine; using System.Collections; public class Location { public Vector3 position; public Quaternion rotation; public Location () { position = Vector3.zero; rotation = Quaternion.identity; } public Location(Vector3 position, Quaternion rotation) { this.position = position; this.rotation = rotation; } }
The following is the code for the SlotAssignment
class:
using UnityEngine; using System.Collections; public class SlotAssignment { public int slotIndex; public GameObject character; public SlotAssignment() { slotIndex = -1; character = null; } }
The following is the code for the Character
class:
using UnityEngine; using System.Collections; public class Character : MonoBehaviour { public Location location; public void SetTarget (Location location) { this.location = location; } }
We will implement two classes—FormationPattern
and FormationManager
:
FormationPattern
pseudo-abstract class:using UnityEngine; using System.Collections; using System.Collections.Generic; public class FormationPattern: MonoBehaviour { public int numOfSlots; public GameObject leader; }
Start
function:void Start() { if (leader == null) leader = transform.gameObject; }
public virtual Vector3 GetSlotLocation(int slotIndex) { return Vector3.zero; }
public bool SupportsSlots(int slotCount) { return slotCount <= numOfSlots; }
public virtual Location GetDriftOffset(List<SlotAssignment> slotAssignments) { Location location = new Location(); location.position = leader.transform.position; location.rotation = leader.transform.rotation; return location; }
using UnityEngine; using System.Collections; using System.Collections.Generic; public class FormationManager : MonoBehaviour { public FormationPattern pattern; private List<SlotAssignment> slotAssignments; private Location driftOffset; }
Awake
function:void Awake() { slotAssignments = new List<SlotAssignment>(); }
public void UpdateSlotAssignments() { for (int i = 0; i < slotAssignments.Count; i++) { slotAssignments[i].slotIndex = i; } driftOffset = pattern.GetDriftOffset(slotAssignments); }
public bool AddCharacter(GameObject character) { int occupiedSlots = slotAssignments.Count; if (!pattern.SupportsSlots(occupiedSlots + 1)) return false; SlotAssignment sa = new SlotAssignment(); sa.character = character; slotAssignments.Add(sa); UpdateSlotAssignments(); return true; }
public void RemoveCharacter(GameObject agent) { int index = slotAssignments.FindIndex(x => x.character.Equals(agent)); slotAssignments.RemoveAt(index); UpdateSlotAssignments(); }
public void UpdateSlots() { GameObject leader = pattern.leader; Vector3 anchor = leader.transform.position; Vector3 slotPos; Quaternion rotation; rotation = leader.transform.rotation; foreach (SlotAssignment sa in slotAssignments) { // next step } }
foreach
loop:Vector3 relPos; slotPos = pattern.GetSlotLocation(sa.slotIndex); relPos = anchor; relPos += leader.transform.TransformDirection(slotPos); Location charDrift = new Location(relPos, rotation); Character character = sa.character.GetComponent<Character>(); character.SetTarget(charDrift);
The FormationPattern
class contains the relative positions to a given slot. For example, a child CircleFormation
class will implement the GetSlotLocation
class, given the number of slots and its locations over 360 degrees. It is intended to be a basic class, so it is up to the manager to add a layer for permissions and rearrangement. That way, the designer can focus on simple formation scripting, deriving from the base class.
The FormationManager
class, as stated earlier, handles the high-level layer and arranges the locations in line with the formation's needs and permissions. The calculations are based on the leader's position and rotation, and they apply the necessary transformations given the pattern's principles.
It is worth mentioning that the FormationManager
and FormationPattern
classes are intended to be components of the same object. When the leader field in the manager is set to null, the leader is the object itself. That way, we could have a different leader object in order to have a clean inspector window and class modularity.