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逻辑
- 将拖动对象单独放在一个层(如
Dragging),在Mouse3D的mouseColliderLayerMask中排除该层,确保Raycast只检测地面/墙面。 - 移除
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
相关产品推荐
相关产品推荐

