日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

Unity Joystick手势操作

發布時間:2024/3/12 编程问答 35 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Unity Joystick手势操作 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

Unity Joystick手勢操作

作者:無聊


實現原因

由于制作Demo的需要,第三方的相關插件都過于重量級,所以就自己實現了一個簡單的手勢操作方案。

基本功能

本文實現了一個簡易的Unity JoyStick手勢操作,主要實現三個功能,操縱桿(Joystick)、相機旋轉(Rotate)與縮放(Scale)。

基本邏輯結構如下:

protected void LateUpdate() {AroundByMobileInput(); }void AroundByMobileInput() {if (Input.touchCount > 0 && Input.touchCount <= 2){for (int i = 0; i < Input.touchCount; i++){if (Input.touches[i].phase == TouchPhase.Began){判斷可能存在縮放操作的計時標記如果在屏幕左半邊,則初始化Joystick記錄觸摸信息,包括位置、ID如果在屏幕右半邊,則初始化Rotate記錄觸摸信息,包括位置、ID計時器增加 不能操作縮放}else if (Input.touches[i].phase == TouchPhase.Moved || Input.touches[i].phase == TouchPhase.Stationary){根據ID來執行相應操作,Joystick還是Rotate操作計時器操作}else if (Input.touches[i].phase == TouchPhase.Canceled || Input.touches[i].phase == TouchPhase.Ended){同樣根據ID來執行最后的收尾工作}}}根據計時器的時間判斷是否可以進行縮放操作 canScaleif (canScale){雙指縮放操作} }

手勢操作的實現均是根據Unity提供的Input的TouchPhase來判斷狀態,然后三個主要功能Joystick、Rotate和Scale根據其狀態和Input的Position變換等各種屬性來進行操作。

下面分別列出三個主要功能,各自單獨的具體代碼實現。

Joystick

Joystick的實現原理是記錄觸摸點第一幀的初始位置,顯示操縱桿背景圖。然后根據之后觸摸的位置與初始位置間的相對位移計算出偏移量,即是主角需要使用的位移值,顯示操縱桿。最后當觸摸停止時候清空數據。

public GameObject m_OnPad;// Joystick的GameObject public Image m_Bottom;// 背景圖片 public Image m_Stick;// 操縱桿圖片private Vector3 m_BeginPos = Vector2.zero;// Joystick初始化位置 private Vector2 m_CenterPos = Vector2.zero; private Vector3 m_Dir = Vector3.zero;// 最后計算相對的偏移距離,主角需要使用的位移值private float m_DisLimit = 1.0f;// 用于限定操縱桿在一定范圍內 private int m_MoveFingerId = -1; private bool m_HasMove = false;protected virtual void Start() {m_DisLimit = m_Bottom.rectTransform.sizeDelta.x / 2; }protected void AroundByMobileInput() {if (Input.touchCount > 0 && Input.touchCount <= 2){for (int i = 0; i < Input.touchCount; i++){if (Input.touches[i].phase == TouchPhase.Began){if (!EventSystem.current.IsPointerOverGameObject(Input.touches[i].fingerId)){m_MoveFingerId = Input.touches[i].fingerId;m_BeginPos = Input.touches[i].rawPosition;showJoyStick();}}else if (Input.touches[i].phase == TouchPhase.Moved || Input.touches[i].phase == TouchPhase.Stationary){if (Input.touches[i].fingerId == m_MoveFingerId){setStickCenterPos(Input.touches[i].position);}}else if (Input.touches[i].phase == TouchPhase.Canceled || Input.touches[i].phase == TouchPhase.Ended){if (Input.touches[i].fingerId == m_MoveFingerId){hideJoyStick();m_MoveFingerId = -1;}}}} }Vector2 convertTouchPosToUIPos(Vector2 touchPosition) {Vector2 localPoint;RectTransformUtility.ScreenPointToLocalPointInRectangle(m_OnPad.transform as RectTransform, touchPosition, null, out localPoint);return localPoint; }void showJoyStick() {m_Bottom.gameObject.SetActive(true);m_Stick.gameObject.SetActive(true);m_OnPad.SetActive(true);m_CenterPos = convertTouchPosToUIPos(m_BeginPos);m_Bottom.rectTransform.localPosition = m_CenterPos;m_Stick.rectTransform.localPosition = m_CenterPos;m_Dir.x = 0;m_Dir.z = 0;m_Dir.Normalize(); }void hideJoyStick() {m_OnPad.SetActive(false);m_Bottom.rectTransform.localPosition = Vector2.zero;m_Stick.rectTransform.localPosition = Vector2.zero;m_Dir.x = 0;m_Dir.z = 0;m_Dir.Normalize(); }void setStickCenterPos(Vector2 touch) {Vector2 pos = convertTouchPosToUIPos(touch);float dis = Vector2.Distance(pos, m_CenterPos);if (dis > m_DisLimit){Vector2 dir = pos - m_CenterPos;dir.Normalize();dir * = m_DisLimit;pos = m_CenterPos + dir;}m_Stick.transform.localPosition = pos;m_Dir.x = pos.x - m_CenterPos.x;m_Dir.z = pos.y - m_CenterPos.y;m_Dir.Normalize(); }

