工程截图
UIJoystick.cs
using System;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.Events;
using UnityEngine.EventSystems;
using UnityEngine.Serialization;
/// <summary>
/// 摇杆
/// </summary>
public class UIJoystick : MonoBehaviour, IPointerDownHandler, IPointerUpHandler, IDragHandler
{
[SerializeField] private Image m_Background;
[SerializeField] private Image m_Foreground;
private RectTransform m_BackgroundRect;
private RectTransform m_ForegroundRect;
private float constraintRadius = 0;
private float sqrMagnitude = 0;
// 定义摇杆事件类
[Serializable]
public class OnBeginEvent : UnityEvent { }
[Serializable]
public class OnMoveEvent : UnityEvent { }
[Serializable]
public class OnStopEvent : UnityEvent { }
// 防止序列化变量重命名后丢失引用
[FormerlySerializedAs("onBegin")]
[SerializeField]
private OnBeginEvent m_OnBegin = new OnBeginEvent();
[FormerlySerializedAs("onMove")]
[SerializeField]
private OnMoveEvent m_OnMove = new OnMoveEvent();
[FormerlySerializedAs("onStop")]
[SerializeField]
private OnStopEvent m_OnStop = new OnStopEvent();
public Vector2 normalized { get; private set; }
void Awake()
{
m_Background.alphaHitTestMinimumThreshold = 0.1f;
m_BackgroundRect = m_Background.GetComponent<RectTransform>();
m_ForegroundRect = m_Foreground.GetComponent<RectTransform>();
constraintRadius = (m_BackgroundRect.sizeDelta.x - m_ForegroundRect.sizeDelta.x) / 2;
sqrMagnitude = constraintRadius * constraintRadius;
}
private void OnEnable()
{
m_ForegroundRect.anchoredPosition = Vector2.zero;
}
private Vector2 foregroundPosition
{
get { return m_ForegroundRect.anchoredPosition; }
set { m_ForegroundRect.anchoredPosition = value; }
}
public void OnPointerDown(PointerEventData eventData)
{
Normalize(eventData);
m_OnBegin.Invoke();
m_OnMove.Invoke();
}
public void OnPointerUp(PointerEventData eventData)
{
foregroundPosition = Vector2.zero;
m_OnStop.Invoke();
}
public void OnDrag(PointerEventData eventData)
{
if (!eventData.IsPointerMoving())
return;
Normalize(eventData);
m_OnMove.Invoke();
}
private void Normalize(PointerEventData eventData)
{
//eventData.pressPosition: 事件触发时鼠标或手指的坐标
//eventData.position: 为鼠标或手指的实时坐标
foregroundPosition = ScreenPointToLocalPointInRectangle(eventData.position);
normalized = foregroundPosition.normalized;
//考虑到摄像机可能存在旋转(仅考虑绕Y轴旋转)
//需要将摇杆方向矢量做一次与像机旋转方向相反的旋转。
float cameraAngleY = -Camera.main.transform.localEulerAngles.y;
float cameraRadianY = cameraAngleY / 180 * Mathf.PI;
//旋转基向量
Vector2 i = new Vector2(Mathf.Cos(cameraRadianY), Mathf.Sin(cameraRadianY));
Vector2 j = new Vector2(-Mathf.Sin(cameraRadianY), Mathf.Cos(cameraRadianY));
normalized = normalized.x * i + normalized.y * j;
normalized.Normalize();
}
private Vector2 ScreenPointToLocalPointInRectangle(Vector2 screenPoint)
{
Vector2 localPoint = Vector2.zero;
RectTransformUtility.ScreenPointToLocalPointInRectangle(m_BackgroundRect, screenPoint, null, out localPoint);
return GetAdjustedPosition(localPoint);
}
private Vector2 GetAdjustedPosition(Vector2 pos)
{
//sqrMagnitude: 开方前的值
//magnitude: 开方后的值
if (pos.sqrMagnitude <= sqrMagnitude)
return pos;
float magnitude = pos.magnitude;
float cos = pos.x / magnitude;
float sin = pos.y / magnitude;
float x = constraintRadius * cos;
float y = constraintRadius * sin;
pos.x = x;
pos.y = y;
return pos;
}
}
UIJoystickTest.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class UIJoystickTest : MonoBehaviour
{
public void OnJoystickMove(UIJoystick joystick)
{
Debug.LogFormat("Joystick Move: {0}", joystick.normalized);
}
public void OnJoystickStop()
{
Debug.Log("Joystick Stop");
}
}
运行测试
示例:摇杆与导航配合控制玩家行走
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.AI;
public class PlayerController : MonoBehaviour
{
private Transform mTransform;
private NavMeshAgent agent;
private bool joysticking = false;
private Vector3 joystickForward = Vector3.zero;
private float lastPointerDownTime = 0;
private void Awake()
{
mTransform = transform;
agent = this.GetComponent<NavMeshAgent>();
}
//判断 鼠标/手指 Click
private bool IsPointerClick()
{
if (Input.touchSupported)
{
if (Input.touchCount > 0)
{
if (Input.GetTouch(0).phase == TouchPhase.Began)
lastPointerDownTime = Time.realtimeSinceStartup;
if (Input.GetTouch(0).phase == TouchPhase.Ended)
return Time.realtimeSinceStartup - lastPointerDownTime < 0.2f;
}
return false;
}
if (Input.GetMouseButtonDown(0))
lastPointerDownTime = Time.realtimeSinceStartup;
if (Input.GetMouseButtonUp(0))
return Time.realtimeSinceStartup - lastPointerDownTime < 0.2f;
return false;
}
void Update()
{
//导航到鼠标点击位置
if (IsPointerClick())
{
bool overUI = false;
if (Input.touchSupported)
overUI = Input.touchCount > 0 && EventSystem.current.IsPointerOverGameObject(Input.GetTouch(0).fingerId);
else
overUI = EventSystem.current.IsPointerOverGameObject();
if (overUI)
return; //点在了UGUI上
Vector3 mousePosition = Input.mousePosition;
Ray ray = Camera.main.ScreenPointToRay(mousePosition);
RaycastHit hit;
if (Physics.Raycast(ray, out hit))
{
agent.isStopped = false;
agent.stoppingDistance = 0f;
agent.SetDestination(hit.point);
}
}
if (joysticking)
agent.Move(joystickForward * agent.speed * Time.deltaTime);
}
// UIJoystick Begin Event
public void OnUIJoystickBegin()
{
joysticking = true;
if (!agent.isStopped)
agent.isStopped = true;//终止之前的寻路
}
// UIJoystick Move Event
public void OnUIJoystickMove(UIJoystick joystick)
{
Vector3 forward = new Vector3(joystick.normalized.x, 0, joystick.normalized.y);
mTransform.rotation = Quaternion.LookRotation(forward, Vector3.up);
joystickForward = forward;
}
// UIJoystick Stop Event
public void OnUIJoystickStop()
{
joysticking = false;
}
}
使用UIJoystickDispatcher使摇杆与角色模块解耦
using UnityEngine;
/// <summary>
/// 调度摇杆事件
/// 将摇杆功能与其他响应模块解耦
/// </summary>
public class UIJoystickDispatcher : GameEventBehaviour
{
public void OnBegin()
{
FireEvent(GameEventType.UI_JOYSTICK_BEGIN);
}
public void OnMove(UIJoystick joystick)
{
JoystickMove move = new JoystickMove();
move.forward = new Vector3(joystick.normalized.x, 0, joystick.normalized.y);
move.rotation = Quaternion.LookRotation(move.forward, Vector3.up);
FireEvent(GameEventType.UI_JOYSTICK_MOVE, move);
}
public void OnStop()
{
FireEvent(GameEventType.UI_JOYSTICK_STOP);
}
}
public struct JoystickMove
{
//前进方向
public Vector3 forward;
//旋转方位
public Quaternion rotation;
}