How I understand Input System actions and bindings
- Action supposed to be client-action, which is abstraction higher then reading inputs. It is game (application) events like “Jump” or “Shoot”.
- Bindings is just reading inputs in all ways you can achieve it.
What I want to achieve: implement cross platform camera Zoom action. For mouse device it is simple as adding Mouse/Scroll/Y binding with Clamp(-1, 1) processor, so after that I can “Zoom” anything depending on -1, 0, 1 values.
With touchscreen device it is much harder. There is some tutorials from devs where they create actions like “Touch”, “Drag”, etc. But as I’ve mentioned in 1st section I don’t want to use actions in that way. So I’ve started to search a way to create suitable binding.
So I’ve found two possibilities
- Implement custom processor which will triggered by any Touch #0/Delta or Touch #0/Position (no matter what indeed, because we just want to trigger processor here). Then in
Process
method access to actual controls throughTouchscreen.current.TryGetChildControl<TouchControl>("/touch0");
(same for /touch1) and then read current position / start position / touch phase. It is working solution but it looks kinda hacky. - Implement custom composite binding. It looks like more straight way to implement such things, because in examples when we want to read values from mouse but only when left button is holding we can use composite with one modifier and set modifier to something like /leftButton. But while implementing composite for pinching I see no proper way to make conditional input (source code for OneModifierComposite.cs consist of some under the hood magic which I don’t understand so I don’t know how it becomes conditional)
How can it be implemented with composites? How to check if particular composite part is performed by now?
What I’ve tried:
- Check by
InputBindingCompositeContext.EvaluateMagnitude(partIndex)
andInputBindingCompositeContext.GetPressTime(partIndex)
but those values never become default after first actuation - Check by
InputBindingCompositeContext.ReadAsButton(partIndex)
but I get errorsInvalidOperationException: Cannot read value of type 'float' from control '/Touchscreen/touch0/position' bound to action
UPD: I’ve solved it by using /touch #0 and /touch #1 + InputBindingCompositeContext.ReadValue<TouchPhase, TouchPhaseComparer>(partIndex)
then I can read touch phase / position / start position / etc from solid touch data
Note: while implementing I was getting invalid TouchPhase values without any visible reason. All because of returning 0 in TouchPhaseComparer as compare result, it seems inner code returns some default values when bindings are equal, so returning just 1 instead of 0 solved all problems.
#if UNITY_EDITOR
using System.Collections.Generic;
using Unity.Mathematics;
using UnityEditor;
using UnityEngine;
using UnityEngine.InputSystem;
using UnityEngine.InputSystem.Layouts;
using UnityEngine.InputSystem.LowLevel;
using UnityEngine.InputSystem.Utilities;
using TouchPhase = UnityEngine.InputSystem.TouchPhase;
[InitializeOnLoad]
#endif
[DisplayStringFormat("{firstTouch}+{secondTouch}")]
public class PinchingComposite : InputBindingComposite<float>
{
[InputControl(layout = "Value")]
public int firstTouch;
[InputControl(layout = "Value")]
public int secondTouch;
public float negativeScale = 1f;
public float positiveScale = 1f;
private struct TouchStateComparer : IComparer<TouchState>
{
public int Compare(TouchState x, TouchState y) => 1;
}
// This method computes the resulting input value of the composite based
// on the input from its part bindings.
public override float ReadValue(ref InputBindingCompositeContext context)
{
var touch_0 = context.ReadValue<TouchState, TouchStateComparer>(firstTouch);
var touch_1 = context.ReadValue<TouchState, TouchStateComparer>(secondTouch);
if (touch_0.phase != TouchPhase.Moved || touch_1.phase != TouchPhase.Moved)
return 0f;
var startDistance = math.distance(touch_0.startPosition, touch_1.startPosition);
var distance = math.distance(touch_0.position, touch_1.position);
var unscaledValue = startDistance / distance - 1f; // startDistance divide by distance to invert value
return unscaledValue * (unscaledValue < 0 ? negativeScale : positiveScale);
}
// This method computes the current actuation of the binding as a whole.
public override float EvaluateMagnitude(ref InputBindingCompositeContext context) => 1f;
static PinchingComposite() => InputSystem.RegisterBindingComposite<PinchingComposite>();
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
static void Init() { } // Trigger static constructor.
}