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

Unity 3D中如何检测玩家持有的物体是否处于移动状态?

稳定检测玩家持有3D物体的移动状态

你的核心问题是:物体挂载为玩家子物体后,本地变换未改变,但世界空间位置/旋转随玩家移动而变化,且逐帧对比容易因微小抖动导致状态频繁切换。以下是精准稳定的解决方案:

核心思路

放弃本地变换检测,改用世界空间位置/旋转对比+阈值过滤,忽略浮点精度误差或微小抖动带来的误判;也可结合物理组件(Rigidbody)直接读取速度,更高效稳定。

方案1:世界空间对比+阈值过滤

适合无Rigidbody的物体,通过对比当前与上一采样帧的世界位置/旋转差值,超过阈值才判定为移动。

using UnityEngine;

public class ObjectMovementDetector : MonoBehaviour
{
    private Vector3 _lastWorldPosition;
    private Quaternion _lastWorldRotation;
    
    // 根据物体大小调整阈值,避免微小抖动触发误判
    [Header("检测阈值")]
    public float positionThreshold = 0.001f;
    public float rotationThreshold = 0.01f;
    
    public bool isMoving { get; private set; }

    void Start()
    {
        // 初始化初始世界状态
        _lastWorldPosition = transform.position;
        _lastWorldRotation = transform.rotation;
    }

    void Update()
    {
        // 计算当前与上一帧的世界空间差值
        float positionDiff = Vector3.Distance(transform.position, _lastWorldPosition);
        float rotationDiff = Quaternion.Angle(transform.rotation, _lastWorldRotation);

        // 超过阈值则判定为移动,并更新上一帧状态
        if (positionDiff > positionThreshold || rotationDiff > rotationThreshold)
        {
            isMoving = true;
            Debug.Log("Moved");
            _lastWorldPosition = transform.position;
            _lastWorldRotation = transform.rotation;
        }
        else
        {
            isMoving = false;
            Debug.Log("Not Moved");
        }
    }
}

方案2:利用Rigidbody速度检测(推荐)

如果物体带有Rigidbody组件,直接读取物理引擎的速度值,更平滑稳定,无需逐帧对比位置:

using UnityEngine;

public class ObjectMovementDetector : MonoBehaviour
{
    private Rigidbody _rb;
    
    // 速度阈值,忽略微小物理抖动
    public float velocityThreshold = 0.01f;
    
    public bool isMoving { get; private set; }

    void Start()
    {
        _rb = GetComponent<Rigidbody>();
        // 确保Rigidbody存在
        if (_rb == null)
        {
            Debug.LogError("物体未挂载Rigidbody组件,请添加后使用此方案");
        }
    }

    // 用FixedUpdate处理物理相关逻辑更准确
    void FixedUpdate()
    {
        if (_rb == null) return;
        
        // 检测速度大小是否超过阈值
        isMoving = _rb.velocity.magnitude > velocityThreshold;
        Debug.Log(isMoving ? "Moved" : "Not Moved");
    }
}

方案3:固定间隔采样优化

如果逐帧检测仍有抖动问题,可以改为固定时间间隔采样,减少检测频率,避免微小波动触发状态切换:

using UnityEngine;

public class ObjectMovementDetector : MonoBehaviour
{
    private Vector3 _lastWorldPosition;
    private Quaternion _lastWorldRotation;
    
    public float positionThreshold = 0.001f;
    public float rotationThreshold = 0.01f;
    // 采样间隔,比如每0.1秒检测一次
    public float sampleInterval = 0.1f;
    
    private float _timer;
    public bool isMoving { get; private set; }

    void Start()
    {
        _lastWorldPosition = transform.position;
        _lastWorldRotation = transform.rotation;
    }

    void Update()
    {
        _timer += Time.deltaTime;
        if (_timer >= sampleInterval)
        {
            float positionDiff = Vector3.Distance(transform.position, _lastWorldPosition);
            float rotationDiff = Quaternion.Angle(transform.rotation, _lastWorldRotation);

            isMoving = positionDiff > positionThreshold || rotationDiff > rotationThreshold;
            Debug.Log(isMoving ? "Moved" : "Not Moved");

            _lastWorldPosition = transform.position;
            _lastWorldRotation = transform.rotation;
            _timer = 0f;
        }
    }
}

关键注意事项

  • 阈值调整:根据物体的实际大小和使用场景调整阈值,比如大型物体可以把位置阈值设为0.01f,小型物体设为0.0001f。
  • 物理物体优先用方案2:Rigidbody的速度是物理引擎计算的平滑值,不会出现逐帧位置对比的抖动问题。
  • 性能优化:如果对性能要求高,可把Vector3.Distance替换为Vector3.SqrMagnitude(平方距离对比,省去开方运算):
    float positionDiffSqr = (transform.position - _lastWorldPosition).sqrMagnitude;
    if (positionDiffSqr > positionThreshold * positionThreshold)
    {
        // 判定为移动
    }
    

内容的提问来源于stack exchange,提问作者Crazy Dawg

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.15 12:15:56