Reputation: 77
I am getting the above error and I tried to use debug.log to print where the error is. I am creating a type of tank obj. which fire to some instantiated obj. Instantiated object are attacker.
In update I am using foreach loop to loop through all the instantiated object. If found and If object are in range fire.
void Update () {
if (attacker!= null )
{
//Debug.Log("inside att");
attacker = FindObjectsOfType<Attacker>();
}
// fire only if attacker is in range
if (IfInRange() && attacker!= null && running)
{
faceTowardTarget();
Fire();
}
}
bool IfInRange()
{// run through all the instantiated attacker
foreach (Attacker currentAttacker in attacker)
This works fine but sometimes gives above. At the end in the console the loop goes on and on and currentAttacker is null in the end. I tried to print that in console. but it don't go in other if statement
{ //if attacker is in range
if (attacker != null )
{
Debug.Log(currentAttacker.GetComponent<Health>().isAlive);
if (Vector2.Distance(transform.position, currentAttacker.transform.position) < minDistance)
{
attackerPos = currentAttacker.transform;
return true;
}
}
if (currentAttacker == null)
{
Debug.Log("curre Attacker null");
running = false;
return false;
}
}return false;
}
Attacker have a simple health script which deal with damage if hit by projectile.
Void update()
{
if (health <= 0)
{
**is there any better way to destroy an obj. If i destroy gameObject that error appear and it get stuck in foreach loop**
// Destroy(gameObject);
noOfKilled++;
isAlive = false;
scoreHolder.addScore(scoreValue);
}
}
Thank you so much for helping. I tried searching but unable to resolve this.
Upvotes: 1
Views: 1030
Reputation: 1712
I'm going to assume, the error shows up when instantiating another tank after the first tank is destroyed? all your tanks are clones of tank1, so when tank1 is destroyed, you get a null pointer, because its trying to instantiate an object thats not there anymore.
2 solutions...
A) UGLY: change tank1's initial position to somewhere it will never be destroyed, say 5000,5000,5000. since the tank cant be destroyed, it will never be null
B) THE SMART, CORRECT WAY: make a prefab. make a folder called prefabs, drag tank1 into it. select your script which spawns tanks, and drag the PREFAB copy of tank1 into it. now you always have an instance of tank1 and your null pointer is gone!
i cannot stress enough the importance of proper use of prefabs, not just for performance and reliabilty, but sanity as well...
Upvotes: 1
Reputation: 505
FindObjectsOfType<Attacker>(); //returns an array
will return an array, not an object, use attackers[0] for firstone or use
FindObjectOfType<Attacker>()
(will return the first found).
Using objectsOfType you always have an empty array, so
if (attacker==null)
of course will return false because the array isn't null, its just empty. This is why it doesn't go in your second if statement;
Also
void update()
{
if (health <= 0)
{
**is there any better way to destroy an obj. If i destroy gameObject that error appear and it get stuck in foreach loop**
noOfKilled++;
isAlive = false;
scoreHolder.addScore(scoreValue);
Destroy(gameObject);// destroy it after at the end, not before it have something else to do
}
}
Upvotes: 0
Reputation: 125275
The quick and dirty way to fix this is to use the DestroyImmediate
function instead of the Destroy
function. Using DestroyImmediate(gameObject)
will destroy the object in that frame and FindObjectsOfType<Attacker>()
cannot find it so it won't be accessed in the foreach
loop.
The proper way to this is to create a List
in your main code to hold the instantiated Attacker
script:
public GameObject attackerPrefab;
public List<Attacker> attacker = new List<Attacker>();
When you instantiate the prefab, add the Attacker
script to the List
:
//Instantiate
GameObject obj = Instantiate(attackerPrefab);
//Get Attacker component
Attacker tempAttacker = obj.GetComponent<Attacker>();
//Add Attacker component to List
attacker.Add(tempAttacker);
Finally, when you want to destroy the attacker object in your health script, remove it from the List
then destroy it. By removing it from the List
before destroying it, you won't be accessing an object marked as destroyed.
//Get the script that stores the List of Attacker
Test otherScript = GameObject.Find("NameOfGameObjectYourScriptIsAttachedTo").GetComponent<Test>();
//Remove from the List
otherScript.attacker.Remove(gameObject.GetComponent<Attacker>());
//Now, destroy this gameObject
Destroy(gameObject);
Upvotes: 5
Reputation: 328
There many options to solve this problem. One Option is don't use a foreach loop when u edit the loop. But when u want to keep the foreach loop just save the objects in a other list and destroy them after the foreach loop.
Example1
for(int i = 0; i < attackers.Count; i++)
{
//if in range
attackers.RemoveAt(i);
i--;
}
Example2
List<string> attackers = new List<string>();
List<string> _shootAt = new List<string>();
foreach(string attacker in attackers)
{
//if in range
_shootAt.Add(attacker);
}
foreach (string attacker in _shootAt)
{
//if in range
attackers.Remove(attacker);
}
I hope that can help you
Upvotes: 2