Tanoda
UI_Knob.cs
Go to the documentation of this file.
1
3
6
25{
26 [RequireComponent(typeof(Image))]
27 [AddComponentMenu("UI/Extensions/UI_Knob")]
28 public class UI_Knob : Selectable, IPointerDownHandler, IPointerUpHandler, IPointerEnterHandler, IPointerExitHandler, IDragHandler, IInitializePotentialDragHandler
29 {
30 public enum Direction { CW, CCW };
31 [Tooltip("Direction of rotation CW - clockwise, CCW - counterClockwise")]
33 [HideInInspector]
34 public float knobValue;
35 [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")]
36 public float maxValue = 0;
37 [Tooltip("How many rotations knob can do, if higher than max value, the latter will limit max value")]
38 public int loops = 1;
39 [Tooltip("Clamp output value between 0 and 1, useful with loops > 1")]
40 public bool clampOutput01 = false;
41 [Tooltip("snap to position?")]
42 public bool snapToPosition = false;
43 [Tooltip("Number of positions to snap")]
44 public int snapStepsPerLoop = 10;
45 [Space(30)]
47 private float _currentLoops = 0;
48 private float _previousValue = 0;
49 private float _initAngle;
50 private float _currentAngle;
51 private Vector2 _currentVector;
52 private Quaternion _initRotation;
53 private bool _canDrag = false;
54 private bool _screenSpaceOverlay;
55
56 protected override void Awake()
57 {
58 _screenSpaceOverlay = GetComponentInParent<Canvas>().rootCanvas.renderMode == RenderMode.ScreenSpaceOverlay;
59 }
60
61 public override void OnPointerUp(PointerEventData eventData)
62 {
63 _canDrag = false;
64 }
65 public override void OnPointerEnter(PointerEventData eventData)
66 {
67 _canDrag = true;
68 }
69 public override void OnPointerExit(PointerEventData eventData)
70 {
71 _canDrag = false;
72 }
73
74
75 public override void OnPointerDown(PointerEventData eventData)
76 {
77 _canDrag = true;
78
79 base.OnPointerDown(eventData);
80
81 _initRotation = transform.rotation;
82 if (_screenSpaceOverlay)
83 {
84 _currentVector = eventData.position - (Vector2)transform.position;
85 }
86 else
87 {
88 _currentVector = eventData.position - (Vector2)Camera.main.WorldToScreenPoint(transform.position);
89 }
90 _initAngle = Mathf.Atan2(_currentVector.y, _currentVector.x) * Mathf.Rad2Deg;
91 }
92
93 public void OnDrag(PointerEventData eventData)
94 {
95 //CHECK IF CAN DRAG
96 if (!_canDrag)
97 {
98 return;
99 }
100
101 if (_screenSpaceOverlay)
102 {
103 _currentVector = eventData.position - (Vector2)transform.position;
104 }
105 else
106 {
107 _currentVector = eventData.position - (Vector2)Camera.main.WorldToScreenPoint(transform.position);
108 }
109 _currentAngle = Mathf.Atan2(_currentVector.y, _currentVector.x) * Mathf.Rad2Deg;
110
111 Quaternion addRotation = Quaternion.AngleAxis(_currentAngle - _initAngle, this.transform.forward);
112 addRotation.eulerAngles = new Vector3(0, 0, addRotation.eulerAngles.z);
113
114 Quaternion finalRotation = _initRotation * addRotation;
115
116 if (direction == Direction.CW)
117 {
118 knobValue = 1 - (finalRotation.eulerAngles.z / 360f);
119
120 if (snapToPosition)
121 {
122 SnapToPosition(ref knobValue);
123 finalRotation.eulerAngles = new Vector3(0, 0, 360 - 360 * knobValue);
124 }
125 }
126 else
127 {
128 knobValue = (finalRotation.eulerAngles.z / 360f);
129
130 if (snapToPosition)
131 {
132 SnapToPosition(ref knobValue);
133 finalRotation.eulerAngles = new Vector3(0, 0, 360 * knobValue);
134 }
135 }
136
137 //PREVENT OVERROTATION
138 if (Mathf.Abs(knobValue - _previousValue) > 0.5f)
139 {
140 if (knobValue < 0.5f && loops > 1 && _currentLoops < loops - 1)
141 {
142 _currentLoops++;
143 }
144 else if (knobValue > 0.5f && _currentLoops >= 1)
145 {
146 _currentLoops--;
147 }
148 else
149 {
150 if (knobValue > 0.5f && _currentLoops == 0)
151 {
152 knobValue = 0;
153 transform.localEulerAngles = Vector3.zero;
154 InvokeEvents(knobValue + _currentLoops);
155 return;
156 }
157 else if (knobValue < 0.5f && _currentLoops == loops - 1)
158 {
159 knobValue = 1;
160 transform.localEulerAngles = Vector3.zero;
161 InvokeEvents(knobValue + _currentLoops);
162 return;
163 }
164 }
165 }
166
167 //CHECK MAX VALUE
168 if (maxValue > 0)
169 {
170 if (knobValue + _currentLoops > maxValue)
171 {
173 float maxAngle = direction == Direction.CW ? 360f - 360f * maxValue : 360f * maxValue;
174 transform.localEulerAngles = new Vector3(0, 0, maxAngle);
175 InvokeEvents(knobValue);
176 return;
177 }
178 }
179
180 transform.rotation = finalRotation;
181 InvokeEvents(knobValue + _currentLoops);
182
183 _previousValue = knobValue;
184 }
185 private void SnapToPosition(ref float knobValue)
186 {
187 float snapStep = 1 / (float)snapStepsPerLoop;
188 float newValue = Mathf.Round(knobValue / snapStep) * snapStep;
189 knobValue = newValue;
190 }
191 private void InvokeEvents(float value)
192 {
193 if (clampOutput01)
194 value /= loops;
195 OnValueChanged.Invoke(value);
196 }
197
198 public virtual void OnInitializePotentialDrag(PointerEventData eventData)
199 {
200 eventData.useDragThreshold = false;
201 }
202 }
203
204 [System.Serializable]
205 public class KnobFloatValueEvent : UnityEvent<float> { }
206
207}
System.Drawing.Image Image
Definition: TestScript.cs:37
override void OnPointerEnter(PointerEventData eventData)
Definition: UI_Knob.cs:65
void OnDrag(PointerEventData eventData)
Definition: UI_Knob.cs:93
virtual void OnInitializePotentialDrag(PointerEventData eventData)
Definition: UI_Knob.cs:198
override void OnPointerExit(PointerEventData eventData)
Definition: UI_Knob.cs:69
override void OnPointerDown(PointerEventData eventData)
Definition: UI_Knob.cs:75
KnobFloatValueEvent OnValueChanged
Definition: UI_Knob.cs:46
override void OnPointerUp(PointerEventData eventData)
Definition: UI_Knob.cs:61
Credit Erdener Gonenc - @PixelEnvision.