Indie-Game-Jam / Assets / Carter Games / Utilities / UI Button Switch (Experimental) / UIButtonSwitch.cs
UIButtonSwitch.cs
Raw
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.InputSystem;
using System.Collections;

/****************************************************************************************************************************
 * 
 *  --{   Carter Games Utilities Script   }--
 *							  
 *  UI Button Switch
 *	    A base class that make controller input on UI elements easier.
 *	    
 *	Requirements:
 *	    - New Unity Input System
 *	    - InputActions class "Actions" (Edit Lines: 80, 110, 113, 131, 160, 164, 171, 175, 188, 192, 199, 203, 216, 220, 225, 229, if not)
 *	    - Input Map called "Movement" of type Vector2 for controllers. (Edit Lines: 160, 164, 171, 175, 188, 192, 199, 203, 216, 220, 225, 229, if not)
 *			
 *  Written by:
 *      Jonathan Carter
 *      E: jonathan@carter.games
 *      W: https://jonathan.carter.games
 *			        
 *	Last Updated: 18/12/2020 (d/m/y)				
 * 
****************************************************************************************************************************/

namespace CarterGames.Utilities
{
    /// <summary>
    /// Class | the base class for the UI Button Switch scripts. Handles the main funcionaility of the system.
    /// </summary>
    public class UIButtonSwitch : MonoBehaviour
    {
        /// <summary>
        /// GameObject Array | A grouping of all the buttons in the menu to effect.
        /// </summary>
        [Header("UI Buttons")]
        [Tooltip("The buttons to effect, these should have UIBS Button Actions on them.")]
        [SerializeField] internal GameObject[] buttons;
        [Space(5f)]

        /// <summary>
        /// Bool | Defines whether or not the menu navigates up/down.
        /// </summary>
        [Tooltip("If the menu navigates Up/Down the set this to true, for Left/Right leave it false.")]
        [SerializeField] private bool isUD;

        /// <summary>
        /// Bool | Defines if the position should be reset when enabled.
        /// </summary>
        [Tooltip("Defines whether or not the position is reset when this manager is enabled.")]
        [SerializeField] private bool resetPos;
        [Space(5f)]

        /// <summary>
        /// Bool | Defines whether or not to use a grid format for the movement.
        /// </summary>
        [Tooltip("Defines whether or not to use all directions (grid format).")]
        [SerializeField] private bool useGrid;

        /// <summary>
        /// Int | Defines how many columns are in the grid format, not needed if grid format is not in use.
        /// </summary>
        [SerializeField] private int maxColumns;
        [Space(5f)]

        /// <summary>
        /// UnityEvent | All effects to run on the active option.
        /// </summary>
        [SerializeField] private UnityEvent effects;

        /// <summary>
        /// Bool | Defines if the coroutine running? 
        /// </summary>
        internal bool isCoR;

        /// <summary>
        /// Actions | The input actions to check against (Edit if called something else).
        /// </summary>
        internal Actions action;

        /// <summary>
        /// Bool | is the player using a controller?
        /// </summary>
        internal bool isUsingController;

        /// <summary>
        /// Int | The position the user is at in the menu.
        /// </summary>
        internal int pos;

        /// <summary>
        /// Int | The max position the user can be at.
        /// </summary>
        internal int maxPos;

        /// <summary>
        /// InputDevice | The variable to check if a controller is active.
        /// </summary>
        public InputDevice currentActiveDevice;


        /// ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
        /// <summary>
        /// Unity OnEnable | Enables the input and position.
        /// </summary>
        /// ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
        private void OnEnable()
        {
            action = new Actions();
            action.Enable();

            ForceButtonDelay();

            if (resetPos)
            {
                pos = 0;
                effects.Invoke();
            }
        }


        /// ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
        /// <summary>
        /// Unity OnDisable | Stops the input and all.
        /// </summary>
        /// ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
        private void OnDisable()
        {
            StopAllCoroutines();
            action.Disable();
            isCoR = false;
        }


        /// ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
        /// <summary>
        /// Unity Start | Does basic setup for the position & runs the effects for the position 0 button.
        /// </summary>
        /// ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
        private void Start()
        {
            pos = 0;
            maxPos = buttons.Length - 1;
            isCoR = false;

            if (effects != null)
            {
                effects.Invoke();
            }
        }


