210 lines
7.0 KiB
C#
210 lines
7.0 KiB
C#
using System;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using UnityEngine;
|
|
using UnityEngine.EventSystems;
|
|
|
|
public class Joystick : MonoBehaviour, IPointerDownHandler, IDragHandler, IPointerUpHandler
|
|
{
|
|
public float Horizontal { get { return input.x; } }
|
|
public float Vertical { get { return input.y; } }
|
|
public Vector2 Direction { get { return input; } }
|
|
|
|
public float HandleRange
|
|
{
|
|
get { return handleRange; }
|
|
set { handleRange = Mathf.Abs(value); }
|
|
}
|
|
|
|
public float DeadZone
|
|
{
|
|
get { return deadZone; }
|
|
set { deadZone = Mathf.Abs(value); }
|
|
}
|
|
|
|
public AxisOptions AxisOptions { get { return AxisOptions; } set { axisOptions = value; } }
|
|
public bool SnapX { get { return snapX; } set { snapX = value; } }
|
|
public bool SnapY { get { return snapY; } set { snapY = value; } }
|
|
|
|
[SerializeField] private float handleRange = 1;
|
|
[SerializeField] private float deadZone = 0;
|
|
[SerializeField] private AxisOptions axisOptions = AxisOptions.Both;
|
|
[SerializeField] private bool snapX = false;
|
|
[SerializeField] private bool snapY = false;
|
|
|
|
[SerializeField] protected RectTransform background = null;
|
|
[SerializeField] private RectTransform handle = null;
|
|
private RectTransform baseRect = null;
|
|
|
|
private Canvas canvas;
|
|
private Camera cam;
|
|
|
|
private Vector2 input = Vector2.zero;
|
|
private Vector2 previousSnappedInput = Vector2.zero;
|
|
|
|
protected virtual void Start()
|
|
{
|
|
HandleRange = handleRange;
|
|
DeadZone = deadZone;
|
|
baseRect = GetComponent<RectTransform>();
|
|
canvas = GetComponentInParent<Canvas>();
|
|
if (canvas == null)
|
|
Debug.LogError("The Joystick is not placed inside a canvas");
|
|
|
|
Vector2 center = new Vector2(0.5f, 0.5f);
|
|
background.pivot = center;
|
|
handle.anchorMin = center;
|
|
handle.anchorMax = center;
|
|
handle.pivot = center;
|
|
handle.anchoredPosition = Vector2.zero;
|
|
}
|
|
|
|
public virtual void OnPointerDown(PointerEventData eventData)
|
|
{
|
|
previousSnappedInput = Vector2.zero;
|
|
OnDrag(eventData);
|
|
}
|
|
|
|
public void OnDrag(PointerEventData eventData)
|
|
{
|
|
cam = null;
|
|
if (canvas.renderMode == RenderMode.ScreenSpaceCamera)
|
|
cam = canvas.worldCamera;
|
|
|
|
Vector2 position = RectTransformUtility.WorldToScreenPoint(cam, background.position);
|
|
Vector2 radius = background.sizeDelta / 2;
|
|
input = (eventData.position - position) / (radius * canvas.scaleFactor);
|
|
FormatInput();
|
|
HandleInput(input.magnitude, input.normalized, radius, cam);
|
|
handle.anchoredPosition = input * radius * handleRange;
|
|
|
|
// Send event when value changes significantly (for 360-degree smooth movement)
|
|
Vector2 currentSnapped = new Vector2(SnapToDiscrete(input.x), SnapToDiscrete(input.y));
|
|
if ((currentSnapped.x == -1 || currentSnapped.x == 1 || currentSnapped.y == -1 || currentSnapped.y == 1) &&
|
|
currentSnapped != previousSnappedInput)
|
|
{
|
|
EventBus.Publish(new JoystickPressEvent());
|
|
}
|
|
else if (currentSnapped == Vector2.zero && previousSnappedInput != Vector2.zero) {
|
|
EventBus.Publish(new JoystickRealeaseEvent());
|
|
}
|
|
previousSnappedInput = currentSnapped;
|
|
}
|
|
|
|
protected virtual void HandleInput(float magnitude, Vector2 normalised, Vector2 radius, Camera cam)
|
|
{
|
|
if (magnitude > deadZone)
|
|
{
|
|
// Normalize to ensure speed is always 1 (full speed) or 0 (no movement)
|
|
// This keeps 360-degree direction but binary speed
|
|
input = normalised;
|
|
}
|
|
else
|
|
input = Vector2.zero;
|
|
}
|
|
|
|
private void FormatInput()
|
|
{
|
|
if (axisOptions == AxisOptions.Horizontal)
|
|
input = new Vector2(input.x, 0f);
|
|
else if (axisOptions == AxisOptions.Vertical)
|
|
input = new Vector2(0f, input.y);
|
|
}
|
|
|
|
private Vector2 SnapTo8Directions(Vector2 input)
|
|
{
|
|
// Snap to 8 directions: N, NE, E, SE, S, SW, W, NW
|
|
// Returns values of -1, 0, or 1 for each axis
|
|
|
|
if (input.magnitude < 0.4f)
|
|
return Vector2.zero;
|
|
|
|
// Calculate angle in degrees (0 = up/North, 90 = right/East)
|
|
float angle = Mathf.Atan2(input.x, input.y) * Mathf.Rad2Deg;
|
|
|
|
// Normalize angle to 0-360
|
|
if (angle < 0) angle += 360f;
|
|
|
|
// Snap to 8 directions (every 45 degrees)
|
|
// 0° = N, 45° = NE, 90° = E, 135° = SE, 180° = S, 225° = SW, 270° = W, 315° = NW
|
|
float snappedAngle = Mathf.Round(angle / 45f) * 45f;
|
|
|
|
// Convert back to direction vector
|
|
float rad = snappedAngle * Mathf.Deg2Rad;
|
|
Vector2 snapped = new Vector2(Mathf.Sin(rad), Mathf.Cos(rad));
|
|
|
|
// Ensure values are exactly -1, 0, or 1
|
|
snapped.x = Mathf.Round(snapped.x);
|
|
snapped.y = Mathf.Round(snapped.y);
|
|
|
|
return snapped;
|
|
}
|
|
|
|
private float SnapToDiscrete(float value)
|
|
{
|
|
// Snap to -1, 1, or 0 for 8-directional movement
|
|
// Use a small threshold to ensure diagonal movement works
|
|
const float threshold = 0.1f;
|
|
if (Mathf.Abs(value) < threshold)
|
|
return 0;
|
|
return value > 0 ? 1 : -1;
|
|
}
|
|
|
|
private float SnapFloat(float value, AxisOptions snapAxis)
|
|
{
|
|
if (value == 0)
|
|
return value;
|
|
|
|
if (axisOptions == AxisOptions.Both)
|
|
{
|
|
float angle = Vector2.Angle(input, Vector2.up);
|
|
if (snapAxis == AxisOptions.Horizontal)
|
|
{
|
|
if (angle < 22.5f || angle > 157.5f)
|
|
return 0;
|
|
else
|
|
return (value > 0) ? 1 : -1;
|
|
}
|
|
else if (snapAxis == AxisOptions.Vertical)
|
|
{
|
|
if (angle > 67.5f && angle < 112.5f)
|
|
return 0;
|
|
else
|
|
return (value > 0) ? 1 : -1;
|
|
}
|
|
return value;
|
|
}
|
|
else
|
|
{
|
|
if (value > 0)
|
|
return 1;
|
|
if (value < 0)
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
public virtual void OnPointerUp(PointerEventData eventData)
|
|
{
|
|
input = Vector2.zero;
|
|
handle.anchoredPosition = Vector2.zero;
|
|
previousSnappedInput = Vector2.zero;
|
|
|
|
EventBus.Publish(new JoystickRealeaseEvent());
|
|
}
|
|
|
|
protected Vector2 ScreenPointToAnchoredPosition(Vector2 screenPosition)
|
|
{
|
|
Vector2 localPoint = Vector2.zero;
|
|
if (RectTransformUtility.ScreenPointToLocalPointInRectangle(baseRect, screenPosition, cam, out localPoint))
|
|
{
|
|
Vector2 pivotOffset = baseRect.pivot * baseRect.sizeDelta;
|
|
return localPoint - (background.anchorMax * baseRect.sizeDelta) + pivotOffset;
|
|
}
|
|
return Vector2.zero;
|
|
}
|
|
}
|
|
|
|
public enum AxisOptions { Both, Horizontal, Vertical }
|
|
public struct JoystickPressEvent { }
|
|
public struct JoystickRealeaseEvent { } |