UGUI实现Unity虚拟摇杆

写在前面
好久没更新了,最近一段比较忙,快毕业了,在外面实习的同事还得整整毕设,重点是论文不好写啊。。好吧,这些都是废话,说点正事。。我们知道Unity实现虚拟摇杆其实比较简单,也有挺多虚拟摇杆插件,比较出名的比如EasyTouch。。功能比较强大了。。有兴趣大家可以去学习一下,源码就不建议看了,比较绕。。再说说现在手游常见的操作方式,动作类基本上都是虚拟摇杆+按钮的操作方式,移动设备上除了外接设备之外(很少见),基本都离不开虚拟摇杆,所以学习制作一个虚拟摇杆还是很有必要滴。。。

写在前面:
不说废话,直接进入正题。
先看一下用到的几个接口:using UnityEngine.EventSystem;
1.IPointerDownHandler
2.IPointerUpHandler
3.IDragHandler
我们去看一下官方给出的解释:

https://docs.unity3d.com/Manual/SupportedEvents.html
这个地址是Unity事件System所支持的所有事件接口的描述。
关于上面的三个接口可以通过过下面这个地址去了解一下:
https://docs.unity3d.com/550/Documentation/ScriptReference/EventSystems.IPointerDownHandler.html

看一下它的Description,说的是如果你想要获得点击按下的回调则可以实现该接口。
其他两个接口就不细说了,和这个情况是基本一样的。

接下来说一下思路,怎么去做出一个虚拟遥杆,那么首先最基本的是它的UI组成,我们可以用两
个UGUI的Image来进行组合。
UGUI实现Unity虚拟摇杆_第1张图片

分析一下我们的需求,我们希望在我们点击遥杆的任一位置时,小圆能跟随我们一起去移动,那么首先我们要获取的就是点击屏幕坐标的位置。

如何获取点击屏幕坐标的位置呢,很方便的是实现上述的三个接口后我们都会拿到一个EventData 类型的参数,参数属性中油包含点击的位置(屏幕坐标系下的)。
或者可以直接使用Input.mousePosition(不推荐使用,因为这个是全屏幕响应的,而使用三个接口的参数来获取只会在你实现接口的物体上才会响应)

接着怎么让小圆移动到我们点击的位置呢?
如果是3D物体的话,我们可以直接使用worldSpace下的position来进行计算位移,但是这一套在UI里并不好用,难道Unity没有提供一个方便的设置UI位置的方法或者属性么?显然不是,在所有的UGUI空间中,都会有一个基础的组件RectTransform,就像3D物体中的Transform组件一样,RectTransform中有一个属性anchoredPosition,这个属性是用来干嘛的呢?一起看一下官方给出的解释:
https://docs.unity3d.com/530/Documentation/ScriptReference/RectTransform-anchoredPosition.html

大致意思就是UI相对于其锚点的位置。这是一个可以set的属性,这个属性对我们来说很重要,因为我们只需要把锚点设置到大圆的中心,那么我们得到的anchoredPosition就是我们最终控制物体移动的方向。
刚刚说了anchoredPosition是UI相对于其锚点的位置,我们先来看一下现在小圆的锚点在哪里:
UGUI实现Unity虚拟摇杆_第2张图片
选中之后发现他的默认锚点就是在父物体的中心位置,我们不需要进行调整。
接着就是去设置小圆的anchoredPosition去他移动到我们设置的位置,那么我们可以直接用获取到的点击屏幕的位置么? 显然不行,屏幕是左下角为原点,而anchoredPosition是以其锚点为原点的,所以我们要先获取到屏幕坐标系中大圆的中心点位置的坐标。关于这个坐标怎么获取,大家可以去百度百度,我查了不少 ,发现都不太适合,最后发现的一个API:
RectTransformUtility.WorldToScreenPoint(Camera camera, Vector3 pos)
这个API 会返回对应Camera下的屏幕坐标,这里需要强调的是我们的UICamera在Canvas overly模式下并不是主摄像机,
UGUI实现Unity虚拟摇杆_第3张图片
所以这个方法传入的Camera是canvas.GetComponent()而不是Camera.main,有兴趣的小伙伴可以自己去尝试一下二者的区别。通过这个坐标的返回值我们就可以拿到屏幕坐标系下的大圆中心点。
现在我们有了两个Vector:
UGUI实现Unity虚拟摇杆_第4张图片
A是大圆中心的屏幕坐标,B是我们点击位置的屏幕坐标,那么C = B - A;
所以我们的anchoredPosition = C;这个向量也是我们在世界坐标系里去移动的要用到的一个向 量。
最后我们需要把这个坐标转换到世界坐标系作为移动的方向向量,转换的方式也很简单。
new Vector3(anchoredPosition.x, y, anchoredPosition.y) 就是我们需要的世界坐标系下的方向向量,这里的y是移动物体的position.y;

