Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
NEW: 'Stick' interaction.
  • Loading branch information
Rene Damm committed Jul 27, 2018
1 parent 5c94619 commit 430b36e
Show file tree
Hide file tree
Showing 6 changed files with 149 additions and 9 deletions.
Expand Up @@ -558,8 +558,14 @@ private void StopTimeout(int mapIndex, int controlIndex, int bindingIndex, int i
/// Perform a phase change on the given interaction. Only visible to observers
/// if it happens to change the phase of the action, too.
/// </summary>
/// <param name="newPhase"></param>
/// <param name="trigger"></param>
/// <param name="newPhase">New phase to transition the interaction to.</param>
/// <param name="trigger">Information about the binding and control that triggered the phase change.</param>
/// <param name="remainStartedAfterPerformed">If true, then instead of going back to <see cref="InputActionPhase.Waiting"/>
/// after transitioning to <see cref="InputActionPhase.Performed"/>, the interaction (and thus potentially the action)
/// will remain in <see cref="InputActionPhase.Started"/> phase. This is useful for interactions that use
/// <see cref="InputActionPhase.Started"/> to signal the start of a continuous interaction, then use <see
/// cref="InputActionPhase.Performed"/> during the interaction and then <see cref="InputActionPhase.Cancelled"/> when
/// the interaction stops.</param>
/// <remarks>
/// Multiple interactions on the same binding can be started concurrently but the
/// first interaction that starts will get to drive an action until it either cancels
Expand All @@ -574,7 +580,7 @@ private void StopTimeout(int mapIndex, int controlIndex, int bindingIndex, int i
/// it comes first; then the TapInteraction cancels because the button is held for too
/// long and the SlowTapInteraction will get to drive the action next).
/// </remarks>
internal void ChangePhaseOfInteraction(InputActionPhase newPhase, ref TriggerState trigger)
internal void ChangePhaseOfInteraction(InputActionPhase newPhase, ref TriggerState trigger, bool remainStartedAfterPerformed = false)
{
var interactionIndex = trigger.interactionIndex;
var bindingIndex = trigger.bindingIndex;
Expand Down Expand Up @@ -632,9 +638,13 @@ internal void ChangePhaseOfInteraction(InputActionPhase newPhase, ref TriggerSta
}
else if (actionStates[actionIndex].interactionIndex == trigger.interactionIndex)
{
var phaseAfterPerformedOrCancelled = InputActionPhase.Waiting;
if (newPhase == InputActionPhase.Performed && remainStartedAfterPerformed)
phaseAfterPerformedOrCancelled = InputActionPhase.Started;

// Any other phase change goes to action if we're the interaction driving
// the current phase.
ChangePhaseOfAction(newPhase, ref trigger);
ChangePhaseOfAction(newPhase, ref trigger, phaseAfterPerformedOrCancelled);

// We're the interaction driving the action and we performed the action,
// so reset any other interaction to waiting state.
Expand All @@ -653,13 +663,23 @@ internal void ChangePhaseOfInteraction(InputActionPhase newPhase, ref TriggerSta
}

// If the interaction performed or cancelled, go back to waiting.
if (newPhase == InputActionPhase.Performed || newPhase == InputActionPhase.Cancelled)
// Exception: if it was performed and we're to remain in started state, set the interaction
// to started. Note that for that phase transition, there are no callbacks being
// triggered (i.e. we don't call 'started' every time after 'performed').
if (newPhase == InputActionPhase.Performed && remainStartedAfterPerformed)
{
interactionStates[interactionIndex].phase = InputActionPhase.Started;
}
else if (newPhase == InputActionPhase.Performed || newPhase == InputActionPhase.Cancelled)
{
ResetInteraction(trigger.mapIndex, trigger.bindingIndex, trigger.interactionIndex);
}
////TODO: reset entire chain
}

// Perform a phase change on the action. Visible to observers.
internal void ChangePhaseOfAction(InputActionPhase newPhase, ref TriggerState trigger)
internal void ChangePhaseOfAction(InputActionPhase newPhase, ref TriggerState trigger,
InputActionPhase phaseAfterPerformedOrCancelled = InputActionPhase.Waiting)
{
Debug.Assert(trigger.mapIndex >= 0 && trigger.mapIndex < totalMapCount);
Debug.Assert(trigger.controlIndex >= 0 && trigger.controlIndex < totalControlCount);
Expand Down Expand Up @@ -688,12 +708,12 @@ internal void ChangePhaseOfAction(InputActionPhase newPhase, ref TriggerState tr

case InputActionPhase.Performed:
CallActionListeners(map, ref action.m_OnPerformed, ref trigger);
actionStates[actionIndex].phase = InputActionPhase.Waiting; // Go back to waiting after performing action.
actionStates[actionIndex].phase = phaseAfterPerformedOrCancelled;
break;

case InputActionPhase.Cancelled:
CallActionListeners(map, ref action.m_OnCancelled, ref trigger);
actionStates[actionIndex].phase = InputActionPhase.Waiting; // Go back to waiting after cancelling action.
actionStates[actionIndex].phase = phaseAfterPerformedOrCancelled;
break;
}
}
Expand Down Expand Up @@ -751,6 +771,7 @@ private void CallActionListeners(InputActionMap actionMap, ref InlinedArray<Inpu
Profiler.EndSample();
}

////REVIEW: does this really add value? should we just allow whatever transitions?
private void ThrowIfPhaseTransitionIsInvalid(InputActionPhase currentPhase, InputActionPhase newPhase, ref TriggerState trigger)
{
// Can only go to Started from Waiting.
Expand Down
Expand Up @@ -92,6 +92,11 @@ public void Performed()
m_State.ChangePhaseOfInteraction(InputActionPhase.Performed, ref m_TriggerState);
}

public void PerformedAndStayStarted()
{
m_State.ChangePhaseOfInteraction(InputActionPhase.Performed, ref m_TriggerState, remainStartedAfterPerformed: true);
}

public void Cancelled()
{
m_State.ChangePhaseOfInteraction(InputActionPhase.Cancelled, ref m_TriggerState);
Expand Down
@@ -0,0 +1,38 @@
////REVIEW: this should not have to cast to InputControl<Vector2>; interactions should have the same
//// ReadValue<TValue>() API available to them that action callbacks do; this way this interaction
//// here, for example, will also work with composites

namespace UnityEngine.Experimental.Input.Interactions
{
/// <summary>
/// Starts when stick leaves deadzone, performs while stick moves outside
/// of deadzone, cancels when stick goes back into deadzone.
/// </summary>
public class StickInteraction : IInputInteraction
{
public void Process(ref InputInteractionContext context)
{
var stick = context.control as InputControl<Vector2>;
if (stick == null)
return;

var value = stick.ReadValue();
if (value.x > 0 && value.y > 0)
{
if (!context.isStarted)
context.Started();
else
context.PerformedAndStayStarted();
}
else if (context.isStarted)
{
// Went back to below deadzone.
context.Cancelled();
}
}

public void Reset()
{
}
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion Packages/com.unity.inputsystem/InputSystem/InputManager.cs
Expand Up @@ -1164,8 +1164,9 @@ internal void InitializeData()
interactions.AddTypeRegistration("Hold", typeof(HoldInteraction));
interactions.AddTypeRegistration("Tap", typeof(TapInteraction));
interactions.AddTypeRegistration("SlowTap", typeof(SlowTapInteraction));
interactions.AddTypeRegistration("Stick", typeof(StickInteraction));
//interactions.AddTypeRegistration("DoubleTap", typeof(DoubleTapInteraction));
interactions.AddTypeRegistration("Swipe", typeof(SwipeInteraction));
//interactions.AddTypeRegistration("Swipe", typeof(SwipeInteraction));

// Register composites.
composites.AddTypeRegistration("Axis", typeof(AxisComposite));
Expand Down
Expand Up @@ -506,6 +506,70 @@ public void Actions_CanPerformPressAndReleaseInteraction()
Assert.That(performedReceivedCalls, Is.EqualTo(1));
}

[Test]
[Category("Actions")]
public void Actions_CanPerformStickInteraction()
{
var gamepad = InputSystem.AddDevice<Gamepad>();

var performedReceivedCalls = 0;
var startedReceivedCalls = 0;
var cancelledReceivedCalls = 0;

var action = new InputAction(binding: "/<Gamepad>/leftStick", interactions: "stick");
action.performed +=
ctx => ++ performedReceivedCalls;
action.started +=
ctx => ++ startedReceivedCalls;
action.cancelled +=
ctx => ++ cancelledReceivedCalls;
action.Enable();

// Go out of deadzone.
InputSystem.QueueStateEvent(gamepad, new GamepadState {leftStick = new Vector2(0.345f, 0.456f)});
InputSystem.Update();

Assert.That(startedReceivedCalls, Is.EqualTo(1));
Assert.That(performedReceivedCalls, Is.Zero);
Assert.That(cancelledReceivedCalls, Is.Zero);

startedReceivedCalls = 0;
performedReceivedCalls = 0;
cancelledReceivedCalls = 0;

// Move around.
InputSystem.QueueStateEvent(gamepad, new GamepadState {leftStick = new Vector2(0.456f, 0.567f)});
InputSystem.Update();

Assert.That(startedReceivedCalls, Is.EqualTo(0));
Assert.That(performedReceivedCalls, Is.EqualTo(1));
Assert.That(cancelledReceivedCalls, Is.EqualTo(0));

startedReceivedCalls = 0;
performedReceivedCalls = 0;
cancelledReceivedCalls = 0;

// Go back into deadzone.
InputSystem.QueueStateEvent(gamepad, new GamepadState {leftStick = new Vector2(0.011f, 0.011f)});
InputSystem.Update();

Assert.That(startedReceivedCalls, Is.EqualTo(0));
Assert.That(performedReceivedCalls, Is.EqualTo(0));
Assert.That(cancelledReceivedCalls, Is.EqualTo(1));

startedReceivedCalls = 0;
performedReceivedCalls = 0;
cancelledReceivedCalls = 0;

// Make sure nothing happens if we move around in deadzone.
InputSystem.QueueStateEvent(gamepad, new GamepadState {leftStick = new Vector2(0.012f, 0.012f)});
InputSystem.Update();

Assert.That(startedReceivedCalls, Is.EqualTo(0));
Assert.That(performedReceivedCalls, Is.EqualTo(0));
Assert.That(cancelledReceivedCalls, Is.EqualTo(0));
}

[Test]
[Category("Actions")]
public void Actions_CanAddActionsToMap()
Expand Down

0 comments on commit 430b36e

Please sign in to comment.