Rotate

Rotate同理,也是根據觸摸點第一幀的初始位置,與之后的位置的相對位移來計算相機的旋轉量。

public Vector2 CurrentAngles; private Vector2 targetAngles;private int m_RotateFingerId = -1; private Vector2 m_PreRotatePos;// 用于保存上一幀手指的觸碰位置 private bool m_HasRotate = false;protected virtual void Start() {CurrentAngles = targetAngles = transform.eulerAngles; }protected void AroundByMobileInput() {if (Input.touchCount > 0 && Input.touchCount <= 2){for (int i = 0; i < Input.touchCount; i++){if (Input.touches[i].phase == TouchPhase.Began){if (!EventSystem.current.IsPointerOverGameObject(Input.touches[i].fingerId)){if (!m_HasRotate){m_RotateFingerId = Input.touches[i].fingerId;m_PreRotatePos = Input.touches[i].position;m_HasRotate = true;}}}else if (Input.touches[i].phase == TouchPhase.Moved || Input.touches[i].phase == TouchPhase.Stationary){if (Input.touches[i].fingerId == m_RotateFingerId){Vector2 delta = Input.touches[i].position - m_PreRotatePos;targetAngles.y += delta.x;targetAngles.x -= delta.y;//Range.//targetAngles.x = Mathf.Clamp(targetAngles.x, angleRange.min, angleRange.max);m_PreRotatePos = Input.touches[i].position;}}else if (Input.touches[i].phase == TouchPhase.Canceled || Input.touches[i].phase == TouchPhase.Ended){if (Input.touches[i].fingerId == m_RotateFingerId){m_RotateFingerId = -1;m_HasRotate = false;}}}}//Lerp.CurrentAngles = Vector2.Lerp(CurrentAngles, targetAngles, Time.deltaTime);//Update transform position and rotation.transform.rotation = Quaternion.Euler(CurrentAngles); }

Scale

Scale則是根據兩根手指觸碰的距離與初始距離的相對大小來計算相機的位移。

public Transform target;// 主角public float CurrentDistance; private float targetDistance;private bool m_IsSingleFinger = true;private Vector2 oldPosition1;// 記錄上一次手機觸摸位置判斷用戶是在左放大還是縮小手勢 private Vector2 oldPosition2;protected virtual void Start() {CurrentDistance = targetDistance = Vector3.Distance(transform.position, target.position); }protected void AroundByMobileInput() {if (Input.touchCount == 1){m_IsSingleFinger = true;}if (Input.touchCount > 1){// 計算出當前兩點觸摸點的位置 if (m_IsSingleFinger){oldPosition1 = Input.GetTouch(0).position;oldPosition2 = Input.GetTouch(1).position;}if (Input.touches[0].phase == TouchPhase.Moved && Input.touches[1].phase == TouchPhase.Moved){var tempPosition1 = Input.GetTouch(0).position;var tempPosition2 = Input.GetTouch(1).position;float currentTouchDistance = Vector3.Distance(tempPosition1, tempPosition2);float lastTouchDistance = Vector3.Distance(oldPosition1, oldPosition2);// 計算上次和這次雙指觸摸之間的距離差距 // 然后去更改攝像機的距離 targetDistance -= (currentTouchDistance - lastTouchDistance) * Time.deltaTime * mouseSettings.wheelSensitivity;// 備份上一次觸摸點的位置,用于對比 oldPosition1 = tempPosition1;oldPosition2 = tempPosition2;m_IsSingleFinger = false;}}//Range//targetDistance = Mathf.Clamp(targetDistance, distanceRange.min, distanceRange.max);CurrentDistance = Mathf.Lerp(CurrentDistance, targetDistance, Time.deltaTime);transform.position = target.position - transform.forward * CurrentDistance; }

