I have this code, to Instantiate a sprite
AppleSpawner.cs
public class appleSpawner : MonoBehaviour
{
private int isRunning = 1;
private readonly int[] positions = { -10, -5, 0, 5, 10 };
public int NumberOfSeconds;
System.Random rand = new System.Random();
private void Update()
{
if (isRunning == 1) StartCoroutine(Wait());
}
public IEnumerator Wait()
{
int randomX = rand.Next(5);
isRunning = 0;
yield return new WaitForSeconds(NumberOfSeconds);
Instantiate(this, new Vector3(randomX, 5, 0), transform.rotation);
}
}
Here comes question#1: Is this even the best/easiest way to wait X seconds between instantiating a new copy?
I have a character too, with an oncollision event, if it collides with one of the copys, it should destroy it. This works fine, problem is, if I collide with the last generated copy, it destroys it and then the copys stop spawning.
character.cs
private void OnCollisionEnter2D(Collision2D collision)
{
if (collision.gameObject.tag == "apple")
{
Destroy(collision.gameObject);
points++;
text.text = "points: " + pont;
}
}
Do not mix different responsibilities in one class.
It makes your code bug-prone and increasingly harder to maintain as that one class grows bigger. You should have a spawner, which spawns apples, and an apple prefab with collider. Two different scripts.
public class Spawner : MonoBehaviour {
private static readonly int[] positions = { -10, -5, 0, 5, 10 };
public int NumberOfSeconds;
private System.Random rand = new System.Random();
public GameObject Prefab;
public IEnumerator Start() {
while (true) {
int randomX = positions[rand.Next(5)];
Instantiate(Prefab, new Vector3(randomX, 5, 0), transform.rotation);
yield return new WaitForSeconds(NumberOfSeconds);
}
}
}
Objects with that component should not have colliders or sprite in your case, so they will never be destroyed on collision with player. Prefabs which are spawned are your regular apples which player collect or avoid.
Since the spawner does not produce another spawners, we put spawn logic in a loop.
You probably want random position from array instead of raw random number, so I fixed it.
Generalize.
Now your spawner does the only function - spawning. It's no longer bound to apples, so you can spawn carrots with another spawner. The logic stays the same.
What is this magic in Start?
You can make Start a coroutine. It works just like any other coroutine: runs until yield return, yield break or end of function. The latter two end the coroutine, otherwise it waits, and then continues running. It's better then flags in Update controlling coroutines.
If you need a script that should run every frame, then use Update.
First, Create a new empty game object called "Applespawner". Put Applespawner.cs in it.
Change this
private void Update()
{
if (isRunning == 1) StartCoroutine(Wait());
}
to
private void Start()
{
if (isRunning == 1) StartCoroutine(Wait());
}
You set isRunning to 0 in Awake(), makes no sense to call IEnumerator in Update function.
You should add this line to your Applespawner.cs public GameObject Apple = null;
And attach your Apple prefab to Applespawner.cs's Apple slot in the editor. If you do not know how to make a prefab, google it.
I think there are 2 best way that you can initiate gameobject every X seconds.
First way
public IEnumerator Wait()
{
while(true){
yield return new Waitforseconds(X seconds);
Instantiate an object.
}
}
second way
float spawnRate = 1f;
float nextTimeToSpawn = 0;
private void Update()
{
if (Time.time >= nextTimeToSpawn){
nextTimeToSpawn = Time.time + 1f / spawnRate;
//Spawn something
}
}
I personally prefer the second way. Hope this helps.
Related
For Unity (Lastest Version & C#)
How do I make an object fall from the sky at random places (random x but fixed y) in a 2D game
How do I delete them when they get off-screen and create a timing between objects falling
Whenever I run the program it randomly creates object but keeps naming them this: myObj, myObj(Clone), myObj(Clone)(Clone), myObj(Clone)(Clone)(Clone) every time a new object is instantiated. More importantly the instantiate part keeps running, spawning a bunch of objects and not deleting them.
Also, I just don't get how to delete the objects as soon as they get out of from the camera. Do I have to create a different script for it or smthn?
public class RandomObjectsFalling : MonoBehaviour
{
public GameObject obj;
public float timebeforedie = 1f;
void Start()
{
StartCoroutine(SpawnBlocks());
}
IEnumerator SpawnBlocks()
{
while(x <= 1)
{
float randomPosition = Random.Range(-10f, 10f);
GameObject clone = (GameObject)Instantiate(prefab, new Vector3(randomPosition, 8, 0), Quaternion.identity); //problem is this keeps getting called creating a whole bunch of objects but they don't get destroyed
yield return new WaitForSeconds(timebeforedie);
Destroy(clone);
}
}
}
Use this code to Spawn Your Obj
public GameObject prefab;
public bool startSpawning;
public float spawnDelay;
private float _currentSpawnDelay;
private void Start()
{
_currentSpawnDelay = spawnDelay;
}
private void Update()
{
if (startSpawning)
{
_currentSpawnDelay -= Time.deltaTime;
if (_currentSpawnDelay < 0)
{
_currentSpawnDelay = spawnDelay;
var randomPosition = Random.Range(-10f, 10f);
Instantiate(prefab, new Vector3(randomPosition, 8, 0), Quaternion.identity);
}
}
}
And This To Destroy Them But This Code Need To be Attach On Obj You That you want to Spawn
private void Update()
{
if (gameObject.transform.position.x < -20)
{
Destroy(gameObject);
}
}
Attach The Destroy Code to your Obj Prefab
Similar to most people on here I'm fairly new to the unity scene and I'm attempting to create a specific mechanism in my game: I want to create a bush where I can spawn fruit items from; the fruit on the bush take a specific amount of time to grow and can't be interacted with until they're fully grown. Once grown as soon as you grab the fruit, it spawns another in the position of the original to repeats the cycle.
However, how the code works is it spawns a first fruit, grows and interacts once grown but once I grab the first fruit, the plant stops growing and just shows the fully grown object(however after every time you grab that fruit it spawns another fruit as intended) ,this object doesn't react with gravity unlike the original which applies gravity after interaction.
The object is a prefab and contains the following script:
using System.Collections; using System.Collections.Generic;
using UnityEngine; using UnityEngine.XR.Interaction.Toolkit;
public class GrowthScript : MonoBehaviour { Collider ObjectCol; [SerializeField] private GameObject Item;
public int GrowTime = 6;
public float MinSize = 0.1f;
public float MaxSize = 1f;
public float Timer = 0f;
private XRGrabInteractable grabInteractable = null;
public bool IsMaxSize = false;
public bool CanRegrow = false;
// Start is called before the first frame update
void Start()
{
Debug.Log("Growing");
IsMaxSize = false;
ObjectCol = GetComponent<Collider>();
// if the plant isnt full size, it starts a routine to grow
if (IsMaxSize == false)
{
ObjectCol.enabled = !ObjectCol.enabled;
StartCoroutine(Grow());
}
}
private void Awake()
{
grabInteractable = GetComponent<XRGrabInteractable>();
grabInteractable.onSelectEntered.AddListener(Obtain);
}
private IEnumerator Grow()
{
Vector3 startScale = new Vector3(MinSize, MinSize, MinSize);
Vector3 maxScale = new Vector3(MaxSize, MaxSize, MaxSize);
do
{
transform.localScale = Vector3.Lerp(startScale, maxScale, Timer / GrowTime);
Timer += Time.deltaTime;
yield return null;
}
while (Timer < GrowTime);
IsMaxSize = true;
CanRegrow = true;
Debug.Log("Grown");
ObjectCol.enabled = !ObjectCol.enabled;
}
private void Obtain(XRBaseInteractor interactor)
{
if (CanRegrow == true)
{
GameObject instance = Instantiate(Item, transform.position, transform.rotation) as GameObject;
CanRegrow = false;
}
}
}
It would be Deeply appreciated if I could receive help on why the prefab doesn't run the code on respawn or a way to solve the problem.
Much appreciated (Good Luck)
Ive been at it all day trying to solve this, because I dont want to make a separate script for every bullet, instead I want to have a single EnemyBulletMovement script, that can move each bullet in a different way, depending on the BulletType int that I feed into it when I instantiate it.
The problem is that if I fire multiple bullets at the same time, they will all change BulletType as soon as another bullet is instantiated, because they are all sharing the same script.
My goal is to have all bullets have a private script, but no matter what I try the variable will still be changed for all active bullets every time I try to change it for just one of them.
EnemyController script that Instantiates the bullets and gives them a BulletType value:
GameObject enemyBullet = ObjectPooler.SharedInstance.GetPooledEnemyBullet();
if (enemyBullet != null)
{
Quaternion rotationAmount = Quaternion.Euler(0, 0, 0);
Quaternion postRotation = transform.rotation * rotationAmount;
enemyBullet.transform.position = transform.position;
enemyBullet.transform.rotation = postRotation;
enemyBullet.SetActive(true);
enemyBullet.GetComponent<EnemyBulletMovement>().SetBullet(2);
}
GameObject enemyBullet2 = ObjectPooler.SharedInstance.GetPooledEnemyBullet();
if (enemyBullet2 != null)
{
Quaternion rotationAmount = Quaternion.Euler(0, 3, 0);
Quaternion postRotation = transform.rotation * rotationAmount;
enemyBullet2.transform.position = transform.position;
enemyBullet2.transform.rotation = postRotation;
enemyBullet2.SetActive(true);
enemyBullet2.GetComponent<EnemyBulletMovement>().SetBullet(0);
}
Shared EnemyBulletMotion script, that gets the bulletType from the above script. This is the problem:
public void SetBullet(int typeIndex)
{
bulletType = typeIndex;
Debug.Log(bulletType);
}
private void BulletMotion()
{
if (bulletType == 0)
{
rb.velocity = pos;
}
if (bulletType == 1)
{
axis = Mathf.Sin(Time.deltaTime * -frequency) * magnitude * transform.up; // Up down Wave motion
transform.Rotate(Vector3.forward * sideMag); // Side Sway Motion (+ Wave = Spiral Motion)
rb.velocity = pos + axis; // Combine all Motions
}
if (bulletType == 2)
{
Debug.Log("Shootin");
axis = Mathf.Sin(Time.deltaTime * -frequency) * magnitude * transform.up; // Up down Wave motion
transform.Rotate(Vector3.forward * -sideMag); // Side Sway Motion (+ Wave = Spiral Motion)
rb.velocity = pos + axis; // Combine all Motions
}
}
EDIT: This Debug Shootin never appears in my console though, so maybe the bulletType is not being read correctly here after all? For Clarification, the first Debug.Log(bulletType); return a 0 or a 2 so it is working. But the second Debug.Log("Shootin"); does not get printed, ever.
I tried private, static, and made an Instance out of the whole EnemyBulletMovement script but nothing works.
When I debug this code, the script works, the bulletType will change from 2 to 0 and back, but when it does it will change both bullets, so both will fly the same way, when what I want is to have each bullet follow its own motion. If there is no way to do this I will have to create a separate script for each bulletmotion, but im only showing 2 here and I already have a lot of them, thats why I wanted to try this out and make it work with if statements and then use case statements later.
EDIT: Added ObjectPooler script below.
public List<GameObject> pooledEnemyBullets;
public GameObject EnemyBulletToPool;
public int EnemyBulletAmountToPool;
pooledEnemyBullets = new List<GameObject>();
public static ObjectPooler SharedInstance;
void Awake()
{
SharedInstance = this;
}
void Start()
{
for (int i = 0; i < EnemyBulletAmountToPool; i++)
{
GameObject obj3 = (GameObject)Instantiate(EnemyBulletToPool);
obj3.SetActive(false);
pooledEnemyBullets.Add(obj3);
}
}
public GameObject GetPooledEnemyBullet()
{
//1
for (int i = 0; i < pooledEnemyBullets.Count; i++)
{
//2
if (!pooledEnemyBullets[i].activeInHierarchy)
{
return pooledEnemyBullets[i];
}
}
//3
return null;
}
}
Bullet type should be declared as private int bulletType. If you declare bullet type as private static int bulletType it will be same for all bullets.
If it is already declared as private int bulletType, then you should check the logic where you use object pooler. You may be processing the same bullet over and over, or processing all bullets in the pool.
So here is the code, right now i only spawn each frame x Prefabs (Its a simple Game im new to unity so the player is a ball and dodges stuff etc. So i have an amount of prefabs (Floors) that is spawning in front of the palyer)
public static int numSpawned = 0;
//Creates an Array to store PreFabs into it
public static GameObject[] thePath;
//Sets the x value of the spawning postion to 100
public int SpawningDistance = 100;
public int numToSpawn = 6;
//Calls the PreFabs Folder
private void Start()
{
thePath = Resources.LoadAll<GameObject>("PreFabs");
}
//Sets the Spawn Position and ++ the numSpawned
void SpawnRandomObject()
{
int withItem = Random.Range(0,6);
GameObject myObj = Instantiate(thePath[withItem]) as GameObject;
numSpawned++;
myObj.transform.position = transform.position + new Vector3(0, 0, 0);
}
//Til now it Spawns every Frame so thats what you have to fix
void Update()
{ //compares the Values
if (numToSpawn > numSpawned)
{
//where your object spawns from
transform.position = new Vector3(SpawningDistance, 0, 0);
SpawnRandomObject();
SpawningDistance = SpawningDistance + 100;
}
}
Til now its the problem that i dont know how to controlle the spawning amount and speed so i need help for that. All Prefabs are beeing stored in an array and then spawned randomly out of it.
Instead of using method SpawnRandomObject() every frame, you could create coroutine and yield WaitForSeconds(value).
[SerializeField] private float delaySpawnInSeconds = 0.5f;
...
private void Start()
{
thePath = Resources.LoadAll<GameObject>("PreFabs");
StartCoroutine(SpawnFloors());
}
Our coroutine:
private IEnumerator SpawnFloors()
{
while (numToSpawn > numSpawned)
{
//SpawnRandomObject(); //if U want to spawn object then wait
yield return new WaitForSeconds(delaySpawnInSeconds);
SpawnRandomObject(); //if U want to wait then spawn object
}
}
Doc: https://docs.unity3d.com/Manual/Coroutines.html
To get spawning amount, you could use Captain Skyhawk's idea to:
Keep an array of spawned objects and check the count.
Arrays:
Doc: https://docs.unity3d.com/2018.4/Documentation/ScriptReference/Array-length.html
You can also use lists, for example:
private List<GameObject> exampleList = new List<GameObject>();
private int sizeOfExampleList;
...\\Add items to the list in the code.\\...
private void exampleMethod ()
{
sizeOfExampleList = exampleList.Count;
}
...
I hope I helped You! :-)
I am trying to spawn some GameObjects in game based on the current player position, essentially trying to make an infinite runner type of game...I made a function so I can spawn the pylons with it but for some reason the function gets called only once per frame, It does not get called the second time with a different parameter.
Why does the second call of the function not work?
This is my code:
public class CameraScript : MonoBehaviour {
public float cameraSpeed = 1;
public float horizontalSpeed;
private int spawnIndex;
public float spawnNormPylonDis;
public float spawnCoinPylonDis;
private int currPosition ;
public GameObject[] pilons;
public GameObject spawnMainPoint;
public Transform[] spawnPoints;
public Transform[] coinsSpawnPoint;
public float enamySpeed;
private int currentPoint;
public Transform[] pointPosition;
// Use this for initialization
void Start () {
//This spawns the Pilons.
spawnMainPoint.transform.position = pointPosition [0].position;
currPosition = (int) transform.position.z;
}
// Update is called once per frame
void FixedUpdate () {
spawnMainPoint.transform.position = Vector3.MoveTowards (spawnMainPoint.transform.position, pointPosition[currentPoint].position, Time.deltaTime * horizontalSpeed);
SpawnPylon (pilons[1],spawnPoints,spawnNormPylonDis,"Check function");
SpawnPylon (pilons [0], spawnPoints, spawnCoinPylonDis,"Check the second function");
GetComponent<Rigidbody> ().velocity = transform.forward * cameraSpeed;
//the next if statements make the Pilons spawn randomly and corectly.
if (spawnMainPoint.transform.position == pointPosition [currentPoint].position) {
currentPoint++;
}
if (currentPoint == pointPosition.Length) {
currentPoint = 0;
}
}
/*This function spanws the a GameObject randomly at a GameObject's position and it takes 2 arguments :
Argument 1: type GameObject
2: type Transform[]*/
void SpawnPylon (GameObject whatSpawn,Transform[] whereSpawn,float spawnDistance,string plm)
{
bool hasSpawnedPylon = false;
if (currPosition != (int)transform.position.z)
{
if ((int)transform.position.z % spawnDistance == 0)
{
Debug.Log (plm);
if (!hasSpawnedPylon)
{
//this makes the GameObject spawn randomly
spawnIndex = Random.Range (0, spawnPoints.Length);
//This is instantiationg the GameObject
Instantiate (whatSpawn, whereSpawn [spawnIndex].position, whereSpawn [spawnIndex].rotation);
//this makes shore that the GameObject is not spawned multiple times at aproximetley the same position.
currPosition = (int)transform.position.z;
}
}
}
else
{
hasSpawnedPylon = false;
}
}
}
Here I have a picture with the script in the inspector:
Script Inspector
And here is the console, trying to figure it out by using Debug.Log () for the calls of the function.
Using Debug.Log for the calls.
I tested it and it works fine under some conditions. The problem most likely is that your spawnNormPylonDis and spawnCoinPylonDis are public variables that are the same in the inspector, or spawnNormPylonDis is a multiple of spawnCoinPylonDis, pretty much with your code, you cannot have spawnNormPylonDis be 1. If they are then in the SpawnPylon function because it has already spawned one. You yourself commented.
//this makes shore that the GameObject is not spawned multiple times at aproximetley the same position.
currPosition = (int)transform.position.z;
I tested this with values of 2 and 3 and both functions got called, however, it would skip the second function if they were both divisible (6).
Therefore you got bottlenecked at this line of code because you set the currPosition after the first function call.
if (currPosition != (int)transform.position.z)
By making a basic wrapper function all is well in the world! So we set the currPosition after we checked both spawning calls, and voila, problem solved.
void SpawnPylon(int index, Transform[] spawnPoint, float dist, string debug)
{
if ((int)transform.position.z % dist == 0)
{
//Debug.Log(debug);
//this makes the GameObject spawn randomly
spawnIndex = Random.Range(0, spawnPoints.Length);
//This is instantiationg the GameObject
GameObject go = Instantiate(pilons[index], spawnPoint[spawnIndex].position, spawnPoint[spawnIndex].rotation) as GameObject;
go.name = string.Format("ID: {0}", index);
}
}
private void SpawnMultiplePylons()
{
if (currPosition != (int)transform.position.z)
{
SpawnPylon(1, spawnPoints, spawnNormPylonDis, "Spawn1");
SpawnPylon(0, spawnPoints, spawnCoinPylonDis, "Spawn2");
currPosition = (int)transform.position.z;
}
}
I tested this and it worked. I hope that it also works for you!