Targeting Enemy after death raises bug

EDIT: While I was able to recreate this several times, even after exiting and entering the Editor, and not changing a single script It now seems to be working as intended. The only thing I did was first trying to see if I can target a different target, and then trying again and it now works properly. I have no idea what happened.
The only thing I noticed was that the editor compiled again (I didn’t change any code).
----------------------------------------------------------------------------------------------------------------------

Hi all, getting the following bug on:
(Unity 3rd Person Combat & Traversal > Enemy AI> Dead States)

Killing the enemy cancels the target event, it also properly destroys the “Target” script. But if I press TAB again when the enemy is on screen after death I get the following errors.

From what I understand I should have all the proper null checks implemented in my code.
I tried copying the “Targeter” and “Target” Scripts from gitlab to make sure I don’t have any typos or missing operators.

Error Details:

MissingReferenceException: The object of type 'Target' 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.
Targeter.SelectTarget () (at Assets/Scripts/Combat/Targeting/Targeter.cs:49)
PlayerFreeLookState.OnTarget () (at Assets/Scripts/StateMachines/Player/PlayerFreeLookState.cs:57)
InputReader.OnTarget (UnityEngine.InputSystem.InputAction+CallbackContext context) (at Assets/Scripts/InputReader.cs:57)
UnityEngine.InputSystem.Utilities.DelegateHelpers.InvokeCallbacksSafe[TValue] (UnityEngine.InputSystem.Utilities.CallbackArray`1[System.Action`1[TValue]]& callbacks, TValue argument, System.String callbackName, System.Object context) (at ./Library/PackageCache/com.unity.inputsystem@1.5.0/InputSystem/Utilities/DelegateHelpers.cs:46)
UnityEngine.InputSystem.LowLevel.<>c__DisplayClass7_0:<set_onUpdate>b__0(NativeInputUpdateType, NativeInputEventBuffer*)
UnityEngineInternal.Input.NativeInputSystem:NotifyUpdate(NativeInputUpdateType, IntPtr)
MissingReferenceException while executing 'performed' callbacks of 'Player/Target[/Keyboard/tab]'
UnityEngine.InputSystem.LowLevel.NativeInputRuntime/<>c__DisplayClass7_0:<set_onUpdate>b__0 (UnityEngineInternal.Input.NativeInputUpdateType,UnityEngineInternal.Input.NativeInputEventBuffer*)
UnityEngineInternal.Input.NativeInputSystem:NotifyUpdate (UnityEngineInternal.Input.NativeInputUpdateType,intptr)

Scripts:
(if any more are needed I will provide)

Targeter:

using System;
using System.Collections;
using System.Collections.Generic;
using Cinemachine;
using UnityEngine;

public class Targeter : MonoBehaviour
{
    [SerializeField] private CinemachineTargetGroup cineTargetGroup;

    private Camera mainCamera;
    private List<Target> targets = new List<Target>();

    public Target CurrentTarget { get; private set; }

    private void Start()
    {
        mainCamera = Camera.main;
    }


    private void OnTriggerEnter(Collider other)
    {
        if (!other.TryGetComponent<Target>(out Target target)) { return; }

        targets.Add(target);
        target.OnDestroyed += RemoveTarget; //whenever a target is destroyed, subscribe to OnDestroyed Event on "Target.cs", AND call the RemoveTarget method.
    }


    private void OnTriggerExit(Collider other)
    {
        if (!other.TryGetComponent<Target>(out Target target)) { return; }

        RemoveTarget(target);
    }

    public bool SelectTarget()
    {
        if (targets.Count == 0) { return false; }

        Target closestTarget = null;
        float closestTargetDistance = Mathf.Infinity;

        foreach (Target target in targets)
        {
            Vector2 viewPos = mainCamera.WorldToViewportPoint(target.transform.position);

            if (viewPos.x < 0 || viewPos.x > 1 || viewPos.y < 0 || viewPos.y > 1)  //check to see if target is within ViewportPoint (target is visible?)
            {
                continue;
            }

            Vector2 toCenter = viewPos - new Vector2(0.5f, 0.5f); //returns current viewpos distance from screen center
            if (toCenter.sqrMagnitude < closestTargetDistance)
            {
                closestTarget = target;
                closestTargetDistance = toCenter.sqrMagnitude;  //update closest target distance
            }
        }

        if (closestTarget == null) { return false; } //either no targets or no targets on the screen

        CurrentTarget = closestTarget;
        cineTargetGroup.AddMember(CurrentTarget.transform, 1f, 2f);

        return true;
    }

