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

Unity运行时无网格系统下如何让两个对象贴合对齐

Unity运行时拖动对象避免重叠问题解决方案

问题场景

运行时通过鼠标拖动一个忽略Raycast的立方体,采用世界空间鼠标控制其移动到地面立方体上,但两个对象会发生重叠。曾尝试用空物体作为对象的最深定位点,但该方案在墙面场景下不适用,需可行解决方案。

当前使用脚本

1. Mouse3D(处理鼠标世界空间位置)

using UnityEngine;

public class Mouse3D : MonoBehaviour
{
    public static Mouse3D Instance { get; private set; }

    [SerializeField] private LayerMask mouseColliderLayerMask = new LayerMask();
    [SerializeField] private Transform debugVisual; 
    public bool lockMouse;
    public bool showDebugInformations;

    [Header("Debug Only")] public Vector3 mousePosition = Vector3.zero;
    public string objectName;
    public string layerMask;

    public static int x;
    public static int y;
    public static int z;

    private void Awake()
    {
        Instance = this;
    }

    private void Update()
    {
        if (lockMouse)
        {
            Cursor.lockState = CursorLockMode.Locked;
            Cursor.visible = false;
        }
        else
        {
            Cursor.lockState = CursorLockMode.None;
            Cursor.visible = true;
        }

        Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);

        if (Physics.Raycast(ray, out RaycastHit raycastHit, 999f, mouseColliderLayerMask))
        {
            transform.position = raycastHit.point;
        }

        if (showDebugInformations)
        {
            debugVisual.position = GetMouseWorldPosition();
            mousePosition = GetMouseWorldPosition();
            //objectName = raycastHit.transform.parent.name;
            //layerMask = raycastHit.ToString();
        }
    }

    public static Vector3 GetMouseWorldPosition() => Instance.GetMouseWorldPosition_Instance();

    private Vector3 GetMouseWorldPosition_Instance()
    {
        Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
        if (Physics.Raycast(ray, out RaycastHit raycastHit, 999f, mouseColliderLayerMask))
        {
            x = (int)raycastHit.point.x; y = (int)raycastHit.point.y; z = (int)raycastHit.point.z;

            return raycastHit.point;
        }
        else
        {
            return Vector3.zero;
        }
    }
}

2. BuildAssetMovement(控制立方体移动)

using UnityEngine;

public class BuildAssetMovement : MonoBehaviour
{
    void Update()
    {
        // moving by 1 unit
        this.transform.SetPositionAndRotation(new Vector3(
            Mathf.RoundToInt(Mouse3D.x),
            Mathf.RoundToInt(Mouse3D.y),
            Mathf.RoundToInt(Mouse3D.z)),
            this.transform.rotation);
    }
}

可行解决方案

方案1:基于碰撞体尺寸与表面法线的位置偏移

利用拖动对象的碰撞体边界尺寸,结合Raycast命中表面的法线方向,计算偏移量确保对象贴合表面,同时保持网格对齐。

修改BuildAssetMovement脚本:

using UnityEngine;

public class BuildAssetMovement : MonoBehaviour
{
    private Collider _objectCollider;
    private Vector3 _halfBoundsExtents;

    void Awake()
    {
        _objectCollider = GetComponent<Collider>();
        if (_objectCollider != null)
        {
            // 获取对象碰撞体的半尺寸,用于计算偏移
            _halfBoundsExtents = _objectCollider.bounds.extents;
        }
    }

    void Update()
    {
        Ray mouseRay = Camera.main.ScreenPointToRay(Input.mousePosition);
        if (Physics.Raycast(mouseRay, out RaycastHit hitInfo, 999f, Mouse3D.Instance.mouseColliderLayerMask))
        {
            // 根据表面法线计算偏移,让对象贴合表面
            Vector3 positionOffset = Vector3.Scale(_halfBoundsExtents, hitInfo.normal);
            Vector3 targetPosition = hitInfo.point + positionOffset;

            // 保持1单位网格对齐
            targetPosition = new Vector3(
                Mathf.RoundToInt(targetPosition.x),
                Mathf.RoundToInt(targetPosition.y),
                Mathf.RoundToInt(targetPosition.z));

            transform.SetPositionAndRotation(targetPosition, transform.rotation);
        }
    }
}

方案2:重叠检测+位置修正

在设置对象位置前,用Physics.OverlapBox检测目标位置是否与其他对象重叠,若重叠则沿表面法线方向偏移一个网格单位,避免重叠。

修改BuildAssetMovement脚本:

using UnityEngine;

public class BuildAssetMovement : MonoBehaviour
{
    private Collider _objectCollider;
    private Vector3 _boundsExtents;

    void Awake()
    {
        _objectCollider = GetComponent<Collider>();
        if (_objectCollider != null)
        {
            _boundsExtents = _objectCollider.bounds.extents;
        }
    }

    void Update()
    {
        Ray mouseRay = Camera.main.ScreenPointToRay(Input.mousePosition);
        if (Physics.Raycast(mouseRay, out RaycastHit hitInfo, 999f, Mouse3D.Instance.mouseColliderLayerMask))
        {
            // 初始位置:命中点加上对象半高(基础贴合)
            Vector3 targetPosition = hitInfo.point + new Vector3(0, _boundsExtents.y, 0);
            // 网格对齐
            targetPosition = new Vector3(
                Mathf.RoundToInt(targetPosition.x),
                Mathf.RoundToInt(targetPosition.y),
                Mathf.RoundToInt(targetPosition.z));

            // 检测目标位置是否存在重叠
            Collider[] overlappingColliders = Physics.OverlapBox(
                targetPosition, 
                _boundsExtents, 
                transform.rotation, 
                Mouse3D.Instance.mouseColliderLayerMask);

            foreach (var collider in overlappingColliders)
            {
                // 排除自身碰撞体
                if (collider.gameObject != gameObject)
                {
                    // 沿表面法线偏移1个网格单位,避免重叠
                    targetPosition += hitInfo.normal * 1f;
                    targetPosition = new Vector3(
                        Mathf.RoundToInt(targetPosition.x),
                        Mathf.RoundToInt(targetPosition.y),
                        Mathf.RoundToInt(targetPosition.z));
                    break;
                }
            }

            transform.SetPositionAndRotation(targetPosition, transform.rotation);
        }
    }
}

方案3:优化Mouse3D的Raycast逻辑

  1. 将拖动对象单独放在一个层(如Dragging),在Mouse3D的mouseColliderLayerMask中排除该层,确保Raycast只检测地面/墙面。
  2. 移除Mouse3D中暴露的静态x/y/z变量,改用GetMouseWorldPosition()返回完整Vector3,降低耦合:

修改Mouse3D的相关部分:

// 移除以下静态变量
// public static int x;
// public static int y;
// public static int z;

private Vector3 GetMouseWorldPosition_Instance()
{
    Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
    if (Physics.Raycast(ray, out RaycastHit raycastHit, 999f, mouseColliderLayerMask))
    {
        return raycastHit.point;
    }
    else
    {
        return Vector3.zero;
    }
}

然后在BuildAssetMovement中直接使用返回的Vector3:

Vector3 mouseWorldPos = Mouse3D.GetMouseWorldPosition();
Vector3 targetPosition = new Vector3(
    Mathf.RoundToInt(mouseWorldPos.x),
    Mathf.RoundToInt(mouseWorldPos.y),
    Mathf.RoundToInt(mouseWorldPos.z));

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.01 21:01:44