| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286 |
- /// Credit Tomasz Schelenz
- /// Sourced from - https://bitbucket.org/SimonDarksideJ/unity-ui-extensions/issues/46/feature-uiknob#comment-29243988
- using System;
- using UnityEngine.Events;
- using UnityEngine.EventSystems;
- /// <summary>
- /// KNOB controller
- ///
- /// Fields
- /// - direction - direction of rotation CW - clockwise CCW - counter clock wise
- /// - knobValue - Output value of the control
- /// - maxValue - max value knob can rotate to, if higher than loops value or set to 0 - it will be ignored, and max value will be based on loops
- /// - loops - how any turns around knob can do
- /// - clampOutput01 - if true the output knobValue will be clamped between 0 and 1 regardless of number of loops.
- /// - snapToPosition - snap to step. NOTE: max value will override the step.
- /// - snapStepsPerLoop - how many snap positions are in one knob loop;
- /// - OnValueChanged - event that is called every frame while rotating knob, sends <float> argument of knobValue
- /// NOTES
- /// - script works only in images rotation on Z axis;
- /// - while dragging outside of control, the rotation will be canceled
- /// </summary>
- ///
- namespace UnityEngine.UI.Extensions
- {
- [RequireComponent(typeof(Image))]
- [AddComponentMenu("UI/Extensions/UI_Knob")]
- public class UI_Knob : Selectable, IPointerDownHandler, IPointerUpHandler, IPointerEnterHandler, IPointerExitHandler, IDragHandler, IInitializePotentialDragHandler
- {
- public enum Direction { CW, CCW };
- [Tooltip("Direction of rotation CW - clockwise, CCW - counterClockwise")]
- public Direction direction = Direction.CW;
- [HideInInspector]
- public float KnobValue;
- [Tooltip("Max value of the knob, maximum RAW output value knob can reach, overrides snap step, IF set to 0 or higher than loops, max value will be set by loops")]
- public float MaxValue = 0;
- [Tooltip("How many rotations knob can do, if higher than max value, the latter will limit max value")]
- public int Loops = 0;
- [Tooltip("Clamp output value between 0 and 1, useful with loops > 1")]
- public bool ClampOutput01 = false;
- [Tooltip("snap to position?")]
- public bool SnapToPosition = false;
- [Tooltip("Number of positions to snap")]
- public int SnapStepsPerLoop = 10;
- [Tooltip("Parent touch area to extend the touch radius")]
- public RectTransform ParentTouchMask;
- [Tooltip("Default background color of the touch mask. Defaults as transparent")]
- public Color MaskBackground = new Color(0, 0, 0, 0);
- [Space(30)]
- public KnobFloatValueEvent OnValueChanged;
- private float _currentLoops = 0;
- private float _previousValue = 0;
- private float _initAngle;
- private float _currentAngle;
- private Vector2 _currentVector;
- private Quaternion _initRotation;
- private bool _canDrag = false;
- private bool _screenSpaceOverlay;
- protected override void Awake()
- {
- _screenSpaceOverlay = GetComponentInParent<Canvas>().rootCanvas.renderMode == RenderMode.ScreenSpaceOverlay;
- }
- protected override void Start()
- {
- CheckForParentTouchMask();
- }
- private void CheckForParentTouchMask()
- {
- if (ParentTouchMask)
- {
- Image maskImage = ParentTouchMask.gameObject.GetOrAddComponent<Image>();
- maskImage.color = MaskBackground;
- EventTrigger trigger = ParentTouchMask.gameObject.GetOrAddComponent<EventTrigger>();
- trigger.triggers.Clear();
- //PointerDownEvent
- EventTrigger.Entry pointerDownEntry = new EventTrigger.Entry();
- pointerDownEntry.eventID = EventTriggerType.PointerDown;
- pointerDownEntry.callback.AddListener((data) => { OnPointerDown((PointerEventData)data); });
- trigger.triggers.Add(pointerDownEntry);
- //PointerUpEvent
- EventTrigger.Entry pointerUpEntry = new EventTrigger.Entry();
- pointerUpEntry.eventID = EventTriggerType.PointerUp;
- pointerUpEntry.callback.AddListener((data) => { OnPointerUp((PointerEventData)data); });
- trigger.triggers.Add(pointerUpEntry);
- //PointerEnterEvent
- EventTrigger.Entry pointerEnterEntry = new EventTrigger.Entry();
- pointerEnterEntry.eventID = EventTriggerType.PointerEnter;
- pointerEnterEntry.callback.AddListener((data) => { OnPointerEnter((PointerEventData)data); });
- trigger.triggers.Add(pointerEnterEntry);
- //PointerExitEvent
- EventTrigger.Entry pointerExitEntry = new EventTrigger.Entry();
- pointerExitEntry.eventID = EventTriggerType.PointerExit;
- pointerExitEntry.callback.AddListener((data) => { OnPointerExit((PointerEventData)data); });
- trigger.triggers.Add(pointerExitEntry);
- //DragEvent
- EventTrigger.Entry dragEntry = new EventTrigger.Entry();
- dragEntry.eventID = EventTriggerType.Drag;
- dragEntry.callback.AddListener((data) => { OnDrag((PointerEventData)data); });
- trigger.triggers.Add(dragEntry);
- }
- }
- public override void OnPointerUp(PointerEventData eventData)
- {
- _canDrag = false;
- }
- public override void OnPointerEnter(PointerEventData eventData)
- {
- _canDrag = true;
- }
- public override void OnPointerExit(PointerEventData eventData)
- {
- _canDrag = false;
- }
- public override void OnPointerDown(PointerEventData eventData)
- {
- _canDrag = true;
- base.OnPointerDown(eventData);
- _initRotation = transform.rotation;
- if (_screenSpaceOverlay)
- {
- _currentVector = eventData.position - (Vector2)transform.position;
- }
- else
- {
- _currentVector = eventData.position - (Vector2)Camera.main.WorldToScreenPoint(transform.position);
- }
- _initAngle = Mathf.Atan2(_currentVector.y, _currentVector.x) * Mathf.Rad2Deg;
- }
- public void OnDrag(PointerEventData eventData)
- {
- //CHECK IF CAN DRAG
- if (!_canDrag)
- {
- return;
- }
- if (_screenSpaceOverlay)
- {
- _currentVector = eventData.position - (Vector2)transform.position;
- }
- else
- {
- _currentVector = eventData.position - (Vector2)Camera.main.WorldToScreenPoint(transform.position);
- }
- _currentAngle = Mathf.Atan2(_currentVector.y, _currentVector.x) * Mathf.Rad2Deg;
- Quaternion addRotation = Quaternion.AngleAxis(_currentAngle - _initAngle, this.transform.forward);
- addRotation.eulerAngles = new Vector3(0, 0, addRotation.eulerAngles.z);
- Quaternion finalRotation = _initRotation * addRotation;
- if (direction == Direction.CW)
- {
- KnobValue = 1 - (finalRotation.eulerAngles.z / 360f);
- if (SnapToPosition)
- {
- SnapToPositionValue(ref KnobValue);
- finalRotation.eulerAngles = new Vector3(0, 0, 360 - 360 * KnobValue);
- }
- }
- else
- {
- KnobValue = (finalRotation.eulerAngles.z / 360f);
- if (SnapToPosition)
- {
- SnapToPositionValue(ref KnobValue);
- finalRotation.eulerAngles = new Vector3(0, 0, 360 * KnobValue);
- }
- }
- UpdateKnobValue();
- transform.rotation = finalRotation;
- InvokeEvents(KnobValue + _currentLoops);
- _previousValue = KnobValue;
- }
- private void UpdateKnobValue()
- {
- //PREVENT OVERROTATION
- if (Mathf.Abs(KnobValue - _previousValue) > 0.5f)
- {
- if (KnobValue < 0.5f && Loops > 1 && _currentLoops < Loops - 1)
- {
- _currentLoops++;
- }
- else if (KnobValue > 0.5f && _currentLoops >= 1)
- {
- _currentLoops--;
- }
- else
- {
- if (KnobValue > 0.5f && _currentLoops == 0)
- {
- KnobValue = 0;
- transform.localEulerAngles = Vector3.zero;
- InvokeEvents(KnobValue + _currentLoops);
- return;
- }
- else if (KnobValue < 0.5f && _currentLoops == Loops - 1)
- {
- KnobValue = 1;
- transform.localEulerAngles = Vector3.zero;
- InvokeEvents(KnobValue + _currentLoops);
- return;
- }
- }
- }
- //CHECK MAX VALUE
- if (MaxValue > 0)
- {
- if (KnobValue + _currentLoops > MaxValue)
- {
- KnobValue = MaxValue;
- float maxAngle = direction == Direction.CW ? 360f - 360f * MaxValue : 360f * MaxValue;
- transform.localEulerAngles = new Vector3(0, 0, maxAngle);
- InvokeEvents(KnobValue);
- return;
- }
- }
- }
- public void SetKnobValue(float value, int loops = 0)
- {
- Quaternion newRoation = Quaternion.identity;
- KnobValue = value;
- _currentLoops = loops;
- if (SnapToPosition)
- {
- SnapToPositionValue(ref KnobValue);
- }
- if (direction == Direction.CW)
- {
- newRoation.eulerAngles = new Vector3(0, 0, 360 - 360 * KnobValue);
- }
- else
- {
- newRoation.eulerAngles = new Vector3(0, 0, 360 * KnobValue);
- }
- UpdateKnobValue();
- transform.rotation = newRoation;
- InvokeEvents(KnobValue + _currentLoops);
- _previousValue = KnobValue;
- }
- private void SnapToPositionValue(ref float knobValue)
- {
- float snapStep = 1 / (float)SnapStepsPerLoop;
- float newValue = Mathf.Round(knobValue / snapStep) * snapStep;
- knobValue = newValue;
- }
- private void InvokeEvents(float value)
- {
- if (ClampOutput01)
- value /= Loops;
- OnValueChanged.Invoke(value);
- }
- public virtual void OnInitializePotentialDrag(PointerEventData eventData)
- {
- eventData.useDragThreshold = false;
- }
- }
- [System.Serializable]
- public class KnobFloatValueEvent : UnityEvent<float> { }
- }
|