Alternatives to Destroy() or SetActive() - c#

I have a problem with my script. I'm really looking for a solution without having to post code. ( I'm using Unity 2019 with C#. )
Here's the dilemma:
I have two scripts
1) EnemyDamage
2) EnemySpawn
Both scripts work fine until one of the enemies is killed.
Basically when my enemy('s) die I use Destroy(gameObject) which ultimately is causing the error on my spawn script because the EnemySpawn is still trying to access the destroyed enemy.
The way my spawn script works is multiple enemies can be chosen from a list. Then using coroutines, the enemies appear on screen and then disappear using SetActive(true/false). So even if I elect to set the enemy to false in EnemyDamage, the EnemySpawn will just set it back to true.
So what I need is another way to hide my enemy. ( And I can't just move it out of camera sight because the EnemySpawn will just put it back on a random spawn point in camera view again )
What are some alternatives to Destroy(), SetActive(), or moving out of camera range?

Try adding this in the the spawn enemy script. It should remove an enemy from the list when that enemy is destroyed .The enemysToRemove variable is there because you cant edit a list while iterating through it (thanks Hristo).
public List<GameObject> enemys;
List<GameObject> enemysToRemove = new List<GameObject>();
void Update () {
foreach(GameObject enemy in enemys){
if (enemy == null) {
enemysToRemove.Add (enemy);
}
}
foreach (GameObject item in enemysToRemove) {
enemys.Remove (item);
}
}
Alternatively, put try and catch in-front of everything you do to the enemy`s in the spawn enemy script. Like this:
public List<GameObject> enemys;
foreach(GameObject enemy in enemys){
try{
//thing I want to do to this enemy
}catch{
Debug.Log ("enemy destroyed");
}
}

Related

Detecting collisions between two separate animations

My animations enters a state and it's collision with enemy projectiles either defends himself or gets hurt depending on what animation he is currently in. I'm trying to detect collisions with the ONTRIGGERENTER function and boxcollider2D's but it's not having any affect. I want to figure out how to correctly facilitate trigger enters and collisions between these animations.
I've already tried giving my sprite a tag and calling that tag when the ONTRIGGERENTER function is called but it didn't work. I also tried a more complicated way of calling the tag of the collider but that didn't work either. How do you access the trigger when it's an animation?
string DefenceAnimTag = "Defending";
string DestroyEnemTag = "Eliminated";
//This code is connected to the enemy projectile and trying to
//sense when it's collided with the player
void OnTriggerEnter(Collider2D col)
{
if (col.tag == Defending)
{
GetComponent<Animator>().SetBool("Elim", true);
}
else { //deal damage to the player }
}
//The first method didn't work so I tried a second method.
//Here is an alternative attempt at detecting triggers in
//animations.
void OnCollisionStay2D(Collider2D col)
{
if (col.gameobject.CompareString("Defending"))
{
GetComponent<Animator>().SetBool("Elim", true);
}
}
//This method didn't work either even though the Animation
//WOULD be Defending
I expected enemy projectiles to collide with my player when he was defending himself and get defeated. I knew that it was working when the Enemy projectiles transition into their Elim state, where they get defeated by enemy defenses, however they made collisions and then continued unaffected.
Okay, okay, I figured it out. Because a Gameobject can have many animations to it, you cannot go by the tag that the GameObject in the inspector has. You have to go by the title of the Animations that is currently playing. In order to access the animation currently playing we must use the following code:
AnimatorClipInfo[] m_AnimClipInf;
string m_ClipName;
void Start()
{
//Set up our projectile for possible Elimination
//upon Collision
Getcomponent<Animator>().SetBool("Elim", false);
}
void OnTriggerEnter2D(Collider2D col)
{
m_AnimClipInf = col.GetComponent<Animator>
().GetCurrentAnimatorClipInfo(0);
m_ClipName = m_AnimClipInf[0].clip.name;
if (m_ClipName == "Defending")
{
GetComonent<Animator>().SetBool("Elim", true);
//Projectile gets eliminated
}
}

Changing delay spawn at runtime [C#]

this is the code where the problem is situated:
public class EnemySpawnScript : MonoBehaviour {
private float previousEnemyDelay = 0;
public void enemySpawn (GameObject player, LevelData level){
//loop every enemy inside the level to instantiate them. EnemyDataSpawn has got some infos about every enemy
foreach (EnemyDataSpawn enemyDataSpawn in level.enemyDataSpawner) {
StartCoroutine (InstantiateObject (player,enemyDataSpawn));
}
}
IEnumerator InstantiateObject (GameObject player, EnemyDataSpawn enemyDataSpawn)
{
//this handles the delay of the spawn of every enemy.
previousEnemyDelay += enemyDataSpawn.delay;
//here is the problem if i want to stop enemies to spawn and then continue to spawn them again.
yield return new WaitForSeconds (previousEnemyDelay);
//after waiting, i do something.
}
This code works if I just want to instantiate every enemy after a delay specified inside enemyDataSpawn (I didn't write the "instantiate enemy" part (i used object pooling to achieve that), situated after the yield inside IEnumerator because it's not important for my problem).
The problem is that i have an integer (called maxOnScreen) for every kind of enemy, and if the number of the ACTIVE enemies with the same name is equal to maxOnScreen, then i want to stop to instantiate every other enemy until one enemy (which active number was equal to maxOnScreen) will be deactivated or destroyed. If one enemy is not activated anymore and the number of active enemies with the same name is not equal to maxOnScreen anymore, than I want to instantiate the next enemy in the list again with his delay as my actual code already does. I don't know how to achieve this with IEnumerator so my question is: is it possible to achieve this with IEnumerator or i should use some other methods?
Simple example: there are 7 enemies active, 3 named "bad soldier" and 4 named "good soldier". The maxOnScreen variable of good soldier is "4" so i don't want that other enemies will instantiate unless one of "good soldier" will be destroyed/deactivated. If the total of active "good soldier" is 3 (so less than maxOnScreen again), i want that the game goes on spawning the remaining enemies to spawn.
Thank you in advance
You might use a single enemy-spawning coroutine instead of launching each coroutine for each enemy you want to spawn. This way you can easily control when to spawn an enemy and when to delay its spawning:
public class EnemySpawnScript : MonoBehaviour {
public void enemySpawn (GameObject player, LevelData level){
StartCoroutine(InstantiateObjects(player, level.enemyDataSpawner));
}
IEnumerator InstantiateObjects (GameObject player, IEnumerable<EnemyDataSpawn> enemyDataSpawnList){
foreach(var enemyDataSpawn in enemyDataSpawnList){
while( /* too many enemies of enemyDataSpawn type */ ) {
yield return new WaitForSeconds(enemyDataSpawn.delay);
}
// instantiate enemyDataSpawn
}
}
}

Logic issue spawning a set number of game objects when game object is destroyed if collides with something?

I'm having a dilemma with a basic game in Unity with C# - I need to spawn 10 cubes in random locations, however once a cube is spawned I destroy it if it detects a collision with another game object in this script ATTACHED to the cube object:
void OnCollisionStay(Collision col){
if (col.gameObject.tag == "Maze" || col.gameObject.tag == "Player") {
//Destroy cube
Destroy(this.gameObject);
spawnCubes.numCubesExist--;
Debug.Log ("touching!!" + spawnCubes.numCubesExist);
spawnCubes.touchedMaze = true;
}
}
In another script in my scene I spawn the cubes here. I am using public static int numCubesExist to enumerate the number of cubes in the scene (although this doesn't work) and NUMTOBESPAWNED to set the number of cubes that SHOULD end up in the scene.
In the cube script, numCubesExist is decreased by 1 if collision is detected and when a cube is spawned by cubeSpawner (); I increment. My logic is that my while statement would keep spawning UNTIL numCubesExist == NUMTOBESPAWNED but this does not happen.
void Start () {
while(numCubesExist <= NUMTOBESPAWNED)
{
cubeSpawner ();
}
}
void cubeSpawner()
{
//Random textures
switch (Random.Range (0, 3)) {
case 0:
cube.GetComponent<Renderer> ().material = color1;
break;
case 1:
cube.GetComponent<Renderer> ().material = color2;
break;
case 2:
cube.GetComponent<Renderer> ().material = color3;
break;
case 3:
cube.GetComponent<Renderer> ().material = color4;
break;
}
//Random positions
if (UIManagerScript.sceneNum == 0) {
position = new Vector3 (Random.Range (7.0F, 26.0F), (float)1.4, Random.Range (-9.0F, 10.0F));
Debug.Log (position);
}
else if(UIManagerScript.sceneNum == 1) {
position = new Vector3 (Random.Range (7.0F, 47.0F), (float)1.4, Random.Range (-9.0F, 10.0F));
Debug.Log (position);
}
else if(UIManagerScript.sceneNum == 2) {
position = new Vector3 (Random.Range (-4.7F, 44.0F), (float)1.4, Random.Range (-23.0F, 25.0F));
Debug.Log (position);
}
//spawn
Instantiate (cube, position, Quaternion.identity);
Debug.Log ("spawned it!"+numCubesExist);
numCubesExist++;
}
My Debug.Log statements within the collision on the cube script are printed AFTER all the spawn statements are printed, so what I'm seeing is the cubes spawn the correct number, then half of them are destroyed and NOT REPLACED by the OnCollisionStay. So I'm always left with less cubes than NUMTOBESPAWNED.
I'm new to Unity- what is the best way to achieve this? Where should I call my while loop (In update just results in a million cubes)?
EDIT to enumerate cubes:
GameObject[] allObjects = GameObject.FindGameObjectsWithTag("blueCube");
foreach (GameObject go in allObjects) {
cubesInScene++;
}
Here's what I see, I hope this helps!
Your spawner does not guarantee the cube is not spawning right next (or into) a "Maze" or "Player" object, so most probably it happens every now and then and your cubes get destroyed right after they spawned. As "Nick Otten" highlights, your spawn-loop is already over when this happens so no new cubes are spawned in place of the destroyed ones.To solve this, you can do several things. Here's a few examples:
Create spawner GOs in the Scene and guarantee both "Maze" and "Player" are outside of it's pre-defined range (let's say a 10 units globe), as well as the cubes are guaranteed spawned inside; you can make it simple -as of coding- and add a collider to your spawners' "globe", and with IgnoreLayerCollision make unwanted items to repel ("stay outside") and others to fall through ("stay inside"). E.g. cubes repel and player(s) stay inside or vice versa.
You can apply a different collision mechanism in "spawning phase". I.e. on OnCollisionEnter, you make the objects repel / bounce-off / etc each other, instead of cubes getting destroyed. As soon as spawning phase is over, it is guaranteed nothing is touching and you can switch to "destroy mechanism" / "play mode"
Slow and an overhead so I don't recommend it, but you can keep spawning and spawning until all cubes stay (i.e. none gets destroyed after it was spawned)
Instead of "numCubesExist" and such, why don't you create a cube prefab queue (and hide/requeue them in script instead of destroy)? Here's a nice tutorial on how to do this: Object Pooling Tutorial (I also recommend Sebastian's other tutorials, he is doing a great job in teaching Unity)
In your cube spawner method, instead of a switch, why don't you do something like this?//class variable
private Material[] colorMats; //fill up in Start() with color mats.
//then in cubeSpawner:
cube.GetComponent<Renderer>().material = colorMats[Random.Range(0, colorMats.Length)];So whenever you make change to your materials (e.g. add a new one), you have to alter your initialization code only, at one place, in Start()

NullReferenceException & using methods without direct reference

using UnityEngine;
using System.Collections;
public class Gamemanager : MonoBehaviour {
public GameObject AttackButton; //reference to attack button game object
public GameObject RocketGO; // reference to rocket game object
public GameObject EnemySpawner;//reference to meteorite spawner gameobject
public GameObject navbuttons;
public GameObject Power1SpawnerGO; // reference to power1 spawner
public GameObject Enemy1bluespawner;
// Use this for initialization
public enum GamemanagerState
{
Opening ,
Gameplay ,
Gameover,
}
GamemanagerState GMState;
void Start ()
{
GMState = GamemanagerState.Opening;
}
void UpdateGameManagerState()
{
switch(GMState)
{
case GamemanagerState.Opening:
//set attack button to true
AttackButton.SetActive(true);
//hide game over
//set navigational buttons to false
navbuttons.SetActive(false);
break;
case GamemanagerState.Gameplay:
//set attack button false
AttackButton.SetActive(false);
//set navbuttons to true
navbuttons.SetActive(true);
//set playership to active
RocketGO.GetComponent<Rocketdamage>().Init();
//Start EnemySpawner
EnemySpawner.GetComponent<meteoritespawner>().startEnemySpawner();
Enemy1bluespawner.GetComponent<Enemy1Spawner>().startEnemy1blueSpawner(); // Enemy blue spawner
//Active Rocket bullete fire
RocketGO.GetComponent<Rocketshooting>().startBulleteFire();
//Activate Enemyblue1 bulletefire
Enemy1bluespawner.GetComponent<Enemyblue1shooting>().startEBulleteFire();
//Active power1 spawner
Power1SpawnerGO.GetComponent<PowerSpawner>().startPower1Spawner();
break;
case GamemanagerState.Gameover:
//stop enemy spawner
EnemySpawner.GetComponent<meteoritespawner>().StopEnemySpawning();
Enemy1bluespawner.GetComponent<Enemy1Spawner>().StopEnemy1blueSpawning();
//DeActive Rocket bullete fire
RocketGO.GetComponent<Rocketshooting>().StopBulleteFire();
//Stop power1 spawning
Power1SpawnerGO.GetComponent<PowerSpawner>().StopPower1Spawning();
//Deactive Enemyblue1 Bullete fire
Enemy1bluespawner.GetComponent<Enemyblue1shooting>().StopEBulleteFire();
//display gameover
//set game state to opening state after 8 sec
Invoke("changeToOpeningState" , 8f);
break;
}
}
public void setGameManagerState(GamemanagerState state)
{
GMState = state;
UpdateGameManagerState ();
}
//call this function when player call attack button
public void Startpaly()
{
GMState = GamemanagerState.Gameplay;
UpdateGameManagerState ();
}
public void changeToOpeningState()
{
setGameManagerState (GamemanagerState.Opening);
}
}
This is my script.......and everything is running fine! But the line number 67 and 89 i.e.
//line no 67
Enemy1bluespawner.GetComponent<Enemyblue1shooting>().startEBulleteFire();
and:
//line no 89
Enemy1bluespawner.GetComponent<Enemyblue1shooting>().StopEBulleteFire();
under UpdateGameManagerState() function.
are giving me NullreferenceException.
After so much of thinking, I have come up with this issue that may have been occurring!
Here Enemy1bluespawner is object(with which Spawning script is attached) to spawn enemy....and Enemyprefab is attached to Enemy1bluespawner...but however, I am able to call all the scripts/methods of Enemy PREFAB(Without direct reference of Enemy prefab - I don't know why?).
Everything working fine but line number 67 and 89 is giving me NullreferenceException.
Scenario - I have empty object - Enemy1bluespawner with which Enemyspawning script is attached - which spawn the enemy at particular interval of time.........
So, when I am calling this function :
Enemy1bluespawner.GetComponent<Enemyblue1shooting>().startEBulleteFire();
To start bullete fire of the enemy but now enemy is not on the screen it will come after 5 seconds
The enemy is not on the screen it comes after 5 seconds - so may be that's why Unity is throwing NullreferenceException(at runtime)
Questions:
How am I able to access Enemy prefab scripts/methods without direct reference thru Enemy1bluespawn Object?
Solution to remove NullreferenceException. When enemy is not on screen and I am calling methoda related to it.
I hope you guys are understanding it. Any little help will be great. Thank you! :) :)
You reference the Enemy1Spawner script, but never the Enemyblue1shooting. For what I'm understanding, your goal is to spawn enemies with the Enemy1Spawner script, and those spawned enemies are the objects that have the Enemyblue1shooting component (that will actually shoot).
If I'm right, you'll have to get the GameObject of the spawned enemy and then use the GetComponent method. What seems to be happening is a NullPointerException because the spawner doesn't have a Enemyblue1shooting component; the object that has it is it's child.
Try making a method in the Enemy1bluespawner script that returns the enemy at hand and, only then, using GetComponent<Enemyblue1shooting>() to get the right component. That'll do the trick.
Example:
public GameObject GetEnemy(int n)
{
return enemies.get(n); // Gets the n enemy in your array
}
...
Enemy1bluespawner.GetComponent<Enemy1Spawner>().startEnemy1blueSpawner(); // Enemy blue spawner
GameObject enemy = Enemy1bluespawner.GetComponent<Enemy1Spawner>().GetEnemy(0);
enemy.GetComponent<Enemyblue1shooting>().startEBulleteFire();
Don't forget to do some null validation. The perfect scenario would be returning the script itself, not a GameObject, but this way it may be a bit easier to see.
That might be also your question about calling prefab's methods. You don't call a prefab's method; you call the method of the instantiated prefab (the spawned enemy).
easiest but not the best is
if(Enemy1bluespawner.GetComponent<Enemyblue1shooting>()!=null)
Enemy1bluespawner.GetComponent<Enemyblue1shooting>().startEBulleteFire();

Identify a selected enemy prefab to play death particle system

I am trying to play a particle effect when an enemy is killed but it seems to play on a randomly selected one rather than the one that was hit. However the enemy that was hit still disappears and still add points to the score.
At the moment I have three scripts to carry this out (All have been shortened so I'm only showing the relevant code):
One which is attached to boxes that are thrown at enemies that detects if they have collided with an enemy prefab.
void OnCollisionEnter (Collision theCollision) {
if (canProjectileKill == true) {
// If the projectile hits any game object with the tag "Enemy" or "EnemyContainer".
if (theCollision.gameObject.tag == "Enemy") {
GameObject.Find("EnemyExplosion").GetComponent<enemyDeath>().ProjectileHasHitEnemy();
// Destroy the projectile itself.
Destroy (gameObject);
// Destroy the game object that the projectile has collided with (E.g. the enemy).
Destroy (theCollision.gameObject);
GameObject.Find("Manager").GetComponent<projectileSpawner>().deleteProjectile();
}
}
}
Another that is attached to the enemy prefabs which detects if they have been hit by a box.
void OnCollisionEnter (Collision theCollision) {
if(theCollision.gameObject.tag == "Projectile") {
GameObject.Find("EnemyExplosion").GetComponent<enemyDeath>().EnemyHasBeenHit();
}
}
I then run an if statement asking if both the box has hit the enemy prefab AND if the enemy prefab has been hit by the box in an attempt to identify a single prefab rather than all of them. However this still doesn't work.
public bool HasProjectileHitEnemy;
public bool HasEnemyBeenHitByProjectile;
void Start () {
gameObject.particleSystem.Stop();
HasProjectileHitEnemy = false;
HasEnemyBeenHitByProjectile = false;
}
public void ProjectileHasHitEnemy () {
// From projectile.
HasProjectileHitEnemy = true;
}
public void EnemyHasBeenHit () {
// From enemy.
HasEnemyBeenHitByProjectile = true;
PlayParticleSystem();
}
public void PlayParticleSystem () {
if (HasEnemyBeenHitByProjectile == true && HasProjectileHitEnemy == true) {
gameObject.particleSystem.Play();
HasProjectileHitEnemy = false;
HasEnemyBeenHitByProjectile = false;
}
}
}
I am aware this is a long question but I have been stuck on this for over a week, so any help would be much appreciated. Thank you :)
I'm not sure what kind of object EnemyExplosion is, but your problem seems to be the search for this object. During OnCollisionEnter you know exactly between which objects the collision occurred. But now you're starting a search for any object that is called EnemyExplosion. That's the reason your particles appear random.
Update:
Ok, with your structure something like that
EnemyContainer
- EnemyExplosion
- Particle System
- EnemyModel
- Collider
If EnemyModel contains the collider, you can get to EnemyExplosion and finally enemyDeath the following way.
var explosion = theCollision.transform.parent.gameObject.GetComponent<EnemyExplosion>();
explosion.GetComponent<enemyDeath>().ProjectileHasHitEnemy();
Now that you're accessing the correct object, you can remove some of your double checks and rely on one collider event.
I seem to have found a way around this. Instead I've just set it to instantiate the particle system whenever an enemy detects that it has collided with a projectile. I then use a Coroutine to delete the particle system 2 seconds after.

Categories