You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Unity中如何正确传递对象自身引用?解决空引用异常

Unity空引用异常修复:敌人随玩家移动触发移动的正确实现

问题场景

需求为敌人仅在玩家移动时才移动,创建敌人对象时需将其加入PlayerMovement的敌人列表。原实现代码运行时抛出以下异常:

NullReferenceException: Object reference not set to an instance of an object

原EnemyMovement代码

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

public class EnemyMovement : MonoBehaviour
{
    public PlayerMovement player;

    void Awake()
    {
      player.Subscribe(this);
    }

    public void Move()
    {
      print("Enemy moved!");
    }
}

原PlayerMovement代码

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

public class PlayerMovement : MonoBehaviour
{
  public static ArrayList enemies;

  void Start()
    {
      enemies = new ArrayList();
    }

  void Message(Direction direction)
  {
    foreach(EnemyMovement enemy in enemies)
    {
      enemy.Move();
    }
  }

  public void Subscribe(EnemyMovement enemy)
  {
    enemies.Add(enemy);
  }
}

错误原因分析

空引用异常的触发点主要有三个:

  1. EnemyMovement的player字段未赋值:如果未在Unity Inspector面板给Enemy对象的player字段拖入PlayerMovement实例,调用player.Subscribe(this)时player为null。
  2. 静态列表enemies初始化时机滞后:EnemyMovement的Awake执行时机可能早于PlayerMovement的Start,此时enemies还未被实例化,调用enemies.Add(enemy)会抛出异常。
  3. 非泛型ArrayList类型不安全:遍历ArrayList时强制转换为EnemyMovement,若列表中混入其他类型对象也可能引发问题。

修复方案

  1. 提前初始化敌人列表:将enemies的初始化从Start移到Awake,或用静态构造器保证列表首次访问时就完成实例化。
  2. 自动获取PlayerMovement实例:无需手动在Inspector赋值,让EnemyMovement自动查找场景中的玩家组件,避免遗漏。
  3. 使用泛型List替代ArrayList:提升类型安全性,避免强制转换引发的潜在错误。
  4. 可选:单例模式确保玩家实例唯一:若场景中只有一个玩家,单例模式能更可靠地获取玩家实例。

修正后的完整代码

修改后的PlayerMovement

using System.Collections.Generic;
using UnityEngine;

public class PlayerMovement : MonoBehaviour
{
    // 使用泛型List替代ArrayList,类型更安全
    public static List<EnemyMovement> enemies;

    // 单例模式,方便其他脚本快速获取玩家实例
    public static PlayerMovement Instance { get; private set; }

    void Awake()
    {
        // 提前初始化列表,避免Awake阶段访问时为空
        enemies = enemies ?? new List<EnemyMovement>();
        // 单例初始化逻辑
        if (Instance == null)
        {
            Instance = this;
            DontDestroyOnLoad(gameObject); // 可选:跨场景保留玩家对象
        }
        else
        {
            Destroy(gameObject);
        }
    }

    // 玩家移动时调用此方法,通知所有敌人移动
    public void OnPlayerMove(Direction direction)
    {
        // 用空条件运算符避免敌人被销毁后引发异常
        foreach (EnemyMovement enemy in enemies)
        {
            enemy?.Move();
        }
    }

    public void Subscribe(EnemyMovement enemy)
    {
        // 检查敌人有效性,避免重复添加
        if (enemy != null && !enemies.Contains(enemy))
        {
            enemies.Add(enemy);
        }
    }

    // 敌人销毁时取消订阅,避免空引用
    public void Unsubscribe(EnemyMovement enemy)
    {
        if (enemy != null && enemies.Contains(enemy))
        {
            enemies.Remove(enemy);
        }
    }
}

// 补充原代码缺失的Direction枚举定义
public enum Direction
{
    Up, Down, Left, Right
}

修改后的EnemyMovement

using UnityEngine;

public class EnemyMovement : MonoBehaviour
{
    void Awake()
    {
        // 自动查找玩家实例,无需手动赋值
        PlayerMovement player = PlayerMovement.Instance ?? FindObjectOfType<PlayerMovement>();
        player?.Subscribe(this);
    }

    public void Move()
    {
        print("Enemy moved!");
    }

    // 敌人销毁时主动取消订阅
    void OnDestroy()
    {
        PlayerMovement.Instance?.Unsubscribe(this);
    }
}

额外注意事项

  • 确保场景中存在且仅存在一个带有PlayerMovement组件的对象(单例模式下)。
  • 玩家移动逻辑中,需要调用PlayerMovement.Instance.OnPlayerMove(对应方向)来触发敌人移动。
  • 空条件运算符?.能有效避免因对象销毁导致的后续空引用异常。

内容的提问来源于stack exchange,提问作者user3574603

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.17 03:20:44