I am currently speaking from a place of study in the Unity.3D.RPG series but this inquiry transcends such banal stereotyping. We got some RTS inspirations tossing their gremlins into the gears. I factored the movement logic to support parties of player characters and formation movement logic.
The code examples below correctly navigates agents to precalculated positions inside a List<T>
. The agents are fed a target Vector3
based on their rank in another list dedicated to currently selected units. The failure of my logic is that the entire formation will not rotate as I have it so far. The individuals all rotate and path independently, but the alignment of positions remains fixed in the same direction with every call to these functions.
TL;DR : this moves units into a tactical arrangement but does not rotate the formation as a whole. I want to rotate the whole formation.
namespace E04.Movement
{
public class ReadyUnit
{
public PlayerControl player { get; set; }
public int rank { get; set; }
}
public class Formation : MonoBehaviour
{
public Formation Instance { get; private set; }
enum order
{
loose,
column,
circle,
vanguard,
firingLine
}
[Header("All player characters share the same SINGLE PathOrganizer in the scene.")]
[SerializeField] order arrangement = order.loose;
[Range(1, 15)][SerializeField] int rank;
PathOrganizer organizer;
ReadyUnit unit;
public static List<ReadyUnit> readyUnitList = new List<ReadyUnit>();
private void Awake()
{
unit = new ReadyUnit { player = gameObject.GetComponent<PlayerControl>(), rank = rank };
}
private void Start()
{
if (IsLeader())
{
unit.rank = 0;
}
else
{
unit.rank = rank;
}
/* There should only be ONE instance of PathOrganizer in the scene */
organizer = FindFirstObjectByType<PathOrganizer>();
Selector.Instance.UnitSelectedEvent += OnUnitSelected;
}
void OnUnitSelected(object sender, EventArgs e)
{
if (Selector.Instance.GetSelectedPlayersList().Contains(gameObject.GetComponent<PlayerControl>()) &&
readyUnitList.Contains(unit) == false)
{
readyUnitList.Add(unit);
}
if (Selector.Instance.GetSelectedPlayersList().Contains(gameObject.GetComponent<PlayerControl>()) == false &&
readyUnitList.Contains(unit))
{
readyUnitList.Remove(unit);
}
if (Selector.Instance.GetSelectedUnit() == gameObject.GetComponent<PlayerControl>())
{
unit.rank = 0;
}
else
{
unit.rank = rank;
}
SortUnitsByRank();
}
void SortUnitsByRank()
{
readyUnitList.Sort((leftHand, rightHand) => leftHand.rank.CompareTo(rightHand.rank));
}
public void FallInStep(Vector3 point)
{
if (point == null)
{
return;
}
if (readyUnitList == null)
{
return;
}
SortUnitsByRank();
MoveToPosition(point);
}
void MoveToPosition(Vector3 point)
{
switch (arrangement)
{
case order.loose:
LooseArrangement(point);
break;
case order.column:
ColumnArrangement(point);
break;
case order.circle:
CircleArrangement(point);
break;
case order.vanguard:
VanguardArrangement(point);
break;
case order.firingLine:
FiringLine(point);
break;
}
}
bool IsLeader()
{
Debug.Log("F - IsLeader() " + Selector.Instance.GetSelectedUnit().name);
return Selector.Instance.GetSelectedUnit() == gameObject.GetComponent<PlayerControl>();
}
void LooseArrangement(Vector3 point)
{
float offset = Random.Range(-2f, 2f);
List<Vector3> values = new List<Vector3>
{
point,
point + new Vector3(offset, 0, offset),
point + new Vector3(offset, 0, offset),
point + new Vector3(offset, 0, offset),
point + new Vector3(offset, 0, offset),
point + new Vector3(offset, 0, offset),
point + new Vector3(offset, 0, offset),
point + new Vector3(offset, 0, offset),
point + new Vector3(offset, 0, offset),
point + new Vector3(offset, 0, offset),
point + new Vector3(offset, 0, offset),
point + new Vector3(offset, 0, offset)
};
for (int i = 0; i < readyUnitList.Count; i++)
{
PlayerControl player = readyUnitList[i].player;
organizer.transform.position = point;
organizer.AssignPosition(values[i]);
player.GetComponent<MovementManager>().MoveToDestination(organizer.ConfirmedPosition());
}
}
void ColumnArrangement(Vector3 point)
{
List<Vector3> values = new List<Vector3>()
{
point,
point + new Vector3(2f, 0, 0),
point + new Vector3(0, 0, -2f),
point + new Vector3(2f, 0, -2f),
point + new Vector3(0, 0, -4f),
point + new Vector3(2f, 0, -4f),
point + new Vector3(0, 0, -6f),
point + new Vector3(2f, 0, -6f),
point + new Vector3(0, 0, -8f),
point + new Vector3(2f, 0, -8f),
point + new Vector3(0, 0, -10f),
point + new Vector3(2f, 0, -10f),
};
for (int i = 0; i < readyUnitList.Count; i++)
{
PlayerControl player = readyUnitList[i].player;
organizer.transform.position = point;
organizer.AssignPosition(values[i]);
player.GetComponent<MovementManager>().MoveToDestination(organizer.ConfirmedPosition());
}
}
void CircleArrangement(Vector3 point)
{
float diameter = 2.5f;
for (int i = 0; i < readyUnitList.Count; i++)
{
float angle = (float)(i * (2f * Math.PI / readyUnitList.Count));
float cos = (float)Math.Cos(angle) * diameter;
float sin = (float)Math.Sin(angle) * diameter;
point = new Vector3(point.x + cos, point.y, point.z + sin);
PlayerControl player = readyUnitList[i].player;
player.GetComponent<MovementManager>().MoveToDestination(point);
}
}
void VanguardArrangement(Vector3 point)
{
List<Vector3> values = new List<Vector3>()
{
point,
point + new Vector3(1f, 0, -1f),
point + new Vector3(-1f, 0, -1f),
point + new Vector3(2f, 0, -2f),
point + new Vector3(-2f, 0, -2f),
point + new Vector3(3f, 0, -3f),
point + new Vector3(-3f, 0, -3f),
point + new Vector3(4f, 0, -4f),
point + new Vector3(-4f, 0, -4f),
point + new Vector3(5f, 0, -5f),
point + new Vector3(-5f, 0, -5f),
point + new Vector3(0, 0, -1f),
};
for (int i = 0; i < readyUnitList.Count; i++)
{
PlayerControl player = readyUnitList[i].player;
organizer.transform.position = point;
organizer.AssignPosition(values[i]);
player.GetComponent<MovementManager>().MoveToDestination(organizer.ConfirmedPosition());
}
}
void FiringLine(Vector3 point)
{
List<Vector3> values = new List<Vector3>()
{
point,
point + new Vector3(1f, 0, 0),
point + new Vector3(-1f, 0, 0),
point + new Vector3(2f, 0, 0),
point + new Vector3(-2f, 0, 0),
point + new Vector3(3f, 0, 0),
point + new Vector3(-3f, 0, 0),
point + new Vector3(4f, 0, 0),
point + new Vector3(-4f, 0, 0),
point + new Vector3(5f, 0, 0),
point + new Vector3(-5f, 0, 0),
point + new Vector3(6f, 0, 0),
};
for (int i = 0; i < readyUnitList.Count; i++)
{
PlayerControl player = readyUnitList[i].player;
organizer.transform.position = point;
organizer.AssignPosition(values[i]);
player.GetComponent<MovementManager>().MoveToDestination(organizer.ConfirmedPosition());
}
}
}
}
This reaches across the aisle to:
{
public class PathOrganizer : MonoBehaviour
{
[SerializeField] GameObject destinationMarker = null;
NavMeshAgent agent;
Vector3 callback = Vector3.zero;
private void Awake()
{
agent = GetComponent<NavMeshAgent>();
}
public void ClearDestinationMarkers()
{
foreach(Transform child in gameObject.transform)
{
Destroy(child.gameObject);
}
}
public void AssignPosition(Vector3 point)
{
SteerFormation();
GameObject waypoint = Instantiate(destinationMarker, transform.position, Quaternion.identity);
waypoint.transform.SetParent(gameObject.transform, true);
waypoint.transform.localPosition = point;
waypoint.transform.eulerAngles = new Vector3(90, 0, 0);
callback = point;
}
public Vector3 ConfirmedPosition()
{
return callback;
}
public void SteerFormation()
{
Vector3 heading = Input.mousePosition - transform.position;
float distance = heading.magnitude;
Vector3 direction = heading / distance;
agent.destination = Input.mousePosition;
transform.LookAt(direction);
}
}
That works just dandy. That last method (SteerFormation) is where I snag. What you see there is just a place holder command until I can finally score a “W” against this challenge. I have tried more complicated rotation logic outside of finding headings and magnitudes, at one point I even aligned my waypoint container with the camera’s field of view. The previous failures do indeed turn the container GameObject (PathOrganizer) but the positions within are never altered.
The problem as I see it is that these positions that are instantiated (via destinationMarker) reflect global positioning and are really no more than stamps on the mesh where they popped into existance. I want these markers to ride inside of the PathOrganizer like it is a bus and the agents are dogs chasing the bus. In theory, if I steer the bus the whole flock follows in kind. I have investigated similar queries but haven’t found (or perhaps did not understand) a satisfying solution.
If you have seen a question worded exactly like this on Stack Overflow then I would say, “Nuh-Uh,! I Didn’t write that post. Pants on Fire! Pants on Fire!”
But I won’t… Because I am more mature than you.