您的位置:首页 > 移动开发 > Unity3D

UGUI实现Unity虚拟摇杆

2016-10-06 21:17 513 查看
写在前面

好久没更新了,最近一段比较忙,快毕业了,在外面实习的同事还得整整毕设,重点是论文不好写啊。。好吧,这些都是废话,说点正事。。我们知道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来进行组合。



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

如何获取点击屏幕坐标的位置呢,很方便的是实现上述的三个接口后我们都会拿到一个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相对于其锚点的位置,我们先来看一下现在小圆的锚点在哪里:



选中之后发现他的默认锚点就是在父物体的中心位置,我们不需要进行调整。

接着就是去设置小圆的anchoredPosition去他移动到我们设置的位置,那么我们可以直接用获取到的点击屏幕的位置么? 显然不行,屏幕是左下角为原点,而anchoredPosition是以其锚点为原点的,所以我们要先获取到屏幕坐标系中大圆的中心点位置的坐标。关于这个坐标怎么获取,大家可以去百度百度,我查了不少 ,发现都不太适合,最后发现的一个API:

RectTransformUtility.WorldToScreenPoint(Camera camera, Vector3 pos)

这个API 会返回对应Camera下的屏幕坐标,这里需要强调的是我们的UICamera在Canvas overly模式下并不是主摄像机,



所以这个方法传入的Camera是canvas.GetComponent()而不是Camera.main,有兴趣的小伙伴可以自己去尝试一下二者的区别。通过这个坐标的返回值我们就可以拿到屏幕坐标系下的大圆中心点。

现在我们有了两个Vector:



A是大圆中心的屏幕坐标,B是我们点击位置的屏幕坐标,那么C = B - A;

所以我们的anchoredPosition = C;这个向量也是我们在世界坐标系里去移动的要用到的一个向 量。

最后我们需要把这个坐标转换到世界坐标系作为移动的方向向量,转换的方式也很简单。

new Vector3(anchoredPosition.x, y, anchoredPosition.y) 就是我们需要的世界坐标系下的方向向量,这里的y是移动物体的position.y;

我们希望在点击大圆以外位置时,小圆依然可以跟随点击位置,但是不会超出大圆的范围,

怎么去实现这个效果呢?

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



然后我们希望点击或拖拽大圆之外的范围时,小圆不会超出大圆的半径范围,那么就要获取大圆的半径,然后对点击的位置进行判断。关于大圆的半径的获取,其实就是大圆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;
/// <summary>
/// 遥杆原点
/// </summary>
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<Image>();
this.dragImg = transform.GetChild(0).GetChild(0).GetComponent<Image>();
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<Camera>(), 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 虚拟遥杆 UGUI