Unity测试MonoBehaviour位置变化遇空引用错误,求解决方案
解决Unity玩家移动测试中的NullReferenceException问题
问题概述
使用Unity新输入系统(通过OnMove方法)实现玩家WASD移动功能,运行游戏时控制正常,但编写PlayMode和EditMode测试代码验证位置变化时,触发NullReferenceException: 对象引用未设置到对象的实例错误,需要修正测试逻辑并解决空引用问题。
报错核心原因
- 缺少Rigidbody2D组件:
PlayerMovement类的移动逻辑依赖Rigidbody2D.MovePosition,但测试代码创建GameObject时未添加该组件,导致_rigidBody变量为空,调用ProcessMovementOfShip时触发空引用。 - 类成员变量赋值错误:
OnMove方法中重新声明了局部变量_rawInput,未给类的成员变量赋值(虽然这不影响测试,但属于运行时bug)。 - EditMode测试的物理限制:EditMode下默认不运行物理系统,直接调用依赖物理的移动方法无法正常更新位置。
修复方案
1. 修正PlayerMovement类
先解决类自身的空引用风险和变量赋值错误:
using UnityEngine; using UnityEngine.InputSystem; public class PlayerMovement : MonoBehaviour { [Header("Player Movement")] [Range(5f, 20f)][SerializeField] float _moveSpeed = 15f; private Rigidbody2D _rigidBody; private Vector2 _rawInput; void Awake() { // 自动添加Rigidbody2D并配置为Kinematic(适配MovePosition逻辑) _rigidBody = GetComponent<Rigidbody2D>(); if (_rigidBody == null) { _rigidBody = gameObject.AddComponent<Rigidbody2D>(); _rigidBody.bodyType = RigidbodyType2D.Kinematic; } } void FixedUpdate() { ProcessMovementOfShip(_rawInput); } public void ProcessMovementOfShip(Vector2 input) { // 空引用防护 if (_rigidBody == null) return; Vector3 delta = input * _moveSpeed * Time.fixedDeltaTime; Vector3 newPosition = transform.position + delta; _rigidBody.MovePosition(newPosition); } private void OnMove(InputValue value) { // 赋值给类成员变量,而非局部变量 _rawInput = value.Get<Vector2>(); } }
2. 修复PlayMode测试代码
PlayMode下需要等待物理更新,同时确保组件初始化完成:
using UnityEngine; using UnityEngine.TestTools; using NUnit.Framework; using System.Collections; public class PlayerMovementPlayModeTests { [UnityTest] public IEnumerator Player_Moves_After_ProcessMovementCall() { var testObject = new GameObject(); var playerMovement = testObject.AddComponent<PlayerMovement>(); // 等待Awake执行完成,确保Rigidbody2D被初始化 yield return null; Vector2 startPos = playerMovement.transform.position; playerMovement.ProcessMovementOfShip(new Vector2(1, 0)); // 等待FixedUpdate执行,让物理系统完成位置更新 yield return new WaitForFixedUpdate(); Vector2 endPos = playerMovement.transform.position; // 用Approximately避免浮点精度误差导致的断言失败 Assert.IsFalse(Vector2.Approximately(startPos, endPos)); // 清理测试对象 Object.Destroy(testObject); } }
3. 修复EditMode测试代码
EditMode下物理系统不运行,改为测试移动逻辑的计算正确性,或手动配置Kinematic刚体:
using UnityEngine; using NUnit.Framework; public class PlayerMovementEditModeTests { [Test] public void ProcessMovement_Calculates_Correct_Position() { var testObject = new GameObject(); var playerMovement = testObject.AddComponent<PlayerMovement>(); // 手动添加并配置Rigidbody2D var rb = testObject.AddComponent<Rigidbody2D>(); rb.bodyType = RigidbodyType2D.Kinematic; Vector3 startPos = testObject.transform.position; Vector2 input = new Vector2(1, 0); // 计算预期的位置增量 float expectedDelta = input.x * _moveSpeed * Time.fixedDeltaTime; playerMovement.ProcessMovementOfShip(input); // 断言位置计算正确(带容差处理浮点误差) Assert.AreEqual(startPos.x + expectedDelta, testObject.transform.position.x, 0.001f); // 清理测试对象 Object.DestroyImmediate(testObject); } }
关键注意事项
- 刚体依赖处理:移动逻辑依赖
Rigidbody2D,必须确保组件存在,可通过代码自动添加或测试时手动添加。 - EditMode限制:EditMode下物理系统默认不运行,测试物理相关逻辑时,要么模拟计算,要么使用Kinematic刚体(
MovePosition对Kinematic物体在EditMode下可立即生效)。 - 浮点精度问题:断言位置变化时,优先使用
Vector2.Approximately或带容差的Assert.AreEqual,避免因浮点精度误差导致测试失败。
内容的提问来源于stack exchange,提问作者Fredrik Olofsson
相关产品推荐
相关产品推荐