        /// ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
        /// <summary>
        /// Unity Update | Handles the movement around the menu & what controller is active if there is one.
        /// </summary>
        /// ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
        private void Update()
        {
            // Active Controller if enabled.
            isUsingController = IsControllerActive();

            // The menu movement (Non grid).
            if (isUsingController)
            {
                if (!isCoR)
                {
                    if (isUD)
                    {
                        if (action.Menu.Movement.ReadValue<Vector2>().y > .1f)
                        {
                            UpdatePos(1);
                        }
                        else if (action.Menu.Movement.ReadValue<Vector2>().y < -.1f)
                        {
                            UpdatePos(-1);
                        }
                    }
                    else
                    {
                        if (action.Menu.Movement.ReadValue<Vector2>().x > .1f)
                        {
                            UpdatePos(1);
                        }
                        else if (action.Menu.Movement.ReadValue<Vector2>().x < -.1f)
                        {
                            UpdatePos(-1);
                        }
                    }
                }
            }
            else
            {
                if (!isCoR)
                {
                    if (isUD)
                    {
                        if (action.Menu.Movement.ReadValue<Vector2>().y > .1f)
                        {
                            UpdatePos(1);
                        }
                        else if (action.Menu.Movement.ReadValue<Vector2>().y < -.1f)
                        {
                            UpdatePos(-1);
                        }
                    }
                    else
                    {
                        if (action.Menu.Movement.ReadValue<Vector2>().x > .1f)
                        {
                            UpdatePos(1);
                        }
                        else if (action.Menu.Movement.ReadValue<Vector2>().x < -.1f)
                        {
                            UpdatePos(-1);
                        }
                    }
                }
            }


            // The menu movement (Grid)
            if (!isCoR)
            {
                if (useGrid)
                {
                    if (action.Menu.Movement.ReadValue<Vector2>().y > .1f)
                    {
                        UpdatePos(-maxColumns, true);
                    }
                    else if (action.Menu.Movement.ReadValue<Vector2>().y < -.1f)
                    {
                        UpdatePos(maxColumns, true);
                    }

                    if (action.Menu.Movement.ReadValue<Vector2>().x > .1f)
                    {
                        UpdatePos(1, true);
                    }
                    else if (action.Menu.Movement.ReadValue<Vector2>().x < -.1f)
                    {
                        UpdatePos(-1, true);
                    }
                }
            }


            // Confirm Button.
            if (!isCoR)
            {
                if (action.Menu.Accept.phase.Equals(InputActionPhase.Performed))
                {
                    if (buttons[pos].GetComponent<UIBSButtonActions>().canPerformActions)
                    {
                        buttons[pos].GetComponent<UIBSButtonActions>().action.Invoke();
                        StartCoroutine(ButtonDelay());
                    }
                }
            }
        }


        /// ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
        /// <summary>
        /// Fixed Update | Used to set whether or not a controller is active.
        /// </summary>
        /// ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
        private void FixedUpdate()
        {
            InputSystem.onActionChange += (obj, change) =>
            {
                if (change == InputActionChange.ActionPerformed)
                {
                    var inputAction = (InputAction)obj;
                    var lastControl = inputAction.activeControl;
                    currentActiveDevice = lastControl.device;
                }
            };
        }


        /// ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
        /// <summary>
        /// Method | Updates the position in the menu.
        /// </summary>
        /// <param name="value">value to change to.</param>
        /// ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
        private void UpdatePos(int value, bool useGrid = false)
        {
            pos += value;

            if (useGrid)
            {
                if (pos <= -1)
                {
                    pos = (pos + 1) + maxPos;
                }
                else if (pos >= (maxPos + 1))
                {
                    pos = (pos - 1) - maxPos;
                }
            }
            else
            {
                if (pos.Equals(maxPos + 1))
                {
                    pos = 0;
                }
                else if (pos.Equals(-1))
                {
                    pos = maxPos;
                }
            }

            if (effects != null)
            {
                effects.Invoke();
            }

            StartCoroutine(ButtonDelay());
        }


        /// ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
        /// <summary>
        /// Method | Checks to see if a controller (either XB or PS is plugged in).
        /// </summary>
        /// <returns>True if there is a supported controller plugged in, false if not.</returns>
        /// ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
        private bool IsControllerActive()
        {
            if (currentActiveDevice != null)
                if (currentActiveDevice.displayName.Contains("Xbox") || currentActiveDevice.displayName.Contains("Play"))
                    return true;
                else
                    return false;
            else
                return false;
        }


        /// ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
        /// <summary>
        /// Coroutine | Runs a short button delay on the menu buttons.
        /// </summary>
        /// ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
        internal IEnumerator ButtonDelay()
        {
            isCoR = true;
            yield return new WaitForSecondsRealtime(.3f);
            isCoR = false;
        }


        /// ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
        /// <summary>
        /// Method | Forces the button delay to run.
        /// </summary>
        /// ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
        private void ForceButtonDelay()
        {
            StartCoroutine(ButtonDelay());
        }
    }
}