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

Unity测试MonoBehaviour位置变化遇空引用错误,求解决方案

解决Unity玩家移动测试中的NullReferenceException问题

问题概述

使用Unity新输入系统(通过OnMove方法)实现玩家WASD移动功能,运行游戏时控制正常,但编写PlayMode和EditMode测试代码验证位置变化时,触发NullReferenceException: 对象引用未设置到对象的实例错误,需要修正测试逻辑并解决空引用问题。

报错核心原因

  1. 缺少Rigidbody2D组件:PlayerMovement类的移动逻辑依赖Rigidbody2D.MovePosition,但测试代码创建GameObject时未添加该组件,导致_rigidBody变量为空,调用ProcessMovementOfShip时触发空引用。
  2. 类成员变量赋值错误:OnMove方法中重新声明了局部变量_rawInput,未给类的成员变量赋值(虽然这不影响测试,但属于运行时bug)。
  3. 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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.09 06:01:46