You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

266 lines
10 KiB
C#

using System;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
namespace CnControls
{
[Flags]
public enum ControlMovementDirection
{
Horizontal = 0x1,
Vertical = 0x2,
Both = Horizontal | Vertical
}
/// <summary>
/// Simple joystick class
/// Contains logic for creating a simple joystick
/// </summary>
public class SimpleJoystick : MonoBehaviour, IDragHandler, IPointerUpHandler, IPointerDownHandler
{
/// <summary>
/// Current event camera reference. Needed for the sake of Unity Remote input
/// </summary>
public Camera CurrentEventCamera { get; set; }
// ------- Inspector visible variables ---------------------------------------
/// <summary>
/// The range in non-scaled pixels for which we can drag the joystick around
/// </summary>
public float MovementRange = 50f;
/// <summary>
/// The name of the horizontal axis for this joystick to update
/// </summary>
public string HorizontalAxisName = "Horizontal";
/// <summary>
/// The name of the vertical axis for this joystick to update
/// </summary>
public string VerticalAxisName = "Vertical";
/// <summary>
/// Should the joystick be hidden when the user releases the finger?
/// [Space(15f)] attribute is needed only for the editor, it creates some spacing in the inspector
/// </summary>
[Space(15f)]
[Tooltip("Should the joystick be hidden on release?")]
public bool HideOnRelease;
/// <summary>
/// Should the joystick be moved along with the finger
/// </summary>
[Tooltip("Should the Base image move along with the finger without any constraints?")]
public bool MoveBase = true;
/// <summary>
/// Should the joystick be moved along with the finger
/// </summary>
[Tooltip("Should the joystick snap to finger? If it's FALSE, the MoveBase checkbox logic will be ommited")]
public bool SnapsToFinger = true;
/// <summary>
/// Joystick movement direction
/// Specifies the axis along which it can move
/// </summary>
[Tooltip("Constraints on the joystick movement axis")]
public ControlMovementDirection JoystickMoveAxis = ControlMovementDirection.Both;
/// <summary>
/// Image of the joystick base
/// </summary>
[Tooltip("Image of the joystick base")]
public Image JoystickBase;
/// <summary>
/// Image of the stick itself
/// </summary>
[Tooltip("Image of the stick itself")]
public Image Stick;
/// <summary>
/// Rect Transform of the touch zone
/// </summary>
[Tooltip("Touch Zone transform")]
public RectTransform TouchZone;
// ---------------------------------------------------------------------------
private Vector2 _initialStickPosition;
private Vector2 _intermediateStickPosition;
private Vector2 _initialBasePosition;
private RectTransform _baseTransform;
private RectTransform _stickTransform;
private float _oneOverMovementRange;
protected VirtualAxis HorizintalAxis;
protected VirtualAxis VerticalAxis;
private void Awake()
{
_stickTransform = Stick.GetComponent<RectTransform>();
_baseTransform = JoystickBase.GetComponent<RectTransform>();
_initialStickPosition = _stickTransform.anchoredPosition;
_intermediateStickPosition = _initialStickPosition;
_initialBasePosition = _baseTransform.anchoredPosition;
_stickTransform.anchoredPosition = _initialStickPosition;
_baseTransform.anchoredPosition = _initialBasePosition;
_oneOverMovementRange = 1f / MovementRange;
if (HideOnRelease)
{
Hide(true);
}
}
private void OnEnable()
{
// When we enable, we get our virtual axis
HorizintalAxis = HorizintalAxis ?? new VirtualAxis(HorizontalAxisName);
VerticalAxis = VerticalAxis ?? new VirtualAxis(VerticalAxisName);
// And register them in our input system
CnInputManager.RegisterVirtualAxis(HorizintalAxis);
CnInputManager.RegisterVirtualAxis(VerticalAxis);
}
private void OnDisable()
{
// When we disable, we just unregister our axis
// It also happens before the game object is Destroyed
CnInputManager.UnregisterVirtualAxis(HorizintalAxis);
CnInputManager.UnregisterVirtualAxis(VerticalAxis);
}
public virtual void OnDrag(PointerEventData eventData)
{
// Unity remote multitouch related thing
// When we feed fake PointerEventData we can't really provide a camera,
// it has a lot of private setters via not created objects, so even the Reflection magic won't help a lot here
// Instead, we just provide an actual event camera as a public property so we can easily set it in the Input Helper class
CurrentEventCamera = eventData.pressEventCamera ?? CurrentEventCamera;
// We get the local position of the joystick
Vector3 worldJoystickPosition;
RectTransformUtility.ScreenPointToWorldPointInRectangle(_stickTransform, eventData.position,
CurrentEventCamera, out worldJoystickPosition);
// Then we change it's actual position so it snaps to the user's finger
_stickTransform.position = worldJoystickPosition;
// We then query it's anchored position. It's calculated internally and quite tricky to do from scratch here in C#
var stickAnchoredPosition = _stickTransform.anchoredPosition;
// Some bitwise logic for constraining the joystick along one of the axis
// If the "Both" option was selected, non of these two checks will yield "true"
if ((JoystickMoveAxis & ControlMovementDirection.Horizontal) == 0)
{
stickAnchoredPosition.x = _intermediateStickPosition.x;
}
if ((JoystickMoveAxis & ControlMovementDirection.Vertical) == 0)
{
stickAnchoredPosition.y = _intermediateStickPosition.y;
}
_stickTransform.anchoredPosition = stickAnchoredPosition;
// Find current difference between the previous central point of the joystick and it's current position
Vector2 difference = new Vector2(stickAnchoredPosition.x, stickAnchoredPosition.y) - _intermediateStickPosition;
// Normalisation stuff
var diffMagnitude = difference.magnitude;
var normalizedDifference = difference / diffMagnitude;
// If the joystick is being dragged outside of it's range
if (diffMagnitude > MovementRange)
{
if (MoveBase && SnapsToFinger)
{
// We move the base so it maps the new joystick center position
var baseMovementDifference = difference.magnitude - MovementRange;
var addition = normalizedDifference * baseMovementDifference;
_baseTransform.anchoredPosition += addition;
_intermediateStickPosition += addition;
}
else
{
_stickTransform.anchoredPosition = _intermediateStickPosition + normalizedDifference * MovementRange;
}
}
// We should now calculate axis values based on final position and not on "virtual" one
var finalStickAnchoredPosition = _stickTransform.anchoredPosition;
// Sanity recalculation
Vector2 finalDifference = new Vector2(finalStickAnchoredPosition.x, finalStickAnchoredPosition.y) - _intermediateStickPosition;
// We don't need any values that are greater than 1 or less than -1
var horizontalValue = Mathf.Clamp(finalDifference.x * _oneOverMovementRange, -1f, 1f);
var verticalValue = Mathf.Clamp(finalDifference.y * _oneOverMovementRange, -1f, 1f);
// Finally, we update our virtual axis
HorizintalAxis.Value = horizontalValue;
VerticalAxis.Value = verticalValue;
}
public void OnPointerUp(PointerEventData eventData)
{
// When we lift our finger, we reset everything to the initial state
_baseTransform.anchoredPosition = _initialBasePosition;
_stickTransform.anchoredPosition = _initialStickPosition;
_intermediateStickPosition = _initialStickPosition;
HorizintalAxis.Value = VerticalAxis.Value = 0f;
// We also hide it if we specified that behaviour
if (HideOnRelease)
{
Hide(true);
}
}
public void OnPointerDown(PointerEventData eventData)
{
// When we press, we first want to snap the joystick to the user's finger
if (SnapsToFinger)
{
CurrentEventCamera = eventData.pressEventCamera ?? CurrentEventCamera;
Vector3 localStickPosition;
Vector3 localBasePosition;
RectTransformUtility.ScreenPointToWorldPointInRectangle(_stickTransform, eventData.position,
CurrentEventCamera, out localStickPosition);
RectTransformUtility.ScreenPointToWorldPointInRectangle(_baseTransform, eventData.position,
CurrentEventCamera, out localBasePosition);
_baseTransform.position = localBasePosition;
_stickTransform.position = localStickPosition;
_intermediateStickPosition = _stickTransform.anchoredPosition;
}
else
{
OnDrag(eventData);
}
// We also want to show it if we specified that behaviour
if (HideOnRelease)
{
Hide(false);
}
}
/// <summary>
/// Simple "Hide" behaviour
/// </summary>
/// <param name="isHidden">Whether the joystick should be hidden</param>
private void Hide(bool isHidden)
{
JoystickBase.gameObject.SetActive(!isHidden);
Stick.gameObject.SetActive(!isHidden);
}
}
}