disabling gameObjects after seconds using object pooling and coroutines - c#

I'm developing a simple VR shooter in unity, i use object pooling for the bullets (Lasers here)
this is the ObjectPool script, here i istantiate a list of bullets in the start() method and disable them.
public class ObjectPool : MonoBehaviour
{
public static ObjectPool instance;
private List<GameObject> pooledObject = new List<GameObject>();
private int amountToPool = 20;
[SerializeField] private GameObject laserPrefab;
private void Awake()
{
if (instance == null)
{
instance = this;
}
}
// Start is called before the first frame update
void Start()
{
for (int i = 0; i < amountToPool; i++)
{
GameObject obj = Instantiate(laserPrefab);
obj.SetActive(false);
pooledObject.Add(obj);
}
}
public GameObject GetPooledObject()
{
for (int i = 0; i < pooledObject.Count; i++)
{
if (!pooledObject[i].activeInHierarchy)
{
return pooledObject[i];
}
}
return null;
}
}
This is the Script attached to the gun where i get the pooled bullet and set it to active
public class FireLaserGun : MonoBehaviour
{
public GameObject laserBeamModel;
public Transform laserSpawnPoint;
// Start is called before the first frame update
public void FireGun()
{
GameObject laser = ObjectPool.instance.GetPooledObject();
if (laser != null)
{
laser.transform.position = laserSpawnPoint.position;
laser.transform.rotation = laserSpawnPoint.rotation;
laser.SetActive(true);
Debug.Log("BOOM");
}
else
{
Debug.Log("Laser is null");
}
}
}
I'm trying to disable the bullet after two seconds was fired using a coroutine in the script that moves the bullets:
public class LaserBeamMove : MonoBehaviour
{
private Rigidbody rb;
public float thrust = 10.0f;
float waitTime = 2.0f;
private IEnumerator coroutine;
// Start is called before the first frame update
void Start()
{
rb = GetComponent<Rigidbody>();
coroutine = WaitToDisable(waitTime);
StartCoroutine(coroutine);
}
void FixedUpdate()
{
rb.velocity = transform.forward * thrust;
}
private IEnumerator WaitToDisable(float waitTime)
{
yield return new WaitForSeconds(waitTime);
gameObject.SetActive(false);
Debug.Log("bullet disabled after " + waitTime + "seconds");
}
}
Strangely first seconds of the game everything seems fine, all the bullets start as inactive and every bullet becomes active when fired and inactive after two seconds.
After some seconds bullets dont become inactive anymore (actually only some of them do).
this is a screenshot of the console log, when i fire i print "BOOM" and when a bullet becomes inactive i print "bullet disabled after 2 seconds"
As you can see this down't work for every bullet and i don't understand why.
Am i doing something wrong with the courutine?
https://i.stack.imgur.com/rMJgg.png

Take the code you have in void Start() and move it to a new void OnEnable. The reason is that Start is only called once, the first time the object holding the script is enabled (e.g when the scene loads, when you instantiate a bullet etc), OnEnable is called every time the object is enabled, which is what you want here. You could also use Awake but IMHO that's best kept separately for initialisation stuff only so you can keep the code tidy and separate out game logic from init logic.

Related

IEnumerator script is not working and Coroutine is not running? Unity,C#

I am writing a script for Enemy in my game, where they will attack hero using Coroutine at a certain interval. Though, while running the game, Enemy is not attacking. I have created two events for enemy animation specific for attack. The IE numerator part of code is not running. Can anyone tell what is going wrong?
I wrote Debug.Log("Hello"), to verify if it executes but it doesn't print.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class EnemyAttack : MonoBehaviour
{
[SerializeField] private float range = 3f;
[SerializeField] private float timeBetweenAttacks = 1f;
private Animator anim;
private GameObject player;
private bool playerInRange;
private BoxCollider[] weaponColliders;
// Start is called before the first frame update
void Start()
{
weaponColliders = GetComponentInChildren <BoxCollider[]> ();
player = GameManager.instance.Player;
anim = GetComponent <Animator> ();
StartCoroutine (attack());
}
// Update is called once per frame
void Update()
{
if(Vector3.Distance(transform.position,GameManager.instance.Player.transform.position) < range)
{
playerInRange = true;
}else{
playerInRange = false;
}
}
public IEnumerator attack()
{
Debug.Log("Hello");
if(playerInRange && !GameManager.instance.GameOver)
{
anim.Play("Attack");
yield return new WaitForSeconds(timeBetweenAttacks);
}
yield return null;
StartCoroutine(attack());
}
public void EnemyBeginAttack(){
foreach(var weapon in weaponColliders){
weapon.enabled = true;
}
}
public void EnemyEndAttack(){
foreach(var weapon in weaponColliders){
weapon.enabled = false;
}
}
}
The issue is likely the code weaponColliders = GetComponentInChildren<BoxCollider[]>();. GetComponentInChildren should only be called with component types (or interface types), but BoxCollider[] is an array type.
You should instead use GetComponentsInChildren<BoxCollider>();.

