File size: 14,519 Bytes
18a519f | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 | using System;
using UnityEngine.InputSystem.LowLevel;
namespace UnityEngine.InputSystem
{
/// <summary>
/// Information passed to <see cref="IInputInteraction">interactions</see>
/// when their associated controls trigger.
/// </summary>
/// <seealso cref="IInputInteraction.Process"/>
public struct InputInteractionContext
{
/// <summary>
/// The action associated with the binding.
/// </summary>
/// <remarks>
/// If the binding is not associated with an action, this is <c>null</c>.
/// </remarks>
/// <seealso cref="InputBinding.action"/>
public InputAction action => m_State.GetActionOrNull(ref m_TriggerState);
/// <summary>
/// The bound control that changed its state to trigger the binding associated
/// with the interaction.
/// </summary>
/// <remarks>
/// In case the binding associated with the interaction is a composite, this is
/// one of the controls that are part of the composite.
/// </remarks>
/// <seealso cref="InputBinding.path"/>
public InputControl control => m_State.GetControl(ref m_TriggerState);
/// <summary>
/// The phase the interaction is currently in.
/// </summary>
/// <remarks>
/// Each interaction on a binding has its own phase independent of the action the binding is applied to.
/// If an interaction gets to "drive" an action at a particular point in time, its phase will determine
/// the phase of the action.
/// </remarks>
/// <seealso cref="InputAction.phase"/>
/// <seealso cref="Started"/>
/// <seealso cref="Waiting"/>
/// <seealso cref="Performed"/>
/// <seealso cref="Canceled"/>
public InputActionPhase phase => m_TriggerState.phase;
/// <summary>
/// Time stamp of the input event that caused <see cref="control"/> to trigger a change in the
/// state of <see cref="action"/>.
/// </summary>
/// <seealso cref="InputEvent.time"/>
public double time => m_TriggerState.time;
/// <summary>
/// Timestamp of the <see cref="InputEvent"/> that caused the interaction to transition
/// to <see cref="InputActionPhase.Started"/>.
/// </summary>
/// <seealso cref="InputEvent.time"/>
public double startTime => m_TriggerState.startTime;
/// <summary>
/// Whether the interaction's <see cref="IInputInteraction.Process"/> method has been called because
/// a timer set by <see cref="SetTimeout"/> has expired.
/// </summary>
/// <seealso cref="SetTimeout"/>
public bool timerHasExpired
{
get => (m_Flags & Flags.TimerHasExpired) != 0;
internal set
{
if (value)
m_Flags |= Flags.TimerHasExpired;
else
m_Flags &= ~Flags.TimerHasExpired;
}
}
/// <summary>
/// True if the interaction is waiting for input
/// </summary>
/// <remarks>
/// By default, an interaction will return this this phase after every time it has been performed
/// (<see cref="InputActionPhase.Performed"/>). This can be changed by using <see cref="PerformedAndStayStarted"/>
/// or <see cref="PerformedAndStayPerformed"/>.
/// </remarks>
/// <seealso cref="InputActionPhase.Waiting"/>
public bool isWaiting => phase == InputActionPhase.Waiting;
/// <summary>
/// True if the interaction has been started.
/// </summary>
/// <seealso cref="InputActionPhase.Started"/>
/// <seealso cref="Started"/>
public bool isStarted => phase == InputActionPhase.Started;
/// <summary>
/// Compute the current level of control actuation.
/// </summary>
/// <returns>The current level of control actuation (usually [0..1]) or -1 if the control is actuated
/// but does not support computing magnitudes.</returns>
/// <seealso cref="ControlIsActuated"/>
/// <seealso cref="InputControl.EvaluateMagnitude()"/>
public float ComputeMagnitude()
{
return m_TriggerState.magnitude;
}
/// <summary>
/// Return true if the control that triggered the interaction has been actuated beyond the given threshold.
/// </summary>
/// <param name="threshold">Threshold that must be reached for the control to be considered actuated. If this is zero,
/// the threshold must be exceeded. If it is any positive value, the value must be at least matched.</param>
/// <returns>True if the trigger control is actuated.</returns>
/// <seealso cref="InputControlExtensions.IsActuated"/>
/// <seealso cref="ComputeMagnitude"/>
public bool ControlIsActuated(float threshold = 0)
{
return InputActionState.IsActuated(ref m_TriggerState, threshold);
}
/// <summary>
/// Mark the interaction has having begun.
/// </summary>
/// <remarks>
/// Note that this affects the current interaction only. There may be multiple interactions on a binding
/// and arbitrary many interactions may concurrently be in started state. However, only one interaction
/// (usually the one that starts first) is allowed to drive the action's state as a whole. If an interaction
/// that is currently driving an action is canceled, however, the next interaction in the list that has
/// been started will take over and continue driving the action.
///
/// <example>
/// <code>
/// public class MyInteraction : IInputInteraction<float>
/// {
/// public void Process(ref IInputInteractionContext context)
/// {
/// if (context.isWaiting && context.ControlIsActuated())
/// {
/// // We've waited for input and got it. Start the interaction.
/// context.Started();
/// }
/// else if (context.isStarted && !context.ControlIsActuated())
/// {
/// // Interaction has been completed.
/// context.Performed();
/// }
/// }
///
/// public void Reset()
/// {
/// // No reset code needed. We're not keeping any state locally in the interaction.
/// }
/// }
/// </code>
/// </example>
/// </remarks>
public void Started()
{
m_TriggerState.startTime = time;
m_State.ChangePhaseOfInteraction(InputActionPhase.Started, ref m_TriggerState);
}
public void Performed()
{
if (m_TriggerState.phase == InputActionPhase.Waiting)
m_TriggerState.startTime = time;
m_State.ChangePhaseOfInteraction(InputActionPhase.Performed, ref m_TriggerState);
}
public void PerformedAndStayStarted()
{
if (m_TriggerState.phase == InputActionPhase.Waiting)
m_TriggerState.startTime = time;
m_State.ChangePhaseOfInteraction(InputActionPhase.Performed, ref m_TriggerState,
phaseAfterPerformed: InputActionPhase.Started);
}
public void PerformedAndStayPerformed()
{
if (m_TriggerState.phase == InputActionPhase.Waiting)
m_TriggerState.startTime = time;
m_State.ChangePhaseOfInteraction(InputActionPhase.Performed, ref m_TriggerState,
phaseAfterPerformed: InputActionPhase.Performed);
}
public void Canceled()
{
if (m_TriggerState.phase != InputActionPhase.Canceled)
m_State.ChangePhaseOfInteraction(InputActionPhase.Canceled, ref m_TriggerState);
}
/// <summary>
/// Put the interaction back into <see cref="InputActionPhase.Waiting"/> state.
/// </summary>
/// <seealso cref="InputAction.phase"/>
/// <seealso cref="InputActionPhase"/>
/// <seealso cref="Started"/>
/// <seealso cref="Performed"/>
/// <seealso cref="Canceled"/>
public void Waiting()
{
if (m_TriggerState.phase != InputActionPhase.Waiting)
m_State.ChangePhaseOfInteraction(InputActionPhase.Waiting, ref m_TriggerState);
}
/// <summary>
/// Start a timeout that triggers within <paramref name="seconds"/>.
/// </summary>
/// <param name="seconds">Number of seconds before the timeout is triggered.</param>
/// <remarks>
/// An interaction might wait a set amount of time for something to happen and then
/// do something depending on whether it did or did not happen. By calling this method,
/// a timeout is installed such that in the input update that the timer expires in, the
/// interaction's <see cref="IInputInteraction.Process"/> method is called with <see cref="timerHasExpired"/>
/// being true.
///
/// Changing the phase of the interaction while a timeout is running will implicitly cancel
/// the timeout.
///
/// <example>
/// <code>
/// // Let's say we're writing a Process() method for an interaction that,
/// // after a control has been actuated, waits for 1 second for it to be
/// // released again. If that happens, the interaction performs. If not,
/// // it cancels.
/// public void Process(ref InputInteractionContext context)
/// {
/// // timerHasExpired will be true if we get called when our timeout
/// // has expired.
/// if (context.timerHasExpired)
/// {
/// // The user did not release the control quickly enough.
/// // Our interaction is not successful, so cancel.
/// context.Canceled();
/// return;
/// }
///
/// if (context.ControlIsActuated())
/// {
/// if (!context.isStarted)
/// {
/// // The control has been actuated. We want to give the user a max
/// // of 1 second to release it. So we start the interaction now and then
/// // set the timeout.
/// context.Started();
/// context.SetTimeout(1);
/// }
/// }
/// else
/// {
/// // Control has been released. If we're currently waiting for a release,
/// // it has come in time before out timeout expired. In other words, the
/// // interaction has been successfully performed. We call Performed()
/// // which implicitly removes our ongoing timeout.
/// if (context.isStarted)
/// context.Performed();
/// }
/// }
/// </code>
/// </example>
/// </remarks>
/// <seealso cref="timerHasExpired"/>
public void SetTimeout(float seconds)
{
m_State.StartTimeout(seconds, ref m_TriggerState);
}
/// <summary>
/// Override the default timeout value used by <see cref="InputAction.GetTimeoutCompletionPercentage"/>.
/// </summary>
/// <param name="seconds">Amount of total successive timeouts TODO</param>
/// <exception cref="ArgumentException"></exception>
/// <remarks>
/// By default, timeout completion will be entirely determine by the timeout that is currently
/// running, if any. However, some interactions (such as <see cref="Interactions.MultiTapInteraction"/>)
/// will have to run multiple timeouts in succession. Thus, completion of a single timeout is not
/// the same as completion of the interaction.
///
/// You can use this method to account for this.
///
/// Whenever a timeout completes, the timeout duration will automatically be accumulated towards
/// the total timeout completion time.
///
/// <example>
/// <code>
/// // Let's say we're starting our first timeout and we know that we will run three timeouts
/// // in succession of 2 seconds each. By calling SetTotalTimeoutCompletionTime(), we can account for this.
/// SetTotalTimeoutCompletionTime(3 * 2);
///
/// // Start the first timeout. When this timeout expires, it will automatically
/// // count one second towards the total timeout completion time.
/// SetTimeout(2);
/// </code>
/// </example>
/// </remarks>
/// <seealso cref="InputAction.GetTimeoutCompletionPercentage"/>
public void SetTotalTimeoutCompletionTime(float seconds)
{
if (seconds <= 0)
throw new ArgumentException("Seconds must be a positive value", nameof(seconds));
m_State.SetTotalTimeoutCompletionTime(seconds, ref m_TriggerState);
}
/// <summary>
/// Read the value of the binding that triggered processing of the interaction.
/// </summary>
/// <typeparam name="TValue">Type of value to read from the binding. Must match the value type of the control
/// or composite in effect for the binding.</typeparam>
/// <returns>Value read from the binding.</returns>
public TValue ReadValue<TValue>()
where TValue : struct
{
return m_State.ReadValue<TValue>(m_TriggerState.bindingIndex, m_TriggerState.controlIndex);
}
internal InputActionState m_State;
internal Flags m_Flags;
internal InputActionState.TriggerState m_TriggerState;
internal int mapIndex => m_TriggerState.mapIndex;
internal int controlIndex => m_TriggerState.controlIndex;
internal int bindingIndex => m_TriggerState.bindingIndex;
internal int interactionIndex => m_TriggerState.interactionIndex;
[Flags]
internal enum Flags
{
TimerHasExpired = 1 << 1
}
}
}
|