我们希望在点击大圆以外位置时,小圆依然可以跟随点击位置,但是不会超出大圆的范围,
怎么去实现这个效果呢?
首先前面说过用到三个几口都是只会在实现方法的物体上才会响应,那么当我们点击大圆之外的附近的位置时,事件就不会被触发了,我的做法是创建一个Image作为大圆的父UI,调整为全透明,将接口的实现放在该UI上,这样我们检测的范围就可以自己去定义了。

UGUI实现Unity虚拟摇杆_第5张图片
然后我们希望点击或拖拽大圆之外的范围时,小圆不会超出大圆的半径范围,那么就要获取大圆的半径,然后对点击的位置进行判断。关于大圆的半径的获取,其实就是大圆UI的width的一半,因为我们创建的Image是一个正方形UI,同样RectTransform中有提供这样的属性方便我们去获取。
radius = GetComponent().rect.size.x / 2;
然后就是对anchoredPosition进行范围限制了,这里直接使用向量中的一个Clamp函数去处理就可以:
anchoredPosition = Vector2.ClampMagnitude(C, radius);参数是传入的向量以及限制的长度

ok,最后给出程序:

/*========================================================
  Company:  jiangsumingtong
  Author:   fanlitao
  Date:     2017-04-10
  Version:  1.0.0
  =======================================================*/

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

public class Joystick : MonoBehaviour, IPointerDownHandler, IPointerUpHandler, IDragHandler{

    public float m_Radius = 5.0f;

    Image stickImg;
    Image dragImg;
    /// 
    /// 遥杆原点
    /// 
    Vector2 stickOrigin;

    #region 委托事件
    public delegate void PointerDwon(Vector2 pos);
    public delegate void PointerUp(Vector2 pos);
    public delegate void DragSomthing(Vector2 pos);

    public static event PointerDwon OnTouchDown;
    public static event PointerUp OnTouchUp;
    public static event DragSomthing OnDraging;
    #endregion

    public Canvas canvas;

    private void Start()
    {
        this.stickImg = transform.GetChild(0).GetComponent();
        this.dragImg = transform.GetChild(0).GetChild(0).GetComponent();
        m_Radius = stickImg.rectTransform.rect.size.x / 2;
        Debug.Log(m_Radius);

        //Debug.Log(Camera.main.WorldToScreenPoint(transform.position));

        ///RectTransformUtility.WorldToScreenPoint(Camera camera, Vector3 worldPostion)
        ///这里的Camera是UICamera.和Camera.main是不同的.
        this.stickOrigin = RectTransformUtility.WorldToScreenPoint(canvas.GetComponent(), transform.GetChild(0).position);
        Debug.Log(stickOrigin);
    }

    public void OnDrag(PointerEventData eventData)
    {
        //Debug.Log("=============OnDrag==============:" + eventData.position);
        Vector2 pointer = eventData.position;
        Vector2 localPos = pointer - this.stickOrigin;
        localPos = Vector2.ClampMagnitude(localPos, this.m_Radius);
        this.dragImg.rectTransform.anchoredPosition = localPos;
        //执行事件
        if(OnDraging != null)
        {
            OnDraging(localPos);
        }
    }

    public void OnPointerDown(PointerEventData eventData)
    {
        //Debug.Log("=============OnPointerDown==============:" + eventData.position);
        Vector2 pointer = eventData.position;
        Vector2 localPos = pointer - this.stickOrigin;
        localPos = Vector2.ClampMagnitude(localPos, this.m_Radius);
        this.dragImg.rectTransform.anchoredPosition = localPos; 
        if(OnTouchDown != null)
        {
            OnTouchDown(localPos);
        }
    }

    public void OnPointerUp(PointerEventData eventData)
    {
        //Debug.Log("=============OnPointerUp==============:" + eventData.position);
        this.dragImg.rectTransform.anchoredPosition = Vector2.zero;
        if(OnTouchUp != null)
        {
            OnTouchUp(eventData.position);
        }
    }

}



你可能感兴趣的:(Unity)