屏幕拖拽旋转转盘

发表于2018-12-12
评论0 3.4k浏览
如图,黄色圆形作为旋转对象,受外力绕圆心顺时针或逆时针旋转
简化模型:
图例:
以旋转对象的旋转中心为坐标原点建立坐标系:

当拖拽时施加力的作用点刚好在Y轴时,作用力的方向与物体旋转方向如上图所示,X轴同理。

当拖拽时施加力的作用点在第一象限时,以施加力的作用点为坐标原点建立子坐标系,作用力的方向(子坐标系中所在的象限)与物体旋转方向如下图所示,其他象限同理:

分析出拖拽力的方向与物体旋转方向的关系,接下来就是代码实现,包括原始计算过程与化简后的代码:

using UnityEngine;
using UnityEngine.UI;
using UnityEngine.EventSystems;
public class RotateController : MonoBehaviour, IDragHandler
{
    public enum DragType
    {
        //固定旋转速度, 匀速
        ConstSpeed,
        //根据鼠标移动速度改变旋转速度
        MouseSpeed,
        //不旋转
        NotRotate
    }
    //旋转物体
    [SerializeField] Transform rotateObj;
    //旋转方式
    [SerializeField] DragType drapType;
    //固定单位速度
    [SerializeField] float constSpeed;
    //可变速度系数
    [SerializeField] float mouseSpeed;
    //屏幕中心屏幕坐标
    Vector2 screenCenter = new Vector2(0.5f * Screen.width, 0.5f * Screen.height);
    void Start()
    {
    }
    //鼠标拖拽回调
    Vector3 tmpEulerAngle;
    Vector2 tmpPressByObjPos;
    public void OnDrag(PointerEventData eventData)
    {
        ////eventData.position : 坐标原点为屏幕左下点, 该坐标系下拖拽位置坐标
        ////drapPos : 以屏幕中心为坐标原点, 转化拖拽位置坐标
        //Vector2 drapPos = eventData.position - screenCenter;
        ////旋转UI的锚点设置在屏幕中间, 即以屏幕中心为坐标原点, 即与drapPos同坐标系
        ////drapPosByObj : 旋转物体指向拖拽点的向量, 即以旋转物体为坐标原点, 拖拽点相对于旋转物体的相对坐标
        //Vector2 drapPosByObj = drapPos - new Vector2(rotateObj.localPosition.x, rotateObj.localPosition.y);
        //计算拖拽点相对于旋转物体的相对坐标
        tmpPressByObjPos = eventData.position - screenCenter - new Vector2(rotateObj.localPosition.x, rotateObj.localPosition.y);
        //读取旋转物体旋转角度
        tmpEulerAngle = rotateObj.rotation.eulerAngles;
        //计算角度变化量, 绕Z轴旋转, 对Z值重新赋值
        tmpEulerAngle.z += CalculationAngleByDelta(drapType, eventData.delta, tmpPressByObjPos);
        //设置旋转物体旋转角度
        rotateObj.rotation = Quaternion.Euler(tmpEulerAngle);
    }
    //计算旋转角度变化量
    float CalculationAngleByPoint(DragType dragtype, Vector2 startPoint, Vector2 endPoint)
    {
        return CalculationAngleByDelta(dragtype, endPoint - startPoint, endPoint);
    }
    //计算旋转角度变化量
    float CalculationAngleByDelta(DragType dragtype, Vector2 delta, Vector2 endPoint)
    {
        //起点向量 startPoint : 旋转物体的旋转中心指向拖拽起点的向量
        //终点向量 endPoint   : 旋转物体的旋转中心指向拖拽终点的向量
        //偏移向量 delta      : 拖拽起点指向拖拽终点的向量
        //向量关系 : delta = endPoint - startPoint
        //endPoint: 终点向量, 拖拽停止点, 相对旋转物体的坐标值, 是作用力的作用点
        //delta: 偏移向量, 拖拽的距离, 可视为作用力
        //偏移量较小忽略不计
        if (delta.sqrMagnitude < 0.01f)
            return 0;
        if(dragtype == DragType.ConstSpeed)
        {
            //定速, 每次旋转角度的变化量为固定值, 不受拖拽距离影响
            //速度变化量 = 旋转方向 * 单位速度
            return CalculationDirection02(delta, endPoint) * constSpeed;
        }
        if (dragtype == DragType.MouseSpeed)
        {
            //终点向量 endPoint
            //偏移量 delta = endPoint - startPoint
            //起点向量 startPoint = endPoint - delta
            //偏移量夹角 Vector2.Angle(endPoint, startPoint)
            //可变速度, 速度变化量 = 旋转方向 * 速度系数 * 角度偏移量
            return CalculationDirection02(delta, endPoint) * mouseSpeed * Vector2.Angle(endPoint, endPoint - delta);
        }
        return 0;
    }
    int tmpDirectParame;
    //计算旋转方向,  (计算原理见CalculationDirection00方法及注释)
    //忽略特殊情况判断(endPoint.x = 0 | endPoint.y = 0 | delta.x = 0 | delta.y = 0)
    //虽然极大地减少了计算量, 而且特殊情况出现的概率也比较小, 但由于没有对特殊情况判断, 在水平或竖直方向拖动时不会发生旋转, 慎用
    int CalculationDirection02(Vector2 delta, Vector2 endPoint)
    {
        tmpDirectParame = 0;
        int pointSignX = (int)Mathf.Sign(endPoint.x);
        int pointSignY = (int)Mathf.Sign(endPoint.y);
        if (pointSignX * pointSignY > 0)
        {
            if (delta.x < 0 && delta.y > 0)
                tmpDirectParame = pointSignX;
            else if (delta.x > 0 && delta.y < 0)
                tmpDirectParame = -pointSignX;
        }
        else if (pointSignX * pointSignY < 0)
        {
            if (delta.x > 0 && delta.y > 0)
                tmpDirectParame = pointSignX;
            else if (delta.x < 0 && delta.y < 0)
                tmpDirectParame = -pointSignX;
        }
        return tmpDirectParame;
    }
    //计算旋转方向, 化简计算过程 (计算原理见CalculationDirection00方法及注释)
    int CalculationDirection01(Vector2 delta, Vector2 endPoint)
    {
        tmpDirectParame = 0;
        //获取正负号
        int pointSignX = (int)Mathf.Sign(endPoint.x);
        int pointSignY = (int)Mathf.Sign(endPoint.y);
        int deltaSignX = (int)Mathf.Sign(delta.x);
        int deltaSignY = (int)Mathf.Sign(delta.y);
        //Y轴
        if (endPoint.x.IsZero())
        {
            if (delta.x > 0)
                tmpDirectParame = -pointSignY;
            else if (delta.x < 0)
                tmpDirectParame = pointSignY;
        }
        //X轴
        else if (endPoint.y.IsZero())
        {
            if (delta.y > 0)
                tmpDirectParame = pointSignX;
            else if (delta.y < 0)
                tmpDirectParame = -pointSignX;
        }
        //象限内
        else
        {
            if (delta.x.IsZero())
                tmpDirectParame = deltaSignX * pointSignY;
            else if (delta.y.IsZero())
                tmpDirectParame = -deltaSignY * pointSignX;
            else
            {
                if (pointSignX * pointSignY > 0)
                {
                    if (delta.x < 0 && delta.y > 0)
                        tmpDirectParame = pointSignX;
                    else if (delta.x > 0 && delta.y < 0)
                        tmpDirectParame = -pointSignX;
                }
                else if (pointSignX * pointSignY < 0)
                {
                    if (delta.x > 0 && delta.y > 0)
                        tmpDirectParame = pointSignX;
                    else if (delta.x < 0 && delta.y < 0)
                        tmpDirectParame = -pointSignX;
                }
            }
        }
        return tmpDirectParame;
    }
    //计算旋转方向, 化简之前原始计算过程(有多余判断)
    int CalculationDirection00(Vector2 delta, Vector2 endPoint)
    {
        //endPoint: 终点向量, 可视为作用点相对坐标
        //delta: 偏移向量, 可视为作用力
        //旋转方向参数
        tmpDirectParame = 0;
        //计算旋转方向
        //坐标原点
        if (endPoint.x.IsZero() && endPoint.y.IsZero())
        {
            tmpDirectParame = 0;
        }
        //Y轴正半轴
        else if (endPoint.x.IsZero() && endPoint.y > 0)
        {
            //作用力方向(Y轴右侧/左侧方向)
            if (delta.x > 0)
                tmpDirectParame = -1;
            else if (delta.x < 0)
                tmpDirectParame = 1;
        }
        //Y轴负半轴
        else if (endPoint.x.IsZero() && endPoint.y < 0)
        {
            //作用力方向(Y轴右侧/左侧方向)
            if (delta.x > 0)
                tmpDirectParame = 1;
            else if (delta.x < 0)
                tmpDirectParame = -1;
        }
        //X轴正半轴
        else if (endPoint.x > 0 && endPoint.y.IsZero())
        {
            //作用力方向(X轴上测/下侧方向)
            if (delta.y > 0)
                tmpDirectParame = 1;
            else if (delta.y < 0)
                tmpDirectParame = -1;
        }
        //X轴负半轴
        else if (endPoint.x < 0 && endPoint.y.IsZero())
        {
            //作用力方向(X轴上测/下侧方向)
            if (delta.y > 0)
                tmpDirectParame = -1;
            else if (delta.y < 0)
                tmpDirectParame = 1;
        }
        //第一象限
        else if (endPoint.x > 0 && endPoint.y > 0)
        {
            //作用力方向(相对以作用点为原点的子坐标系位置)
            //子坐标系原点
            if (delta.x.IsZero() && delta.y.IsZero())
                tmpDirectParame = 0;
            //子坐标系Y轴正半轴
            else if (delta.x.IsZero() && delta.y > 0)
                tmpDirectParame = 1;
            //子坐标系Y轴负半轴
            else if (delta.x.IsZero() && delta.y < 0)
                tmpDirectParame = -1;
            //子坐标系X轴正半轴
            else if (delta.x > 0 && delta.y.IsZero())
                tmpDirectParame = -1;
            //子坐标系X轴负半轴
            else if (delta.x < 0 && delta.y.IsZero())
                tmpDirectParame = 1;
            //子坐标系第一象限
            else if (delta.x > 0 && delta.y > 0)
                tmpDirectParame = 0;
            //子坐标系第二象限
            else if (delta.x < 0 && delta.y > 0)
                tmpDirectParame = 1;
            //子坐标系第三象限
            else if (delta.x < 0 && delta.y < 0)
                tmpDirectParame = 0;
            //子坐标系第四象限
            else if (delta.x > 0 && delta.y < 0)
                tmpDirectParame = -1;
        }
        //第二象限
        else if (endPoint.x < 0 && endPoint.y > 0)
        {
            if (delta.x.IsZero() && delta.y.IsZero())
                tmpDirectParame = 0;
            else if (delta.x.IsZero() && delta.y > 0)
                tmpDirectParame = -1;
            else if (delta.x.IsZero() && delta.y < 0)
                tmpDirectParame = 1;
            else if (delta.x > 0 && delta.y.IsZero())
                tmpDirectParame = -1;
            else if (delta.x < 0 && delta.y.IsZero())
                tmpDirectParame = 1;
            else if (delta.x > 0 && delta.y > 0)
                tmpDirectParame = -1;
            else if (delta.x < 0 && delta.y > 0)
                tmpDirectParame = 0;
            else if (delta.x < 0 && delta.y < 0)
                tmpDirectParame = 1;
            else if (delta.x > 0 && delta.y < 0)
                tmpDirectParame = 0;
        }
        //第三象限
        else if (endPoint.x < 0 && endPoint.y < 0)
        {
            if (delta.x.IsZero() && delta.y.IsZero())
                tmpDirectParame = 0;
            else if (delta.x.IsZero() && delta.y > 0)
                tmpDirectParame = -1;
            else if (delta.x.IsZero() && delta.y < 0)
                tmpDirectParame = 1;
            else if (delta.x > 0 && delta.y.IsZero())
                tmpDirectParame = 1;
            else if (delta.x < 0 && delta.y.IsZero())
                tmpDirectParame = -1;
            else if (delta.x > 0 && delta.y > 0)
                tmpDirectParame = 0;
            else if (delta.x < 0 && delta.y > 0)
                tmpDirectParame = -1;
            else if (delta.x < 0 && delta.y < 0)
                tmpDirectParame = 0;
            else if (delta.x > 0 && delta.y < 0)
                tmpDirectParame = 1;
        }
        //第四象限
        else if (endPoint.x > 0 && endPoint.y < 0)
        {
            if (delta.x.IsZero() && delta.y.IsZero())
                tmpDirectParame = 0;
            else if (delta.x.IsZero() && delta.y > 0)
                tmpDirectParame = 1;
            else if (delta.x.IsZero() && delta.y < 0)
                tmpDirectParame = -1;
            else if (delta.x > 0 && delta.y.IsZero())
                tmpDirectParame = 1;
            else if (delta.x < 0 && delta.y.IsZero())
                tmpDirectParame = -1;
            else if (delta.x > 0 && delta.y > 0)
                tmpDirectParame = 1;
            else if (delta.x < 0 && delta.y > 0)
                tmpDirectParame = 0;
            else if (delta.x < 0 && delta.y < 0)
                tmpDirectParame = -1;
            else if (delta.x > 0 && delta.y < 0)
                tmpDirectParame = 0;
        }
        return tmpDirectParame;
    }
}
public static class Tool
{
    //判断float为0
    public static bool IsZero(this float f)
    {
        //return Mathf.Approximately(f, 0);
        if (f > -0.0001f && f < 0.0001f)
            return true;
        return false;
    }
}

如社区发表内容存在侵权行为,您可以点击这里查看侵权投诉指引