开发手札:CameraRTS精准性优化

      虽然三维软件提供了基本的物体RTS操作,但是对于用户来说过于复杂。
      这些操作方式需要用户理解什么是三维空间、XYZ坐标系、欧拉角等。但是用户视角下,就一个二维屏幕+动来动去的鼠标光标。
      之前写过一套RTM组件,RTM组件,讲解了移动操作。
      这次基于Camera视口(二维屏幕)+屏幕坐标(鼠标光标)来实现一套全新的物体RTS操作。
      一.Translate
      移动的原理是根据RayHitPoint获取视椎平面,使物体在视椎平面相对移动,如下:
在这里插入图片描述

      根据hitpointhp1的相对坐标计算centerc1的坐标
      接下来实现代码:

using UnityEngine;
using UnityEngine.EventSystems;

public abstract class RTSBaseComp : MonoBehaviour
{
    public bool isOperating = false;

    protected Vector3 rayHitPoint;          //交点
    protected Vector3 worldBaisPos;         //世界坐标偏移量
    protected float planeDistance;          //交点平面距离

    protected virtual void Awake()
    {

    }

    protected virtual void Start()
    {
        EventTriggerListener.Get(gameObject).onLeftPointDown.AddListener(LeftPointDownCallback);
        EventTriggerListener.Get(gameObject).onLeftPointUp.AddListener(LeftPointUpCallback);
    }

    protected virtual void Update()
    {

    }

    protected virtual void LeftPointDownCallback(GameObject go, PointerEventData data)
    {
        if (CameraControl.Instance.IsPointRaycastHit(out var rayhit))
        {
            rayHitPoint = rayhit.point;
            worldBaisPos = transform.position - rayHitPoint;
            planeDistance = CameraControl.Instance.GetParallelPlaneDistance(rayHitPoint);
            isOperating = true;
        }
    }

    protected virtual void LeftPointUpCallback(GameObject go, PointerEventData data)
    {
        isOperating = false;
    }

    protected virtual void OnDestroy()
    {
        EventTriggerListener.Get(gameObject).onLeftPointDown.RemoveListener(LeftPointDownCallback);
        EventTriggerListener.Get(gameObject).onLeftPointUp.RemoveListener(LeftPointUpCallback);
    }
}

      基类中封装最基本的用户鼠标操作。

/// <summary>
/// 根据世界坐标获取平行于视锥体平面
/// 获取平面距离
/// </summary>
/// <param name="screenPos"></param>
/// <param name="wpos"></param>
/// <returns></returns>
public float GetParallelPlaneDistance(Vector3 wpos)
{
    Vector3 from = mainTransform.position;
    Vector3 end = wpos;
    Vector3 f2e = end - from;
    float f2eDistance = Vector3.Distance(from, end);
    float deg = Vector3.Angle(f2e, mainTransform.forward);
    float pdistance = Mathf.Cos(deg * Mathf.Deg2Rad) * f2eDistance;
    return pdistance;
}
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.EventSystems;

/// <summary>
/// 基于HitPoint视锥平面
/// </summary>
public class RTSTranslateComp : RTSBaseComp
{
    public UnityAction<Vector3> OnTranslatingListener;
    public UnityAction<Vector3> OnEndTranslateListener;

    protected override void Update()
    {
        base.Update();

        if (isOperating)
        {
            Vector2 csPos = new Vector2(Input.mousePosition.x, Input.mousePosition.y);
            Vector3 cwPos = CameraControl.Instance.GetScreenToWorldPos(csPos, planeDistance);
            transform.position = cwPos + worldBaisPos;

            OnTranslatingListener?.Invoke(transform.position);
        }
    }

    protected override void LeftPointUpCallback(GameObject go, PointerEventData data)
    {
        base.LeftPointUpCallback(go, data);

        OnEndTranslateListener?.Invoke(transform.position);
    }
}

      PS:其中有一些依赖函数是我框架代码内的,只标注意义,因为以前都有讲解过原理,所以节省篇幅。
      效果如下:
在这里插入图片描述
      二.Rotate
      旋转的原理是根据RayHitPoint获取视椎平面Plane。物体中心到Plane投影点为旋转中心,Camera坐标系为基准。HitPoint绕Z轴旋转,依据左手定则,如下:
在这里插入图片描述
      需要注意的是基于RayHitPoint视椎平面的旋转。
      接下来实现代码:

using UnityEngine;
using UnityEngine.Events;
using UnityEngine.EventSystems;

/// <summary>
/// 基于RayHitPoint视锥平面
/// </summary>
public class RTSRotateComp : RTSBaseComp
{
    public UnityAction<Vector3> OnRotatingListener;
    public UnityAction<Vector3> OnEndRotateListener;

