限制物体旋转角度

817 阅读4分钟

  实现一个移动端滑动屏幕,物体跟随转动,但同时转动的角度有限制的功能。主要是要判断物体是否旋转超过了限定范围内的初始角度和偏移角度。

  这里直接通过四元数的点积运算来判断旋转角度。用欧拉角容易出现问题,对于旋转来说。360°是一个循环。-1° 也可以算是359°

Quaternion.Dot(Quaternion a,Quaternion b)

  该方法可以根据点乘的结果,判断ab对应欧拉角的关系。

  例如有两个Quaternion实例q1(x1,y1,z1,w1)q2(x2,y2,z2,w2),则float f = Quaternion.Dot(q1,q2);即f = x1*x2+y1*y2+z1*z2+w1*w2,结果值f的范围为[-1,1]。当f=+(-)1时,q1q2对应的欧拉角是相等的,即旋转状态是一致的。特别地,当f = -1时,说明其中一个rotation比另外一个rotation多旋转了360°。

  如上图所示,如果物体当前位置等于初始位置,那么当前位置和初始位置的点积就是1

  当物体正方向旋转大约90°的时候,当前位置和初始位置的点积是0.7

  当物体正方向旋转180°的时候,当前位置和初始位置的点积是0

using UnityEngine;

public class RotateLimited : MonoBehaviour
{
    //旋转物体
    public Transform m_transform;

    //为true表示物体从偏移位置转到初始位置
    //为false表示物体从初始位置转到偏移位置
    public bool m_forward = true;

    //旋转轴
    public Type m_axis = Type.y;

    //旋转角度-360~360
    public float m_rotateValue = 0;

    //要旋转的欧拉角
    Vector3 m_offsetEuler;

    //旋转到起点前
    public delegate void OverStart(Quaternion startRotation,Quaternion endRotation);

    //旋转超过终点后
    public delegate void OverEnd(Quaternion startRotation, Quaternion endRotation);

    //旋转轴的类型
    public enum Type
    {
        x,
        y,
        z
    }

    //初始四元数
    Quaternion m_initRotation;

    //偏移四元数
    Quaternion m_offsetRotation;

    //偏移点积
    float m_offsetDot = 0;


    private void Start()
    {
        //如果旋转角度大于360或小于-360 ,则四元数的点积也会变为负数,这会让计算出现问题
        //所以直接从源头上解决问题,让旋转角度限制在-360~360之间
        switch (m_axis)
        {
            case Type.x:
                m_offsetEuler = new Vector3(m_rotateValue % 360,0,0);
                break;
            case Type.y:
                m_offsetEuler = new Vector3(0,m_rotateValue % 360,0);
                break;
            case Type.z:
                m_offsetEuler = new Vector3(0,0,m_rotateValue % 360);
                break;
        }

        m_initRotation = m_transform.rotation;
        //正向
        if (m_forward)
        {
            //这里不要通过Quaternion.Euler(euler1 + euler2)这种方法来获取偏移四元数
            //通过生成一个物体,来间接传递偏移后的角度
            var _temp = new GameObject();
            _temp.transform.rotation = m_transform.rotation;
            _temp.transform.Rotate(m_offsetEuler);
            m_offsetRotation = _temp.transform.rotation;
            Destroy(_temp);
        }
        //反向
        else
        {
            m_transform.Rotate(m_offsetEuler);
            m_offsetRotation = m_transform.rotation;
        }
        m_offsetDot = Quaternion.Dot(m_initRotation, m_offsetRotation);
    }


    public void OnRotate(OverStart overStart,OverEnd overEnd)
    {
        //正向
        if (m_forward)
        {
            //移动超过起点
            if(CurrentDot(m_offsetRotation) < m_offsetDot)
            {
                overStart(m_initRotation, m_offsetRotation);
            }
            //移动超过终点
            else if (CurrentDot(m_initRotation) < m_offsetDot)
            {
                overEnd(m_initRotation, m_offsetRotation);
            }
        }
        //反向
        else
        {
            //移动超过起点
            if (CurrentDot(m_initRotation) < m_offsetDot)
            {
                overStart(m_offsetRotation, m_initRotation);
            }
            //移动超过终点
            else if (CurrentDot(m_offsetRotation) < m_offsetDot)
            {
                overEnd(m_offsetRotation, m_initRotation);
            }
        }
    }

