Hi, welcome.
I theoretically solved runtime rebinding when active input controls are present eg. pedals or thrust lever produce an actuation leading to the PerformInteractiveRebinding() method completing early: see further below.
But 2 bugs:
- when the active input control is ignored in favor of the proper candiate the user actuated, the path delivered with the control the user actuated is still that of the active input control, ie. the new Input System apparently already ignores the active input control after a fix like mentioned in ** Runtime rebinding still impossible with devices that have non-zero default values ** but when I actuate the input control I want to use, that candidates path is that of the ignored input control.
- candidates having magnitude < 0 are listed as first candidates.
Code:
private struct actionIdentifier
{
public InputAction action;
public Text textOnButton;
};
private actionIdentifier aIdWaitingToGetCompleted;
private Dictionary<string, float> alreadyActuatedControls;
private int maxTries = 1500; // 5 seconds at 300 Hz
private int tries;
IEnumerator rescindIgnoredKey()
{
yield return null;
KeyPressListener.rescindIgnoreKey();
}
private void cancelControl(InputActionRebindingExtensions.RebindingOperation rO)
{
// restore the text (which is the "display string" of the InputControl)
aIdWaitingToGetCompleted.textOnButton.text = aIdWaitingToGetCompleted.action.bindings[0].ToDisplayString(InputBinding.DisplayStringOptions.DontOmitDevice & InputBinding.DisplayStringOptions.DontUseShortDisplayNames);
aIdWaitingToGetCompleted.action.Enable();
// enable default functionality of cancel key again
StartCoroutine(rescindIgnoredKey());
}
private void completeControl(InputActionRebindingExtensions.RebindingOperation rO)
{
// sanity check all candidates https://docs.unity3d.com/Packages/com.unity.inputsystem@1.1/api/UnityEngine.InputSystem.InputActionRebindingExtensions.RebindingOperation.html#UnityEngine_InputSystem_InputActionRebindingExtensions_RebindingOperation_candidates
InputControlList<InputControl> candidates = rO.candidates;
int candidateNumber = 0;
foreach (InputControl candidate in candidates)
{
float candidateMagnitude = rO.magnitudes[candidateNumber];
float initialMagnitude;
if (alreadyActuatedControls.TryGetValue(candidate.path, out initialMagnitude))
{
Debug.Log("candidate: " + candidate.path);
Debug.Log("his current magnitude: " + candidateMagnitude);
Debug.Log("his magnitude initially present: " + initialMagnitude);
// if the current magnitude is not significant (here: outside 10 %) different from the initial one, ignore this "actuation"
// note: if the InputControl initially has magnitude 1 (eg. the resting position of a users thrust lever), the user will move the thrust lever towards 0 for detection. It will always actuate when not 0 and thus become a candidate and if nothing else is actuated, eventually get chosen.
if (candidateMagnitude > .9f * initialMagnitude && candidateMagnitude < 1.1f * initialMagnitude)
{
candidateNumber++;
continue;
}
}
// some InputControl s with invalid magnitude get delivered as candidates
if(candidateMagnitude < 0)
{
Debug.Log("-1: " + candidate.path);
candidateNumber++;
continue;
}
break;
}
if(candidateNumber < candidates.Count)
{
Debug.Log("found candidate: " + rO.candidates[candidateNumber].path);
Debug.Log(candidates);
// set the detected InputControl to be used
aIdWaitingToGetCompleted.action.ApplyBindingOverride(rO.candidates[candidateNumber].path);
// save the detected InputControl
PlayerPrefs.SetString(aIdWaitingToGetCompleted.action.name, aIdWaitingToGetCompleted.action.bindings[0].overridePath);
PlayerPrefs.Save();
}
else
{
if(tries++ < maxTries)
{
Debug.Log("trying again for time: " + tries);
candidates.Dispose();
aIdWaitingToGetCompleted.action.PerformInteractiveRebinding()
.WithControlsHavingToMatchPath("<Keyboard>")
.WithControlsHavingToMatchPath("<Gamepad>")
.WithControlsHavingToMatchPath("<Joystick>")
.WithCancelingThrough("<Keyboard>/escape")
.OnCancel(cancelControl)
.OnComplete(completeControl)
.Start();
return;
}
}
candidates.Dispose();
aIdWaitingToGetCompleted.textOnButton.text = aIdWaitingToGetCompleted.action.GetBindingDisplayString(InputBinding.DisplayStringOptions.DontOmitDevice & InputBinding.DisplayStringOptions.DontUseShortDisplayNames);
aIdWaitingToGetCompleted.action.Enable();
// internal function
KeyPressListener.rescindIgnoreKey();
}
private void rebindInput(actionIdentifier aId)
{
// internal function to prevent default behaviour of the key which is going to be used to cancel the rebind detection
KeyPressListener.ignoreKey(KeyCode.Escape);
aId.textOnButton.text = " Actuate an input, ESC to cancel ";
aId.action.Disable();
// the Button for the InputAction in aId was clicked (to configure that InputAction), now store it outside the scope of this function for the asynchronous completion handler be able to access it
aIdWaitingToGetCompleted = aId;
// find all controls currently "actuated" such as pedals and thrust levers, often an R_-axis.
alreadyActuatedControls = new Dictionary<string, float>();
// https://docs.unity3d.com/Packages/com.unity.inputsystem@1.1/api/UnityEngine.InputSystem.InputSystem.html#UnityEngine_InputSystem_InputSystem_FindControls_System_String_
// see Examples there
InputControlList<InputControl> allControls = InputSystem.FindControls("*/*");
foreach(InputControl control in allControls)
{
float magnitude = control.EvaluateMagnitude();
// https://docs.unity3d.com/Packages/com.unity.inputsystem@1.1/api/UnityEngine.InputSystem.InputControl.html#UnityEngine_InputSystem_InputControl_EvaluateMagnitude
if (magnitude > 0f)
{
Debug.Log("already actuated: " + control.path);
alreadyActuatedControls.Add(control.path, magnitude);
}
}
tries = 0;
Debug.Log("number of found connected InputControl s: " + allControls.Count);
Debug.Log("number of already actuated InputControls s: " + alreadyActuatedControls.Count);
// https://docs.unity3d.com/Packages/com.unity.inputsystem@1.1/api/UnityEngine.InputSystem.InputControlList-1.html
// note the remark
allControls.Dispose();
// https://docs.unity3d.com/Packages/com.unity.inputsystem@1.1/api/UnityEngine.InputSystem.InputActionRebindingExtensions.RebindingOperation.html
// https://docs.unity3d.com/Packages/com.unity.inputsystem@1.1/api/UnityEngine.InputSystem.InputActionRebindingExtensions.html#UnityEngine_InputSystem_InputActionRebindingExtensions_PerformInteractiveRebinding_UnityEngine_InputSystem_InputAction_System_Int32_
// note the note about having to dispose the InputActionRebindingExtensions.RebindingOperation returned from inputAction.PerformInteractiveRebinding() appears to be wrong:
// Disposing inside the completion handler causes an Exception of a type similar to activeCodeException, also Unity Editor console does not warn about missing Dispose as it does in other places
aId.action.PerformInteractiveRebinding()
.WithControlsHavingToMatchPath("<Keyboard>")
.WithControlsHavingToMatchPath("<Gamepad>")
.WithControlsHavingToMatchPath("<Joystick>")
.WithCancelingThrough("<Keyboard>/escape")
.OnCancel(cancelControl)
.OnComplete(completeControl)
.Start();
}
// create Buttons to click to configure an InputAction
public bool populateInputView(InputAction[] inputActions)
{
for (int i = 0; i < inputActions.Length; i++)
{
// ... private code omitted
// create memory of the InputAction each Button configures
actionIdentifier aId = new actionIdentifier
{
action = inputActions[i],
textOnButton = GameObject.Find(actionBinderInstance.name + "/Button/Text").GetComponent<Text>()
};
aId.textOnButton.text = aId.action.GetBindingDisplayString(InputBinding.DisplayStringOptions.DontOmitDevice & InputBinding.DisplayStringOptions.DontUseShortDisplayNames);
GameObject.Find(actionBinderInstance.name + "/Label").GetComponent<Text>().text = aId.action.name;
GameObject.Find(actionBinderInstance.name + "/defaultValue").GetComponent<Text>().text = aId.action.bindings[0].path;
// create a closure to the memory so that the click handler can access the associated InputAction
GameObject.Find(actionBinderInstance.name + "/Button").GetComponent<Button>().onClick.AddListener(delegate { rebindInput(aId); });
// ... private code omitted
}
// ... private code omitted
return true;
}
Results:
already actuated: /Mouse/position
number of found connected InputControl s: 148
number of already actuated InputControls s: 1
-1: /Thrustmaster T.16000M/slider
-1: /Thrustmaster T.16000M/rz
-1: /Thrustmaster T.16000M/hat/x
-1: /Thrustmaster T.16000M/hat/y
trying again for time: 1
-1: /Thrustmaster T.16000M/stick
-1: /Thrustmaster T.16000M/stick/x
-1: /Thrustmaster T.16000M/stick/right
found candidate: /Thrustmaster T.16000M/slider
(Stick:/Thrustmaster T.16000M/stick,Axis:/Thrustmaster T.16000M/stick/x,Button:/Thrustmaster T.16000M/stick/right,Axis:/Thrustmaster T.16000M/slider,Axis:/Thrustmaster T.16000M/rz,DpadAxis:/Thrustmaster T.16000M/hat/x,DpadAxis:/Thrustmaster T.16000M/hat/y)
(slider is an Rz control I believe) I actually moved the joysticks x-axis to the right.
already actuated: /Mouse/position
number of found connected InputControl s: 192
number of already actuated InputControls s: 1
-1: /HuiJia USB GamePad/rz
-1: /HuiJia USB GamePad/z
-1: /HuiJia USB GamePad/ry
-1: /HuiJia USB GamePad/rx
-1: /HuiJia USB GamePad/rz
-1: /HuiJia USB GamePad/z
-1: /HuiJia USB GamePad/ry
-1: /HuiJia USB GamePad/rx
-1: /HuiJia USB GamePad/hat/x
-1: /HuiJia USB GamePad/hat/y
-1: /HuiJia USB GamePad/hat/x
-1: /HuiJia USB GamePad/hat/y
trying again for time: 1
-1: /HuiJia USB GamePad/rz
-1: /HuiJia USB GamePad/z
-1: /HuiJia USB GamePad/ry
-1: /HuiJia USB GamePad/rx
-1: /HuiJia USB GamePad/rz
-1: /HuiJia USB GamePad/z
-1: /HuiJia USB GamePad/ry
-1: /HuiJia USB GamePad/rx
-1: /HuiJia USB GamePad/hat/x
-1: /HuiJia USB GamePad/hat/y
-1: /HuiJia USB GamePad/hat/x
-1: /HuiJia USB GamePad/hat/y
trying again for time: 2
-1: /HuiJia USB GamePad/rz
-1: /HuiJia USB GamePad/z
-1: /HuiJia USB GamePad/ry
-1: /HuiJia USB GamePad/rx
-1: /HuiJia USB GamePad/rz
-1: /HuiJia USB GamePad/z
-1: /HuiJia USB GamePad/ry
-1: /HuiJia USB GamePad/rx
-1: /HuiJia USB GamePad/hat/x
-1: /HuiJia USB GamePad/hat/y
-1: /HuiJia USB GamePad/hat/x
-1: /HuiJia USB GamePad/hat/y
trying again for time: 3
-1: /HuiJia USB GamePad/rz
-1: /HuiJia USB GamePad/z
-1: /HuiJia USB GamePad/ry
-1: /HuiJia USB GamePad/rx
-1: /HuiJia USB GamePad/rz
-1: /HuiJia USB GamePad/z
-1: /HuiJia USB GamePad/ry
-1: /HuiJia USB GamePad/rx
-1: /HuiJia USB GamePad/hat/x
-1: /HuiJia USB GamePad/hat/y
-1: /HuiJia USB GamePad/hat/x
-1: /HuiJia USB GamePad/hat/y
trying again for time: 4
-1: /HuiJia USB GamePad/rz
-1: /HuiJia USB GamePad/z
-1: /HuiJia USB GamePad/ry
-1: /HuiJia USB GamePad/rx
-1: /HuiJia USB GamePad/rz
-1: /HuiJia USB GamePad/z
-1: /HuiJia USB GamePad/ry
-1: /HuiJia USB GamePad/rx
-1: /HuiJia USB GamePad/hat/x
-1: /HuiJia USB GamePad/hat/y
-1: /HuiJia USB GamePad/hat/x
-1: /HuiJia USB GamePad/hat/y
trying again for time: 5
-1: /HuiJia USB GamePad/rz
-1: /HuiJia USB GamePad/z
-1: /HuiJia USB GamePad/ry
-1: /HuiJia USB GamePad/rx
-1: /HuiJia USB GamePad/rz
-1: /HuiJia USB GamePad/z
-1: /HuiJia USB GamePad/ry
-1: /HuiJia USB GamePad/rx
-1: /HuiJia USB GamePad/hat/x
-1: /HuiJia USB GamePad/hat/y
-1: /HuiJia USB GamePad/hat/x
-1: /HuiJia USB GamePad/hat/y
trying again for time: 6
-1: /HuiJia USB GamePad/rz
-1: /HuiJia USB GamePad/z
-1: /HuiJia USB GamePad/ry
-1: /HuiJia USB GamePad/rx
-1: /HuiJia USB GamePad/rz
-1: /HuiJia USB GamePad/z
-1: /HuiJia USB GamePad/ry
-1: /HuiJia USB GamePad/rx
-1: /HuiJia USB GamePad/hat/x
-1: /HuiJia USB GamePad/hat/y
-1: /HuiJia USB GamePad/hat/x
-1: /HuiJia USB GamePad/hat/y
trying again for time: 7
-1: /HuiJia USB GamePad/rz
-1: /HuiJia USB GamePad/z
-1: /HuiJia USB GamePad/ry
-1: /HuiJia USB GamePad/rx
-1: /HuiJia USB GamePad/rz
-1: /HuiJia USB GamePad/z
-1: /HuiJia USB GamePad/ry
-1: /HuiJia USB GamePad/rx
-1: /HuiJia USB GamePad/hat/x
-1: /HuiJia USB GamePad/hat/y
-1: /HuiJia USB GamePad/hat/x
-1: /HuiJia USB GamePad/hat/y
trying again for time: 8
-1: /HuiJia USB GamePad/rz
-1: /HuiJia USB GamePad/z
-1: /HuiJia USB GamePad/ry
-1: /HuiJia USB GamePad/rx
-1: /HuiJia USB GamePad/rz
-1: /HuiJia USB GamePad/z
-1: /HuiJia USB GamePad/ry
-1: /HuiJia USB GamePad/rx
-1: /HuiJia USB GamePad/hat/x
-1: /HuiJia USB GamePad/hat/y
-1: /HuiJia USB GamePad/hat/x
-1: /HuiJia USB GamePad/hat/y
trying again for time: 9
-1: /HuiJia USB GamePad/rz
-1: /HuiJia USB GamePad/z
-1: /HuiJia USB GamePad/ry
-1: /HuiJia USB GamePad/rx
-1: /HuiJia USB GamePad/rz
-1: /HuiJia USB GamePad/z
-1: /HuiJia USB GamePad/ry
-1: /HuiJia USB GamePad/rx
-1: /HuiJia USB GamePad/hat/x
-1: /HuiJia USB GamePad/hat/y
-1: /HuiJia USB GamePad/hat/x
-1: /HuiJia USB GamePad/hat/y
trying again for time: 10
-1: /HuiJia USB GamePad/rz
-1: /HuiJia USB GamePad/z
-1: /HuiJia USB GamePad/ry
-1: /HuiJia USB GamePad/rx
-1: /HuiJia USB GamePad/rz
-1: /HuiJia USB GamePad/z
-1: /HuiJia USB GamePad/ry
-1: /HuiJia USB GamePad/rx
-1: /HuiJia USB GamePad/hat/x
-1: /HuiJia USB GamePad/hat/y
-1: /HuiJia USB GamePad/hat/x
-1: /HuiJia USB GamePad/hat/y
trying again for time: 11
-1: /HuiJia USB GamePad/rz
-1: /HuiJia USB GamePad/z
-1: /HuiJia USB GamePad/ry
-1: /HuiJia USB GamePad/rx
-1: /HuiJia USB GamePad/rz
-1: /HuiJia USB GamePad/z
-1: /HuiJia USB GamePad/ry
-1: /HuiJia USB GamePad/rx
-1: /Thrustmaster T.16000M/slider
-1: /Thrustmaster T.16000M/rz
-1: /HuiJia USB GamePad/hat/x
-1: /HuiJia USB GamePad/hat/y
-1: /HuiJia USB GamePad/hat/x
-1: /HuiJia USB GamePad/hat/y
-1: /Thrustmaster T.16000M/hat/x
-1: /Thrustmaster T.16000M/hat/y
trying again for time: 12
-1: /Thrustmaster T.16000M/stick
-1: /Thrustmaster T.16000M/stick/x
-1: /Thrustmaster T.16000M/stick/y
-1: /Thrustmaster T.16000M/stick/right
-1: /Thrustmaster T.16000M/stick/down
-1: /HuiJia USB GamePad/rz
-1: /HuiJia USB GamePad/z
-1: /HuiJia USB GamePad/ry
-1: /HuiJia USB GamePad/rx
-1: /Thrustmaster T.16000M/slider
-1: /Thrustmaster T.16000M/rz
found candidate: /HuiJia USB GamePad/rz
(Stick:/Thrustmaster T.16000M/stick,Axis:/Thrustmaster T.16000M/stick/x,Axis:/Thrustmaster T.16000M/stick/y,Button:/Thrustmaster T.16000M/stick/right,Button:/Thrustmaster T.16000M/stick/down,Axis:/HuiJia USB GamePad/rz,Axis:/HuiJia USB GamePad/z,Axis:/HuiJia USB GamePad/ry,Axis:/HuiJia USB GamePad/rx,Axis:/Thrustmaster T.16000M/slider,Axis:/Thrustmaster T.16000M/rz,Axis:/HuiJia USB GamePad/rz,Axis:/HuiJia USB GamePad/z,Axis:/HuiJia USB GamePad/ry,Axis:/HuiJia USB GamePad/rx,DpadAxis:/HuiJia USB GamePad/hat/x,DpadAxis:/HuiJia USB GamePad/hat/y,DpadAxis:/Thrustmaster T.16000M/hat/x,DpadAxis:/Thrustmaster T.16000M/hat/y,DpadAxis:/HuiJia USB GamePad/hat/x,DpadAxis:/HuiJia USB GamePad/hat/y)
“HuiJia USB GamePad” is a GameCube Controller adapter which has 2 ports and 1 is connected with a wired original GameCube controller. Here I too moved the Thrustmaster joysticks x-axis to the right.
FYI @VideoVillain , perhaps you can benefit from this.
FYI @rz_0lento , perhaps you can contribute your solution