使用带偏移的 Raycast 命中点移动对象

Move object using Raycast hitpoint with offset

我正在尝试创建一个简单的 ObjectMover class 来在基地内移动物体(想想 Clash of Clans 基地编辑)。

我遇到的问题是,当使用 RayCast 选择一个对象时,它会跳转到 RayCast hit.point,因为对象的 collider 可以被击中边缘然后将移动到 hit.point.

的中心

我试过使用偏移量,我确信这是微不足道的事情,但脑子放屁,找不到解决方案。

ObjectMover.cs

    using UnityEngine;
    using System.Collections;
    public class ObjectMover : MonoBehaviour
    {
#pragma warning disable 0649
        [SerializeField] private GameObject _tmpObjectToMove;
        [SerializeField] private LayerMask _groundLayerMask;
        [SerializeField] private LayerMask _objectsLayerMask;
#pragma warning restore 0649
    
        private Camera _cam;
        private GameObject _movableObject;
        private bool _objectIsSelected;
        private Vector3 _objectSelectionOffset;
    
        private void Awake()
        {
            _cam = Camera.main;
        }
    
        private void Start()
        {
            //TMP call and object instantiation for testing purposes
            GameObject obj = Instantiate(_tmpObjectToMove, Vector3.zero, Quaternion.identity);
            MakeObjectMoveable(obj);
        }
    
        private IEnumerator UpdatePosition()
        {
            while (_movableObject != null)
            {
                if (Input.GetButtonDown("Fire1"))
                {
                    TestObjectSelection();
                }
                else if (Input.GetButtonUp("Fire1"))
                {
                    if (_objectIsSelected)
                    {
                        _objectIsSelected = false;
                    }
                }
    
                if (_objectIsSelected)
                {
                    _movableObject.transform.position = GetNewPosition();
                }
    
                yield return null;
            }
        }
    
        public void MakeObjectMoveable(GameObject objectToMakeMovable)
        {
            _movableObject = objectToMakeMovable;
            StartCoroutine(UpdatePosition());
        }
    
        private Vector3 GetNewPosition()
        {
            if (_movableObject != null)
            {
                Ray ray = _cam.ScreenPointToRay(Input.mousePosition);
                if (Physics.Raycast(ray.origin, ray.direction, out RaycastHit hitInfo, 200f, _groundLayerMask, QueryTriggerInteraction.Ignore))
                {
                    Vector3 pos = hitInfo.point - _objectSelectionOffset;
                    return new Vector3(pos.x, 0f, pos.z);
                }
            }
            return _movableObject.transform.position;
        }
    
        private void TestObjectSelection()
        {
            Ray ray = _cam.ScreenPointToRay(Input.mousePosition);
            if (Physics.Raycast(ray.origin, ray.direction, out RaycastHit hitInfo, 200f, _objectsLayerMask, QueryTriggerInteraction.Ignore))
            {
                if (hitInfo.transform.gameObject == _movableObject)
                {
                    Vector3 difference = hitInfo.point - _movableObject.transform.position;
                    _objectSelectionOffset = new Vector3(difference.x, 0f, difference.z);
                    _objectIsSelected = true;
                }
            }
        }
    }

我没看到什么?

一个问题是你的区别在于光线与建筑物表面和地面的碰撞,你将它添加到地面以获得变换的原点。

相反,根据玩家光线击中地面的位置设置偏移量:

    private void TestObjectSelection()
    {
        Ray ray = _cam.ScreenPointToRay(Input.mousePosition);
        if (Physics.Raycast(ray.origin, ray.direction, out RaycastHit hitInfo, 200f, _objectsLayerMask, QueryTriggerInteraction.Ignore))
        {
            if (hitInfo.transform.gameObject == _movableObject)
            {
                Plane groundPlane = new Plane(Vector3.up, Vector3.zero);
                float groundDistance;
                groundPlane.Raycast(ray, out groundDistance);

                Vector3 groundPoint = ray.GetPoint(groundDistance);

                // groundPoint is guaranteed y=0, so _objectSelectionOffset y=0;
                _objectSelectionOffset = _movableObject.transform.position - groundPoint;

                _objectIsSelected = true;
            }
        }
    }

如果你的地面不是平面,你可以使用另一个 Physics.Raycast 得到 groundPoint:

    private void TestObjectSelection()
    {
        Ray ray = _cam.ScreenPointToRay(Input.mousePosition);
        if (Physics.Raycast(ray.origin, ray.direction, out RaycastHit hitInfo, 200f, _objectsLayerMask, QueryTriggerInteraction.Ignore))
        {
            if (hitInfo.transform.gameObject == _movableObject)
            {
                if (Physics.Raycast(ray.origin, ray.direction, out RaycastHit hitInfo, 200f, _groundLayerMask, QueryTriggerInteraction.Ignore)) 
                {
                    _objectSelectionOffset = _movableObject.transform.position - hitInfo.point;

                    _objectIsSelected = true;
                }
            }
        }
    }

无论哪种方式,然后你可以根据偏移量设置位置:

    private Vector3 GetNewPosition()
    {
        if (_movableObject != null)
        {
            Ray ray = _cam.ScreenPointToRay(Input.mousePosition);
            if (Physics.Raycast(ray.origin, ray.direction, out RaycastHit hitInfo, 200f, _groundLayerMask, QueryTriggerInteraction.Ignore))
            {
                return hitInfo.point + _objectSelectionOffset;
            }
        }
        return _movableObject.transform.position;
    }