In the manager:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class FormationsManager : MonoBehaviour
{
public Transform squadMemeberPrefab;
public int numberOfSquadMembers = 20;
public int columns = 4;
public int gaps = 10;
public Formations formations;
private int numofmembers;
// Use this for initialization
void Start()
{
numofmembers = numberOfSquadMembers;
formations.Init(numberOfSquadMembers, columns, gaps);
GenerateSquad();
}
// Update is called once per frame
void Update()
{
if (numofmembers != numberOfSquadMembers)
{
GenerateSquad();
}
}
private void GenerateSquad()
{
Transform go = squadMemeberPrefab;
for (int i = 0; i < formations.newpositions.Count; i++)
{
go = Instantiate(squadMemeberPrefab);
go.position = formations.newpositions[i];
go.tag = "Squad Member";
go.transform.parent = gameObject.transform;
}
}
}
And the Formations script:
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Formations : MonoBehaviour
{
public List<Vector3> newpositions;
private int numberOfSquadMembers;
private int columns;
private int gaps;
private List<Quaternion> quaternions;
private Vector3 FormationSquarePositionCalculation(int index)
{
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>();
for (int i = 0; i < numberOfSquadMembers; i++)
{
Vector3 pos = FormationSquarePositionCalculation(i);
Vector3 position = new Vector3(transform.position.x + pos.x, 0, transform.position.y + pos.y);
newpositions.Add(position);
}
}
public void Init(int numberOfSquadMembers, int columns, int gaps)
{
this.numberOfSquadMembers = numberOfSquadMembers;
this.columns = columns;
this.gaps = gaps;
FormationSquare();
}
}
What I want to do is in the FormationsManager in the Update not only just calling GenerateSquad but to add the new once to the last/next position of the existing already formation.
void Update()
{
if (numofmembers != numberOfSquadMembers)
{
GenerateSquad();
}
}
If the value of numberOfSquadMembers is 20 first time and then I changed it to 21 add new object to the end of the formation and same if I change the value of numberOfSquadMembers for example from 20 to 19 or from 21 to 5 destroy the amount of objects from the end and keep the formation shape.
The soldiers the last line is on the right side.
So if I change the value to add more then add it to the right and if I change to less destroy from the right side. The most left line of soldiers is the first.
It is possible if you keep GameObject instances inside FormationsManager class, and then reuse them in GenerateSquad method.
In FormationsManager class, add and modify code as follows.
public GameObject squadMemeberPrefab;
List<GameObject> SquadMembers = new List<GameObject>();
void Update()
{
if (numofmembers != numberOfSquadMembers)
{
numofmembers = numberOfSquadMembers;
formations.Init(numberOfSquadMembers, columns, gaps);
GenerateSquad();
}
}
private void GenerateSquad()
{
Transform go = squadMemeberPrefab;
List<GameObject> newSquadMembers = new List<GameObject>();
int i = 0;
for (; i < formations.newpositions.Count; i++)
{
if (i < SquadMembers.Count)
go = SquadMembers[i];
else
{
go = Instantiate(squadMemeberPrefab);
newSquadMembers.Add(go);
}
go.position = formations.newpositions[i];
go.tag = "Squad Member";
go.transform.parent = gameObject.transform;
}
for (; i < SquadMembers.Count; i++)
Destroy(SquadMembers[i]);
SquadMembers = newSquadMembers;
}
However, I recommend you to consider GameObject Pool (Object Pool), which can thoroughly resolve such object recycle problem. For this purpose, you can use ClientScene.RegisterSpawnHandler. Go to this Unity Documentation page and search text "GameObject pool". You can see an example code there.
Related
I have a List<GameObject> and I need to remove doubles that have the same position.y and position.x (position.z is irrelevant).
I'm thinking that I need to compare all elements in a list and go through the previous elements of the same list and compare if they have the same positions and then, if not, populate the new List<GameObject>().
I don't really know how to do this as I'm not an experienced programmer.
If you wish to just remove the duplicates from an existing list, you don't necessarily have to create a new one. You can iterate over the list and use the RemoveAt method to get rid of the undesired elements.
Example
using System.Collections.Generic;
using UnityEngine;
public class TestScript : MonoBehaviour
{
public List<GameObject> collection = new List<GameObject>();
private void Start()
{
RemoveDuplicatesFrom(collection);
}
private void RemoveDuplicatesFrom(List<GameObject> collection)
{
for (int i = 0; i < collection.Count; i++)
{
for (int j = i + 1; j < collection.Count; j++)
{
if (!Match(collection[i], collection[j]))
continue;
collection.RemoveAt(j);
j--;
}
}
}
private bool Match(GameObject object1, GameObject object2)
{
return
object1.transform.position.x == object2.transform.position.x &&
object1.transform.position.y == object2.transform.position.y;
}
}
The function below should work just fine
public static List<GameObject> Delete_Doubles(List<GameObject> gameobjects){
List<Vector2> occupied_coord = new List<Vector2>();
List<GameObject> gameobjects_after = new List<GameObjects>();
Vector2 t_vector = new Vector2();
Vector3 pos = new Vector3();
pos = gameobjects[0].Transform.position;
t_vector.x = pos.x;
t_vector.y = pos.y;
gameobjects_after.Add(gameobjects[0]);
occupied_coord.Add(t_vector);
for(int i = 1; i < gameobjects.Count; i++)
{
pos = gameobjects[i].Transform.position;
t_vector.x = pos.x;
t_vector.y = pos.y;
if (!occupied_coord.Contains(t_vector))
{
gameobjects_after.Add(gameobjects[i]);
occupied_coord.Add(t_vector);
}
}
return gameobjects_after;
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Spin : MonoBehaviour
{
public GameObject prefabToRotate;
[Range(1, 100)]
public int numberOfObjects = 5;
[Range(1, 500)]
public float[] speeds;
public bool randomNumbersOfObjects = false;
public bool randomSpeed = false;
private List<GameObject> instantiatedObjects = new List<GameObject>();
// Start is called before the first frame update
void Start()
{
speeds = new float[numberOfObjects];
if(randomNumbersOfObjects == true)
{
numberOfObjects = Random.Range(1, 100);
}
if(randomSpeed == true)
{
for(int i = 0; i < speeds.Length; i++)
{
speeds[i] = Random.Range(1, 500);
}
}
for(int i = 0; i < numberOfObjects; i++)
{
GameObject go = Instantiate(prefabToRotate);
instantiatedObjects.Add(go);
}
}
// Update is called once per frame
void Update()
{
for (int i = 0; i < numberOfObjects; i++)
{
instantiatedObjects[i].transform.Rotate(Vector3.down, speeds[i] * Time.deltaTime);
}
}
}
And how can I get random numbers and random speeds from the Range sliders ? 1, 100 and 1, 500 ? I want also to be able to change this values of the sliders in the Update and it will update in real time while running the game the number of objects and the random speeds.
You set the length of your 'speeds' array to 'numberOfObjects', then you change the value of 'numberOfObjects', but your 'speeds' array still equals the old value of 'numberOfObjects'. Try setting the length of 'speeds' after you assign a random value to 'numberOfObjects', like so
if (randomNumbersOfObjects == true)
{
numberOfObjects = Random.Range(1, 100);
}
speeds = new float[numberOfObjects];
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class GenerateStairs : MonoBehaviour
{
public GameObject stairsPrefab;
public int delay = 3;
public int stairsNumber = 5;
public int stairsHeight = 0;
public Vector3 stairsPosition;
public Vector2 stairsSize;
// Use this for initialization
void Start ()
{
StartCoroutine(BuildStairs());
}
// Update is called once per frame
void Update ()
{
}
private IEnumerator BuildStairs()
{
for (float i = 0; i <= stairsSize.x; i++)
{
for (float k = 0; k <= stairsSize.y; k++)
{
stairsPosition = new Vector3(i, stairsHeight, k);
GameObject stairs = Instantiate(stairsPrefab, stairsPosition, Quaternion.identity);
stairs.transform.localScale = new Vector3(stairsSize.x, 1 , stairsSize.y);
stairsHeight += 1;
yield return new WaitForSeconds(delay);
}
}
}
private void CalculateNextStair()
{
}
}
I messed it up. For example I want to build 5 stairs but the loops are over the stairs size and not number of stairs.
Second it's creating 10 sets of stairs not 5 stairs:
Another problem is how can I make that each stair will be build slowly ? Now it's just Instantiate slowly with delay but how can I generate each stair with delay?
Screenshot of the script inspector:
My current code:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class GenerateStairs : MonoBehaviour
{
public GameObject stairsPrefab;
public float delay = 0.3f;
public int stairsNumber = 5;
public int stairsPositions = 0;
public int stairsStartPositionHeight = 0;
public float stairsScalingHaight = 1;
public Vector2 stairsPosition;
public Vector2 stairsSize;
// Use this for initialization
void Start()
{
StartCoroutine(BuildStairs());
}
// Update is called once per frame
void Update()
{
}
private IEnumerator BuildStairs()
{
for (float i = 0; i <= stairsNumber; i++)
{
// x=0f, y=z=stairsHeight
stairsPosition = new Vector3(0f, stairsPositions, stairsPositions);
GameObject stairs = Instantiate(
stairsPrefab,
stairsPosition,
Quaternion.identity);
stairs.transform.localScale = new Vector3(
stairsSize.x,
stairsScalingHaight,
stairsSize.y);
stairsStartPositionHeight += 1;
yield return new WaitForSeconds(delay);
}
}
private void CalculateNextStair()
{
}
}
There's no reason to loop over the size of the stairs at all; you want to loop over stairsNumber, which is yet unused in your code.
Also, you don't need to change the x component of your stairs' positions. Keep it at 0f (or whatever you need).
The y and z components of your stairs positions (relative to the starting point) should both be factors of stairHeight. In this particular case, you want them to be equal to stairHeight, so that you get "square" step shapes.
private IEnumerator BuildStairs()
{
for (int i = 0; i <= stairsNumber ; i++) {
// x=0f, y=z=stairsHeight
stairsPosition = new Vector3(0f, stairsHeight, stairsHeight);
GameObject stairs = Instantiate(
stairsPrefab,
stairsPosition,
Quaternion.identity);
stairs.transform.localScale = new Vector3(
stairsSize.x,
1f ,
stairsSize.y);
stairsHeight += 1f;
yield return new WaitForSeconds(delay);
}
}
If you change stairSize to be a Vector3, then you can just use stairSize directly as the localScale, and increment stairsHeight by stairsSize.y instead of just 1f.
If you want to offset the starting position of your stairs, you need to include an offset. I recommend keeping it separate from the height counter until you need to add them.
Also, if you want to have rectangular sized steps, keep a widthFactor to multiply by the height to find how far each step moves horizontally.
Combining these changes might look like this:
Vector3 stairSize;
float stepWidthFactor=1f;
Vector3 stairsStartPosition;
private IEnumerator BuildStairs() {
for (int i = 0; i <= stairsNumber ; i++) {
stairsPosition = new Vector3(
stairsStartPosition.x,
stairsStartPosition.y + stairsHeight,
stairsStartPosition.z + stairsHeight*stepWidthFactor);
GameObject stairs = Instantiate(
stairsPrefab,
stairsPosition,
Quaternion.identity);
stairsHeight += stairsSize.y;
stairs.transform.localScale = stairSize;
yield return new WaitForSeconds(delay);
}
}
I have this pooling script:
using UnityEngine;
using System.Collections.Generic;
public class ObjectPool
{
private GameObject prefab;
private List<GameObject> pool;
public ObjectPool(GameObject prefab, int initialSize)
{
this.prefab = prefab;
this.pool = new List<GameObject>();
for (int i = 0; i < initialSize; i++)
{
AllocateInstance();
}
}
public GameObject GetInstance()
{
if (pool.Count == 0)
{
AllocateInstance();
}
int lastIndex = pool.Count - 1;
GameObject instance = pool[lastIndex];
pool.RemoveAt(lastIndex);
instance.SetActive(true);
return instance;
}
public void ReturnInstance(GameObject instance)
{
instance.SetActive(false);
pool.Add(instance);
}
protected virtual GameObject AllocateInstance()
{
GameObject instance = (GameObject)GameObject.Instantiate(prefab);
instance.SetActive(false);
pool.Add(instance);
return instance;
}
}
And i'm using it with this script to instantiate objects.
It should put the objects in random positions around the terrain area.
But instead all the objects are in the same position at x = 0 , y = 20 , z = 0
Not random at all.
using System;
using UnityEngine;
using Random = UnityEngine.Random;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
public class InstantiateObjects : MonoBehaviour
{
public GameObject Spaceship;
public int spaceshipsStartingHeight = 20;
[HideInInspector]
public GameObject[] spaceships;
// for tracking properties change
private Vector3 _extents;
private int _spaceshipCount;
private float _spaceshipSize;
private List<int> randomNumbers = new List<int>();
private ObjectPool bulletPool;
/// <summary>
/// How far to place spheres randomly.
/// </summary>
public Vector3 Extents;
/// <summary>
/// How many spheres wanted.
/// </summary>
public int SpaceShipCount;
public float SpaceShipSize;
// Use this for initialization
void Start()
{
rndNumbers();
Clone();
spaceships = GameObject.FindGameObjectsWithTag("SpaceShip");
}
private void OnValidate()
{
// prevent wrong values to be entered
Extents = new Vector3(Mathf.Max(0.0f, Extents.x), Mathf.Max(0.0f, Extents.y), Mathf.Max(0.0f, Extents.z));
SpaceShipCount = Mathf.Max(0, SpaceShipCount);
SpaceShipSize = Mathf.Max(0.0f, SpaceShipSize);
}
private void Reset()
{
Extents = new Vector3(250.0f, 20.0f, 250.0f);
SpaceShipCount = 100;
SpaceShipSize = 20.0f;
}
// Update is called once per frame
void Update()
{
}
private void Clone()
{
if (Extents == _extents && SpaceShipCount == _spaceshipCount && Mathf.Approximately(SpaceShipSize, _spaceshipSize))
return;
// cleanup
var ShipsToDestroy = GameObject.FindGameObjectsWithTag("SpaceShip");
foreach (var t in ShipsToDestroy)
{
if (Application.isEditor)
{
DestroyImmediate(t);
}
else
{
Destroy(t);
}
}
var withTag = GameObject.FindWithTag("Terrain");
if (withTag == null)
throw new InvalidOperationException("Terrain not found");
bulletPool = new ObjectPool(Spaceship, SpaceShipCount);
for (var i = 0; i < SpaceShipCount; i++)
{
GameObject o = bulletPool.GetInstance();
o.tag = "SpaceShip";
o.transform.SetParent(base.gameObject.transform);
o.transform.localScale = new Vector3(SpaceShipSize, SpaceShipSize, SpaceShipSize);
// get random position
var x = Random.Range(-Extents.x, Extents.x);
var y = Extents.y; // sphere altitude relative to terrain below
var z = Random.Range(-Extents.z, Extents.z);
// now send a ray down terrain to adjust Y according terrain below
var height = 10000.0f; // should be higher than highest terrain altitude
var origin = new Vector3(x, height, z);
var ray = new Ray(origin, Vector3.down);
RaycastHit hit;
var maxDistance = 20000.0f;
var nameToLayer = LayerMask.NameToLayer("Terrain");
var layerMask = 1 << nameToLayer;
if (Physics.Raycast(ray, out hit, maxDistance, layerMask))
{
var distance = hit.distance;
y = height - distance + y; // adjust
}
else
{
Debug.LogWarning("Terrain not hit, using default height !");
}
//o.transform.Rotate(0.0f,randomNumbers[i],0.0f);
// place !
o.transform.position = new Vector3(x, y + spaceshipsStartingHeight, z);
}
_extents = Extents;
_spaceshipCount = SpaceShipCount;
_spaceshipSize = SpaceShipSize;
}
public void rndNumbers()
{
}
}
The answer is to remove the whole // cleanUp part inside the Clone(). Now it's creating random objects using the pooling.
The problem is in this script:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[System.Serializable]
public class PatrolData
{
public Transform target = null;
public float minDistance = 5f;
public float lingerDuration = 5f;
public float desiredHeight = 10f;
public float flightSmoothTime = 10f;
public float maxFlightspeed = 10f;
public float flightAcceleration = 1f;
public float levelingSmoothTime = 0.5f;
public float maxLevelingSpeed = 10000f;
public float levelingAcceleration = 2f;
}
public class PatrolOverTerrain : MonoBehaviour
{
public FlyToOverTerrain flyOverTerrain;
public LookAtCamera lookAtCamera;
public enum PatrolMode { Clamp, Wrap, PingPong };
public PatrolData[] patrolPoints;
public PatrolMode mode = PatrolMode.Wrap;
private int iterator = 0;
private int index = 0;
private float lingerDuration = 0f;
private int oldLength = 0;
public List<GameObject> TeleportationBooths = new List<GameObject>();
public Vector3 distanceFromTarget;
private void Start()
{
GameObject[] tempObj = GameObject.FindGameObjectsWithTag("Teleportation Booth");
for (int i = 0; i < tempObj.Length; i++)
{
//Add to list only if it does not exist
if (!TeleportationBooths.Contains(tempObj[i]))
{
TeleportationBooths.Add(tempObj[i]);
}
}
//Get the current Size
if (tempObj != null)
{
oldLength = tempObj.Length;
}
GeneratePatrolPoints();
}
private void OnEnable()
{
if (patrolPoints.Length > 0)
{
lingerDuration = patrolPoints[index].lingerDuration;
}
}
private void Update()
{
//Check if oldLength has changed
if (oldLength != TeleportationBooths.Count)
{
//Update oldLength
oldLength = TeleportationBooths.Count;
//Call your the function
GeneratePatrolPoints();
}
int length = patrolPoints.Length;
if (!flyOverTerrain) return;
if (patrolPoints.Length < 1) return;
if (index < 0) return;
// Getting exception out of index on line 89.
// Need to make a list also for the Cubes(buildings).
var patrol = patrolPoints[index];
if (lingerDuration <= 0)
{
iterator++;
switch (mode)
{
case PatrolMode.Clamp:
index = (iterator >= length) ? -1 : iterator;
break;
case PatrolMode.Wrap:
iterator = Modulus(iterator, length);
index = iterator;
break;
case PatrolMode.PingPong:
index = PingPong(iterator, length);
break;
}
if (index < 0) return;
patrol = patrolPoints[index];
flyOverTerrain.target = patrol.target;
flyOverTerrain.desiredHeight = patrol.desiredHeight;
flyOverTerrain.flightSmoothTime = patrol.flightSmoothTime;
flyOverTerrain.maxFlightspeed = patrol.maxFlightspeed;
flyOverTerrain.flightAcceleration = patrol.flightAcceleration;
flyOverTerrain.levelingSmoothTime = patrol.levelingSmoothTime;
flyOverTerrain.maxLevelingSpeed = patrol.maxLevelingSpeed;
flyOverTerrain.levelingAcceleration = patrol.levelingAcceleration;
lookAtCamera.target = patrol.target;
lookAtCamera.RotationSpeed = 3;
lingerDuration = patrolPoints[index].lingerDuration;
}
Vector3 targetOffset = Vector3.zero;
if ((bool)patrol.target)
{
targetOffset = transform.position - patrol.target.position;
}
float sqrDistance = patrol.minDistance * patrol.minDistance;
if (targetOffset.sqrMagnitude <= sqrDistance)
{
flyOverTerrain.target = null;
lookAtCamera.target = null;
lingerDuration -= Time.deltaTime;
}
else
{
flyOverTerrain.target = patrol.target;
lookAtCamera.target = patrol.target;
}
distanceFromTarget = transform.position - patrol.target.position;
}
private int PingPong(int baseNumber, int limit)
{
if (limit < 2) return 0;
return limit - Mathf.Abs(limit - Modulus(baseNumber, limit + (limit - 2)) - 1) - 1;
}
private int Modulus(int baseNumber, int modulus)
{
return (modulus == 0) ? baseNumber : baseNumber - modulus * (int)Mathf.Floor(baseNumber / (float)modulus);
}
public void GeneratePatrolPoints()
{
patrolPoints = new PatrolData[TeleportationBooths.Count];
for (int i = 0; i < patrolPoints.Length; i++)
{
patrolPoints[i] = new PatrolData();
patrolPoints[i].target = TeleportationBooths[i].transform;
patrolPoints[i].minDistance = 30f;
patrolPoints[i].lingerDuration = 3f;
patrolPoints[i].desiredHeight = 20f;
patrolPoints[i].flightSmoothTime = 10f;
patrolPoints[i].maxFlightspeed = 10f;
patrolPoints[i].flightAcceleration = 3f;
patrolPoints[i].levelingSmoothTime = 0.5f;
patrolPoints[i].maxLevelingSpeed = 10000f;
patrolPoints[i].levelingAcceleration = 2f;
}
}
}
In this part inside the Update i'm comparing the old length of a list with the current length:
//Check if oldLength has changed
if (oldLength != TeleportationBooths.Count)
{
//Update oldLength
oldLength = TeleportationBooths.Count;
//Call your the function
GeneratePatrolPoints();
}
So in case i create a new objects without destroying the old ones first it will be fine the length is not the same and it will call GeneratePatrolPoints() and will update the targets with the new targets just added:
patrolPoints[i].target = TeleportationBooths[i].transform;
The problem is when i check the ui toggle and first destroy the objects and then create them again the length is the same as before so it will not call GeneratePatrolPoints() and will not update the targets.
So i'm getting missing object exception.
I'm updating the list but i also need to update the targets again.
In this script i decide if the create new objects and to add them to the list so the length will not be the same and everything is right or to destroy first the current objects and then create new ones but then the length will be the same:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class GenerateObjectsButton : MonoBehaviour
{
[SerializeField]
private InstantiateObjects[] instantiateobjects;
private bool toggleOnOf;
public Toggle toggle;
private void Start()
{
toggle.onValueChanged.AddListener((value) =>
{
MyListener(value);
});
}
public void MyListener(bool value)
{
if (value)
{
//do the stuff when the toggle is on
toggleOnOf = true;
}
else
{
//do the stuff when the toggle is off
toggleOnOf = false;
}
}
public void OnButton()
{
for (int i = 0; i < instantiateobjects.Length; i++)
{
if (toggleOnOf == false)
{
instantiateobjects[i].generateObjectOnTerrain();
}
else
{
instantiateobjects[i].DestroyObjects();
instantiateobjects[i].generateObjectOnTerrain();
}
}
}
}
The problem is with the first script with comparing the length with the list count.
The solution is to add a new bool variable in the GenerateObjectsButton script:
public bool destroyed = false;
And set it to true after destroying and creating new objects again:
public void OnButton()
{
for (int i = 0; i < instantiateobjects.Length; i++)
{
if (toggleOnOf == false)
{
instantiateobjects[i].generateObjectOnTerrain();
}
else
{
instantiateobjects[i].DestroyObjects();
instantiateobjects[i].generateObjectOnTerrain();
destroyed = true;
}
}
}
Now destroyed is true.
Back to the PatrolOverTerrain script.
Now i'm not only checking if the length is not the same but also if destroyed is true:
//Check if oldLength has changed
if (oldLength != TeleportationBooths.Count)
{
//Update oldLength
oldLength = TeleportationBooths.Count;
//Call your the function
GeneratePatrolPoints();
}
GameObject go = GameObject.Find("Button");
var destroyed = go.GetComponent<GenerateObjectsButton>().destroyed;
if (destroyed)
{
GeneratePatrolPoints();
}
Now i know when i only created new objects and added them by comparing length or when first destroyed the objects and then created new so the length didn't change but the list it self did.
Anyway this is a working solution.