問題解決

三個主要功能的邏輯很簡潔,但是編寫過程中也遇到了一些問題,一些是實現單獨功能方面的,一些是合并的時候各自操作會沖突的。

1.

手勢操作需要兼容手指觸碰和外設手柄。但是在使用外設手柄的時候有時候遇到Joystick卡住的問題,如圖1

?
圖1. 使用外設手柄Joystick卡住

原因是在外設手柄或虛擬按鍵的時候,遙感的觸碰事件初始獲取的一定是設定的虛擬區域的中心位置,由于之前使用m_BeginPos = Input.touches[i].position獲取的是當前觸摸的位置,當搖桿移動過快時,導致第一次獲取的位置并不是虛擬區域的中心。把此點初始化成了搖桿的中心,而虛擬遙感的移動位置無法超過虛擬區域,造成遙感被卡住。如圖2。

?
圖2. 外設手柄的遙感可移動范圍

而Unity提供了另外一個方法Input.touches.rawPosition,這個方法獲取的是觸摸事件的初始值。則更改初始化位置的代碼為m_BeginPos = Input.touches[i].rawPosition;之后,再使用外設手柄的時候,Joystick的初始化位置就固定為手機設定的初始位置。問題解決,如圖3。

?
圖3. 使用外設手柄出現的問題被解決

