Unity新手技术问询:跨脚本调用方法时遭遇NullReferenceException错误及自行解决记录
作为Unity新手,你遇到的NullReferenceException是跨脚本调用时最常见的问题之一,本质就是你声明的displayController变量没有指向任何实际的组件实例,所以调用它的方法时会报错。下面先梳理你的问题,再给出你找到的解决方案和其他更灵活的替代方案:
问题回顾
你将语言选择下拉菜单的逻辑写在ChoseLanguage脚本(ScriptA)中,想要调用DisplayController脚本(ScriptB)的DropdownSetLanguage()方法。虽然语言选择和PlayerPrefs保存逻辑正常,但调用方法时出现以下错误:
NullReferenceException: Object reference not set to an instance of an object ChoseLanguage.DropdownChooseLanguage () (at Assets/Scripts/ChoseLanguage.cs:28)
你的代码片段:
ScriptA (ChoseLanguage.cs)
using UnityEngine; public class ChoseLanguage: MonoBehaviour { public TMPro.TMP_Dropdown myDrop; DisplayController displayController; // 这里的变量未初始化 public void DropdownChooseLanguage() { if (myDrop.value == 1) PlayerPrefs.SetString("chosenLanguage", "Spanish"); if (myDrop.value == 2) PlayerPrefs.SetString("chosenLanguage", "Japanese"); if (myDrop.value == 3) PlayerPrefs.SetString("chosenLanguage", "Korean"); if (myDrop.value == 4) PlayerPrefs.SetString("chosenLanguage", "Icelandic"); Debug.Log(PlayerPrefs.GetString("chosenLanguage")); displayController.DropdownSetLanguage(); // 调用未初始化的变量导致报错 } }
ScriptB (DisplayController.cs)
using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; using System.Linq; using TMPro; public class DisplayController : MonoBehaviour { ... public void DropdownSetLanguage() { SetFileName(); setLanguage.gameObject.SetActive(false); Start(); } ... }
你找到的解决方案(手动赋值法)
这是新手最容易理解的方法,步骤完全正确:
- 将
DisplayController displayController;修改为公开字段:public DisplayController displayController; - 在Unity编辑器的Inspector面板中,找到挂载了
ChoseLanguage脚本的游戏对象,把挂载了DisplayController脚本的游戏对象拖入这个公开字段中
这种方法直观,适合场景结构简单的情况,能明确看到引用关系。
其他更灵活的解决方案
1. 代码自动查找(适合单实例场景)
如果你的场景中只有一个DisplayController实例,可以在ChoseLanguage的初始化方法中自动查找组件,不用手动拖赋值:
using UnityEngine; public class ChoseLanguage: MonoBehaviour { public TMPro.TMP_Dropdown myDrop; DisplayController displayController; void Awake() { // 查找场景中第一个DisplayController组件 displayController = FindObjectOfType<DisplayController>(); // 如果要指定特定对象,可以用: // displayController = GameObject.Find("你的DisplayController对象名称").GetComponent<DisplayController>(); } public void DropdownChooseLanguage() { // 原有逻辑... if(displayController != null) // 加个空判断更安全,避免意外报错 displayController.DropdownSetLanguage(); } }
注意:如果场景中有多个同类型组件,FindObjectOfType会返回第一个找到的,要确保找的是你需要的那个。
2. 单例模式(适合全局通用控制器)
如果DisplayController是全局唯一的UI控制器(比如整个游戏只有一个语言显示控制),可以把它做成单例,这样任何脚本都能直接调用它的方法:
修改DisplayController脚本:
using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; using System.Linq; using TMPro; public class DisplayController : MonoBehaviour { // 静态单例实例,全局可访问 public static DisplayController Instance; void Awake() { // 确保场景中只有一个实例 if(Instance == null) { Instance = this; DontDestroyOnLoad(gameObject); // 可选,跨场景保留这个对象 } else { Destroy(gameObject); // 如果重复创建就销毁多余的 } } ... public void DropdownSetLanguage() { SetFileName(); setLanguage.gameObject.SetActive(false); Start(); } ... }
然后在ChoseLanguage中调用时直接用:
public void DropdownChooseLanguage() { // 原有逻辑... DisplayController.Instance.DropdownSetLanguage(); // 直接通过单例调用,无需声明变量 }
这种方法适合全局管理类脚本,不用处理字段赋值,非常方便。
新手小贴士
- 遇到
NullReferenceException时,第一反应是检查变量有没有被正确赋值,可以用Debug.Log(displayController)打印看看是不是null - 拆分脚本时尽量按功能模块划分(比如交互逻辑、显示控制、数据存储),避免一个脚本臃肿不堪
- 公开字段虽然方便,但如果不想在Inspector中暴露,可以用
[SerializeField]修饰私有字段,既可以在Inspector赋值,又保持代码封装性:[SerializeField] private DisplayController displayController;
内容的提问来源于stack exchange,提问作者Weasel Bandit

