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.
396 lines
13 KiB
C#
396 lines
13 KiB
C#
3 months ago
|
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||
|
|
||
|
using UnityEngine;
|
||
|
using System.Collections;
|
||
|
using UnityEngine.UI;
|
||
|
using UnityEngine.Events;
|
||
|
using UnityEngine.EventSystems;
|
||
|
|
||
|
namespace Lofelt.NiceVibrations
|
||
|
{
|
||
|
[RequireComponent(typeof(Rect))]
|
||
|
[RequireComponent(typeof(CanvasGroup))]
|
||
|
/// <summary>
|
||
|
/// Add this component to a GUI Image to have it act as a button.
|
||
|
/// Bind pressed down, pressed continually and released actions to it from the inspector
|
||
|
/// Handles mouse and multi touch
|
||
|
/// </summary>
|
||
|
public class MMTouchButton : MonoBehaviour, IPointerDownHandler, IPointerUpHandler, IPointerExitHandler, IPointerEnterHandler, ISubmitHandler
|
||
|
{
|
||
|
/// The different possible states for the button :
|
||
|
/// Off (default idle state), ButtonDown (button pressed for the first time), ButtonPressed (button being pressed), ButtonUp (button being released), Disabled (unclickable but still present on screen)
|
||
|
/// ButtonDown and ButtonUp will only last one frame, the others will last however long you press them / disable them / do nothing
|
||
|
public enum ButtonStates { Off, ButtonDown, ButtonPressed, ButtonUp, Disabled }
|
||
|
[Header("Binding")]
|
||
|
/// The method(s) to call when the button gets pressed down
|
||
|
public UnityEvent ButtonPressedFirstTime;
|
||
|
/// The method(s) to call when the button gets released
|
||
|
public UnityEvent ButtonReleased;
|
||
|
/// The method(s) to call while the button is being pressed
|
||
|
public UnityEvent ButtonPressed;
|
||
|
|
||
|
[Header("Sprite Swap")]
|
||
|
public Sprite DisabledSprite;
|
||
|
public Sprite PressedSprite;
|
||
|
public Sprite HighlightedSprite;
|
||
|
|
||
|
[Header("Color Changes")]
|
||
|
public bool PressedChangeColor = false;
|
||
|
public Color PressedColor = Color.white;
|
||
|
public bool LerpColor = true;
|
||
|
public float LerpColorDuration = 0.2f;
|
||
|
public AnimationCurve LerpColorCurve;
|
||
|
|
||
|
[Header("Opacity")]
|
||
|
/// the new opacity to apply to the canvas group when the button is pressed
|
||
|
public float PressedOpacity = 1f;
|
||
|
public float IdleOpacity = 1f;
|
||
|
public float DisabledOpacity = 1f;
|
||
|
|
||
|
[Header("Delays")]
|
||
|
public float PressedFirstTimeDelay = 0f;
|
||
|
public float ReleasedDelay = 0f;
|
||
|
|
||
|
[Header("Buffer")]
|
||
|
public float BufferDuration = 0f;
|
||
|
|
||
|
[Header("Animation")]
|
||
|
public Animator Animator;
|
||
|
public string IdleAnimationParameterName = "Idle";
|
||
|
public string DisabledAnimationParameterName = "Disabled";
|
||
|
public string PressedAnimationParameterName = "Pressed";
|
||
|
|
||
|
[Header("Mouse Mode")]
|
||
|
/// If you set this to true, you'll need to actually press the button for it to be triggered, otherwise a simple hover will trigger it (better for touch input).
|
||
|
public bool MouseMode = false;
|
||
|
|
||
|
public bool ReturnToInitialSpriteAutomatically { get; set; }
|
||
|
|
||
|
/// the current state of the button (off, down, pressed or up)
|
||
|
public ButtonStates CurrentState { get; protected set; }
|
||
|
|
||
|
protected bool _zonePressed = false;
|
||
|
protected CanvasGroup _canvasGroup;
|
||
|
protected float _initialOpacity;
|
||
|
protected Animator _animator;
|
||
|
protected Image _image;
|
||
|
protected Sprite _initialSprite;
|
||
|
protected Color _initialColor;
|
||
|
protected float _lastClickTimestamp = 0f;
|
||
|
protected Selectable _selectable;
|
||
|
protected float _lastStateChangeAt = -50f;
|
||
|
|
||
|
protected Color _imageColor;
|
||
|
protected Color _fromColor;
|
||
|
protected Color _toColor;
|
||
|
|
||
|
/// <summary>
|
||
|
/// On Start, we get our canvasgroup and set our initial alpha
|
||
|
/// </summary>
|
||
|
protected virtual void Awake()
|
||
|
{
|
||
|
Initialization();
|
||
|
}
|
||
|
|
||
|
protected virtual void Initialization()
|
||
|
{
|
||
|
ReturnToInitialSpriteAutomatically = true;
|
||
|
|
||
|
_selectable = GetComponent<Selectable>();
|
||
|
|
||
|
_image = GetComponent<Image>();
|
||
|
if (_image != null)
|
||
|
{
|
||
|
_initialColor = _image.color;
|
||
|
_initialSprite = _image.sprite;
|
||
|
}
|
||
|
|
||
|
_animator = GetComponent<Animator>();
|
||
|
if (Animator != null)
|
||
|
{
|
||
|
_animator = Animator;
|
||
|
}
|
||
|
|
||
|
_canvasGroup = GetComponent<CanvasGroup>();
|
||
|
if (_canvasGroup != null)
|
||
|
{
|
||
|
_initialOpacity = IdleOpacity;
|
||
|
_canvasGroup.alpha = _initialOpacity;
|
||
|
_initialOpacity = _canvasGroup.alpha;
|
||
|
}
|
||
|
ResetButton();
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Every frame, if the touch zone is pressed, we trigger the OnPointerPressed method, to detect continuous press
|
||
|
/// </summary>
|
||
|
protected virtual void Update()
|
||
|
{
|
||
|
switch (CurrentState)
|
||
|
{
|
||
|
case ButtonStates.Off:
|
||
|
SetOpacity(IdleOpacity);
|
||
|
if ((_image != null) && (ReturnToInitialSpriteAutomatically))
|
||
|
{
|
||
|
_image.sprite = _initialSprite;
|
||
|
}
|
||
|
if (_selectable != null)
|
||
|
{
|
||
|
_selectable.interactable = true;
|
||
|
if (EventSystem.current.currentSelectedGameObject == this.gameObject)
|
||
|
{
|
||
|
if (HighlightedSprite != null)
|
||
|
{
|
||
|
_image.sprite = HighlightedSprite;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case ButtonStates.Disabled:
|
||
|
SetOpacity(DisabledOpacity);
|
||
|
if (_image != null)
|
||
|
{
|
||
|
if (DisabledSprite != null)
|
||
|
{
|
||
|
_image.sprite = DisabledSprite;
|
||
|
}
|
||
|
}
|
||
|
if (_selectable != null)
|
||
|
{
|
||
|
_selectable.interactable = false;
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case ButtonStates.ButtonDown:
|
||
|
|
||
|
break;
|
||
|
|
||
|
case ButtonStates.ButtonPressed:
|
||
|
SetOpacity(PressedOpacity);
|
||
|
OnPointerPressed();
|
||
|
if (_image != null)
|
||
|
{
|
||
|
if (PressedSprite != null)
|
||
|
{
|
||
|
_image.sprite = PressedSprite;
|
||
|
}
|
||
|
if (PressedChangeColor)
|
||
|
{
|
||
|
_image.color = PressedColor;
|
||
|
}
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case ButtonStates.ButtonUp:
|
||
|
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
if ((_image != null) && (PressedChangeColor))
|
||
|
{
|
||
|
if (Time.time - _lastStateChangeAt < LerpColorDuration)
|
||
|
{
|
||
|
float t = LerpColorCurve.Evaluate(Remap(Time.time - _lastStateChangeAt, 0f, LerpColorDuration, 0f, 1f));
|
||
|
_image.color = Color.Lerp(_fromColor, _toColor, t);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
UpdateAnimatorStates();
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// At the end of every frame, we change our button's state if needed
|
||
|
/// </summary>
|
||
|
protected virtual void LateUpdate()
|
||
|
{
|
||
|
if (CurrentState == ButtonStates.ButtonUp)
|
||
|
{
|
||
|
_lastStateChangeAt = Time.time;
|
||
|
_fromColor = PressedColor;
|
||
|
_toColor = _initialColor;
|
||
|
CurrentState = ButtonStates.Off;
|
||
|
}
|
||
|
if (CurrentState == ButtonStates.ButtonDown)
|
||
|
{
|
||
|
_lastStateChangeAt = Time.time;
|
||
|
_fromColor = _initialColor;
|
||
|
_toColor = PressedColor;
|
||
|
CurrentState = ButtonStates.ButtonPressed;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Triggers the bound pointer down action
|
||
|
/// </summary>
|
||
|
public virtual void OnPointerDown(PointerEventData data)
|
||
|
{
|
||
|
if (Time.time - _lastClickTimestamp < BufferDuration)
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (CurrentState != ButtonStates.Off)
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
CurrentState = ButtonStates.ButtonDown;
|
||
|
_lastClickTimestamp = Time.time;
|
||
|
if ((Time.timeScale != 0) && (PressedFirstTimeDelay > 0))
|
||
|
{
|
||
|
Invoke("InvokePressedFirstTime", PressedFirstTimeDelay);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
ButtonPressedFirstTime.Invoke();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
protected virtual void InvokePressedFirstTime()
|
||
|
{
|
||
|
if (ButtonPressedFirstTime != null)
|
||
|
{
|
||
|
ButtonPressedFirstTime.Invoke();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Triggers the bound pointer up action
|
||
|
/// </summary>
|
||
|
public virtual void OnPointerUp(PointerEventData data)
|
||
|
{
|
||
|
if (CurrentState != ButtonStates.ButtonPressed && CurrentState != ButtonStates.ButtonDown)
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
CurrentState = ButtonStates.ButtonUp;
|
||
|
if ((Time.timeScale != 0) && (ReleasedDelay > 0))
|
||
|
{
|
||
|
Invoke("InvokeReleased", ReleasedDelay);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
ButtonReleased.Invoke();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
protected virtual void InvokeReleased()
|
||
|
{
|
||
|
if (ButtonReleased != null)
|
||
|
{
|
||
|
ButtonReleased.Invoke();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Triggers the bound pointer pressed action
|
||
|
/// </summary>
|
||
|
public virtual void OnPointerPressed()
|
||
|
{
|
||
|
CurrentState = ButtonStates.ButtonPressed;
|
||
|
if (ButtonPressed != null)
|
||
|
{
|
||
|
ButtonPressed.Invoke();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Resets the button's state and opacity
|
||
|
/// </summary>
|
||
|
protected virtual void ResetButton()
|
||
|
{
|
||
|
SetOpacity(_initialOpacity);
|
||
|
CurrentState = ButtonStates.Off;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Triggers the bound pointer enter action when touch enters zone
|
||
|
/// </summary>
|
||
|
public virtual void OnPointerEnter(PointerEventData data)
|
||
|
{
|
||
|
if (!MouseMode)
|
||
|
{
|
||
|
OnPointerDown(data);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Triggers the bound pointer exit action when touch is out of zone
|
||
|
/// </summary>
|
||
|
public virtual void OnPointerExit(PointerEventData data)
|
||
|
{
|
||
|
if (!MouseMode)
|
||
|
{
|
||
|
OnPointerUp(data);
|
||
|
}
|
||
|
}
|
||
|
/// <summary>
|
||
|
/// OnEnable, we reset our button state
|
||
|
/// </summary>
|
||
|
protected virtual void OnEnable()
|
||
|
{
|
||
|
ResetButton();
|
||
|
}
|
||
|
|
||
|
public virtual void DisableButton()
|
||
|
{
|
||
|
CurrentState = ButtonStates.Disabled;
|
||
|
}
|
||
|
|
||
|
public virtual void EnableButton()
|
||
|
{
|
||
|
if (CurrentState == ButtonStates.Disabled)
|
||
|
{
|
||
|
CurrentState = ButtonStates.Off;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
protected virtual void SetOpacity(float newOpacity)
|
||
|
{
|
||
|
if (_canvasGroup != null)
|
||
|
{
|
||
|
_canvasGroup.alpha = newOpacity;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
protected virtual void UpdateAnimatorStates()
|
||
|
{
|
||
|
if (_animator == null)
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
if (DisabledAnimationParameterName != null)
|
||
|
{
|
||
|
_animator.SetBool(DisabledAnimationParameterName, (CurrentState == ButtonStates.Disabled));
|
||
|
}
|
||
|
if (PressedAnimationParameterName != null)
|
||
|
{
|
||
|
_animator.SetBool(PressedAnimationParameterName, (CurrentState == ButtonStates.ButtonPressed));
|
||
|
}
|
||
|
if (IdleAnimationParameterName != null)
|
||
|
{
|
||
|
_animator.SetBool(IdleAnimationParameterName, (CurrentState == ButtonStates.Off));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public virtual void OnSubmit(BaseEventData eventData)
|
||
|
{
|
||
|
if (ButtonPressedFirstTime != null)
|
||
|
{
|
||
|
ButtonPressedFirstTime.Invoke();
|
||
|
}
|
||
|
if (ButtonReleased != null)
|
||
|
{
|
||
|
ButtonReleased.Invoke();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
protected virtual float Remap(float x, float A, float B, float C, float D)
|
||
|
{
|
||
|
float remappedValue = C + (x - A) / (B - A) * (D - C);
|
||
|
return remappedValue;
|
||
|
}
|
||
|
}
|
||
|
}
|