C# spawn manager creating waves of enemies too quickly

I'm a beginner making my first game in Unity, following Unity's Create With Code course. I'm creating a shooter game that will use hand tracking. I haven't set up hand tracking yet so i created an OnTrigger input that explodes objects when I hit space. I created the spawn manager below to spawn waves of enemy attack, but they all the waves are spawning enemies too fast. It seems like they're spawning automatically instead of when the first wave has been destroyed.
Is there an easier way to spawn at a slower rate? Or spawn only when there are no more enemies alive?
EDIT: Added Attack script below
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class SpawnAttack : MonoBehaviour
{
public GameObject Trumps;
private float spawnRange = 9;
public int enemyCount;
public int waveNumber = 1;
void Start()
{
SpawnEnemyWave(waveNumber);
//InvokeRepeating("GenerateSpawnPosition", startDelay, randomInterval);
}
// Update is called once per frame
void Update()
{
enemyCount = FindObjectsOfType<Attack>().Length;
if(enemyCount == 0)
{
waveNumber++;
SpawnEnemyWave(waveNumber);
}
}
void SpawnEnemyWave(int enemiesToSpawn)
{
for (int i = 0; i < enemiesToSpawn; i++)
{
Instantiate(Trumps, GenerateSpawnPosition(), Trumps.transform.rotation);
}
}
private Vector3 GenerateSpawnPosition()
{
float spawnPosX = Random.Range(-spawnRange, spawnRange);
float spawnPosZ = Random.Range(-spawnRange, spawnRange);
Vector3 randomPos = new Vector3(spawnPosX, 0, spawnPosZ);
return randomPos;
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Attack : MonoBehaviour
{
public float speed = 0.5f;
public GameObject Player;
private Rigidbody enemyRb;
// Start is called before the first frame update
void Start()
{
enemyRb = GetComponent<Rigidbody>();
Player = GameObject.Find("Player");
this.transform.LookAt(Player.transform);
}
// Update is called once per frame
void Update()
{
Vector3 lookDirection = (Player.transform.position - transform.position).normalized;
enemyRb.AddForce(lookDirection * speed);
transform.Translate(Vector3.forward * Time.deltaTime * speed);
}
private void OnTriggerEnter(Collider other)
{
Destroy(gameObject);
Debug.Log("Game Over");
}
}
I guess that you can use the invoke method:
Invoke("NameOfTheMethod", 1f)
What this method does is that it waits a certain amount of seconds before calling a method. You have to write the name of the method in quotation marks and then select how long you want to wait before the method is called (The "1f" represents the delay in seconds.) In your case, you can make the script wait before spawning enemies.
I don't know your Attack script but I would use something like
public class Attack : MonoBehaviour
{
public static readonly HashSet<Attack> AliveAttackInstances = new HashSet<Attack>();
private void Awake()
{
AliveAttackInstances.Add(this);
}
private void OnDestroy()
{
AliveAttackInstances.Remove(this);
}
}
So you can all the time in a cheaper way check how many and which enemies are alive like
public class SpawnAttack : MonoBehaviour
{
// I would change this type here to make sure your spawned prefab actually
// HAS an Attack attached .. otherwise your enemyCount will always be 0
public Attack Trumps;
...
void Update()
{
if(Attack.AliveAttackInstances.Count == 0)
{
waveNumber++;
SpawnEnemyWave(waveNumber);
}
}
Then in order to add a certain delay before spawning the next wave you could use a simple timer like
public class SpawnAttack : MonoBehaviour
{
public Attack Trumps;
[SerializeField] private float delay = 1f;
private float timer;
...
void Update()
{
if(Attack.AliveAttackInstances.Count == 0)
{
timer -= Time.deltaTime;
if(timer <= 0)
{
timer = delay;
waveNumber++;
SpawnEnemyWave(waveNumber);
}
}
}
Try to use Coroutine.
Here's a video about Coroutines: https://www.youtube.com/watch?v=qolMYyq0nX0
My example:
public class Spawn : MonoBehaviour {
private float TimeToWait = 3f;
public int enemyCount = 0;
public int waveNumber = 0;
public GameObject enemy;
void Start()
{
StartCoroutine(SpawnEnemyWave(waveNumber));
}
void Update()
{
if (enemyCount == 0)
{
waveNumber++;
StartCoroutine(SpawnEnemyWave(waveNumber));
}
if (Input.GetMouseButtonDown(0))
{
enemyCount--;
}
}
IEnumerator SpawnEnemyWave(int enemiesToSpawn)
{
//"Things to do before the seconds."
for (int i = 0; i < enemiesToSpawn; i++)
{
enemyCount++;
}
yield return new WaitForSeconds(TimeToWait); // at this example we wait 3 seconds (float TimeToWait = 3f;)
//"Things to do after the seconds."
for (int i = 0; i < enemiesToSpawn; i++)
{
Debug.Log("New Enemy!");
Instantiate(enemy, transform.position, Quaternion.identity);
}
}
}

How to change a boolean from another script C#

I have 2 script, playerMachanics and enemyBehavior. My enemyBehavior has a boolean that when the boolean is true it moves away from the player. Instead i'm getting the error: "object reference not set to an instance of an object".
I'm sure it means the script can't find the component but i can't quite figure out what's wrong.
public class enemyBehavior : MonoBehaviour
{
public bool evade = false;
public GameObject Player;
public float movementSpeed = 4;
// Start is called before the first frame update
void Start()
{
Player = GameObject.FindGameObjectWithTag("Player");
}
// Update is called once per frame
void Update()
{
transform.LookAt(Player.transform);
transform.position += transform.forward * movementSpeed * Time.deltaTime;
if (evade == true)
{
movementSpeed = -4;
}
}
}
public class playerMechanics : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
}
void OnCollisionEnter(Collision collision)
{
enemyBehavior evade = gameObject.GetComponent<enemyBehavior>();
if (collision.gameObject.name == "coin")
{
Destroy(collision.gameObject);
enemyBehavior script = GetComponent<enemyBehavior>();
script.evade = script.evade == true;
}
}
}
I expected that the movementSpeed would go to -4 but now i'm just getting an error.
Calling getComponent by itself will look for the component attached to the parent object of the script, which is the player in this case I think. So it will always return null.
Add
Public GameObject enemy;
to the playerMechanics class and then go into the designer and drag the game object that has the enemyBehavior script attached into it. There are several problems with the onCollisionEnter method. Something like this
void OnCollisionEnter(Collision collision)
{
if (collision.gameObject.name == "coin")
{
Destroy(collision.gameObject);
enemyBehavior script = enemy.GetComponent<enemyBehavior>();
script.evade = false;
}
}
should get you going in the right direction.
Is the enemy behavior on the player object? See here
enemyBehavior evade = gameObject.GetComponent<enemyBehavior>();
and here
enemyBehavior script = GetComponent<enemyBehavior>();
You need to implement a way to track which enemy instance you are grabbing. Do this by making a variable to hold the enemy script, or by using a singleton on the enemy script (if there is one enemy).
Variable:
public enemyBehaviour enemy;
Singleton:
(enemyBehaviour)
public static enemyBehaviour instance = null;
private static readonly object padLock = new object();
void Awake(){
lock(padLock){
if(instance == null)
instance = this;
}
}
(player)
enemyBehaviour.instance.evade = false;
Look up singletons if you want to learn more.
If I'm right, I think your game mechanics works like this: The player's objective is to collect coins. When they collect one, the enemy near it will come and evade the player.
If that's the case, you should use this:
public class enemyBehavior : MonoBehaviour
{
public bool evade = false;
public GameObject Player;
public float movementSpeed = 4;
void Start()
{
Player = GameObject.FindGameObjectWithTag("Player");
}
void Update()
{
transform.LookAt(Player.transform);
transform.position += transform.forward * movementSpeed * Time.deltaTime;
if (evade)
{
movementSpeed = -4;
}
}
}
public class playerMechanics : MonoBehaviour
{
[SerializeField] enemyBehvaior enemy;
void OnCollisionEnter(Collision collision)
{
if (collision.collider.name == "coin")
{
Destroy(collision.collider.gameObject);
enemy.evade = true;
}
}
}
In your code, you wrote 'collision.gameObject.' This refers to the object the script is attached to. If you want to reference to the object that we hit, use 'collision.collider'.
'[SerializeField]' is a unity attribute, which is used to make a field show up in the inspector without making it public.
Just a heads up, if you're using 2D, make sure the method is signatured 'OnCollisionEnter2D(Collision2D collision)'.
I hope I answered your question. :)

How to debug why my Unity3D game does not increase score?

I'm doing some practice with Unity. What I'm basically trying to do is by using the singleton design pattern from the gameManager script. I want to prompt the player to hit space, causing score to increase after each time, health to decrease after each time, and money increase once health reaches 0 and the cube is destroyed. I think I got some script assignments mixed up or I'm not sure how to reference the right game object. For whatever reason, hitting space won't increase score or decrease health.
I got 3 texts: score, health, and money, along with a cube in the middle of the scene, with the script, DestroyBySpace, assigned to it.
I have a gameManager empty object assigned to the gameManager script
Here's the script for Game Manager:
private int currentScore;
private int currentHealth;
private int currentMoney;
public Text scoreText;
public Text healthText;
public Text moneyText;
public static GameManager instance;
void Start()
{
currentScore = 0;
currentHealth = 20;
currentMoney = 0;
updateScore();
updateMoney();
updateHealth();
}
void Awake()
{
instance = this;
}
public void adjustScore(int newScore)
{
currentScore += newScore;
updateScore();
}
public void adjustHealthAndMoney(int newHealth, int newMoney)
{
currentHealth -= newHealth;
updateHealth();
if (currentHealth == 0)
{
currentMoney += newMoney;
updateMoney();
Destroy(gameObject);
}
}
void updateScore()
{
scoreText.text = "Score: " + currentScore;
}
void updateHealth()
{
healthText.text = "Health: " + currentHealth;
}
void updateMoney()
{
moneyText.text = "Money: " + currentMoney;
}
and here's my script for DestroyBySpace:
public int scoreValue;
public int healthValue;
public int moneyValue;
public GameObject target;
public GameManager gameManager;
void Start()
{
GameObject gameManagerObject = GameObject.FindWithTag("GameManager");
if (gameManagerObject != null)
{
gameManager = gameManagerObject.GetComponent<GameManager>();
}
if (gameManager == null)
{
Debug.Log("Cannot find Game Manager script");
}
}
void onTriggerEnter()
{
if (Input.GetKeyDown(KeyCode.Space))
{
GameManager.instance.adjustHealthAndMoney(healthValue,moneyValue);
GameManager.instance.adjustScore(scoreValue);
}
}
If you could help steer me in the right direction, I would appreciate it.
I think you've mixed up what the monobehavior onTriggerEnter() does.
onTriggerEnter() is called when another GameObject enters the collider of the GameObject the script is attached to. This is not the place to check for keyboard presses.
What you probably want to use is the Update() monobehavior. It runs every frame, so you are guaranteed that if the user presses a key the function will be able to see it.
Your code would look something like this:
void Update(){
if (Input.GetKeyDown(KeyCode.Space))
{
GameManager.instance.adjustHealthAndMoney(healthValue,moneyValue);
GameManager.instance.adjustScore(scoreValue);
}
}
Also, I think you misunderstood what gameObject refers to when you did Destroy(gameObject) in the gameManager script. gameObject refers to the gameObject the script it's mentioned in is attached to. This means when you do Destroy(gameObject) in the gameManager script you are destroying the object that the gameManager script it attached to.
To get rid of the GameObject called target you need to get access to it. You can do that in a couple ways:
1) Destroy(GameObject.Find("target"));
This directly searches for a GameObject called "target" and destroys it. The downside to this is that it requires the GameObject to be called exactly "target" and if there are multiple GameObjects called "target" it will only destroy the first one.
2) Create a public variable (like public GameObject target) and store the physical instance of the target in it through the inspector. Then when you want to destroy it, just do Destroy(target);
This is how I would most likely do it.

