A game about forced loneliness, made by TACStudios
1using System.Runtime.CompilerServices;
2using UnityEngine.InputSystem.LowLevel;
3using UnityEngine.Scripting;
4
5////REVIEW: introduce separate base class for ButtonControl and AxisControl instead of deriving ButtonControl from AxisControl?
6
7namespace UnityEngine.InputSystem.Controls
8{
9 /// <summary>
10 /// An axis that has a trigger point beyond which it is considered to be pressed.
11 /// </summary>
12 /// <remarks>
13 /// By default stored as a single bit. In that format, buttons will only yield 0
14 /// and 1 as values. However, buttons return are <see cref="AxisControl"/>s and
15 /// yield full floating-point values and may thus have a range of values. See
16 /// <see cref="pressPoint"/> for how button presses on such buttons are handled.
17 /// </remarks>
18 public class ButtonControl : AxisControl
19 {
20 private bool m_NeedsToCheckFramePress = false;
21 private uint m_UpdateCountLastPressed = uint.MaxValue;
22 private uint m_UpdateCountLastReleased = uint.MaxValue;
23 private bool m_LastUpdateWasPress;
24 #if UNITY_EDITOR
25 // Editor input updates have a separate block of state memory, so must be checked separately
26 private uint m_UpdateCountLastPressedEditor = uint.MaxValue;
27 private uint m_UpdateCountLastReleasedEditor = uint.MaxValue;
28 private bool m_LastUpdateWasPressEditor;
29 #endif
30
31 internal bool needsToCheckFramePress { get; private set; }
32
33 ////REVIEW: are per-control press points really necessary? can we just drop them?
34 /// <summary>
35 /// The minimum value the button has to reach for it to be considered pressed.
36 /// </summary>
37 /// <value>Button press threshold.</value>
38 /// <remarks>
39 /// The button is considered pressed, if it has a value equal to or greater than
40 /// this value.
41 ///
42 /// By default, this property is set to -1. If the value of the property is negative,
43 /// <see cref="InputSettings.defaultButtonPressPoint"/> is used.
44 ///
45 /// The value can be configured as a parameter in a layout.
46 ///
47 /// <example>
48 /// <code>
49 /// public class MyDevice : InputDevice
50 /// {
51 /// [InputControl(parameters = "pressPoint=0.234")]
52 /// public ButtonControl button { get; private set; }
53 ///
54 /// //...
55 /// }
56 /// </code>
57 /// </example>
58 /// </remarks>
59 /// <seealso cref="InputSettings.defaultButtonPressPoint"/>
60 /// <seealso cref="pressPointOrDefault"/>
61 /// <seealso cref="isPressed"/>
62 public float pressPoint = -1;
63
64 /// <summary>
65 /// Return <see cref="pressPoint"/> if set, otherwise return <see cref="InputSettings.defaultButtonPressPoint"/>.
66 /// </summary>
67 /// <value>Effective value to use for press point thresholds.</value>
68 /// <seealso cref="InputSettings.defaultButtonPressPoint"/>
69 public float pressPointOrDefault => pressPoint > 0 ? pressPoint : s_GlobalDefaultButtonPressPoint;
70
71 /// <summary>
72 /// Default-initialize the control.
73 /// </summary>
74 /// <remarks>
75 /// The default format for the control is <see cref="InputStateBlock.FormatBit"/>.
76 /// The control's minimum value is set to 0 and the maximum value to 1.
77 /// </remarks>
78 public ButtonControl()
79 {
80 m_StateBlock.format = InputStateBlock.FormatBit;
81 m_MinValue = 0f;
82 m_MaxValue = 1f;
83 }
84
85 /// <summary>
86 /// Whether the given value would be considered pressed for this button.
87 /// </summary>
88 /// <param name="value">Value for the button.</param>
89 /// <returns>True if <paramref name="value"/> crosses the threshold to be considered pressed.</returns>
90 /// <seealso cref="pressPoint"/>
91 /// <seealso cref="InputSettings.defaultButtonPressPoint"/>
92 [MethodImpl(MethodImplOptions.AggressiveInlining)]
93 public new bool IsValueConsideredPressed(float value)
94 {
95 return value >= pressPointOrDefault;
96 }
97
98 /// <summary>
99 /// Whether the button is currently pressed.
100 /// </summary>
101 /// <value>True if button is currently pressed.</value>
102 /// <remarks>
103 /// A button is considered pressed if its value is equal to or greater
104 /// than its button press threshold (<see cref="pressPointOrDefault"/>).
105 /// </remarks>
106 /// <example>
107 /// <para>You can use this to read whether specific keys are currently pressed by using isPressed on keys, as shown in the following examples:</para>
108 /// <code>
109 /// <![CDATA[
110 /// // Using KeyControl property directly.
111 /// Keyboard.current.spaceKey.isPressed
112 /// Keyboard.current.aKey.isPressed // etc.
113 ///
114 /// // Using Key enum.
115 /// Keyboard.current[Key.Space].isPressed
116 ///
117 /// // Using key name.
118 /// ((KeyControl)Keyboard.current["space"]).isPressed
119 /// ]]>
120 /// </code>
121 /// <para>Note: The Input System identifies keys by physical layout, not according to the current language mapping of the keyboard. To query the name of the key according to the language mapping, use <see cref="InputControl.displayName"/>.
122 ///
123 /// You can also use this to read mouse buttons, as shown in the following examples:</para>
124 /// <code>
125 /// <![CDATA[
126 /// bool leftPressed = Mouse.current.leftButton.isPressed;
127 /// bool rightPressed = Mouse.current.rightButton.isPressed;
128 /// bool middlePressed = Mouse.current.middleButton.isPressed;
129 /// ]]>
130 /// </code>
131 /// <para>You can also check through all numbered buttons on the mouse: (this example does not cause allocations)</para>
132 /// <code>
133 /// <![CDATA[
134 /// var controls = Mouse.current.allControls;
135 /// for (var i = 0; i < controls.Count; ++i)
136 /// {
137 /// var button = controls[i] as ButtonControl;
138 /// if (button != null && button.isPressed)
139 /// {
140 /// // respond to mouse button press here...
141 /// }
142 /// }
143 /// ]]>
144 /// </code>
145 /// <para>Or you can look up controls by name, like this:</para>
146 /// <code>
147 /// <![CDATA[
148 /// bool leftPressed = ((ButtonControl)Mouse.current["leftButton"]).isPressed;
149 /// ]]>
150 /// </code>
151 /// </example>
152 /// <seealso cref="InputSettings.defaultButtonPressPoint"/>
153 /// <seealso cref="pressPoint"/>
154 /// <seealso cref="InputSystem.onAnyButtonPress"/>
155 public bool isPressed
156 {
157 get
158 {
159 // Take the old path if we don't have the speed gain from already testing wasPressedThisFrame/wasReleasedThisFrame.
160 if (!needsToCheckFramePress)
161 return IsValueConsideredPressed(value);
162
163 #if UNITY_EDITOR
164 if (InputUpdate.s_LatestUpdateType.IsEditorUpdate())
165 return m_LastUpdateWasPressEditor;
166 #endif
167
168 return m_LastUpdateWasPress;
169 }
170 }
171
172 // When we start caring about inter-frame presses, use the info we have to set up the alternate path.
173 // If we don't do this, users could call wasPressedThisFrame/wasReleasedThisFrame twice for the first time in
174 // a single frame, and the returned value may be incorrect until the next frame.
175 private void BeginTestingForFramePresses(bool currentlyPressed, bool pressedLastFrame)
176 {
177 needsToCheckFramePress = true;
178 device.m_ButtonControlsCheckingPressState.Add(this);
179
180 #if UNITY_EDITOR
181 if (InputUpdate.s_LatestUpdateType.IsEditorUpdate())
182 {
183 m_LastUpdateWasPressEditor = currentlyPressed;
184 if (currentlyPressed && !pressedLastFrame)
185 m_UpdateCountLastPressedEditor = device.m_CurrentUpdateStepCount;
186 else if (pressedLastFrame && !currentlyPressed)
187 m_UpdateCountLastReleasedEditor = device.m_CurrentUpdateStepCount;
188 }
189 else
190 #endif
191 {
192 m_LastUpdateWasPress = currentlyPressed;
193 if (currentlyPressed && !pressedLastFrame)
194 m_UpdateCountLastPressed = device.m_CurrentUpdateStepCount;
195 else if (pressedLastFrame && !currentlyPressed)
196 m_UpdateCountLastReleased = device.m_CurrentUpdateStepCount;
197 }
198 }
199
200 /// <summary>
201 /// Whether the press started this frame.
202 /// </summary>
203 /// <value>True if the current press of the button started this frame.</value>
204 /// <remarks>
205 /// The first time this function - or wasReleasedThisFrame - are called, it's possible that extremely fast
206 /// inputs (or very slow frame update times) will result in presses/releases being missed.
207 /// Following the next input system update after either have been called, and from then on until the device is
208 /// destroyed, this ceases to be an issue.
209 /// <example>
210 /// <code>
211 /// // An example showing the use of this property on a gamepad button and a keyboard key.
212 ///
213 /// using UnityEngine;
214 /// using UnityEngine.InputSystem;
215 ///
216 /// public class ExampleScript : MonoBehaviour
217 /// {
218 /// void Update()
219 /// {
220 /// bool buttonPressed = Gamepad.current.aButton.wasPressedThisFrame;
221 /// bool spaceKeyPressed = Keyboard.current.spaceKey.wasPressedThisFrame;
222 /// }
223 /// }
224 /// </code>
225 /// </example>
226 /// _Note_: The Input System identifies keys by physical layout, not according to the current language mapping of the keyboard. To query the name of the key according to the language mapping, use <see cref="InputControl.displayName"/>.
227 ///
228 /// You can also use this property to read mouse buttons. For example:
229 ///
230 /// <example>
231 /// <code>
232 /// Mouse.current.leftButton.wasPressedThisFrame
233 /// Mouse.current.rightButton.wasPressedThisFrame
234 /// Mouse.current.middleButton.wasPressedThisFrame
235 /// </code>
236 /// </example>
237 ///
238 ///
239 /// </remarks>
240 public bool wasPressedThisFrame
241 {
242 get
243 {
244 // Take the old path if this is the first time calling.
245 if (!needsToCheckFramePress)
246 {
247 var currentlyPressed = IsValueConsideredPressed(value);
248 var pressedLastFrame = IsValueConsideredPressed(ReadValueFromPreviousFrame());
249 BeginTestingForFramePresses(currentlyPressed, pressedLastFrame);
250
251 return device.wasUpdatedThisFrame && currentlyPressed && !pressedLastFrame;
252 }
253
254 #if UNITY_EDITOR
255 if (InputUpdate.s_LatestUpdateType.IsEditorUpdate())
256 return InputUpdate.s_UpdateStepCount == m_UpdateCountLastPressedEditor;
257 #endif
258 return InputUpdate.s_UpdateStepCount == m_UpdateCountLastPressed;
259 }
260 }
261
262 /// <summary>
263 /// Whether the press ended this frame.
264 /// </summary>
265 /// <value>True if the current press of the button ended this frame.</value>
266 /// <remarks>
267 /// _Note_: The Input System identifies keys by physical layout, not according to the current language mapping of the keyboard. To query the name of the key according to the language mapping, use <see cref="InputControl.displayName"/>.
268 /// </remarks>
269 /// <example>
270 /// <para>An example showing the use of this property on a gamepad button and a keyboard key:</para>
271 /// <code>
272 /// using UnityEngine;
273 /// using UnityEngine.InputSystem;
274 ///
275 /// public class ExampleScript : MonoBehaviour
276 /// {
277 /// void Update()
278 /// {
279 /// bool buttonPressed = Gamepad.current.aButton.wasReleasedThisFrame;
280 /// bool spaceKeyPressed = Keyboard.current.spaceKey.wasReleasedThisFrame;
281 /// }
282 /// }
283 /// </code>
284 /// </example>
285 public bool wasReleasedThisFrame
286 {
287 get
288 {
289 // Take the old path if this is the first time calling.
290 if (!needsToCheckFramePress)
291 {
292 var currentlyPressed = IsValueConsideredPressed(value);
293 var pressedLastFrame = IsValueConsideredPressed(ReadValueFromPreviousFrame());
294 BeginTestingForFramePresses(currentlyPressed, pressedLastFrame);
295
296 return device.wasUpdatedThisFrame && !currentlyPressed && pressedLastFrame;
297 }
298
299 #if UNITY_EDITOR
300 if (InputUpdate.s_LatestUpdateType.IsEditorUpdate())
301 return InputUpdate.s_UpdateStepCount == m_UpdateCountLastReleasedEditor;
302 #endif
303 return InputUpdate.s_UpdateStepCount == m_UpdateCountLastReleased;
304 }
305 }
306
307 internal void UpdateWasPressed()
308 {
309 var isNowPressed = IsValueConsideredPressed(value);
310
311 if (m_LastUpdateWasPress != isNowPressed)
312 {
313 if (isNowPressed)
314 m_UpdateCountLastPressed = device.m_CurrentUpdateStepCount;
315 else
316 m_UpdateCountLastReleased = device.m_CurrentUpdateStepCount;
317
318 m_LastUpdateWasPress = isNowPressed;
319 }
320 }
321
322 #if UNITY_EDITOR
323 internal void UpdateWasPressedEditor()
324 {
325 var isNowPressed = IsValueConsideredPressed(value);
326
327 if (m_LastUpdateWasPressEditor != isNowPressed)
328 {
329 if (isNowPressed)
330 m_UpdateCountLastPressedEditor = device.m_CurrentUpdateStepCount;
331 else
332 m_UpdateCountLastReleasedEditor = device.m_CurrentUpdateStepCount;
333
334 m_LastUpdateWasPressEditor = isNowPressed;
335 }
336 }
337
338 #endif // UNITY_EDITOR
339
340 // We make the current global default button press point available as a static so that we don't have to
341 // constantly make the hop from InputSystem.settings -> InputManager.m_Settings -> defaultButtonPressPoint.
342 internal static float s_GlobalDefaultButtonPressPoint;
343 internal static float s_GlobalDefaultButtonReleaseThreshold;
344
345 // We clamp button press points to this value as allowing 0 as the press point causes all buttons
346 // to implicitly be pressed all the time. Not useful.
347 internal const float kMinButtonPressPoint = 0.0001f;
348 }
349}