MissingReferenceException: The object of type ‘Enemy’ has been destroyed but you are still trying to access it.
Your script should either check if it is null or you should not destroy the object.
public void OnDamaged(object source, int damage)
{
if (gameObject != null)
{
Destroy(gameObject);
}
}
So basically I made a script for all enemies. Which handells all the basic stuff like dealing and recieving damage(working with another class that handells all the events). But now if the player kills one enemy i get that error. Anyone knows a fix?
The code you posted isn’t where the error came from. That’s where you’re destroying the object, but something else is referencing the destroyed object. Doubleclicking on the error will take you to where the problem is.
It takes me to this section. I think it is because the script is attatched to 3 enemies. I don’t get the error when i kill the first enemy. Only after that when i try to kill another one it doesnt work.
The above code you posted doesn’t really tell us much, but my guess is you have an array/list/whatever collection of your enemy objects being referenced by the player object, and when the player attacks an enemy, it loops through that collection, compares which one is hit, and then calls the OnDamaged method. Something like this:
public LayerMask enemyLayer;
public Enemy[] enemies;
void Attack() {
RaycastHit hit;
if(Physics.Raycast(transform.position, transform.forward, out hit, 10f, enemyLayer) {
for(int i = 0; i < enemies.Length; i++) {
//Assuming "Enemy" has a "Colider" property that references its own Collider component.
if(hit.collider == enemies[i].Collider) {
enemies[i].OnDamaged(this, 10f);
}
}
}
}
If that’s true, then this would be the cause; an enemy gets destroyed, but it’s still referenced in the collection. So the next time the loop runs, it tries to compare some attribute of a non-existent enemy object.
You’d need to remove the reference after the enemy gets destroyed.
That’s a bit different than what I guessed though. In the case here where you’re creating a new local array from a OverlapCircleAll method call, it shouldn’t have any references to destroyed objects, since the overlap-circle wouldn’t detect them. Converting the result into a List wouldn’t change anything either.
This code here is calling a different method than the above OnDamaged method posted originally though. Could you please post both the entire player and enemy scripts?
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Enemy : MonoBehaviour
{
Damage damageScript;
[SerializeField]
int health;
[SerializeField]
GameObject currentGameObject;
void DealDamage()
{
damageScript.DealDamage();
}
private void OnTriggerEnter2D(Collider2D collision)
{
if (collision.tag == "Player")
{
DealDamage();
}
}
public void OnDamaged(object source, int damage)
{
Destroy(gameObject);
}
private void Start()
{
damageScript = GameObject.FindGameObjectWithTag("Enemy").GetComponent<Damage>();
}
}
Damage.cs (the script that handells all the ddamage):
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Damage : MonoBehaviour
{
Player playerScript;
Enemy enemyScript;
[SerializeField]
int damage;
[SerializeField]
string source;
public event EventHandler<int> DeltDamage;
public void DealDamage()
{
OnDeltDamage(damage);
}
protected virtual void OnDeltDamage(int damage)
{
if (DeltDamage != null)
{
DeltDamage(this, damage);
}
}
// Start is called before the first frame update
void Start()
{
if (source == "Enemy")
{
playerScript = GameObject.FindGameObjectWithTag("Player").GetComponent<Player>();
DeltDamage += playerScript.OnDamaged;
}
else if (source == "Player")
{
enemyScript = GameObject.FindGameObjectWithTag("Enemy").GetComponent<Enemy>();
DeltDamage += enemyScript.OnDamaged;
}
}
// Update is called once per frame
void Update()
{
}
}
Player.cs:
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
public class Player : MonoBehaviour
{
[SerializeField]
float speed, checkRadius, jumpForce, wallSlidingSpeed, xWallForce, yWallForce, wallJumpTime;
Rigidbody2D rb;
bool facingRight = true;
bool isGrounded, isTouchingFront, wallSliding, wallJumping;
[SerializeField]
Transform groundCheck, frontCheck;
[SerializeField]
LayerMask whatIsGround;
Animator anim;
private Damage damageScript;
[SerializeField]
int health;
[SerializeField]
float attackCooldown, attackRange;
float nextAttackTime;
[SerializeField]
Transform attackPoint;
[SerializeField]
LayerMask enemyLayer;
public void OnDamaged(object source, int damage)
{
health -= damage;
print($"Ouch you got damaged, health:{health}");
}
// Start is called before the first frame update
void Start()
{
rb = GetComponent<Rigidbody2D>();
anim = GetComponent<Animator>();
damageScript = GameObject.FindGameObjectWithTag("Player").GetComponent<Damage>();
}
void Attack()
{
var enemiesToDamageArray = Physics2D.OverlapCircleAll(attackPoint.position, attackRange, enemyLayer);
var enemiesToDamage = enemiesToDamageArray.ToList();
for (int i = 0; i < enemiesToDamage.Count; i++)
{
damageScript.DealDamage();
enemiesToDamage.RemoveAt(i);
}
}
// Update is called once per frame
void Update()
{
if (Time.time > nextAttackTime)
{
if (Input.GetKeyDown(KeyCode.Mouse0))
{
Attack();
anim.SetTrigger("Attack");
nextAttackTime = Time.time + attackCooldown;
}
}
float input = Input.GetAxisRaw("Horizontal");
rb.velocity = new Vector2(input * speed, rb.velocity.y);
if (input > 0 && facingRight == false)
{
Flip();
}
else if (input < 0 && facingRight)
{
Flip();
}
if (input != 0)
{
anim.SetBool("isRunning", true);
}
else
{
anim.SetBool("isRunning", false);
}
if (isGrounded)
{
anim.SetBool("isJumping", false);
}
else
{
anim.SetBool("isJumping", true);
}
isGrounded = Physics2D.OverlapCircle(groundCheck.position, checkRadius, whatIsGround);
if (Input.GetKeyDown(KeyCode.Space) && isGrounded)
{
rb.velocity = Vector2.up * jumpForce;
}
isTouchingFront = Physics2D.OverlapCircle(frontCheck.position, checkRadius, whatIsGround);
if (isTouchingFront && !isGrounded && input != 0)
{
wallSliding = true;
}
else
{
wallSliding = false;
}
if (wallSliding)
{
rb.velocity = new Vector2(rb.velocity.x, Mathf.Clamp(rb.velocity.y, -wallSlidingSpeed, float.MaxValue));
}
if (Input.GetKeyDown(KeyCode.Space) && wallSliding)
{
wallJumping = true;
Invoke("SetWallJumpingToFalse", wallJumpTime);
}
if (wallJumping)
{
rb.velocity = new Vector2(xWallForce * -input, yWallForce);
}
}
void Flip()
{
transform.localScale = new Vector3(-transform.localScale.x, transform.localScale.y, transform.localScale.z);
facingRight = !facingRight;
}
void SetWallJumpingToFalse()
{
wallJumping = false;
}
private void OnDrawGizmosSelected()
{
Gizmos.color = Color.red;
Gizmos.DrawWireSphere(attackPoint.position, attackRange);
}
}
You’re removing the Enemy from your newly created list you are iterating over, not the original array. I’m guessing this new list disappears after this code completes, so is pretty meaningless to remove it there. Also, removing items from a collection you are iterating over will give problematic results, as you are iterating by index number as you are changing the index numbers due to your removals. This will cause you to skip any items in the collection which follow one which you remove.
say you’re at index 5, you remove the one at index 5 - this causes whatever is at index 6 to change to become index 5, next iteration you are now on index 6, skipping what used to be at 6 but is now at 5
What you should instead probably do, is change enemiesToDamageArray into a list. This is because you can’t remove indexes from an array. Then you use ToArray on the list similar to above. When you remove, you remove from the original list, not the array you are iterating over. When you remove you just call Remove and pass in the reference to the enemy itself, instead of using RemoveAt since you can’t trust index numbers in a collection where you are changing index numbers.
FindGameObjectWithTag (as well as all the other GameObject.Find... methods) will select the first object it finds tagged as “Enemy” in the scene.
This means all of your Damage scripts in your Enemy objects are referencing one specific Damage component on one specific Enemy in the scene. When that enemy is destroyed, the reference to the Damage component becomes null for every other Enemy in the scene.
There’s no need to use GameObject.Find... in this instance. To get the reference of a component on the same GameObject that a script is attached to, you simply call GetComponent:
damageScript = GetComponent<Damage>();
This will now make it so each individual Enemy in the scene is referencing the Damage component on their own GameObjects.
You should make the same change in your Player class as well, where you have this line:
Oh never knew that. There is still one thing now i get this:
ArgumentException: Value does not fall within the expected range.
Damage.Start () (at Assets/Scripts/Damage.cs:46)
from this line:
enemyScript = gameObject.GetComponent();