I'm writing a system that instantiates clouds (prefabs that I created) in random locations. When they are spawned, they move to a spot via Vector2.MoveTowards. What I am trying to do, is check whether they stopped. And if they stopped, to create new ones.
My system:
Script 1 (Procedural Clouds (I know they are not procedurally created, but I started off naming the script this because that is what I want to achieve in the long run) ):
Script 2 (MoveClouds):
This is where I start to struggle. I first tried to use .transform.position = Vector3.MoveTowards(). But my problem here was to check if the clouds had stopped. I tried hasChanged, but that did not work. So after a lot of Googling and trying to make it work, I decided to attach Rigidbody2D to the clouds, because I thought I could check if velocity.magnitude == 0, then they are stopped. I also applied both rb.velocity and rb.addforce, to that there is actual force to check if that force has stopped and a new one should be spawned. I do not want to use RB since they are not going to be related to any physics, they are simply just going to move from one random point to another specific point, then evaporate and when they are evaporated, then a new one spawns.
I know it's a long post, but I appreciate any help! I have been working in Unity for a little over 3 months, so I'm still pretty new to this.
My code: Script 1
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ProceduralClouds : MonoBehaviour
{
public GameObject[] clouds;
Vector3 startPos1, startPos2, startPos3, startPos4, startPos5, startPos6;
[HideInInspector] public GameObject cloud1, cloud2, cloud3, cloud4, cloud5, cloud6;
public bool cloud1Spawned, cloud2Spawned, cloud3Spawned, cloud4Spawned, cloud5Spawned, cloud6Spawned;
private void Start()
{
/*InvokeRepeating(nameof(SpawnCloud1), 0, 20);
InvokeRepeating(nameof(SpawnCloud2), 1, 22);
InvokeRepeating(nameof(SpawnCloud3), 3, 25);
InvokeRepeating(nameof(SpawnCloud4), 0, 18);
InvokeRepeating(nameof(SpawnCloud5), 2, 20);
InvokeRepeating(nameof(SpawnCloud6), 5, 21); */
}
private void Update()
{
startPos1 = new Vector3(Random.Range(15, 25), Random.Range(0, 10), 0);
startPos2 = new Vector3(Random.Range(15, 25), Random.Range(0, 10), 0);
startPos3 = new Vector3(Random.Range(15, 25), Random.Range(0, 10), 0);
startPos4 = new Vector3(Random.Range(15, 25), Random.Range(0, 10), 0);
startPos5 = new Vector3(Random.Range(15, 25), Random.Range(0, 10), 0);
startPos6 = new Vector3(Random.Range(15, 25), Random.Range(0, 10), 0);
// test prints:
print("Cloud 1 spawned: " + cloud1Spawned);
SpawnCloud1();
SpawnCloud2();
SpawnCloud3();
SpawnCloud4();
SpawnCloud5();
SpawnCloud6();
}
private void SpawnCloud1()
{
if (!cloud1Spawned)
{
cloud1 = Instantiate(clouds[Random.Range(0, clouds.Length)], startPos1, Quaternion.identity);
cloud1Spawned = true;
}
}
private void SpawnCloud2()
{
if (!cloud2Spawned)
{
cloud2Spawned = true;
cloud2 = Instantiate(clouds[Random.Range(0, clouds.Length)], startPos2, Quaternion.identity);
}
}
private void SpawnCloud3()
{
if (!cloud3Spawned)
{
cloud3 = Instantiate(clouds[Random.Range(0, clouds.Length)], startPos3, Quaternion.identity);
cloud3Spawned = true;
}
}
private void SpawnCloud4()
{
if (!cloud4Spawned)
{
cloud4 = Instantiate(clouds[Random.Range(0, clouds.Length)], startPos4, Quaternion.identity);
cloud4Spawned = true;
}
}
private void SpawnCloud5()
{
if (!cloud5Spawned)
{
cloud5 = Instantiate(clouds[Random.Range(0, clouds.Length)], startPos5, Quaternion.identity);
cloud5Spawned = true;
}
}
private void SpawnCloud6()
{
if (!cloud6Spawned)
{
cloud6 = Instantiate(clouds[Random.Range(0, clouds.Length)], startPos6, Quaternion.identity);
cloud6Spawned = true;
}
}
}
Script 2:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class MoveClouds : MonoBehaviour
{
public float step;
private ProceduralClouds proCloudsScript;
private GameObject cloud1, cloud2, cloud3, cloud4, cloud5, cloud6;
private Vector3 endPosBottom, endPosMiddle, endPosTop;
private void Start()
{
proCloudsScript = GameObject.Find("ScriptHandler").GetComponent<ProceduralClouds>();
endPosBottom = new Vector3(-26, 8, 0);
endPosMiddle = new Vector3(-26, 10, 0);
endPosTop = new Vector3(-26, 12, 0);
}
private void Update()
{
cloud1 = proCloudsScript.cloud1;
cloud2 = proCloudsScript.cloud2;
cloud3 = proCloudsScript.cloud3;
cloud4 = proCloudsScript.cloud4;
cloud5 = proCloudsScript.cloud5;
cloud6 = proCloudsScript.cloud6;
float step = this.step * Time.deltaTime;
/* cloud1.transform.position = Vector3.MoveTowards(cloud1.transform.position, endPosBottom, step);
cloud2.transform.position = Vector3.MoveTowards(cloud2.transform.position, endPosTop, step);
cloud3.transform.position = Vector3.MoveTowards(cloud3.transform.position, endPosMiddle, step);
cloud4.transform.position = Vector3.MoveTowards(cloud4.transform.position, endPosTop, step);
cloud5.transform.position = Vector3.MoveTowards(cloud5.transform.position, endPosBottom, step);
cloud6.transform.position = Vector3.MoveTowards(cloud6.transform.position, endPosMiddle, step);*/
cloud1.GetComponent<Rigidbody2D>().velocity = Vector2.MoveTowards(cloud1.transform.position,
endPosBottom, step);
cloud2.GetComponent<Rigidbody2D>().velocity = Vector2.MoveTowards(cloud2.transform.position,
endPosMiddle, step);
cloud3.GetComponent<Rigidbody2D>().velocity = Vector2.MoveTowards(cloud3.transform.position,
endPosTop, step);
cloud4.GetComponent<Rigidbody2D>().velocity = Vector2.MoveTowards(cloud4.transform.position,
endPosTop, step);
cloud5.GetComponent<Rigidbody2D>().velocity = Vector2.MoveTowards(cloud5.transform.position,
endPosBottom, step);
cloud6.GetComponent<Rigidbody2D>().velocity = Vector2.MoveTowards(cloud6.transform.position,
endPosMiddle, step);
if (cloud1.GetComponent<Rigidbody2D>().velocity.magnitude == 0 )
{
print("Cloud 1 is still");
proCloudsScript.cloud1Spawned = false;
print(cloud1.GetComponent<Rigidbody2D>().velocity.magnitude);
}
if (cloud2.GetComponent<Rigidbody2D>().velocity.magnitude == 0)
{
print("Cloud 2 is still");
proCloudsScript.cloud2Spawned = false;
print(cloud2.GetComponent<Rigidbody2D>().velocity.magnitude);
}
if (cloud3.GetComponent<Rigidbody2D>().velocity.magnitude == 0)
{
print("Cloud 3 is still");
proCloudsScript.cloud3Spawned = false;
print(cloud3.GetComponent<Rigidbody2D>().velocity.magnitude);
}
if (cloud4.GetComponent<Rigidbody2D>().velocity.magnitude == 0)
{
print("Cloud 4 is still");
proCloudsScript.cloud4Spawned = false;
print(cloud4.GetComponent<Rigidbody2D>().velocity.magnitude);
}
if (cloud5.GetComponent<Rigidbody2D>().velocity.magnitude == 0)
{
print("Cloud 5 is still");
proCloudsScript.cloud5Spawned = false;
print(cloud5.GetComponent<Rigidbody2D>().velocity.magnitude);
}
if (cloud6.GetComponent<Rigidbody2D>().velocity.magnitude == 0)
{
print("Cloud 6 is still");
proCloudsScript.cloud6Spawned = false;
print(cloud6.GetComponent<Rigidbody2D>().velocity.magnitude);
}
}
}
I see a lot of small issues in your code!
I would start with the usage of Vector3
.. if you actually want Vector2
then rather use that ;)
Implementing the same functionality over and over again just to have different numbers is really not good! Try to merger all this together into a single method. This is a) better for maintenance and b) for scalability!
You are passing a position generated by using
Vector2.MoveTowards(cloud1.transform.position, endPosBottom, step);
to a velocity -> makes no sense.
even though it was improved a lot in newer versions, using GetComponent
over ad over again multiple times and every frame is extremely inefficient.
generating new random positions every frame and then throw them away most of the times since you don't spawn a cloud if it still moves is again very inefficient.
So I would do it like this
// Responsible for creating and initializing cloud instances
public class ProceduralClouds : MonoBehaviour
{
// As before here assign your prefabs
// CloudMove is a dedicated script that will go onto your cloud prefabs
// See next code snippet
public CloudMove[] clouds;
// How many clouds should be spawned?
public int amountOfClouds = 6;
// Configure your possible target position in the Inspector
public Vector2[] targetPositions = { new Vector3(-26, 8, 0), new Vector3(-26, 10, 0), new Vector3(-26, 12, 0) };
// This will keep track which target position chose next
// (could make it random as well)
private int currentTargetIndex;
private void Start()
{
// Spawn the first "wave" of clouds
for(var i = 0; i < amountOfClouds; i++)
{
SpawnCloud();
}
}
private void SpawnCloud()
{
// pick a random start position
var startPos = new Vector2(Random.Range(15, 25), Random.Range(0, 10));
// get the next target position
var targetPos = targetPositions [currentTargetIndex];
// Increase the index, wrap around if reaching end of array
currentTargetIndex = (currentTargetIndex + 1) % targetPositions.Length;
// instantiate the cloud
var cloud = Instantiate(clouds[Random.Range(0, clouds.Length)], startPos, Quaternion.identity);
// assign its target position
// Could also pick a random speed here
cloud.targetPosition = targetPos;
// Assign a callback to an event we will implement
// This is way better than poll check stuff in Update
cloud.OnReachedTarget += SpawnCloud;
}
}
And then another script that goes directly on your cloud prefabs:
// Responsible for moving one cloud instance
public class CloudMove : MonoBehaviour
{
// Basically equals your step
// But this way the clouds could even have individual speed values
public float speed = 1;
// Will be assigned by the spawner script
public Vector2 targetPosition;
// Whoever wants can be listening here
public event Action OnReachedTarget;
private void Update ()
{
// Move towards the target position with given speed
transform.position = Vector2.MoveTowards(transform.position, targetPosition, speed * Time.deltaTime);
// Either use this check with a precision of 0.00001
if((Vector2)transform.position == targetPosition)
// Or alternatively if you need a more exact precision
//if(Mathf.Approximately(Vector2.Distance(transform.position, targetPosition), 0))
{
// Invoke the event so whoever is listening is informed
OnReachedTarget?.Invoke();
// You might be interested in destroying this cloud once it reached its target
//Destroy(gameObject):
}
}
}