Unity自己不提供搖桿的組件,開發者可使用牛逼的EasyTouch插件或者應用NGUI實現相關的需求,下面本文經過Unity自身的UGUI屬性,實現虛擬搖桿的功能。 主參考 《Unity:使用 UGUI 的 ScrollRect 製做虛擬搖桿》和鬆神的《UGUI研究院之遊戲搖桿》,分「搖桿UI的構建」和「搖桿事件鏈接」以及「搖桿表現強化」三方面總結制做過程當中的思路筆記。html
一 搖桿UI的搭建ide
核心是使用UGUI 的 ScrollRect Component( 常常用於 Scroll View 的製做),因能響應拖動回彈,同時又能計算對象的X軸和Y軸的偏移,因此選擇此組件開發。 建立兩Image組件,分別顯示搖桿背景和搖桿圓點,學習
(overlay保證是場景攝像機看到的最上層)動畫
(搖桿圓點以底座節點做parent,重置位置)ui
給stickBg_添加ScrollRect組件(實質是一腳本),並在 Inspector 視窗將 Content 欄位拖動設置stick_爲其內容,this
注意,Movement Type 項,將此欄位設置爲 Elastic,英文意思是回彈,則能自動將設置在 Content 的物件拉回置中,拖動原件,會有必定程度的緩衝力使物體不被拉遠,並在放開時自動彈回置中。spa
運行遊戲,發現雖然能拖能回彈,可是隻能矩形拖動,並且矩形區域賊大,不符合實際項目的搖桿需求。 參考鬆神博文,直接繼承重寫ScrollRect組件,代碼以下:.net
using UnityEngine; using System.Collections; using UnityEngine.UI; //ScrollRect組件定義在UI的命名空間 using UnityEngine.EventSystems; public class ScrollCircle : ScrollRect { protected float mRadius; protected override void Start() { this.mRadius = (transform as RectTransform).sizeDelta.x * 0.5f; } public override void OnDrag(PointerEventData eventData) { base.OnDrag(eventData); Vector2 contentPostion = base.content.anchoredPosition; if (contentPostion.magnitude > this.mRadius) { contentPostion = contentPostion.normalized * this.mRadius; } base.content.anchoredPosition = contentPostion; } }
運行遊戲,發現拖動半徑生效。插件
【補料:3d
(直接上傳給你們湊合着用,親身找過資源,都要錢很差找哈哈)】
還有一隱藏bug,就是當搖桿位置佈置得比較靠近屏幕邊緣時,拖動會出現拖不盡的問題,可參考 《Unity3D學習日記(一)使用UGUI製做虛擬搖桿》
二 搖桿事件鏈接
在搖桿UI製做完畢後,接下來監聽和派發搖桿的觸摸事件,即監聽接收到stickBg_的移動,且將移動量傳遞出去給目標對象。這就運用到unity的事件系統的API,以下:
using UnityEngine; //using System.Collections; //可去,使用 Coroutine 才須要此聲明空間 using UnityEngine.Events; //事件派發Interface using UnityEngine.EventSystems; //觸摸begin drag end事件監聽
在操做搖桿時,主要監聽「開始控制( OnBeginDrag )」 和 「控制中( OnDrag )」 以及 「控制結束( OnEndDrag )」三個事件。 接口定義在Event空間的 IDragHandler , IEndDragHandler , IBeginDragHandler類聲明中,因此搖桿腳本類要繼承這三者類,詳細類分析參考官網的 IDragHandler ,
public class FixedJoystickHandler : MonoBehaviour, IDragHandler, IEndDragHandler, IBeginDragHandler { public void OnBeginDrag(PointerEventData eventData) {} public void OnDrag(PointerEventData eventData) {} public void OnEndDrag(PointerEventData eventData) {} }
而後,定義 UnityEvent 事件執行處理對象,負責接收目前操做搖桿的結果,簡單說是當監聽到上述事件時要對哪一個對象執行什麼功能,有點相似於UGUI的 Button ,在 Inspector 窗口有Click 事件項,用以設置當發生Click 時會調用執行哪一個對象的哪一個接口,
官方UnityEvent使用例程以下:
public class ExampleClass : MonoBehaviour { UnityEvent m_MyEvent; void Start() { if (m_MyEvent == null) m_MyEvent = new UnityEvent(); m_MyEvent.AddListener(Ping); } void Update() { if (Input.anyKeyDown && m_MyEvent != null) { m_MyEvent.Invoke(); } } void Ping() { Debug.Log("Ping"); } }
public Transform content; //搖桿位移量「主人」 public UnityEvent beginControl; //開始控制器 public UnityEvent endControl; //結束控制器
注意,stickBg_實時位移時須要將真實的位移量傳遞給UnityEvent類對象以計算移動距離和方向等,即須要傳遞參數,這就要運用到 UnityEvent擴展的模板類 UnityEvent<T0> ,官方例程以下:
using UnityEngine; using UnityEngine.Events; [System.Serializable] public class MyIntEvent : UnityEvent<int> { } public class ExampleClass : MonoBehaviour { public MyIntEvent m_MyEvent; void Start() { if (m_MyEvent == null) m_MyEvent = new MyIntEvent(); m_MyEvent.AddListener(Ping); } void Update() { if (Input.anyKeyDown && m_MyEvent != null) { m_MyEvent.Invoke(5); } } void Ping(int i) { Debug.Log("Ping" + i); } }
可見T0表示傳遞的參數類型,且支持多個形參傳遞,如本節中傳遞的位移量是Vector3,
[System.Serializable] //這裏加Serializable目的是使其正確顯示在Inspector的事件屬性窗口,以下圖 public class VirtualJoystickEvent : UnityEvent<Vector3> { }
public VirtualJoystickEvent controlling;
最後,就是實例化每一個事件監聽行爲,如剛開始被拖動時執行 beginControl 事件等,
public void OnBeginDrag(PointerEventData eventData) { this.beginControl.Invoke(); } public void OnEndDrag(PointerEventData eventData) { this.endControl.Invoke(); }
還有一點要注意的,本節 controlling 傳遞的結果是搖桿的偏移量和方向,stick_ 是 stickBg_的子控件,初始本地座標localPosition爲(0,0,0),移動時 x > 0 表示向右,< 0 是向左,而 y > 0 向上。可是隨着搖桿資源的尺寸大小不一樣,若使用實際的偏移量會因不一樣大小差別而變化,可能會形成影響。 故進行 歸一化 運算,使其x和y值老是介於-1到1之間,從而實現無論各類搖桿資源尺寸改變,被控制的物體接收到的值都是同樣的。
public void OnDrag(PointerEventData eventData) { if (this.content) { this.controlling.Invoke(this.content.localPosition.normalized); } }
到此事件監聽和派發實現完畢,附上完整腳本的代碼:
//FixedJoystickHandler.cs using UnityEngine; using UnityEngine.Events; using UnityEngine.EventSystems; public class FixedJoystickHandler : MonoBehaviour, IDragHandler, IEndDragHandler, IBeginDragHandler { [System.Serializable] public class VirtualJoystickEvent : UnityEvent<Vector3> { } public Transform content; public UnityEvent beginControl; public VirtualJoystickEvent controlling; public UnityEvent endControl; public void OnBeginDrag(PointerEventData eventData) { this.beginControl.Invoke(); } public void OnDrag(PointerEventData eventData) { if (this.content) { this.controlling.Invoke(this.content.localPosition.normalized); } } public void OnEndDrag(PointerEventData eventData) { this.endControl.Invoke(); } }
運行遊戲前,先添加事件處理器controller,對事件接收對象進行賦值,以下圖:
本節將3d模型預製體對象p4賦值給各個control,響應的是Tank.cs中的對應接口方法。 固然控制目標也能夠爲人物角色,平時站在原地不移動的動畫播放狀態爲閒置中Idle,當操做搖桿時通知該角色 Animator 開始移動,讓 Animator 的動做狀態切換爲走路動畫。而後controling控制中的事件不斷通知角色更新當前最新的方向和位移;同理在結束控制後通知角色的 Animator 中止移動,從新切換回原先Idle的動畫播放。
//Tank.cs using System.Collections; using System.Collections.Generic; using UnityEngine; public class Tank : MonoBehaviour { public List<AxleInfo> axleInfos; private float motor_ = 0; public float maxMotor; private float steer_ = 0; public float maxSteer; private float brake_ = 0; public float maxBrake = 100; private Transform wheelList; private void joyStickControl(float x, float y) { steer_ = x * maxSteer; motor_ = y * maxMotor; } private void playerCtrl() { //steer_ = Input.GetAxis("Horizontal") * maxSteer; //motor_ = Input.GetAxis("Vertical") * maxMotor; brake_ = 0; foreach (AxleInfo axleInfo in axleInfos) { if (axleInfo.leftWheel.rpm > 5 && motor_ < 0) //正在前進時「down」 brake_ = maxBrake; else if (axleInfo.leftWheel.rpm < -5 && motor_ > 0) //正在後退時「up」 brake_ = maxBrake; continue; } } void Update () { playerCtrl(); foreach(AxleInfo axleInfo in axleInfos) { if (axleInfo.motor) { axleInfo.leftWheel.motorTorque = motor_; axleInfo.rightWheel.motorTorque = motor_; } if(axleInfo.steering) { axleInfo.leftWheel.steerAngle = steer_; axleInfo.rightWheel.steerAngle = steer_; } axleInfo.leftWheel.brakeTorque = brake_; axleInfo.rightWheel.brakeTorque = brake_; if (axleInfos[1] != null && axleInfo == axleInfos[1]) { WheelRotation(axleInfos[1].leftWheel); } } } private void WheelRotation(WheelCollider collider) { if (wheelList == null) return; Vector3 pos; Quaternion rot; collider.GetWorldPose(out pos, out rot); foreach(Transform w in wheelList) { w.rotation = rot; } } private void Start() { wheelList = transform.Find("WheelList"); } public void beginMove() { Debug.Log("start begin move!!"); } public void doMove(Vector3 drag) { joyStickControl(drag.x, drag.y); } public void endMove() { joyStickControl(0,0); } }
運行遊戲,可看到坦克能經過搖桿和按鍵盤同樣的操縱了~
資料連接:
《Unity3D遊戲開發之使用EasyTouch虛擬搖桿控制人物移動》
三 搖桿表現強化
佔坑
(搖桿半透明化,點擊拖動的時候全體化)
(搖桿一開始隱藏,觸摸時才實際點位置顯示,即浮動式虛擬搖桿)