Respawn destroyed object in Unity

I have this script where as to when i hit a trigger my enemy spawns at a random time then the enemy destroy itself at a random time. I want to respawn the enemy again so it can do this over and over again. Any Suggestions:
public class SpawnManager : MonoBehaviour {
public GameObject Enemy; // the enemy prefab
public float mytimer; // the time to wait before spawn
public float transport;// the time it has to destroy itself
private GameObject _spawndEnemy; // the enemy that was spawnd
void SpawnEnemy()
{
var enemySpawnPoint = GameObject.Find("FFEnemySpawn1").transform;
_spawndEnemy = Instantiate(
Enemy, enemySpawnPoint.position, enemySpawnPoint.rotation) as GameObject;
transport = Random.Range (2,15);
}
void OnTriggerEnter(Collider other)
{
if (other.gameObject.name == "FFTrigger") {
mytimer = Random.Range(0,15);
Invoke("SpawnEnemy", mytimer);
Debug.Log("Spawn Normal");
}
}
void Update()
{
Destroy (_spawndEnemy, transport);
}
}
Hi Ghostdre this question would probably be better answered here Game Dev SO, as for your question I would recommend creating an Enemy class object and data members for the GameObject as well as a time variable which determines how long the Enemy should live before being Destroyed.
e.g.
public class SpawnManager
{
public float lifeTime;
...
void Update()
{
lifeTime -= Time.deltaTime
if (lifeTime <= 0)
{
Destroy (_spawndEnemy, transport);
SpawnEnemy()
}
}
}
Please note that this is an incomplete example, but it should give you an idea of where to go from here.

Categories

Resources