How can I add/remove squad members from the FormationSquare when changing the number of members value ?
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
public class SquadFormation : MonoBehaviour
{
enum Formation
{
Square, Circle, Triangle
}
[Header("Main Settings")]
[Space(5)]
public Transform squadMemeberPrefab;
[Range(4, 100)]
public int numberOfSquadMembers = 20;
[Range(1, 20)]
public int numberOfSquads = 1;
[Range(0, 4)]
public int columns = 4;
public int gaps = 10;
public int circleRadius = 10;
public float yOffset = 0;
[Range(3, 50)]
public float moveSpeed = 3;
[Range(3, 50)]
public float rotateSpeed = 1;
public float threshold = 0.1f;
public bool randomSpeed = false;
[Range(1, 100)]
public int randSpeedMin = 1;
[Range(1, 100)]
public int randSpeedMax = 1;
public bool startRandomFormation = false;
public string currentFormation;
private Formation formation;
private List<Quaternion> quaternions = new List<Quaternion>();
private List<Vector3> newpositions = new List<Vector3>();
private bool move = false;
private bool squareFormation = false;
private List<GameObject> squadMembers = new List<GameObject>();
private float[] step;
private int[] randomSpeeds;
private int index = 0;
private int numofobjects = 0;
// Use this for initialization
void Start()
{
numofobjects = numberOfSquadMembers;
if (startRandomFormation)
{
formation = (Formation)UnityEngine.Random.Range(0, Enum.GetNames(typeof(Formation)).Length);
}
else
{
formation = Formation.Square;
}
currentFormation = formation.ToString();
ChangeFormation();
foreach (Transform child in gameObject.transform)
{
if (child.tag == "Squad Member")
squadMembers.Add(child.gameObject);
}
randomSpeeds = RandomNumbers(randSpeedMin, randSpeedMax, squadMembers.Count);
step = new float[squadMembers.Count];
}
// Update is called once per frame
void Update()
{
if (numofobjects != numberOfSquadMembers)
{
numofobjects = 0;
numofobjects = numberOfSquadMembers;
squadMembers = new List<GameObject>();
FormationSquare();
}
if (Input.GetKeyDown(KeyCode.F))
{
randomSpeeds = RandomNumbers(randSpeedMin, randSpeedMax, squadMembers.Count);
foreach (int speedV in randomSpeeds)
{
if (index == randomSpeeds.Length)
index = 0;
step[index] = speedV * Time.deltaTime;
index++;
}
ChangeFormation();
}
if (move == true)
{
MoveToNextFormation();
}
}
private void ChangeFormation()
{
switch (formation)
{
case Formation.Square:
FormationSquare();
break;
case Formation.Circle:
FormationCircle();
break;
}
}
private Vector3 FormationSquarePositionCalculation(int index) // call this func for all your objects
{
float posX = (index % columns) * gaps;
float posY = (index / columns) * gaps;
return new Vector3(posX, posY);
}
private void FormationSquare()
{
//newpositions = new List<Vector3>();
quaternions = new List<Quaternion>();
Transform go = squadMemeberPrefab;
for (int i = 0; i < numofobjects; i++)
{
if (squadMembers.Count == 0)
go = Instantiate(squadMemeberPrefab);
Vector3 pos = FormationSquarePositionCalculation(i);
if (newpositions.Count > 0)
{
go.position = new Vector3(newpositions[newpositions.Count -1].x + pos.x, 0, newpositions[newpositions.Count - 1].y + pos.y);
}
else
{
go.position = new Vector3(transform.position.x + pos.x, 0, transform.position.y + pos.y);
}
go.Rotate(new Vector3(0, -90, 0));
go.tag = "Squad Member";
go.transform.parent = gameObject.transform;
newpositions.Add(go.transform.position);
}
move = true;
squareFormation = true;
formation = Formation.Circle;
}
private Vector3 FormationCirclePositionCalculation(Vector3 center, float radius, int index, float angleIncrement)
{
float ang = index * angleIncrement;
Vector3 pos;
pos.x = center.x + radius * Mathf.Sin(ang * Mathf.Deg2Rad);
pos.z = center.z + radius * Mathf.Cos(ang * Mathf.Deg2Rad);
pos.y = center.y;
return pos;
}
private void FormationCircle()
{
newpositions = new List<Vector3>();
quaternions = new List<Quaternion>();
Vector3 center = transform.position;
float radius = (float)circleRadius / 2;
float angleIncrement = 360 / (float)numberOfSquadMembers;
for (int i = 0; i < numberOfSquadMembers; i++)
{
Vector3 pos = FormationCirclePositionCalculation(center, radius, i, angleIncrement);
var rot = Quaternion.LookRotation(center - pos);
pos.y = Terrain.activeTerrain.SampleHeight(pos);
pos.y = pos.y + yOffset;
newpositions.Add(pos);
quaternions.Add(rot);
}
move = true;
squareFormation = false;
formation = Formation.Square;
}
private void MoveToNextFormation()
{
if (randomSpeed == false)
{
step[0] = moveSpeed * Time.deltaTime;
}
for (int i = 0; i < squadMembers.Count; i++)
{
squadMembers[i].transform.LookAt(newpositions[i]);
if (randomSpeed == true)
{
squadMembers[i].transform.position =
Vector3.MoveTowards(squadMembers[i].transform.position, newpositions[i], step[i]);
}
else
{
squadMembers[i].transform.position =
Vector3.MoveTowards(squadMembers[i].transform.position, newpositions[i], step[0]);
}
if (Vector3.Distance(squadMembers[i].transform.position, newpositions[i]) < threshold)
{
if (squareFormation == true)
{
Vector3 degrees = new Vector3(0, 0, 0);
Quaternion quaternion = Quaternion.Euler(degrees);
squadMembers[i].transform.rotation = Quaternion.Slerp(squadMembers[i].transform.rotation, quaternion, rotateSpeed * Time.deltaTime);
}
else
{
squadMembers[i].transform.rotation = Quaternion.Slerp(squadMembers[i].transform.rotation, quaternions[i], rotateSpeed * Time.deltaTime);
}
}
}
}
private static int[] RandomNumbers(int min, int max, int howMany)
{
int[] myNumbers = new int[howMany];
for (int i = 0; i < howMany; i++)
{
myNumbers[i] = UnityEngine.Random.Range(min, max);
}
return myNumbers;
}
}
Since the code is a bit long I will also show the places where the problem is: In the Update:
if (numofobjects != numberOfSquadMembers)
{
numofobjects = 0;
numofobjects = numberOfSquadMembers;
squadMembers = new List<GameObject>();
FormationSquare();
}
For example numberOfSquadMembers value is 7 Now I change it to 8 Now numofobjects is 8 And it's going to the FormationSquare method:
private Vector3 FormationSquarePositionCalculation(int index) // call this func for all your objects
{
float posX = (index % columns) * gaps;
float posY = (index / columns) * gaps;
return new Vector3(posX, posY);
}
private void FormationSquare()
{
//newpositions = new List<Vector3>();
quaternions = new List<Quaternion>();
Transform go = squadMemeberPrefab;
for (int i = 0; i < numofobjects; i++)
{
if (squadMembers.Count == 0)
go = Instantiate(squadMemeberPrefab);
Vector3 pos = FormationSquarePositionCalculation(i);
if (newpositions.Count > 0)
{
go.position = new Vector3(newpositions[newpositions.Count -1].x + pos.x, 0, newpositions[newpositions.Count - 1].y + pos.y);
}
else
{
go.position = new Vector3(transform.position.x + pos.x, 0, transform.position.y + pos.y);
}
go.Rotate(new Vector3(0, -90, 0));
go.tag = "Squad Member";
go.transform.parent = gameObject.transform;
newpositions.Add(go.transform.position);
}
move = true;
squareFormation = true;
formation = Formation.Circle;
}
In the FormationSquare mode before I used to make new instance for newpositions List but now my logic say to keep the positions all the time and when creating new squad member/s to add it to the last position of the formation. But also to keep the formation shape.
But what it does now it first adding much more objects. If numofobjects is 8 it will not add new 8 but more. Second problem it's adding the new once on the old once positions so in the end there are places there is 2 or 3 objects at the same position. But I want it to add the new once to last position/s so the formation square will get bigger.
Same idea with the circle formation. I didn't do it yet not sure how but if it's in the circle formation mode make the circle bigger show the current objects moving a bit to the sides or backward and make the circle bigger for the new once.
The main goal is when changing the numberOfSquadMembers while the game is running to update the currently formation.
Answer by madks13 · Jun 28, 2018 at 02:32 PM
You forgot to clear the lists before adding. Also, you wrote :
if (squadMembers.Count == 0)
go = Instantiate(squadMemeberPrefab);
Which means, if squadMembers.Count != 0, you keep using the prefab as "go".
Edit : i want to give a suggestion :
Given that the only thing changing between formations is poisitions + rotations, and that you can base the number of GameObjects on those, you might want to first only calculate position + rotation, then check if those changed, and if they did, update the game objects.
In pseudo-code :
var newpos = List CalculateFormation(paramaters);
If (newpos.count != pos.count || ComparePos(pos, newPos))
{
UpdateSquad(parameters);
}
The answer is too long with the code. Maybe in here it will work :
Here is an example of implementation. Keep in mind i removed anything unnecessary for the example, and the code is not optimised, it IS an example :
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
public enum Formation
{
Unknown,
Square,
Circle,
Triangle
}
public class SquadFormation : MonoBehaviour
{
[Header("Main Settings")]
[Space(5)]
public Transform squadMemeberPrefab;
[Range(4, 100)]
public int numberOfSquadMembers = 20;
[Range(1, 20)]
public int numberOfSquads = 1;
[Range(0, 4)]
public int columns = 4;
public int gaps = 10;
public int circleRadius = 10;
public float yOffset = 0;
[Range(3, 50)]
public float moveSpeed = 3;
[Range(3, 50)]
public float rotateSpeed = 1;
public float threshold = 0.1f;
public bool randomSpeed = false;
[Range(1, 100)]
public int randSpeedMin = 1;
[Range(1, 100)]
public int randSpeedMax = 1;
public bool startRandomFormation = false;
//This will allow the field to be shown in editor while remaining private
[SerializeField]
private Formation formation;
private List<PositionInformation> positionInformations = new List<PositionInformation>();
private List<GameObject> squadMembers = new List<GameObject>();
// Use this for initialization
void Start()
{
if (startRandomFormation)
{
formation = (Formation)UnityEngine.Random.Range(0, Enum.GetNames(typeof(Formation)).Length);
}
else
{
formation = Formation.Square;
}
ChangeFormation();
foreach (Transform child in gameObject.transform)
{
if (child.tag == "Squad Member")
squadMembers.Add(child.gameObject);
}
}
void Update()
{
if (Input.GetKeyDown(KeyCode.F))
{
ChangeFormation();
}
}
private void ChangeFormation()
{
switch (formation)
{
case Formation.Square:
UpdateFormation(new SquareFormationParameters
{
Formation = formation,
SquadSize = numberOfSquadMembers
}.CalculateFormation());
break;
case Formation.Circle:
UpdateFormation(new CircleFormationParameters
{
Formation = formation,
SquadSize = numberOfSquadMembers,
Radius = circleRadius
}.CalculateFormation());
break;
}
}
private void UpdateFormation(IEnumerable<PositionInformation> newPositions)
{
//Not checking a list with 0 elements in case all squad members are dead
if (newPositions == null)
{
return;
}
List<GameObject> updatedSquad = new List<GameObject>();
foreach (var posInfo in newPositions)
{
Transform sm = null;
//You can try to find squad members closest to the new position
//For the sake of simplicity, we simply assign squad members per new position
if (squadMembers.Count > 0)
{
sm = squadMembers.First().transform;
squadMembers.Remove(sm.gameObject);
}
else
{
sm = Instantiate(squadMemeberPrefab);
}
sm.position = posInfo.Position;
sm.rotation = posInfo.Rotation;
updatedSquad.Add(sm.gameObject);
}
//Cleanup in case updated squad size < old squad size
if (squadMembers.Count > 0)
{
GameObject sm = null;
while (squadMembers.Count > 0)
{
sm = squadMembers[0];
squadMembers.RemoveAt(0);
Destroy(sm);
sm = null;
}
}
}
}
public class PositionInformation
{
public Vector3 Position { get; set; }
public Quaternion Rotation { get; set; }
//Custom Equals methods for custom classes are good practice
public override bool Equals(object obj)
{
var other = obj as PositionInformation;
if (other == null) return false;
if (other.GetType() != GetType()) return false;
if (ReferenceEquals(this, null)) return false;
if (ReferenceEquals(this, obj)) return true;
return Position == other.Position && Rotation == other.Rotation;
}
//This is just an example, adapt to what you need or want
public override int GetHashCode()
{
return (Rotation.GetHashCode() * Position.GetHashCode());
}
}
public class FormationBaseParameters
{
public Formation Formation { get; set; }
public int SquadSize { get; set; }
}
public class SquareFormationParameters : FormationBaseParameters
{
//Add square apecific parameters
}
public class CircleFormationParameters : FormationBaseParameters
{
public float Radius { get; set; }
//Other circle specific parameters
}
public static class Formations
{
public static List<PositionInformation> CalculateFormation<T>(this T parameters) where T : FormationBaseParameters
{
List<PositionInformation> result = null;
switch (parameters.Formation)
{
case Formation.Unknown:
break;
case Formation.Square:
result = CalculateFormationSquare(parameters as SquareFormationParameters);
break;
case Formation.Circle:
result = CalculateFormationCircle(parameters as CircleFormationParameters);
break;
case Formation.Triangle:
//Same for all formations
break;
default:
break;
}
return result;
}
private static List<PositionInformation> CalculateFormationSquare(SquareFormationParameters parameters)
{
List<PositionInformation> result = null;
if (parameters != null)
{
result = new List<PositionInformation>();
//Calculate all squad members positions and rotations
}
return result;
}
private static List<PositionInformation> CalculateFormationCircle(CircleFormationParameters parameters)
{
List<PositionInformation> result = null;
if (parameters != null)
{
result = new List<PositionInformation>();
//Calculate all squad members positions and rotations
}
return result;
}
}
Hi,
Thank you for the ideas and suggestions. $$anonymous$$aybe you can show me or give some leading about how to separate the code to some classes or scripts ?
For example all the Formations code in once class and the main engine the manager script will handle each formation. Something like that. So it will be much easier to access each formation.
Since answering here has character number limitations, i answered in my main answer.