This question already has answers here:
destroy a specific gameobject after an amount of time in unity
(3 answers)
Closed 2 years ago.
The problem is in that way it will create more and more objects if automatic or if using the mouse.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ScifiEffects : MonoBehaviour
{
public GameObject spawnEffect;
public bool automaticFire = false;
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
if (automaticFire == false)
{
if (Input.GetMouseButtonDown(0))
{
GameObject effect = Instantiate(spawnEffect, transform.position, Quaternion.FromToRotation(Vector3.up, spawnEffect.transform.forward)) as GameObject;
}
}
else
{
GameObject effect = Instantiate(spawnEffect, transform.position, Quaternion.FromToRotation(Vector3.up, spawnEffect.transform.forward)) as GameObject;
}
}
}
If I add :
Destroy(effect);
Either in the mouse or if automatic the gameobject will be destroy at once and the effect will not happen. I could use a coroutine but then I will have to call the StartCoroutine inside the Update either if automatic or not and it will start many coroutines.
Destroy takes an optional second parameter
t The optional amount of time to delay before destroying the object.
So you can e.g. simply do
if (Input.GetMouseButtonDown(0))
{
GameObject effect = Instantiate(spawnEffect, transform.position, Quaternion.FromToRotation(Vector3.up, spawnEffect.transform.forward)) as GameObject;
Destroy(effect, 3.5f);
}
To destroy the effect object after 3.5 seconds
Note however that you probably should not Instantiate an object every frame in your else case. Rather you could define a certain interval like e.g.
// adjust in the Inspector
public float maxSpawnPerSecond = 2f;
...
private float timer;
void Update()
{
if (!automaticFire)
{
if (Input.GetMouseButtonDown(0))
{
Spawn();
}
}
else
{
if(maxSpawnPerSecond > 0)
{
timer -= Time.deltaTime;
if(timer <= 0)
{
Spawn();
timer = 1 / maxSpawnPerSecond;
}
}
}
}
private void Spawn()
{
GameObject effect = Instantiate(spawnEffect, transform.position, Quaternion.FromToRotation(Vector3.up, spawnEffect.transform.forward)) as GameObject;
Destroy(effect, 3.5f);
}
There are no downsides to starting a bunch of coroutines other than regular coding issues like infinite loops.
Also coroutines has exactly what you need with a method called WaitForSeconds() that only works in coroutines. As far as I know
void Update()
{
if(true) // Change if statement so you don't create an infinite loop and crash your program
{
StartCoroutine("SpecialEffects");
}
}
IEnumerator SpecialEffects()
{
// Special Effects
yield return new WaitForSeconds(seconds);
// Code that destroys something
}
You can add Thread.Sleep(miliseconds); to wait before it happens. You could also make a Task which sleeps for a while and then does the delete, make sure to await it.
Related
This question already has answers here:
What is a NullReferenceException, and how do I fix it?
(27 answers)
Closed 1 year ago.
So my game is a 2D top down movement game and my script does make my enemy attack but it constantly loops the attack animation because I obviously don't know what EXACTLY to put code wise to make the enemy attack when in range of the player to do damage instead of letting him constantly loop. Also i seem to be getting an error when i get close to my enemy as of right now it says
NullReferenceException: Object reference not set to an instance of an object
EnemyCombat.Attack () (at Assets/EnemyCombat.cs:36)
EnemyCombat.Update () (at Assets/EnemyCombat.cs:25)
Also, Here is the EnemyCombat script
enemy attausing System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class EnemyCombat : MonoBehaviour
{
public Animator animator;
public Transform AttackPoint;
public float attackRange = 0.5f;
public LayerMask enemyLayers;
public int attackDamage = 5;
public float attackRate = 2f;
float nextAttackTime = 0f;
// Update is called once per frame
void Update()
{
if (Time.time >= nextAttackTime)
{
Attack();
nextAttackTime = Time.time + 1f / attackRate;
}
}
void Attack()
{
animator.SetTrigger("Attack");
Collider2D[] hitEnemies = Physics2D.OverlapCircleAll(AttackPoint.position, attackRange, enemyLayers);
foreach (Collider2D enemy in hitEnemies)
{
enemy.GetComponent<Enemy>().TakeDamage(attackDamage);
}
}
void OnDrawGizmosSelected()
{
if (AttackPoint == null)
return;
Gizmos.DrawWireSphere(AttackPoint.position, attackRange);
}
}
To fix your endless attack loop:
// Update is called once per frame
void Update()
{
if (attackRate >= nextAttackTime) /* checks that nextAttackTime is less than or equal to the attackRate */
{
Attack();
nextAttackTime = Time.deltaTime * 5f; // adds 5 seconds to nextAttackTime
}
else
{
nextAttackTime -= Time.deltaTime; /* if nextAttackTime is greater than the attackRate, subtract one from nextAttackTime. this only happens once per second because you use Time.deltaTime */
}
}
From that NullReference Error it looks like the major problem you're having is that there is no actual point in the code or in the game hierarchy that you are telling your script which gameObject it is referring to.
Line 36 is trying to retrieve that information with .GetComponent<Enemy()>, so you need to provide that reference.
You could do this in the script fairly easily by creating a public variable that you can drag the enemy gameObject into in your hierarchy in Unity.
Try something like:
public GameObject enemyObject;
This will create a variable in the script visible in the hierarchy when you select the script which you can drag the appropriate gameObject into.
There might need to be some adjustments to it because I can't see the rest of your code, but this seems to be the issue.
Another option would be trying to manually adding it in:
void Start()
{
GameObject enemyObject = gameObject.GetComponent<Enemy>();
}
this is taken from Unity Scripting Documentation
this should be a very simple answer. I'm following a Unity with C# tutorial for making a simple Space Invaders game, and at one point it is shown that when our enemyHolder object has no child objects left (when all enemies are destroyed) the attached text under the winText function should be displayed.
So we have
if (enemyHolder.childCount == 0)
{
winText.enabled = true;
}
When I run the code the text isn't displayed after the enemies are destroyed and no child object is left. It's like the code stops getting read at that point, although the character is still movable and you can generate new shots.
If I create two "Enemy" child objects and tell it to display the winText rather when the childCount reaches 1, it does work.
So why is it not working when the function calls for == 0?
EDIT: Complete class code:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class EnemyController : MonoBehaviour
{
private Transform enemyHolder;
public float speed;
public GameObject shot;
public Text winText;
public float fireRate = 0.997f;
// Start is called before the first frame update
void Start()
{
winText.enabled = false;
InvokeRepeating("MoveEnemy", 0.1f, 0.3f);
enemyHolder = GetComponent<Transform>();
}
void MoveEnemy()
{
enemyHolder.position += Vector3.right * speed;
foreach (Transform enemy in enemyHolder)
{
if (enemy.position.x < -10.5 || enemy.position.x > 10.5)
{
speed = -speed;
enemyHolder.position += Vector3.down * 0.5f;
return;
}
if (enemy.position.y <= -4)
{
GameOver.isPlayerDead = true;
Time.timeScale = 0;
}
if (enemyHolder.childCount == 1)
{
CancelInvoke();
InvokeRepeating("MoveEnemy", 0.1f, 0.25f);
}
if (enemyHolder.childCount == 0)
{
winText.enabled = true;
}
}
}
}
Your code is inside the void MoveEnemy() function.
I'm assuming your script is attached to in-game enemies. Your code doesn't run because the MoveEnemy function no longer runs if there are no enemies.
So, you need to handle enemy movement and scene handling in different scripts.
The code that checks the enemy holder's number of children should be placed inside a void Update() function. This Update() function should be placed on an object that never gets deleted. Its advantage is that it runs every frame.
As a convention, devs generally use separate empty objects or even the camera to attach scripts which contain Update functions that handle the scene. Good luck!
Read more on Update
I've searched around and I just can't get this to work. I think I just don't know the proper syntax, or just doesn't quite grasp the context.
I have a BombDrop script that holds a public int. I got this to work with public static, but Someone said that that is a really bad programming habit and that I should learn encapsulation. Here is what I wrote:
BombDrop script:
<!-- language: c# -->
public class BombDrop : MonoBehaviour {
public GameObject BombPrefab;
//Bombs that the player can drop
public int maxBombs = 1;
// Update is called once per frame
void Update () {
if (Input.GetKeyDown(KeyCode.Space)){
if(maxBombs > 0){
DropBomb();
//telling in console current bombs
Debug.Log("maxBombs = " + maxBombs);
}
}
}
void DropBomb(){
// remove one bomb from the current maxBombs
maxBombs -= 1;
// spawn bomb prefab
Vector2 pos = transform.position;
pos.x = Mathf.Round(pos.x);
pos.y = Mathf.Round(pos.y);
Instantiate(BombPrefab, pos, Quaternion.identity);
}
}
So I want the Bomb script that's attached to the prefabgameobject Bombprefab to access the maxBombs integer in BombDrop, so that when the bomb is destroyed it adds + one to maxBombs in BombDrop.
And this is the Bomb script that needs the reference.
public class Bomb : MonoBehaviour {
// Time after which the bomb explodes
float time = 3.0f;
// Explosion Prefab
public GameObject explosion;
BoxCollider2D collider;
private BombDrop BombDropScript;
void Awake (){
BombDropScript = GetComponent<BombDrop> ();
}
void Start () {
collider = gameObject.GetComponent<BoxCollider2D> ();
// Call the Explode function after a few seconds
Invoke("Explode", time);
}
void OnTriggerExit2D(Collider2D other){
collider.isTrigger = false;
}
void Explode() {
// Remove Bomb from game
Destroy(gameObject);
// When bomb is destroyed add 1 to the max
// number of bombs you can drop simultaneously .
BombDropScript.maxBombs += 1;
// Spawn Explosion
Instantiate(explosion,
transform.position,
Quaternion.identity);
In the documentation it says that it should be something like
BombDropScript = otherGameObject.GetComponent<BombDrop>();
But that doesn't work. Maybe I just don't understand the syntax here. Is it suppose to say otherGameObject? Cause that doesn't do anything. I still get the error : "Object reference not set do an instance of an object" on my BombDropScript.maxBombs down in the explode()
You need to find the GameObject that contains the script Component that you plan to get a reference to. Make sure the GameObject is already in the scene, or Find will return null.
GameObject g = GameObject.Find("GameObject Name");
Then you can grab the script:
BombDrop bScript = g.GetComponent<BombDrop>();
Then you can access the variables and functions of the Script.
bScript.foo();
I just realized that I answered a very similar question the other day, check here:
Don't know how to get enemy's health
I'll expand a bit on your question since I already answered it.
What your code is doing is saying "Look within my GameObject for a BombDropScript, most of the time the script won't be attached to the same GameObject.
Also use a setter and getter for maxBombs.
public class BombDrop : MonoBehaviour
{
public void setMaxBombs(int amount)
{
maxBombs += amount;
}
public int getMaxBombs()
{
return maxBombs;
}
}
use it in start instead of awake and dont use Destroy(gameObject); you are destroying your game Object then you want something from it
void Start () {
BombDropScript =gameObject.GetComponent<BombDrop> ();
collider = gameObject.GetComponent<BoxCollider2D> ();
// Call the Explode function after a few seconds
Invoke("Explode", time);
}
void Explode() {
//..
//..
//at last
Destroy(gameObject);
}
if you want to access a script in another gameObject you should assign the game object via inspector and access it like that
public gameObject another;
void Start () {
BombDropScript =another.GetComponent<BombDrop> ();
}
Can Use this :
entBombDropScript.maxBombs += 1;
Before :
Destroy(gameObject);
I just want to say that you can increase the maxBombs value before Destroying the game object. it is necessary because, if you destroy game object first and then increases the value so at that time the reference of your script BombDropScript will be gone and you can not modify the value's in it.
I have a simple script that is supposed to spawn zombies with time delays that can be inputted. The script seems to work fine however, after I move my character for a while, the gameobject which is running the script stops spawning zombies.If my character stands still, the zombies will continue to spawn. The red arrow is pointing to the gameObject that is spawning the zombies.
using UnityEngine;
using System.Collections;
public class spawn : MonoBehaviour
{
public GameObject zombie;
public float delayTime = 4f;
IEnumerator Start()
{
var obj = Instantiate(zombie, transform.position, transform.rotation) as GameObject;
yield return new WaitForSeconds(delayTime);
StartCoroutine(Start());
}
}
Scene setup is as follows:
First separate your spawning function from your Start function to avoid confusion.
void Start()
{
StartCoroutine(SpawningRoutine());
}
Then you want your coroutine to continue spawning forever? You need a loop inside the coroutine. In the example below I made an infinite loop but you could have a counter for the number of zombies you want for example.
IEnumerator SpawningRoutine()
{
while(true)
{
var obj = Instantiate(zombie, transform.position, transform.rotation) as GameObject;
yield return new WaitForSeconds(delayTime);
}
}
Lastly remember to put this spawn script on an object that is not a zombie. Have a separate spawn object with only this script on it. You probably do this already.
Edit in response to logging:
Add a logging script to your zombie prefab like so:
private static int zombieCounter = 0;
void Start()
{
Debug.Log("Number of zombies spawned so far: " + zombieCounter++);
}
You don't really need to use coroutine for that, InvokeRepeating should be good enough. Also make sure your zombie prefab is private so it doesn't get replaced by other scripts
[SerializeField] private GameObject zombie;
float waitBeforeFirstSpawn = 0f;
float delayTime = 4f;
void Start()
{
InvokeRepeating("SpawnZombie", waitBeforeFirstSpawn, delayTime);
}
void SpawnZombie()
{
GameObject zombieGO = Instantiate(zombie, transform.position, transform.rotation) as GameObject;
}
I'm trying to force Unity to spawn a gameobject every second with a coroutine but I am getting the errors cs1502 & cs1503. (sorry if this is a stupid syntax error)
public class BossCannon : MonoBehaviour
{
public BossBullet BossAmmo;
public float Force;
public bool trigger = false;
void Start ()
{
}
void Update()
{
if (trigger == true)
ShootBullet();
}
void ShotPattern()
{
while (true)
{
StartCoroutine(Shoot);
}
}
public IEnumerator Shoot()
{
trigger = false;
yield return new WaitForSeconds(1f); //Waits 1 second
GameObject b = Instantiate(BossAmmo.gameObject, transform.position, transform.rotation) as GameObject;
trigger = true;
}
}
Try passing the Transform to Instantiate instead of the GameObject.
Transform b = Instantiate(BossAmmo.transform, transform.position, transform.rotation) as Transform;
You are also starting your coroutine wrong. Try this:
StartCoroutine(Shoot());
The errors you are getting are Invalid Parameter errors. A method is expecting one type, and you are passing another type.
It might be helpful to post the full errors you are getting when you compile.
One more note: while(true) is generally a terrible thing to do. When you do get this running, I suspect Unity will suddenly lock up on you.