Changing delay spawn at runtime [C#] - 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
}
}
}

Related

Alternatives to Destroy() or SetActive()

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");
}
}

How to ensure objects are not spawned on top of one another (random spawn location)

I am creating an endless jumping game. I have created a lot of obstacles to spawn randomly for time and for spaces, but now I want to make the obstacles spawn not in the same place as the previous ones, because they are spawning but sometimes they can spawn on the top of the other or near the others or even inside them, so please help me!
code:
using UnityEngine;
using System.Collections;
public class SpawnObstacles : MonoBehaviour {
public GameObject[] Obstacle;
public float MINTObstacle;
public float MAXTObstacle;
public bool spawning = false;
public Transform pos;
void Update()
{
if (!spawning)
{
StartCoroutine("SpawnObstacle");
}
}
IEnumerator SpawnObstacle()
{
spawning = true;
yield return new WaitForSeconds(Random.Range(MINTObstacle, MAXTObstacle));
Vector2 finalposition = new Vector2(Random.Range(3,7), Random.Range(pos.position.y - 6f, pos.position.y - 6f));
Instantiate(Obstacle[Random.Range(0, Obstacle.Length)], finalposition, Quaternion.identity);
spawning = false;
}
}
I think what you want is some kind of bounding box around each obstacle that represents the "padding" that you want around that obstacle, where no other can be placed. You might use a primitive collider for this purpose.
When you create a new obstacle at a random position, you could check to see if its "padding" collides with another obstacle's "padding", and if so, re-calculate the random position.
One issue I foresee if if you have a very crowded space, this check might have to be re-done many many times before a valid location is found. You may want to limit the number of checks it can do, and if it doesn't find a valid spot it just doesn't spawn the object after 20 tries or so.

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();

Unity: Enemy Spawn / Health System

I'm working at an enemy spawn system. This is my code:
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
public class EnemyManager : MonoBehaviour
{
public GameObject shark; // prefab von Shark
public GameObject instanceShark; // globale Instanzvariable von Shark
public Transform[] spawnPoints; // An array of the spawn points this enemy can spawn from.
public float spawnTime = 3f; // How long between each spawn.
public int maximumSharks = 2;
private int currentSharks;
public int healthShark; // current Health von Shark
public int startinghealthShark = 200;
public float sinkSpeed = 2.5f;
bool isDead;
void Start ()
{
healthShark = startinghealthShark;
currentSharks = 0;
}
void Update ()
{
if (currentSharks <= maximumSharks) {
InvokeRepeating ("Spawn", spawnTime, spawnTime);
}
Debug.Log (currentSharks);
}
void Spawn ()
{
// Find a random index between zero and one less than the number of spawn points.
int spawnPointIndex = Random.Range (0, spawnPoints.Length);
// Create an instance of the enemy prefab at the randomly selected spawn point's position and rotation.
instanceShark = Instantiate (shark, spawnPoints[spawnPointIndex].position, spawnPoints[spawnPointIndex].rotation) as GameObject;
currentSharks++;
if (currentSharks >= maximumSharks) {
CancelInvoke("Spawn");
}
}
public void AddDamageToShark (int neuDamageWert) // Addiere zu damage. Public function, können andre scripts auch aufrufen
{
// If the enemy is dead...
if(isDead)
// ... no need to take damage so exit the function.
return;
healthShark -= neuDamageWert;
if (healthShark <= 0) { //tot
Death ();
}
Debug.Log (healthShark);
}
void Death ()
{
// The enemy is dead.
isDead = true;
currentSharks--;
Destroy (instanceShark);
Debug.Log ("dead?");
return;
}
What I want: Spawn enemies as long as maximum amount isn't reached (this part works so far) and destroy the enemy that has been shot and respawn another one (doesn't work).
This codes creates 2 sharks as enemies at the moment. The problem is when I damage one, only the last created instance gets destroyed even though I shot at the first shark. Also the health of the other instance and new spawning instance isn't affected at all.
I'd appreciate any advices, I spent ages with this code and it seems like I need some help regarding the instances - health logic.
Thank you very much
Split your spawn manager behaviour and your enemy behaviour and use interfaces to organize your code in a more SOLID approach. Turn your objects responsible for just one scope (Now your SpawnManager is current responsible for Enemy behaviours/responsabilities)
SpawnManager should be a singleton object with just one responsibility "to manage enemy spawn" with a maximunEnemyCount and a currentEnemyCount. You can spawn always when your currentEnemyCount < maximunEnemyCount.
OnTakeDamage() and OnDeath() should be interfaces of your enemy behaviour (in a separated script from SpawnManager, lets assume EnemyBehaviour script) and your destroy should target just the instance of itself Destroy(this.gameObject)
Remember to notify your SpawnManager when an enemy is dead on your OnDeath method to adjust enemyCurrentCount.
I think this is a more elegant way to do this task and less susceptible to bugs on managing enemy instances.
edited:
linked singleton references that I forgot before
you'll need a unique gameObject for manager that should know how much enemies can live and how many are living, nothing more about the enemies (enemy's health is a responsibility of enemy object in this case).
each enemy need to know when he/she dies and so notify the manager to decrement its counter.

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