    public void Cancel()
    {
        if (CurrentTarget == null) { return; }

        cineTargetGroup.RemoveMember(CurrentTarget.transform);
        CurrentTarget = null;
    }

    private void RemoveTarget(Target target)
    {
        if (CurrentTarget == target)
        {
            cineTargetGroup.RemoveMember(CurrentTarget.transform); //Remove from cinemachine target group
            CurrentTarget = null;
        }
        
        target.OnDestroyed -= RemoveTarget; //unsubscribe from OnDestoryed event on "Target.cs"
        targets.Remove(target);
    }
}

Player State Machine

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PlayerStateMachine : StateMachine
{
    [field: SerializeField] public InputReader InputReader { get; private set; }
    [field: SerializeField] public CharacterController Controller { get; private set; }
    [field: SerializeField] public Animator Animator { get; private set; }
    [field: SerializeField] public Targeter Targeter { get; private set; }
    [field: SerializeField] public ForceReceiver ForceReceiver { get; private set; }
    [field: SerializeField] public WeaponDamage Weapon { get; private set; }
    [field: SerializeField] public Health Health { get; private set; }

    [field: SerializeField] public float FreeLookMovementSpeed { get; private set; }
    [field: SerializeField] public float TargetingMovementSpeed { get; private set; }
    [field: SerializeField] public float RotationDamping { get; private set; }


    [field: SerializeField] public Attack[] Attacks { get; private set; }

    public Transform mainCameraTransform { get; private set; }

    private void Start()
    {
        mainCameraTransform = Camera.main.transform;
        SwitchState(new PlayerFreeLookState(this));
    }

    private void OnEnable()
    {
        Health.OnTakeDamage += HandleTakeDamage;
        Health.OnDie += HandleDie;
    }

    private void OnDisable()
    {
        Health.OnTakeDamage -= HandleTakeDamage;
        Health.OnDie -= HandleDie;

    }

    private void HandleTakeDamage()
    {
        SwitchState(new PlayerImpactState(this));
    }
    public void HandleDie()
    {
        SwitchState(new PlayerDeadState(this));

    }
}

Target

using System;

using System.Collections;

using System.Collections.Generic;

using UnityEngine;

public class Target : MonoBehaviour

{

    public event Action<Target> OnDestroyed;

    private void OnDestroy()

    {

        OnDestroyed?.Invoke(this);

    }

}

Dead State

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class EnemyDeadState : EnemyBaseState
{
    public EnemyDeadState(EnemyStateMachine stateMachine) : base(stateMachine) { }

    public override void Enter()
    {
        //TODO:toggle ragdoll
        stateMachine.Weapon.gameObject.SetActive(false);
        GameObject.Destroy(stateMachine.Target); //removes target on death
    }

    public override void Tick(float deltaTime) { }

    public override void Exit() { }
}

Input Reader:

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.InputSystem;

public class InputReader : MonoBehaviour, Controls.IPlayerActions
{

    public bool IsAttacking { get; private set; }
    public Vector2 MovementValue { get; private set; }

    public event Action JumpEvent;
    public event Action DodgeEvent;
    public event Action TargetEvent;
    public event Action CancelEvent;

    private Controls controls;

    private void Start()
    {
        controls = new Controls();
        controls.Player.SetCallbacks(this);
        controls.Player.Enable();
    }

    private void OnDestory()
    {
        controls.Player.Disable();
    }

    public void OnJump(InputAction.CallbackContext context)
    {
        if (!context.performed) { return; }
        JumpEvent?.Invoke();
    }

    public void OnDodge(InputAction.CallbackContext context)
    {
        if (!context.performed) { return; }
        DodgeEvent?.Invoke();
    }

    public void OnMove(InputAction.CallbackContext context)
    {
        MovementValue = context.ReadValue<Vector2>();
    }

    public void OnLook(InputAction.CallbackContext context)
    {

    }

    public void OnTarget(InputAction.CallbackContext context)
    {
        if (!context.performed) { return; }
        TargetEvent?.Invoke();
    }

    public void OnCancel(InputAction.CallbackContext context)
    {
        if (!context.performed) { return; }
        CancelEvent?.Invoke();
    }

    public void OnAttack(InputAction.CallbackContext context)
    {
        if (context.performed) { IsAttacking = true; }
        if (context.canceled) { IsAttacking = false; }
    }
}

Any suggestions would be much appreciated. :pray:

It’s possible that Unity did not notice a change made to the code (this is rare, but it does happen from time to time). Often, the solution for this, if you’re not seeing the code re-compile, is to return to the code editor and force a save using the save button.

1 Like

Privacy & Terms