NullReferenceException & using methods without direct reference - c#

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

Related

Unity how to check what object triggered a trigger

I am in the middle of creating a simple 3d platformer in unity and i'm trying to make it so if you are in a certain radius and you hit the "f" key the enemy will disappear. the way i'm checking to make sure the enemy is close enough to be attacked is to have trigger sphere and when its triggered and you press "f" the enemy will be removed. i'm currently trying to see what collided with the trigger but i cant figure it out. here is my current code
using UnityEngine;
public class PlayerCombat : MonoBehaviour
{
public GameObject Enemy1;
public GameObject Enemy2;
public GameObject Enemy3;
public GameObject Enemy4;
// Update is called once per frame
void OnTriggerEnter (Trigger triggerInfo)
{
if (triggerInfo.collider.tag == "Enemy" & Input.GetKey("f"))
{
if (triggerInfo.collider.name == Enemy)
{
Enemy1.SetActive(false);
}
}
}
}
The signature is and has always been OnTriggerEnter(Collider) otherwise that message method will not be recognized by Unity and not get called at all!
And then you already have the according Collider ... what else do you need?
public class PlayerCombat : MonoBehaviour
{
// There is no need to know the enemy references beforehand at all
// you will get all required references from the Collider parameter of OnTriggerEnter itself
// I personally would however expose these two settings to the Inspector to be more flexible
// this way you can adjust these two settings within Unity without having to touch your code
public string listenToTag = "Enemy";
public KeyCode listenToKey = KeyCode.F;
// The signature has to be this otherwise the message method is never invoked at all
private void OnTriggerEnter (Collider other)
{
// Rather use "CompareTag" instead of `==` since the latter will silently fail
// for typos and nonexistent tags while "CompareTag" shows an error which is good for your debugging
// And in general you want to use the logical "&&" instead of the bitwise operator "&" for bools
if (other.CompareTag(listenToTag) && Input.GetKey(listenToKey))
{
// simply set the object you collide with inactive
other.gameObject.SetActive(false);
}
}
}
Finally just make sure that all your enemy instances actually have the tag Enemy
First, I'm pretty sure OnTriggerEnter takes a Collider as its parameter, no? https://docs.unity3d.com/ScriptReference/Collider.OnTriggerEnter.html
You can get the name of the collided object like so
//Upon collision with another GameObject,
private void OnTriggerEnter(Collider other)
{
Debug.Log($"collided with {other.gameObject.name}");
//you can check for specific components too
MyComponent myComponent = other.GetComponent<MyComponent>();
if (myComponent != null) {
// do something if it has that component on it!
}
}

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

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
}
}
}

Score Count not working on a prefab

This is semi complicated of a question but I'll do my best to explain it:
I am making this mobile game in which you have to shoot four cubes. I'm trying to make it so when the cubes are shot by a bullet, they're destroyed and a UI text says 1/4, to 4/4 whenever a cube is shot. But it's being really weird and only counts to 1/4 even when all four cubes are shot and destroyed. I put these two scripts on the bullets (I made two separate scripts to see if that would do anything, it didn't)
And to give a better idea of what I'm talking about, here's a screenshot of the game itself.
I've been using Unity for about 6 days, so I apologize for anything I say that's noob-ish.
EDIT
So I combined the two scripts onto an empty gameobject and here's the new script:
using UnityEngine;
using System.Collections;
using UnityEngine.UI;
public class GameManagerScript : MonoBehaviour {
public GameObject cubes;
public Text countText;
public int cubeCount;
public Transform target;
// Use this for initialization
void Start () {
}
void OnTriggerEnter(Collider other)
{
cubes = other.gameObject;
}
// Update is called once per frame
void Update () {
cubes.transform.position = Vector3.MoveTowards(transform.position, target.position, 1f * Time.deltaTime);
if (cubes.gameObject.tag == "BULLET")
{
cubeCount = cubeCount + 1;
countText.text = cubeCount + "/4";
cubes.SetActive(false);
}
}
}
ANOTHER EDIT
I tried everything, so is there a way to detect when all the children in a parent on the Hierarchy are destroyed? Instead of counting up? This can give a better idea:
So I want to be able to detect when Cube, Cube1, Cube2, and Cube3 have all been destroyed.
The answer is pretty simple: Since every individual bullet has that script, each bullet has its own score.
For something like a score you want a single spot to store it, e.g. a script on an empty gameobject that serves as game controller. Just access that in the collision and increase the score (maybe have a look on singletons here).
You can combine those two scripts and actually it might be better to not have this on the bullet, but on the target because there are probably less of them which will save you some performance. (And it does more sense from a logical point of view.)
Edit:
I assume you create the bullets using Instantiate with a prefab. A prefab (= blueprint) is not actually in the game (only objects that are in the scene/hierarchy are in the game). Every use of Instantiate will create a new instance of that prefab with it's own version of components. A singleton is a thing that can only exist once, but also and that is why I mention it here, you can access it without something like Find. It is some sort of static. And an empty gameobject is just an object without visuals. You can easily create one in unity (rightclick > create empty). They are typically used as container and scriptholders.
Edit:
What you want is:
An empty gameobject with a script which holds the score.
A script that detects the collision using OnTriggerEnter and this script will either be on the bullets or on the targets.
Now, this is just a very quick example and can be optimized, but I hope this will give you an idea.
The script for the score, to be placed on an empty gameobject:
public class ScoreManager : MonoBehaviour
{
public Text scoreText; // the text object that displays the score, populate e.g. via inspector
private int score;
public void IncrementScore()
{
score++;
scoreText.text = score.ToString();
}
}
The collision script as bullet version:
public class Bullet : MonoBehaviour
{
private ScoreManager scoreManager;
private void Start()
{
scoreManager = GameObject.FindWithTag("GameManager").GetComponent<ScoreManager>(); // give the score manager empty gameobject that tag
}
private void OnTriggerEnter(Collider other)
{
if(other.CompareTag("Target") == true)
{
// update score
scoreManager.IncrementScore();
// handle target, in this example it's just destroyed
Destroy(other.gameObject);
}
}
}

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