Unity AR:运行时实例化预制体后如何正确获取Script Component?
运行时实例化组件的NullReferenceException问题解决
问题场景
我需要引用仅在运行时而非提前实例化的Component:
- 场景中有个名为
GroundPlacement的游戏对象,挂载了ARTapToPlaceObjects脚本,负责在运行时把地面预制体(ground prefab)实例化到场景里; - 这个地面预制体带有子物体,子物体上挂载了
Spawner脚本; ARTapToPlaceObjects里的objectToPlace变量存的是地面预制体,placedObject是实例化后的地面对象。
现在多个脚本要调用Spawner的功能,但都抛出了NullReferenceException:
ARTapToPlaceObjects的Awake方法里调用FindObjectOfType<Spawner>(),因为预制体还没实例化,得到空引用,导致ResetGround调用spawnerRef的方法时出错;GamePiece脚本的Awake方法里,尝试通过groundPlacementRef.PlacedObject.GetComponentInChildren<Spawner>()获取引用,同样因为地面还没实例化,PlacedObject为空,导致OnMouseDown无法执行。
问题原因
- Unity的
Awake方法是场景加载完成后立刻执行的,这时候地面预制体还没被实例化,Spawner组件根本不在场景里,FindObjectOfType自然找不到东西; GamePiece的Awake执行时机早于地面实例化的时机,groundPlacementRef.PlacedObject还没被赋值,调用GetComponentInChildren肯定会空引用。
解决方法
1. 修复ARTapToPlaceObjects脚本
别在Awake里提前拿Spawner引用,等地面实例化完成后再获取:
// 实例化地面的方法示例 public void PlaceObject() { placedObject = Instantiate(objectToPlace, transform.position, transform.rotation); // 实例化完成后立即获取Spawner spawnerRef = placedObject.GetComponentInChildren<Spawner>(); } // 调用Spawner方法前先判空 public void ResetGround() { spawnerRef?.YourSpawnerMethod(); // 替换成你要调用的实际方法 }
如果调用Spawner的频率不高,也可以每次调用时实时获取,省得存引用:
public void ResetGround() { if (placedObject == null) return; var spawner = placedObject.GetComponentInChildren<Spawner>(); spawner?.YourSpawnerMethod(); }
2. 修复GamePiece脚本
不要在Awake里拿引用,改成在需要用的时候再获取,或者用事件监听地面完成放置的时机:
方法一:在OnMouseDown里实时获取
void OnMouseDown() { if (groundPlacementRef.PlacedObject == null) return; var spawner = groundPlacementRef.PlacedObject.GetComponentInChildren<Spawner>(); spawner?.DoYourThing(); // 替换成实际要调用的方法 }
方法二:用事件通知(更适合频繁调用的场景)
先在ARTapToPlaceObjects里添加事件:
using System; using UnityEngine; public class ARTapToPlaceObjects : MonoBehaviour { // 定义事件,地面放置完成时触发 public event Action<Spawner> OnGroundPlaced; public GameObject objectToPlace; public GameObject placedObject; public void PlaceObject() { placedObject = Instantiate(objectToPlace, transform.position, transform.rotation); var spawner = placedObject.GetComponentInChildren<Spawner>(); // 触发事件,把Spawner引用传出去 OnGroundPlaced?.Invoke(spawner); } }
然后在GamePiece里订阅事件:
public class GamePiece : MonoBehaviour { public ARTapToPlaceObjects groundPlacementRef; private Spawner spawnerRef; void Start() { // 订阅事件 groundPlacementRef.OnGroundPlaced += OnGroundIsPlaced; } void OnGroundIsPlaced(Spawner spawner) { // 拿到Spawner引用 spawnerRef = spawner; } void OnMouseDown() { spawnerRef?.DoYourThing(); } void OnDestroy() { // 销毁前取消订阅,避免内存泄漏 groundPlacementRef.OnGroundPlaced -= OnGroundIsPlaced; } }
内容的提问来源于stack exchange,提问作者buolou
相关产品推荐
相关产品推荐