    private Vector3 rotateAxis;             //旋转轴
    private Vector3 rotateCenter;           //旋转中心
    private Vector3 lastHitPoint;
    private Vector3 crtHitPoint;

    protected override void LeftPointDownCallback(GameObject go, PointerEventData data)
    {
        base.LeftPointDownCallback(go, data);

        rotateAxis = CameraControl.Instance.mainTransform.forward;
        Vector2 csPos = CameraControl.Instance.GetWorldToScreenPos(transform.position);
        rotateCenter = CameraControl.Instance.GetScreenToWorldPos(csPos, planeDistance);

        lastHitPoint = rayHitPoint;
    }

    protected override void Update()
    {
        base.Update();

#if UNITY_EDITOR
        //辅助坐标系
        Debug.DrawLine(rotateCenter, rotateCenter + CameraControl.Instance.mainTransform.right, Color.red);
        Debug.DrawLine(rotateCenter, rotateCenter + CameraControl.Instance.mainTransform.up, Color.green);
        Debug.DrawLine(rotateCenter, rotateCenter + rotateAxis, Color.blue);
#endif

        if (isOperating)
        {
            crtHitPoint = CameraControl.Instance.GetScreenToWorldPos(Input.mousePosition, planeDistance);
            Vector3 f = lastHitPoint - rotateCenter;
            Vector3 t = crtHitPoint - rotateCenter;
            //f到t的角度,左手定则,逆时针
            float deltaAngle = Vector3.SignedAngle(f, t, rotateAxis);
            transform.RotateAround(rotateCenter, rotateAxis, deltaAngle);
            lastHitPoint = crtHitPoint;

            OnRotatingListener?.Invoke(transform.eulerAngles);
        }
    }

    protected override void LeftPointUpCallback(GameObject go, PointerEventData data)
    {
        base.LeftPointUpCallback(go, data);

        OnEndRotateListener?.Invoke(transform.eulerAngles);
    }

}

      效果如下:
在这里插入图片描述

      三.Scale
      常见的缩放功能是鼠标滚轮缩放物体,但是并非基于RayHitPoint,所以视觉上会偏移,为了修正偏移,实现基于RayHitPoint的缩放。
      原理是在缩放的同时,根据RayHitPoint相对位移计算物体中心位移,依据视椎平面相对位移*相对缩放即可。如下:

在这里插入图片描述

      代码实现如下:

using UnityEngine;
using UnityEngine.EventSystems;

public class RTSScaleComp : MonoBehaviour
{
    [Range(0.5f, 2f)]
    public float ScrollSpeed = 1.0f;

    private bool isPointEnter = false;

    private Vector3 rayHitPoint;            //交点
    private Vector3 lastLocalScale;         //当前缩放值
    private Vector3 worldBaisPos;           //世界坐标偏移量
    private float planeDistance;            //交点平面距离

    private

    void Start()
    {
        EventTriggerListener.Get(gameObject).onPointEnter.AddListener(PointEnterCallback);
        EventTriggerListener.Get(gameObject).onPointExit.AddListener(PointExitCallback);
    }

    private void PointEnterCallback(GameObject go, PointerEventData data)
    {
        isPointEnter = true;
    }

    private void PointExitCallback(GameObject go, PointerEventData data)
    {
        isPointEnter = false;
    }

    void Update()
    {
        if (isPointEnter)
        {
            float val = Input.GetAxis("Mouse ScrollWheel");
            if (val == 0)
            {
                if (CameraControl.Instance.IsPointRaycastHit(out var rayhit))
                {
                    rayHitPoint = rayhit.point;
                    worldBaisPos = transform.position - rayHitPoint;
                    lastLocalScale = transform.localScale;
                    planeDistance = CameraControl.Instance.GetParallelPlaneDistance(rayHitPoint);
                }
            }
            else
            {
                ScrollMove(val);

                Vector2 csPos = new Vector2(Input.mousePosition.x, Input.mousePosition.y);
                Vector3 cwPos = CameraControl.Instance.GetScreenToWorldPos(csPos, planeDistance);
                //计算缩放
                Vector3 sca = transform.localScale.Division(lastLocalScale);
                transform.position = cwPos + worldBaisPos.Multiply(sca);
            }
        }
    }

    public void ScrollMove(float val)
    {
        transform.localScale += (Vector3.one * val * ScrollSpeed);
    }

    private void OnDestroy()
    {
        EventTriggerListener.Get(gameObject).onPointEnter.RemoveListener(PointEnterCallback);
        EventTriggerListener.Get(gameObject).onPointExit.RemoveListener(PointExitCallback);
    }
}

      效果如下:
在这里插入图片描述

      这样就实现了一套Camera(用户)视角下,精准的RTS操作。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值