首先我們來參考一下四元數在Unity中的應用:
四元數quaternion的變換比較復雜,但是在unity中已經給我們寫好了相應的函數實現對transform的操作。
在最近的一個項目中,遇到了一個單手指滑動手機屏幕實現對模型的一個旋轉操作,在嘗試了各種unity中的旋轉函數之后都沒能夠達到想要的效果之后,我選擇了用Quaternion.AngleAxis的函數來實現旋轉的操作效果。
首先我們來分析一下Quaternion.AngleAxis(angle,axis),參數angle和axis代表了物體的旋轉角度和旋轉軸心。如下圖:紅色箭頭方向代表物體所圍繞的旋轉軸,旋轉角度可以是自定義的。

接下來,我們就要做兩件事情,確定axis和計算angle。在這個項目中,我們是根據單個手指在手機屏幕上滑動,我們通過記錄滑動的距離,X方向的增量,以及Y軸方向的增量來為后面計算axis和angle打下基礎。unity的Input函數有GetTouch這個函數,我們只需要調用這個函數的相關方法就可以實現需求。
現在,我們在unity中新建一個場景,在場景中新建一個立方塊。

注意立方塊的世界坐標軸,Z軸的朝向應該是朝着攝像機的。根據之前對四元數腳本的分析,立方體的旋轉腳本為:
Gesture.cs:
1 using UnityEngine; 2 using System.Collections; 3 4 public class gesture : MonoBehaviour { 5 public Transform Cube; 6 private float radius = 1080; 7 private Vector3 originalDir = new Vector3(0f,0f,1080f); 8 private Vector3 CenterPos = new Vector3(0, 0, 0); 9 private Vector2 startPos; 10 private Vector2 tempPos; 11 private Vector3 tempVec; 12 private Vector3 normalAxis; 13 private float angle; 14 // Use this for initialization 15 void Start () { 16 Cube = GameObject.Find("Cube").transform; 17 } 18 19 // Update is called once per frame 20 void Update () { 21 if (Input.touchCount == 1) 22 { 23 //Vector2 startPos = Input.compositionCursorPos; 24 if (Input.GetTouch(0).phase == TouchPhase.Began) 25 { 26 startPos = Input.GetTouch(0).position; 27 //tempPos = startPos; 28 } 29 //if (Input.GetTouch(0).phase == TouchPhase.Ended) 30 //{ 31 // tempPos = startPos; 32 //} 33 if (Input.GetTouch(0).phase == TouchPhase.Moved) 34 { 35 tempPos = Event.current.mousePosition; 36 37 float tempX = tempPos.x - startPos.x; 38 39 float tempY = tempPos.y - startPos.y; 40 41 //tempPos = Input.GetTouch(0).deltaPosition; 42 //float tempX = tempPos.x; 43 44 //float tempY = tempPos.y; 45 46 float tempZ = Mathf.Sqrt(radius * radius - tempX * tempX - tempY * tempY); 47 48 tempVec = new Vector3(tempX, tempY, tempZ); 49 50 angle = Mathf.Acos(Vector3.Dot(originalDir.normalized, tempVec.normalized)) * Mathf.Rad2Deg; 51 52 normalAxis = getNormal(CenterPos, originalDir, tempVec); 53 54 Cube.rotation = Quaternion.AngleAxis(2 *angle, normalAxis); 55 56 } 57 } 58 } 59 60 void OnGUI() 61 { 62 GUILayout.Label("StartPos 的坐標值為: "+startPos); 63 GUILayout.Label("tempPos 的坐標值為: " + tempPos); 64 GUILayout.Label("tempVec 的坐標值為: " + tempVec); 65 GUILayout.Label("normalAxis 的坐標值為: " + normalAxis); 66 GUILayout.Label("旋轉角度的值為: " + 2*angle); 67 GUILayout.Label("Cube的四元數角度: " + Cube.rotation); 68 GUILayout.Label("Cube de rotation.x: " + Cube.rotation.eulerAngles.x); 69 GUILayout.Label("Cube de rotation.y: " + Cube.rotation.eulerAngles.y); 70 GUILayout.Label("Cube de rotation.z: " + Cube.rotation.eulerAngles.z); 71 } 72 73 private Vector3 getNormal(Vector3 p1,Vector3 p2,Vector3 p3) 74 { 75 float a = ((p2.y - p1.y) * (p3.z - p1.z) - (p2.z - p1.z) * (p3.y - p1.y)); 76 77 float b = ((p2.z - p1.z) * (p3.x - p1.x) - (p2.x - p1.x) * (p3.z - p1.z)); 78 79 float c = ((p2.x - p1.x) * (p3.y - p1.y) - (p2.y - p1.y) * (p3.x - p1.x)); 80 //a對應的屏幕的垂直方向,b對應的屏幕的水平方向。 81 return new Vector3(a, -b, c); 82 } 83 }
如果我們將這個在新機上運行,會發現在第一次手指滑動旋轉是正常的,但是第二次就會有個跳動的過程。在這里我們需要注意一個問題,四元數函數Quaternion.AngleAxis是將立方體以初始的旋轉角度來進行圍繞着軸Axis旋轉Angle角度,不是在上一個狀態下的增量。如果需要延續上一次的旋轉狀態,就需要將這個物體的rotation恢復到初始的狀態。按照這個思路,我在Cube添加了一個父對象,我們在操作的時候對這個父對象進行操作,然后手指在離開屏幕的時候,將Cube脫離父對象,然后將父對象的rotation進行還原,再將Cube綁定為父物體的子對象,在一下次手指旋轉之后就會接着上一次的旋轉狀態進行旋轉,實現了旋轉的延續。

實現的代碼為:
1 using UnityEngine; 2 using System.Collections; 3 4 public class gesture : MonoBehaviour { 5 public Transform Cube; 6 public Transform RotObj; 7 private float radius = 1080; 8 private Vector3 originalDir = new Vector3(0f,0f,1080f); 9 private Vector3 CenterPos = new Vector3(0, 0, 0); 10 private Vector2 startPos; 11 private Vector2 tempPos; 12 private Vector3 tempVec; 13 private Vector3 normalAxis; 14 private float angle; 15 // Use this for initialization 16 void Start () { 17 Cube = GameObject.Find("Cube").transform; 18 } 19 20 // Update is called once per frame 21 void Update () { 22 if (Input.touchCount == 1) 23 { 24 //Vector2 startPos = Input.compositionCursorPos; 25 if (Input.GetTouch(0).phase == TouchPhase.Began) 26 { 27 startPos = Input.GetTouch(0).position; 28 } 29 if (Input.GetTouch(0).phase == TouchPhase.Moved) 30 { 31 tempPos = Event.current.mousePosition; 32 33 float tempX = tempPos.x - startPos.x; 34 35 float tempY = tempPos.y - startPos.y; 36 37 float tempZ = Mathf.Sqrt(radius * radius - tempX * tempX - tempY * tempY); 38 39 tempVec = new Vector3(tempX, tempY, tempZ); 40 41 angle = Mathf.Acos(Vector3.Dot(originalDir.normalized, tempVec.normalized)) * Mathf.Rad2Deg; 42 43 normalAxis = getNormal(CenterPos, originalDir, tempVec); 44 45 RotObj.rotation = Quaternion.AngleAxis(2 *angle, normalAxis); 46 47 } 48 if (Input.GetTouch(0).phase == TouchPhase.Ended) 49 { 50 Cube.transform.parent = null; 51 RotObj.rotation = Quaternion.identity; 52 Cube.parent = RotObj; 53 } 54 } 55 } 56 57 void OnGUI() 58 { 59 GUILayout.Label("StartPos 的坐標值為: "+startPos); 60 GUILayout.Label("tempPos 的坐標值為: " + tempPos); 61 GUILayout.Label("tempVec 的坐標值為: " + tempVec); 62 GUILayout.Label("normalAxis 的坐標值為: " + normalAxis); 63 GUILayout.Label("旋轉角度的值為: " + 2*angle); 64 GUILayout.Label("Cube的四元數角度: " + Cube.rotation); 65 GUILayout.Label("Cube de rotation.x: " + Cube.rotation.eulerAngles.x); 66 GUILayout.Label("Cube de rotation.y: " + Cube.rotation.eulerAngles.y); 67 GUILayout.Label("Cube de rotation.z: " + Cube.rotation.eulerAngles.z); 68 } 69 70 private Vector3 getNormal(Vector3 p1,Vector3 p2,Vector3 p3) 71 { 72 float a = ((p2.y - p1.y) * (p3.z - p1.z) - (p2.z - p1.z) * (p3.y - p1.y)); 73 74 float b = ((p2.z - p1.z) * (p3.x - p1.x) - (p2.x - p1.x) * (p3.z - p1.z)); 75 76 float c = ((p2.x - p1.x) * (p3.y - p1.y) - (p2.y - p1.y) * (p3.x - p1.x)); 77 //a對應的屏幕的垂直方向,b對應的屏幕的水平方向。 78 return new Vector3(a, -b, c); 79 } 80 }
現在對應着手指的滑動距離,然后調整參數radius,就可以實現比較順滑的旋轉效果,真機實現的效果就不展示了。
