各位朋友,大家好,我是秦元培,歡迎大家關注我的博客,我的博客地址是 http://qinyuanpei.com。最近因為項目需要決定嘗試自己來實現一個虛擬搖桿,所以在今天的文章中我們的目標是使用uGUI來制作一個可以在移動平台穩定運行的虛擬搖桿(請不要問我為什么不使用NGUI來實現,你說我做個虛擬搖桿有必要在項目里導入那么多的資源嘛23333)。關於使用第三方插件來實現虛擬搖桿,請大家參照我以前寫的文章 Unity3D游戲開發之使用EasyTouch虛擬搖桿控制人物移動,在這里就不再贅述了。
  虛擬搖桿這種輸入方式相信大家在手機游戲平台上已經相當的熟悉了,首先我們來簡單了解下虛擬搖桿的設計原理。虛擬搖桿有一張固定的2D貼圖(背景層)和一張可拖動的2D貼圖(控制層)構成,默認情況下控制層在背景層的中心,我們稱這個位置為初始位置,當移動控制層后移動層的位置會發生變化,此時控制層的當前位置和初始位置兩點間可以計算出一個2D向量,通過這個向量我們就可以判斷虛擬搖桿的移動方向。在經典的八方向搖桿導航中搖桿中可移動方向被分成了上、左上、右上、下、左下、右下、左、右共8個方向。我們知道根據三角函數可以非常容易地計算出這個2D向量的角度並由此判定搖桿是在向着這8個方向中的哪一個方向移動。在今天的文章中,我們不需要考慮這8個方向,因為我們可以向任何一個方向進行移動。
  好了,首先在場景中創建兩個Image組件和一個空的游戲體,然后將這兩個Image組件拖拽到這個空的游戲體下使它們稱為其子節點。這里需要注意的是這兩個Image的層級關系。現在我們來編寫腳本,這個腳本將被添加到控制層物體上:
 
/*
 * uGUI虛擬搖桿
 * 作者:秦元培
 * 博客:[url]http://qinyuanpei.com[/url]
 * 時間:2015年10月24日
 */

using UnityEngine;
using System.Collections;
using UnityEngine.UI;
using UnityEngine.EventSystems;

