**Title:** Best Practice for Null Reference Checks in Unity

Message:

Hello everyone,

I’m currently working on an attack system for my game and have created a script that controls the attack logic and interactions with enemies. I wanted to ask how you handle null references in Unity.

At the moment, I occasionally check if certain components, like currentWeaponData or CameraSystemReference, are null before using them. I mostly do these checks when I get an error message, but not always.

Now I’m wondering if it’s a good long-term practice to always include a null check, or if it’s fine to check only when an error occurs. Especially when I later expand the game or project, there might be something I overlook, and an issue could arise much later. Could there be problems if I don’t consistently check for null references everywhere?

What are your best practices in this regard? Should you always check for null references, or is it enough to do so when necessary?

using UnityEngine;

public class AttackScript : MonoBehaviour
{
    [Header("Important References")]
    public PlayerAttackData currentWeaponData; // Zugewiesene Waffendaten aus dem ScriptableObject
    public GameObject rightAttackPosition;
    public GameObject leftAttackPosition;
    public GameObject upAttackPosition;
    public GameObject downAttackPosition;
    public GameObject playerPosition;
    public LayerMask standardEnemy;
    public Camera_System cameraSystemReference; // Referenz auf das Camera_System-Skript

    // Laufzeitvariablen, die nur für diese Instanz gelten:
    private float currentCooldown = 0f;
    private bool isCurrentlyAttacking = false;
    private GameObject activeAttackPosition;

    // Speicherung der Position des Gegners
    private Vector2 enemyPosition;

    void Update()
    {
        // Reduziere den Cooldown-Timer
        if (currentCooldown > 0)
        {
            currentCooldown -= Time.deltaTime;
        }

        UpdateAttackDirection(); // Bestimme die Angriffsrichtung anhand der Tastatureingabe

        // Prüfe, ob ein Angriff ausgelöst wird (linke Maustaste) und der Cooldown abgelaufen ist
        if (Input.GetMouseButtonDown(0) && cameraSystemReference != null && currentCooldown <= 0)
        {
            isCurrentlyAttacking = true;
            cameraSystemReference.TriggerShake(); // Kamera-Shake-Effekt auslösen
            PerformAttack();                // Führe den Angriff aus
            currentCooldown = currentWeaponData.attackCooldown; // Setze den Cooldown zurück
            isCurrentlyAttacking = false;
        }
    }

    private void UpdateAttackDirection()
    {
        // Setze die aktive Angriffsposition je nach gedrückter Taste
        if (Input.GetKey(KeyCode.W))
            activeAttackPosition = upAttackPosition;
        else if (Input.GetKey(KeyCode.S))
            activeAttackPosition = downAttackPosition;
        else if (Input.GetKey(KeyCode.A))
            activeAttackPosition = leftAttackPosition;
        else if (Input.GetKey(KeyCode.D))
            activeAttackPosition = rightAttackPosition;

        // Fallback, falls keine Richtungstaste gedrückt wird
        if (activeAttackPosition == null)
            activeAttackPosition = rightAttackPosition;
    }

    private void PerformAttack()
    {
        if (activeAttackPosition != null && currentWeaponData != null)
        {
            // Erfasse alle Gegner innerhalb der definierten Trefferbox
            Collider2D[] enemiesInRange = Physics2D.OverlapBoxAll(
                activeAttackPosition.transform.position, currentWeaponData.boxSize, 0f, standardEnemy
            );

            foreach (var enemy in enemiesInRange)
            {
                // Hier wird angenommen, dass jeder Gegner sein eigenes Health-Skript hat.
                Enemyhealth.StandartHealth -= currentWeaponData.attackDamage;

                if (enemy != null)
                {
                    enemyPosition = enemy.transform.position;
                    ApplyKnockBackFromAttack(enemy);
                }
            }
        }
    }

    private void ApplyKnockBackFromAttack(Collider2D enemy)
    {
        Rigidbody2D enemyRb = enemy.GetComponent<Rigidbody2D>();

        if (enemyRb != null)
        {
            // Berechne die Richtung des Knockbacks
            Vector2 knockBackDirection = (enemy.transform.position - playerPosition.transform.position).normalized;
            enemyRb.AddForce(knockBackDirection * currentWeaponData.knockBackForce, ForceMode2D.Impulse);
        }
    }

    private void OnDrawGizmos()
    {
        Gizmos.color = Color.blue;
        if (activeAttackPosition != null)
        {
            Gizmos.matrix = Matrix4x4.TRS(activeAttackPosition.transform.position, Quaternion.identity, Vector3.one);
            Gizmos.DrawWireCube(Vector3.zero, currentWeaponData != null ? currentWeaponData.boxSize : Vector2.one);
        }
    }
}
 

Looking forward to your responses!

Thanks in advance!

Oceans of digital ink have been spilled all over the web (and even in print) on the topic of the correct ways to handle runtime errors.

You’re welcome to choose whatever approach you like.

Personally, I do not check things that my script cannot function without. If it is null, I want it to blow up so I can fix it immediately. After all, it’s not optional or external.

Anything optional / external, that’s what you want to check.

If making that distinction is difficult for you, then focus on understanding what is critical and what is optional in your program so you can engineer your solution appropriately.

Every character in code you type is a character that can have a bug in it. Don’t write unnecessary code.

2 Likes

Thank you for your detailed response! It was really helpful, especially the distinction between critical and optional dependencies. I’ll make sure to keep that in mind moving forward!