屏幕拖拽旋转转盘
发表于2018-12-12
如图,黄色圆形作为旋转对象,受外力绕圆心顺时针或逆时针旋转
简化模型:
图例:
以旋转对象的旋转中心为坐标原点建立坐标系:
当拖拽时施加力的作用点刚好在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; } }