public class JoyStick : MonoBehaviour,IPointerDownHandler, IPointerUpHandler, IDragHandler
{
    /// <summary>
    /// 搖桿最大半徑
    /// 以像素為單位
    /// </summary>
    public float JoyStickRadius = 50;
    /// <summary>
    /// 搖桿重置所訴
    /// </summary>
    public float JoyStickResetSpeed = 5.0f;
    /// <summary>
    /// 當前物體的Transform組件
    /// </summary>
    private RectTransform selfTransform;
    /// <summary>
    /// 是否觸摸了虛擬搖桿
    /// </summary>
    private bool isTouched = false;
    /// <summary>
    /// 虛擬搖桿的默認位置
    /// </summary>
    private Vector2 originPosition;
    /// <summary>
    /// 虛擬搖桿的移動方向
    /// </summary>
    private Vector2 touchedAxis;
    public Vector2 TouchedAxis
    {
        get
        {
            if(touchedAxis.magnitude < JoyStickRadius)
                return touchedAxis.normalized / JoyStickRadius;
            return touchedAxis.normalized;
        }
    }
    /// <summary>
    /// 定義觸摸開始事件委托
    /// </summary>
    public delegate void JoyStickTouchBegin(Vector2 vec);
    /// <summary>
    /// 定義觸摸過程事件委托
    /// </summary>
    /// <param name="vec">虛擬搖桿的移動方向</param>
    public delegate void JoyStickTouchMove(Vector2 vec);
    /// <summary>
    /// 定義觸摸結束事件委托
    /// </summary>
    public delegate void JoyStickTouchEnd();
    /// <summary>
    /// 注冊觸摸開始事件
    /// </summary>
    public event JoyStickTouchBegin OnJoyStickTouchBegin;
    /// <summary>
    /// 注冊觸摸過程事件
    /// </summary>
    public event JoyStickTouchMove OnJoyStickTouchMove;
    /// <summary>
    /// 注冊觸摸結束事件
    /// </summary>
    public event JoyStickTouchEnd OnJoyStickTouchEnd;
    void Start ()
    {
        //初始化虛擬搖桿的默認方向
        selfTransform = this.GetComponent<RectTransform>();
        originPosition = selfTransform.anchoredPosition;
    }
    public void OnPointerDown(PointerEventData eventData)
    {
        isTouched = true;
        touchedAxis = GetJoyStickAxis(eventData);
        if(this.OnJoyStickTouchBegin != null)
            this.OnJoyStickTouchBegin(TouchedAxis);
    }
    public void OnPointerUp(PointerEventData eventData)
    {
        isTouched = false;
        selfTransform.anchoredPosition = originPosition;
        touchedAxis = Vector2.zero;
        if(this.OnJoyStickTouchEnd != null)
            this.OnJoyStickTouchEnd();
    }
    public void OnDrag(PointerEventData eventData)
    {
        touchedAxis = GetJoyStickAxis(eventData);
        if(this.OnJoyStickTouchMove != null)
            this.OnJoyStickTouchMove(TouchedAxis);
    }
    void Update()
    {
        //當虛擬搖桿移動到最大半徑時搖桿無法拖動
        //為了確保被控制物體可以繼續移動
        //在這里手動觸發OnJoyStickTouchMove事件
        if(isTouched && touchedAxis.magnitude>=JoyStickRadius)
        {
            if(this.OnJoyStickTouchMove != null)
                this.OnJoyStickTouchMove(TouchedAxis);
        }
        //松開虛擬搖桿后讓虛擬搖桿回到默認位置
        if(selfTransform.anchoredPosition.magnitude > originPosition.magnitude)
            selfTransform.anchoredPosition -= TouchedAxis * Time.deltaTime * JoyStickResetSpeed;
    }
    /// <summary>
    /// 返回虛擬搖桿的偏移量
    /// </summary>
    /// <returns>The joy stick axis.</returns>
    /// <param name="eventData">Event data.</param>
    private Vector2 GetJoyStickAxis(PointerEventData eventData)
    {
        //獲取手指位置的世界坐標
        Vector3 worldPosition;
        if (RectTransformUtility.ScreenPointToWorldPointInRectangle (selfTransform,
                 eventData.position, eventData.pressEventCamera, out worldPosition))
            selfTransform.position = worldPosition;
        //獲取搖桿的偏移量
        Vector2 touchAxis = selfTransform.anchoredPosition-originPosition;
        //搖桿偏移量限制
        if(touchAxis.magnitude >= JoyStickRadius)
        {
            touchAxis = touchAxis.normalized * JoyStickRadius;
            selfTransform.anchoredPosition = touchAxis;
        }
        return touchAxis;
    }
}
在這段腳本中,我們實現了OnPointerDown、OnPointerUp和OnDrag三個uGUI事件接口,然后注冊了相關的事件委托,這里借鑒了EasyTouch的設計,可以使得虛擬搖桿的邏輯和角色控制邏輯相互分離。這里的核心方法是GetJoyStickAxis()方法,通過這個方法我們可以獲得一個Vector2類型的值,它表示的是未標准化過的虛擬搖桿的偏移量。這里的RectTransformUtility.ScreenPointToWorldPointInRectangle()方法表示將一個屏幕坐標轉化為對應RectTransform的世界坐標,RectTransform的anchoredPosition屬性表示的是當前元素在場景中的屏幕坐標。我們知道屏幕坐標是以像素為單位的,因此這里使用屏幕坐標可以計算出虛擬搖桿在水平方向和垂直方向上移動了多少個像素,我們以此來作為虛擬搖桿的偏移量衡量指標。TouchedAxis是經過標准化以后的偏移量,我們將把這個值傳遞到事件委托中以提供給外部來調用。好了,要說的就這些了,沒有說到的大家可以看看代碼里的注釋或者是在博客中給我留言,就是這樣啦。
  接下來,我們在場景中添加一個角色模型來測試我們編寫的虛擬搖桿,因為在JoyStick中我們已經定義了事件委托,所以在這里就是簡單的調用啦。好了,我們一起來看看代碼吧!
/*
 * Joystick3D.cs
 * 3D模式下的虛擬搖桿測試
 * 作者:秦元培
 * 博客:[url]http://qinyuanpei.com[/url]
 * 時間:2015年10月30日
 */

using UnityEngine;
using System.Collections;

public class JoyStick3D : MonoBehaviour
{
    private JoyStick js; 
    void Start ()
    {
        js = GameObject.FindObjectOfType<JoyStick> ();
        js.OnJoyStickTouchBegin += OnJoyStickBegin;
        js.OnJoyStickTouchMove += OnJoyStickMove;
        js.OnJoyStickTouchEnd += OnJoyStickEnd;
    }     
    void OnJoyStickBegin(Vector2 vec)
    {
        Debug.Log("開始觸摸虛擬搖桿");
    }
    void OnJoyStickMove (Vector2 vec)
    {
        Debug.Log("正在移動虛擬搖桿");
        //設置角色朝向
        Quaternion q = Quaternion.LookRotation (new Vector3 (vec.x, 0, vec.y));
        transform.rotation = q;
        //移動角色並播放奔跑動畫
        transform.Translate(Vector3.forward * 75f * Time.deltaTime);
        animation.CrossFade("Run");
    }   
    void OnJoyStickEnd ()
    {
        Debug.Log("觸摸移動搖桿結束");
        //播放默認待機動畫
        animation.CrossFade("idle");
    }
    void OnGUI()
    {
        GUI.Label(new Rect(30,30,200,30),"3D模式下的虛擬搖桿測試");
    }
}
最終程序的運行效果如下圖所示,我們編寫的這個虛擬搖桿可以在手機上完美的運行,歡飲大家來一起測試和吐槽!

 2D模式演示

 3D模式演示