示意
思路
虚拟摇杆由3部分组成
- 触屏区域
在该区域内按住屏幕,可控制摇杆。 - 背景大圆
在触屏区域内按住屏幕时,将该圆(的中心)设置到合适的位置。
合适的位置:因为要限制该圆显示在触屏区域内,因此实际其中心的位置为图中红色区域内。 - 中央摇杆
随手指滑动而移动,限制其与背景大圆中心的最大距离。
具体资源
虚拟摇杆GameObject的层级
触屏控制
摇杆的显示
相关代码
VirtualJoystick.cs
监听触屏、滑屏相关事件,发出自己的事件
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
namespace VirtualJoystick
{
public class VirtualJoystick : MonoBehaviour, IPointerDownHandler, IPointerUpHandler, IDragHandler
{
// bgPos, handlePos
public event Action<Vector2, Vector2> PointerDownEvent;
public event Action<Vector2> DragEvent;
public event Action PointerUpEvent;
public event Action<Vector2> SetDirEvent;
public event Action StopSetDirEvent;
[SerializeField, Header("背景图的RectTrans,用于限制实际的操作区域")]
private RectTransform _bgRectTrans;
[SerializeField]
private VirtualJoystickAppearance _virtualJoystickAppearance;
private Vector2 HandlePos { get { return _virtualJoystickAppearance.HandlePos; } }
[SerializeField, Header("限制背景图中心点的位置")]
private int _limitRangeOffset = 0;
[SerializeField, Header("两点距离多远时,认为停止设置方向")]
private float _stopSetDirDistance = 10;
private Vector2 _minPos;
private Vector2 _maxPos;
private bool _hasInited = false;
private Vector2 _bgPos;
public void OnPointerDown(PointerEventData eventData)
{
if (!_hasInited)
{
Init();
}
Vector2 handlePos = eventData.position;
_bgPos = LimitBgPos(eventData.position);
PointerDownEvent?.Invoke(_bgPos, handlePos);
SetDir(HandlePos, _bgPos);
void Init()
{
int actualLimitRangeOffset = (int)_bgRectTrans.rect.width / 2 + _limitRangeOffset;
float width = GetComponent<RectTransform>().rect.width;
float height = GetComponent<RectTransform>().rect.height;
_minPos = Vector2.one * actualLimitRangeOffset;
_maxPos = new Vector2(width - actualLimitRangeOffset, height - actualLimitRangeOffset);
_hasInited = true;
}
Vector2 LimitBgPos(Vector2 pos)
{
pos = new Vector2(Mathf.Clamp(pos.x, _minPos.x, _maxPos.x), Mathf.Clamp(pos.y, _minPos.y, _maxPos.y));
return pos;
}
}
public void OnDrag(PointerEventData eventData)
{
DragEvent?.Invoke(eventData.position);
SetDir(eventData.position, _bgPos);
}
public void OnPointerUp(PointerEventData eventData)
{
PointerUpEvent?.Invoke();
StopSetDirEvent?.Invoke();
}
private void SetDir(Vector2 handlePos, Vector2 bgPos)
{
if (Vector2.Distance(handlePos, bgPos) > _stopSetDirDistance)
{
Vector2 dir = (handlePos - bgPos).normalized;
SetDirEvent?.Invoke(dir);
Debug.Log("SetDir " + dir);
}
else
{
StopSetDirEvent?.Invoke();
}
}
}
}
VirtualJoystickAppearance.cs
虚拟摇杆的显示(包括设置其位置等)。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
namespace VirtualJoystick
{
public class VirtualJoystickAppearance : MonoBehaviour
{
[SerializeField]
private VirtualJoystick _virtualJoystick;
[SerializeField]
private RectTransform _bgRectTrans;
public Vector2 BgPos{ get { return _bgRectTrans.position; } }
[SerializeField]
private RectTransform _handleRectTrans;
public Vector2 HandlePos
{
get { return _handleRectTrans.position; }
}
[SerializeField]
private CanvasGroup _canvasGroup;
[SerializeField, Range(0, 1)]
private float _hideAlpha = 0.2f;
private Vector2 _bgDefaultPos;
[SerializeField]
private float _handleAndBgMaxDistance = 120;
void Start()
{
Init();
}
private void OnDestroy()
{
Destroy();
}
void Init()
{
var joystickRect = _virtualJoystick.GetComponent<RectTransform>().rect;
_bgDefaultPos = new Vector2(joystickRect.width / 2, joystickRect.height/2);
Hide();
ResetPos();
_virtualJoystick.PointerDownEvent += OnPointerDown;
_virtualJoystick.SetDirEvent += OnSetDir;
_virtualJoystick.StopSetDirEvent += OnStopSetDir;
_virtualJoystick.DragEvent += OnDrag;
_virtualJoystick.PointerUpEvent += OnPointerUp;
}
void Destroy()
{
if (_virtualJoystick != null)
{
_virtualJoystick.PointerDownEvent -= OnPointerDown;
_virtualJoystick.SetDirEvent -= OnSetDir;
_virtualJoystick.StopSetDirEvent -= OnStopSetDir;
_virtualJoystick.DragEvent -= OnDrag;
_virtualJoystick.PointerUpEvent -= OnPointerUp;
}
}
private void Hide()
{
_canvasGroup.alpha = _hideAlpha;
}
private void Show()
{
_canvasGroup.alpha = 1;
}
private void ResetPos()
{
_bgRectTrans.transform.position = _bgDefaultPos;
_handleRectTrans.localPosition = Vector3.zero;
}
private void LimitHandlePos(Vector2 pos)
{
if (Vector2.Distance(pos, (Vector2)_bgRectTrans.transform.position) > _handleAndBgMaxDistance)
{
pos = (Vector2)_bgRectTrans.transform.position + (pos - (Vector2)_bgRectTrans.transform.position).normalized * _handleAndBgMaxDistance;
}
_handleRectTrans.position = pos;
}
#region 关注joystick的一些事件
private void OnPointerDown(Vector2 bgPos, Vector2 handlePos)
{
_bgRectTrans.position = bgPos;
LimitHandlePos(handlePos);
Show();
}
private void OnSetDir(Vector2 dir)
{
if (IsInHide())
{
Show();
}
bool IsInHide()
{
return _canvasGroup.alpha == _hideAlpha;
}
}
private void OnStopSetDir()
{
// Hide();
// ResetPos();
}
private void OnDrag(Vector2 handlePos)
{
LimitHandlePos(handlePos);
}
private void OnPointerUp()
{
Hide();
ResetPos();
}
#endregion
}
}
用于测试的代码
InputMgr.cs
监听虚拟摇杆的事件(SetDirEvent、StopSetDirEvent),控制Player移动。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using VirtualJoystick;
public class InputMgr : MonoBehaviour
{
[SerializeField]
private Player _player;
[SerializeField]
private VirtualJoystick.VirtualJoystick _virtualJoystick;
void Start()
{
Init();
}
private void OnDestroy()
{
Destroy();
}
private void Init()
{
if (_virtualJoystick != null)
{
_virtualJoystick.SetDirEvent += OnSetMoveDir;
_virtualJoystick.StopSetDirEvent += OnStopSetDir;
}
}
private void Destroy()
{
if (_virtualJoystick != null)
{
_virtualJoystick.SetDirEvent -= OnSetMoveDir;
_virtualJoystick.StopSetDirEvent -= OnStopSetDir;
}
}
private void OnSetMoveDir(Vector2 dir)
{
_player?.Move(dir);
}
private void OnStopSetDir()
{
_player?.StopMove();
}
}
Player.cs
外界(InputMgr)调用其Move方法控制其移动。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Player : MonoBehaviour
{
private Vector2 _moveDir;
[SerializeField]
private float _moveSpeed = 3f;
void Start()
{
Init();
}
void Update()
{
if (_moveDir != Vector2.zero)
{
transform.forward = new Vector3(_moveDir.x, 0, _moveDir.y);
transform.Translate(transform.forward * _moveSpeed * Time.deltaTime, Space.World);
}
}
private void Init()
{
_moveDir = Vector2.zero;
}
public void Move(Vector2 dir)
{
_moveDir = dir.normalized;
}
public void StopMove()
{
_moveDir = Vector2.zero;
}
}