if (Input.touches[i].phase == TouchPhase.Began) {if (!EventSystem.current.IsPointerOverGameObject(Input.touches[i].fingerId)){m_MoveFingerId = Input.touches[i].fingerId;m_BeginPos = Input.touches[i].rawPosition;//以前錯誤的使用了Input.touches[i].positionshowJoyStick();} }

rawPosition是觸摸事件的初始位置,而position是當前移動到的位置。

2.

手勢操作與UI布局有時會產生沖突,如圖4。

?
圖4. 手勢操作與UI布局沖突

針對這個問題,Unity已提供很好的解決方法。Unity提供了EventSystem.IsPointerOverGameObject來判斷手勢操作是否點擊到了UI上。直接在所有手勢初始化操作的時候增加一個判斷即可:

if (!EventSystem.current.IsPointerOverGameObject(Input.touches[i].fingerId))

EventSystem.current.IsPointerOverGameObject方法針對的是Unity提供UGUI的,并且需要勾選組件上的Raycast Target才能生效。

修改后點擊按鈕與手勢操作不再沖突,如圖5。

?
圖5. 手勢操作與UI布局不再沖突

3.

Rotate操作最初使用的是Input.GetAxis接口來判斷相機的旋轉角度targetAngles。這樣做會導致Joystick和Rotate操作會沖突,如圖6。

?
圖6. Joystick和Rotate操作沖突

原因是,Input.GetAxis接口并沒有細化到是第幾個手指觸碰,所以如果同時有兩個手指觸碰屏幕的時候會產生沖突。


因此將Input.GetAxis接口換成Input.touches的操作即可。 具體代碼如下:

// 修改前 targetAngles.y += Input.GetAxis("Mouse X"); targetAngles.x -= Input.GetAxis("Mouse Y");// 修改后 Vector2 delta = Input.touches[i].position - preRotatePos; targetAngles.y += delta.x; targetAngles.x -= delta.y; preRotatePos = Input.touches[i].position;

Input.touches可以根據touches[i]判斷是觸碰點的位置和順序,然后根據ID和順序限制觸碰點只能單獨進行Joystick操作或者Rotate操作,根據Input.touches.position的變換來計算旋轉角度。更改后如圖7,問題解決。

?
圖7. Joystick和Rotate操作不再沖突

4.

Joystick操作中,操作桿的位置需要限定在背景圖的范圍之內。

?
圖8. 操作桿的移動范圍

解決該問題使用Unity提供的接口RectTransformUtility.ScreenPointToLocalPointInRectangle,該方法是將屏幕空間上的點轉換為RectTransform的局部空間中位于其矩形平面上的位置。

其中m_OnPad傳入是Joystick的背景GameObject,touchPosition是觸碰的位置,即將當前Joystick的觸碰位置傳入,轉換成背景圖的RectTransform局部空間的坐標,代碼如下:

Vector2 convertTouchPosToUIPos(Vector2 touchPosition) {Vector2 localPoint;RectTransformUtility.ScreenPointToLocalPointInRectangle(m_OnPad.transform as RectTransform, touchPosition, null, out localPoint);return localPoint; }

然后,在顯示Joystick的操作桿位置的時候,然后限定局部坐標的位置與中心的距離,就可以將操作桿設置在圓盤的范圍內。m_CenterPos是觸碰第一幀的時候記錄的初始化坐標,也需要轉換成局部坐標;m_DisLimit是限定局部坐標的位置與中心的距離,需要初始化,具體代碼如下:

void setStickCenterPos(Vector2 touchPosition) {Vector2 pos = convertTouchPosToUIPos(touchPosition);float dis = Vector2.Distance(pos, m_CenterPos);if (dis > m_DisLimit)//{Vector2 dir = pos - m_CenterPos;dir.Normalize();dir * = m_DisLimit;pos = m_CenterPos + dir;}m_Stick.transform.localPosition = pos; }

5.

Scale與Joystick和Rotate沖突,即在兩個手指同時操作的情況下,三個操作同時進行,如圖9。

?
圖9. Scale與Joystick和Rotate沖突

而解決問題很簡單,則是考慮到需求,將需要雙指操作的Scale操作和兩個單指操作的Joystick移動和Rotate區分開來即可。原理是使用一個計時器記錄兩個觸碰點的間隔時間,如果這個時間超過了0.1s,則禁止Scale操作;同樣如果在Scale操作的時候也禁止Joystick和Rotate操作。

偽代碼如下:

bool m_Timer = false;// 判斷計時器開始與否 float m_IntervalTime = 0;// 記錄間隔時間 bool canScale = false;// 判斷能否進行Scale操作void AroundByMobileInput() {if (Input.touchCount > 0 && Input.touchCount <= 2){for (int i = 0; i < Input.touchCount; i++){if (Input.touches[i].phase == TouchPhase.Began){if (i == 0){m_Timer = true;m_IntervalTime = 0;}else if (i == 1){m_Timer = false;}初始化Joystick初始化Rotate}else if (Input.touches[i].phase == TouchPhase.Moved || Input.touches[i].phase == TouchPhase.Stationary){根據ID來執行相應操作,Joystick還是Rotate操作if (m_Timer){m_IntervalTime += Time.unscaledDeltaTime;}}else if (Input.touches[i].phase == TouchPhase.Canceled || Input.touches[i].phase == TouchPhase.Ended){同樣根據ID來執行最后的收尾工作}}}if (m_IntervalTime < 0.1f){canScale = true;}else{canScale = false;}if (canScale){雙指縮放操作} }

同時,如果需要的話可以更進一步限制雙指Scale操作只在屏幕右半邊才有效,這樣只需在初始化操作的時候進行判斷即可

if (Input.touches[i].position.x > Screen.width / 2)

修改之后問題解決,如圖10&11。

?
圖10. Scale與Joystick和Rotate不再沖突

?
圖11. 限定Scale操作在右半邊

6.

在上文功能的具體代碼中可看到,為每種操作都記錄相應的Input.touches[i].fingerId,目的是以便合并的時候下幀使用時可以獲取相同的操作的觸摸點。

總結

以上是生活随笔為你收集整理的Unity Joystick手势操作的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。