using System; using UnityEngine; using UnityEngine.EventSystems; #if USE_NEW_INPUT using UnityEngine.InputSystem.OnScreen; using UnityEngine.InputSystem.Layouts; #endif using Beyond; using System.Collections; using UnityStandardAssets.CrossPlatformInput; using ButtonHandler = Beyond.ButtonHandler; #if USE_NEW_INPUT public class Joystick : OnScreenControl, IPointerDownHandler, IDragHandler, IPointerUpHandler #else public class Joystick : MonoBehaviour, IPointerDownHandler, IDragHandler, IPointerUpHandler #endif { #if USE_NEW_INPUT [InputControl(layout = "Vector2")] [SerializeField] protected string m_ControlPath; #endif [SerializeField] protected ControlType m_ControlType = ControlType.Joystick; public AnimationCurve inputModifier = AnimationCurve.Linear(-1f, -1f, 1f, 1f); public ButtonHandler action; private const float activeActionValue = 0.99f * 2f; private const float actionStartLerpValue = 0.9f * 2f; private CanvasGroup actionCanvasGroup; private CanvasGroup activeActionCanvasGroup; private Vector2 inputProcessed = Vector2.zero; private Vector2 inputProcessedByModifier = Vector2.zero; public string horizontalAxisName = "Horizontal"; // The name given to the horizontal axis for the cross platform input public string verticalAxisName = "Vertical"; // The name given to the vertical axis for the cross platform input protected CrossPlatformInputManager.VirtualAxis m_HorizontalVirtualAxis; // Reference to the joystick in the cross platform input protected CrossPlatformInputManager.VirtualAxis m_VerticalVirtualAxis; // Reference to the joystick in the cross platform input #if USE_NEW_INPUT protected override string controlPathInternal { get => m_ControlPath; set => m_ControlPath = value; } #endif public float Horizontal { get { return (snapX) ? SnapFloat(input.x, AxisOptions.Horizontal) : input.x; } } public float Vertical { get { return (snapY) ? SnapFloat(input.y, AxisOptions.Vertical) : input.y; } } public Vector2 Direction { get { return new Vector2(Horizontal, Vertical); } } void CreateVirtualAxes() { // set axes to use // create new axes based on axes to use if (CrossPlatformInputManager.AxisExists(horizontalAxisName)) { m_HorizontalVirtualAxis = CrossPlatformInputManager.VirtualAxisReference(horizontalAxisName); } else { m_HorizontalVirtualAxis = new CrossPlatformInputManager.VirtualAxis(horizontalAxisName); CrossPlatformInputManager.RegisterVirtualAxis(m_HorizontalVirtualAxis); } if (CrossPlatformInputManager.AxisExists(verticalAxisName)) { m_VerticalVirtualAxis = CrossPlatformInputManager.VirtualAxisReference(verticalAxisName); } else { m_VerticalVirtualAxis = new CrossPlatformInputManager.VirtualAxis(verticalAxisName); CrossPlatformInputManager.RegisterVirtualAxis(m_VerticalVirtualAxis); } } 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 inputAmp = 1; [SerializeField] private float deadZone = 0; [SerializeField] private AxisOptions axisOptions = AxisOptions.Both; [SerializeField] private float m_clampMin = 0.2f; [SerializeField] private bool snapX = false; [SerializeField] private bool snapY = false; [SerializeField] private bool stepXY = true; [SerializeField] private float stepSize = 0.65f; [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 bool m_Dragging; private Vector2 delta; private Vector2 prevInput; protected virtual void Start() { HandleRange = handleRange; DeadZone = deadZone; baseRect = GetComponent(); canvas = GetComponentInParent(); 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; if (action) { actionCanvasGroup = action.GetComponent(); if (actionCanvasGroup) { actionCanvasGroup.alpha = 0f; } if (action.transform.childCount > 0) { activeActionCanvasGroup = action.transform.GetChild(0).GetComponent(); if (activeActionCanvasGroup) { activeActionCanvasGroup.alpha = 0f; } } } CreateVirtualAxes(); } protected virtual void Update() { if (!m_Dragging) { return; } if (m_ControlType == ControlType.Touchpad) { delta = input; delta.x -= prevInput.x; delta.y -= prevInput.y; prevInput = input; inputProcessed.x = Horizontal; inputProcessed.y = Vertical; ClampValue(); if (delta.x == 0f) { inputProcessed.x = 0f; } if (delta.y == 0f) { inputProcessed.y = 0f; } #if USE_NEW_INPUT SendValueToControl(inputProcessed); #endif } } private void ClampValue() { if (inputProcessed.x > 0) { inputProcessed.x = Mathf.Max(inputProcessed.x, m_clampMin); } if (inputProcessed.x < 0) { inputProcessed.x = Mathf.Min(inputProcessed.x, -m_clampMin); } if (inputProcessed.y > 0) { inputProcessed.y = Mathf.Max(inputProcessed.y, m_clampMin); } if (inputProcessed.y < 0) { inputProcessed.y = Mathf.Min(inputProcessed.y, -m_clampMin); } if (stepXY) { float mag = inputProcessed.magnitude; var absM = Mathf.Abs(mag); var sig = Mathf.Sign(mag); if (absM > 0f) { inputProcessed /= absM; if (absM < stepSize) { inputProcessed *= 0.5f; } } } /* if (stepX) { if (inputProcessed.x > 0) { if (inputProcessed.x < stepSize) { inputProcessed.x = 0.5f; } else { inputProcessed.x = 1f; } } else if (inputProcessed.x < 0) { if (inputProcessed.x > -stepSize) { inputProcessed.x = -0.5f; } else { inputProcessed.x = -1f; } } } if (stepY) { if (inputProcessed.y > 0) { if (inputProcessed.y < stepSize) { inputProcessed.y = 0.5f; } else { inputProcessed.y = 1f; } } else if (inputProcessed.y < 0) { if (inputProcessed.y > -stepSize) { inputProcessed.y = -0.5f; } else { inputProcessed.y = -1f; } } } */ } public virtual void OnPointerDown(PointerEventData eventData) { m_Dragging = true; OnDrag(eventData); } void UpdateVirtualAxes(Vector2 value) { //Debug.Log("Virtual axis: "+value); m_HorizontalVirtualAxis.Update(value.x); m_VerticalVirtualAxis.Update(value.y); } 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; if (m_ControlType == ControlType.Joystick) { inputProcessed.x = Horizontal; inputProcessed.y = Vertical; //ClampValue(); if (actionCanvasGroup) { var inputProcessedLength = inputProcessed.magnitude; actionCanvasGroup.alpha = Mathf.InverseLerp(actionStartLerpValue, 1f, Mathf.Clamp01(inputProcessedLength)); if (activeActionCanvasGroup && inputProcessedLength > activeActionValue) { activeActionCanvasGroup.alpha = 1f; } else if (activeActionCanvasGroup) { activeActionCanvasGroup.alpha = 0f; } } ClampValue(); inputProcessedByModifier.x = inputProcessed.x * inputAmp; inputProcessedByModifier.y = inputProcessed.y * inputAmp; //inputProcessedByModifier.x = inputModifier.Evaluate(inputProcessed.x) * inputAmp; //inputProcessedByModifier.y = inputModifier.Evaluate(inputProcessed.y) * inputAmp; #if USE_NEW_INPUT SendValueToControl(inputProcessedByModifier); #endif UpdateVirtualAxes(inputProcessedByModifier); } } protected virtual void HandleInput(float magnitude, Vector2 normalised, Vector2 radius, Camera cam) { if (magnitude > deadZone) { //if (magnitude > 1) // 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 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) { //Debug.Log("input " + input); //Debug.Log("input.magnitude " + input.magnitude); if (input.magnitude >= activeActionValue) { AdditionalAction(); StartCoroutine(DelayPointerUp()); return; } PointerUp(); } private IEnumerator DelayPointerUp() { yield return new WaitForSeconds(0.1f); PointerUp(); } protected void OnDisable() { PointerUp(); } protected void PointerUp() { if (actionCanvasGroup) { actionCanvasGroup.alpha = 0f; } if (activeActionCanvasGroup) { activeActionCanvasGroup.alpha = 0f; } m_Dragging = false; input = Vector2.zero; delta = Vector2.zero; prevInput = Vector2.zero; handle.anchoredPosition = Vector2.zero; UpdateVirtualAxes(input); #if USE_NEW_INPUT SendValueToControl(input); #endif } private void AdditionalAction() { if (action) { action.Click(); } } 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 enum ControlType { Joystick, Touchpad }