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); } }
错误原因分析
空引用异常的触发点主要有三个:
EnemyMovement的player字段未赋值:如果未在Unity Inspector面板给Enemy对象的player字段拖入PlayerMovement实例,调用player.Subscribe(this)时player为null。- 静态列表
enemies初始化时机滞后:EnemyMovement的Awake执行时机可能早于PlayerMovement的Start,此时enemies还未被实例化,调用enemies.Add(enemy)会抛出异常。 - 非泛型
ArrayList类型不安全:遍历ArrayList时强制转换为EnemyMovement,若列表中混入其他类型对象也可能引发问题。
修复方案
- 提前初始化敌人列表:将
enemies的初始化从Start移到Awake,或用静态构造器保证列表首次访问时就完成实例化。 - 自动获取
PlayerMovement实例:无需手动在Inspector赋值,让EnemyMovement自动查找场景中的玩家组件,避免遗漏。 - 使用泛型
List替代ArrayList:提升类型安全性,避免强制转换引发的潜在错误。 - 可选:单例模式确保玩家实例唯一:若场景中只有一个玩家,单例模式能更可靠地获取玩家实例。
修正后的完整代码
修改后的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
相关产品推荐
相关产品推荐