    //旋转的实时点积
    float CurrentDot(Quaternion quaternion)
    {
        return Quaternion.Dot(m_transform.rotation, quaternion);
    }

    //private void OnGUI()
    //{
    //    GUIStyle style = new GUIStyle();
    //    style.fontSize = 54;
    //    style.normal.textColor = Color.blue;
    //    if(m_initRotation != null)
    //        GUI.Label(new Rect(10,10,400,60), $"初始旋转角度:{m_initRotation}",style);

    //    if (m_offsetRotation != null)
    //        GUI.Label(new Rect(10, 100, 400, 60), $"偏移旋转角度:{m_offsetRotation}", style);

    //    if (m_offsetDot != null)
    //        GUI.Label(new Rect(10, 200, 400, 60), $"起始与偏移的点积:{m_offsetDot}", style);

    //    if (m_transform != null && m_initRotation != null)
    //        GUI.Label(new Rect(10, 300, 400, 60), $"与起始的点积:{CurrentDot(m_initRotation)}", style);

    //    if (m_transform != null && m_initRotation != null)
    //        GUI.Label(new Rect(10, 400, 400, 60), $"与偏移的点积:{CurrentDot(m_offsetRotation)}", style);
    //}

}

使用:

public Transform m_transform;

public RotateLimited m_rotateLimited;

// Update is called once per frame
void Update()
{
    Gesture gesture = EasyTouch.current;
    if (gesture == null) return;
    EasyTouch_Rotate(gesture);
}

void EasyTouch_Rotate(Gesture gesture)
{
    //使用EasyTouch插件获取滑动屏幕信息
    if (EasyTouch.EvtType.On_Swipe == gesture.type)
    {
        Vector2 deltaPos = gesture.deltaPosition;
        m_transform.Rotate(Vector3.down * deltaPos.x, Space.World);
        //m_transform.Rotate(Vector3.right * deltaPos.y, Space.World);

        m_rotateLimited.OnRotate(OverStart, OverEnd);
    }
}

void OverStart(Quaternion start, Quaternion end)
{
    //如果物体旋转到初始角度前,设置当前物体旋转角度为初始旋转角度
    m_transform.rotation = start;
}

void OverEnd(Quaternion start, Quaternion end)
{
    //如果物体旋转到初始角度后,设置当前物体旋转角度为偏移旋转角度
    m_transform.rotation = end;
}


  最后就是关于物体通过欧拉角转换得到的的四元数不等于物体直接获取的四元数,这点确实很奇怪,也可能是因为目前还不是很了解四元数的原因。

void Update()
{
    if (Input.GetKey(KeyCode.W))
    {
        transform.Rotate(new Vector3(1, 0, 0));
    }

    if (Input.GetKey(KeyCode.S))
    {
        transform.Rotate(new Vector3(-1, 0, 0));
    }

    if (Input.GetKey(KeyCode.A))
    {
        transform.Rotate(new Vector3(0, 0, -1));
    }

    if (Input.GetKey(KeyCode.D))
    {
        transform.Rotate(new Vector3(0, 0, 1));
    }
}

private void OnGUI()
{
    GUIStyle style = new GUIStyle();
    style.fontSize = 18;
    style.normal.textColor = Color.red;

    GUI.Label(new Rect(10, 80, 400, 54), "欧拉角转换的四元数:" + Quaternion.Euler(transform.eulerAngles),style);

    GUI.Label(new Rect(10, 120, 400, 54), "直接获取的四元数:" + transform.rotation, style);

}

  由上面动图可见,当物体旋转到一定角度时,欧拉角转换的四元数与直接获取的四元数相反。转动到另一些角度时,欧拉角转换的四元数等于直接获取的四元数。

  公司要求要把Unity里的一些场景嵌入到Flutter里,并且要实现一些操作,从没接触过Unity,只好硬顶着上了。