I’m currently hung up and hoping someone out there can help me.
I am trying to instantiate a new game object in a position relative to the previous game object’s position each time one is removed from the game. That is, I want to establish a cycle in which whenever a game object is destroyed, a new one instantiates in a position relative to the one before it.
In practice, I want to have a set of five arches. Then, each time the player passes under an arch, it is destroyed and a new arch instantiates at the end of the line.
The actual game object destruction is handled in another script and works fine. It’s just getting new clones to instantiate in a row that I can’t figure out.
I’m sure that this is probably simpler than I think, but I’m still learning and cannot seem to find a solution.
Any help is appreciated! Thanks!
public class SpawnManager : MonoBehaviour
{
public float spawnRange;
public int archCount;
public int archesToSpawn = 1;
public GameObject archPrefab;
private Transform archPos;
private Transform spawner;
void Start()
{
archPos = archPrefab.GetComponent<Transform>();
spawner = GetComponent<Transform>();
}
void Update()
{
archCount = GameObject.FindGameObjectsWithTag("Archway").Length;
if(archCount <= 4)
{
SpawnArchway(archesToSpawn);
}
}
void SpawnArchway(int archesToSpawn)
{
for (int i = 0; i < archesToSpawn; i++)
{
Instantiate(archPrefab, GenerateSpawnPosition(), Quaternion.Euler(GenerateSpawnRotation()));
}
}
private Vector3 GenerateSpawnPosition()
{
float xPos = Random.Range(archPos.position.x - spawnRange, archPos.position.x + spawnRange);
float zPosOffset = archPos.position.z + 5.0f;
return new Vector3(xPos, 0, zPosOffset);
}
private Vector3 GenerateSpawnRotation()
{
float yRot = Random.Range(-45, 225);
return new Vector3(0, yRot, 0);
}
}
After a couple more hours of fiddling and thinking, I finally got some of this to work and cleaned up my code. Turns out a lot of it was superfluous.
public class SpawnManager : MonoBehaviour
{
public float spawnRange;
public int archCount;
public int archesToSpawn = 1;
public GameObject archPrefab;
public Vector3 spawnPos;
void Start()
{
}
void Update()
{
archCount = GameObject.FindGameObjectsWithTag("Archway").Length;
if(archCount <= 4)
{
SpawnArchway(archesToSpawn);
}
}
void SpawnArchway(int archesToSpawn)
{
for (int i = 0; i < archesToSpawn; i++)
{
Instantiate(archPrefab, GenerateSpawnPosition(), Quaternion.Euler(GenerateSpawnRotation()));
}
}
private Vector3 GenerateSpawnPosition()
{
spawnPos = new Vector3(spawnPos.x, spawnPos.y, spawnPos.z + 10.0f);
return new Vector3(spawnPos.x, spawnPos.y, spawnPos.z);
}
private Vector3 GenerateSpawnRotation()
{
float yRot = Random.Range(-60, 60);
return new Vector3(0, yRot, 0);
}
}
The arches now instantiate a distance apart from each other. Unfortunately, as yet I can only get them to spawn in a straight line. That is, they’re spawning along world space z axis instead of relative to the previous game object (arch).
If anyone reads this, I would still appreciate help with that, too!
For the spawn position, you’re using spawnPos, is that set to be along the Z axis? You’d want it to be relative to the last-instantiated arch. I’m not sure how the rest of your objects and scripts work together, so can’t say for sure the best way to do it.
I would have an overall manager of all the arches and store them in an array. Then when the player goes under one, it tells the manager “I’m getting destroyed”, as well as calls the spawn function (see below). Or the manager hears that it’s getting destroyed and calls the function.
You’re checking the arch count every frame in Update(), and using GameObject.Find. This is not only expensive, it (nearly always?) indicates you should be doing it another way. The only time the number of arches drops below 5 is when one gets destroyed, so that’s what should initiate the spawning. So, a script on the arch itself should call the spawn method in this one just before Destroy(), or as mentioned above, have a manager script do it. Hope that helps.
I see what you mean about the stuff in Update() and I can easily move that to another, less expensive location. That’s a great idea and I will take your advice on that. I think moving it into the PlayerController script under OnTriggerEnter() is the simplest idea, that’s already where the arches are being destroyed from.
As for the position, I do indeed want them to be relative to the z axis of the last-instantiated arch. That’s what I am struggling with at the moment: How do I reference that last-instantiated arch?
Is there no way to capture the transform of the last-instantiated arch? How would an array resolve this?
SpawnManager now looks like this:
public class SpawnManager : MonoBehaviour
{
public float spawnRange;
public int archCount;
public int archesToSpawn = 1;
public GameObject archPrefab;
public Vector3 spawnPos;
void Start()
{
SpawnArchway(5);
}
void Update()
{
}
public void SpawnArchway(int archesToSpawn)
{
for (int i = 0; i < archesToSpawn; i++)
{
Instantiate(archPrefab, GenerateSpawnPosition(), Quaternion.Euler(GenerateSpawnRotation()));
}
}
private Vector3 GenerateSpawnPosition()
{
spawnPos = new Vector3(spawnPos.x, spawnPos.y, spawnPos.z + 10.0f);
return new Vector3(spawnPos.x, spawnPos.y, spawnPos.z);
}
private Vector3 GenerateSpawnRotation()
{
float yRot = Random.Range(-60, 60);
return new Vector3(0, yRot, 0);
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class testArches2 : MonoBehaviour
{
// Distances between spawned objects
// For 10 units in Z, use 0, 0, 10
// Range could go negative, will go in opposite axis direction
[Range(0, 20)]
public float spawnRangeX = 5.0f; // X axis
[Range(0, 20)]
public float spawnRangeY = 5.0f; // Y axis
[Range(0, 20)]
public float spawnRangeZ = 5.0f; // Z axis
[Range(1, 50)]
public int archCount = 5;
public GameObject archPrefab;
public Vector3 spawnPos; // Set in inspector for the location of the first arch
private GameObject[] archesArray; // store generated arches so you can do something with them after spawning (if needed)
// Examples below, with key presses
void Start()
{
archesArray = new GameObject[archCount];
SpawnArchway();
}
public void SpawnArchway()
{
for (int i = 0; i < archCount; i++) // Instantiate can return the spawned object, store it in the array
{
// You may not want to change spawnPos directly, just depends on how you want to implement things
Vector3 tempSpawnPos = new Vector3(spawnPos.x + (spawnRangeX * i), spawnPos.y + (spawnRangeY * i), spawnPos.z + (spawnRangeZ * i));
archesArray[i] = Instantiate(archPrefab, tempSpawnPos, Quaternion.Euler(new Vector3(0, Random.Range(-60, 60), 0)));
}
}
void Update()
{
if (Input.GetKeyDown(KeyCode.C)) // set all arches to a random color (if the shader you're using supports it)
{
for (int i = 0; i < archCount; i++)
{
archesArray[i].GetComponent<Renderer>().material.color = new Color(Random.Range(0.0f, 1.0f), Random.Range(0.0f, 1.0f), Random.Range(0.0f, 1.0f), 1);
}
}
if (Input.GetKeyDown(KeyCode.S)) // adds or subtracts some amount of scaling to all 3 axes equally
{
for (int i = 0; i < archCount; i++)
{
float scaling = Random.Range(-1.0f, 1.0f);
archesArray[i].transform.localScale += new Vector3(scaling, scaling, scaling);
}
}
}
}
You’re very welcome! Always fun to tinker with these things. There are some goodies in there like the Ranges for the Inspector, the color changing, etc. The Array usage is probably the most important concept to look into though.
So I’ve been playing around with the code you suggested, trying to pin down the behavior I want, but I’m still running into problems.
Best I can make it do with this setup is instantiate a set number of arches each time the number of arches dips below a threshold. But then, when that number dips below the threshold, a new set of arches is instantiated; whereas I only want an additional arch to be instantiated. Furthermore, each set of arches that instantiates begins at the initial spawnPos transform.
Not sure what I’m doing wrong.
My two interacting scripts below:
{
public float spawnRangeX = 5.0f;
public float spawnRangeZ = 10.0f;
public int archCount;
public GameObject archPrefab;
public Vector3 spawnPos;
[SerializeField] private GameObject[] archesArray; //Just so I can see those objects
void Start()
{
archesArray = new GameObject[archCount];
SpawnArchway();
}
void Update()
{
}
public void SpawnArchway()
{
for (int i = 0; i < archCount; i++)
{
Vector3 newSpawnPos = new Vector3(Random.Range(spawnPos.x + (spawnRangeX * i), spawnPos.x - (spawnRangeX * i)),
spawnPos.y, spawnPos.z + (spawnRangeZ * i));
archesArray[i] = Instantiate(archPrefab, newSpawnPos, Quaternion.Euler(GenerateSpawnRotation()));
}
}
private Vector3 GenerateSpawnRotation()
{
float yRot = Random.Range(-60, 60);
return new Vector3(0, yRot, 0);
}
}
public class PlayerController : MonoBehaviour
{
public int pointValue;
[SerializeField] float speed;
[SerializeField] float rotSpeed;
private float horizontalInput;
private float verticalInput;
private SpawnManager spawnManager;
private Rigidbody playerRb;
// Start is called before the first frame update
void Start()
{
playerRb = GetComponent<Rigidbody>();
spawnManager = GameObject.Find("Spawner").GetComponent<SpawnManager>();
}
// Update is called once per frame
void Update()
{
horizontalInput = Input.GetAxis("Horizontal");
verticalInput = Input.GetAxis("Vertical");
float mouseRotate = Input.GetAxis("Mouse X");
playerRb.AddRelativeForce (Vector3.forward * verticalInput * speed);
playerRb.AddRelativeForce (Vector3.right * horizontalInput * speed);
if (mouseRotate >= 0)
{
playerRb.transform.Rotate(Vector3.up * Time.deltaTime * rotSpeed);
}
if (mouseRotate <= 0)
{
playerRb.transform.Rotate(Vector3.up * Time.deltaTime * rotSpeed * -1);
}
}
void OnTriggerEnter(Collider other)
{
Destroy(other.gameObject, 0.5f);
spawnManager.archCount = GameObject.FindGameObjectsWithTag("Archway").Length;
if (spawnManager.archCount <= 5)
{
spawnManager.SpawnArchway();
}
}
}
Ah, I get it. I only did the 5-arch instantiate to go along with your code.
Haven’t tested this, but should be pretty close at least…
You can init archesArray and other arrays with a number larger than you’ll need, just to be safe. You can also use a List, because you can add/remove elements (and is probably better-suited for this if you don’t know how many you’ll eventually have). But this uses arrays for now.
Use a variable to keep track of how many have been spawned:
int numSpawned = 0;
For the spawn position, have another array of Vector3 values:
Vector3[ ] spawnPositions;
spawnPositions[0] = spawnPos; // init first index with first position Vector3, will be overwritten
archesArray[0] = null; // to make it match up with the spawnPositions, as these read from the previous index
Change the method to this, so it only spawns one at a time (there’s no for loop or “i” variable anymore). To make 5, just call it 5 times.
First, I want to thank you for taking the time to think about this and get back to me. I really appreciate it!
I did indeed play with this some more and try to work it out; unfortunately I had no luck. I’m still learning this coding stuff and, while I’m sure it is not a very complex problem, it feels complex to me!
Could you be a little more specific? With all the changes I’ve become unclear as to what is what…
Try just getting single arches to spawn (no more for loop), then see about storing the arches and positions, which is a bit trickier with the arrays. Sorry but I can’t take a closer look atm. Good luck!