using System.ComponentModel; using UnityEngine.InputSystem.Layouts; using UnityEngine.InputSystem.Processors; using UnityEngine.InputSystem.Utilities; #if UNITY_EDITOR using System; using UnityEditor; using UnityEngine.InputSystem.Editor; using UnityEngine.UIElements; #endif namespace UnityEngine.InputSystem.Composites { /// /// A single axis value computed from one axis that pulls in the direction () and one /// axis that pulls in the direction (). /// /// /// The limits of the axis are determined by and . /// By default, they are set to [-1..1]. The values can be set as parameters. /// /// /// /// var action = new InputAction(); /// action.AddCompositeBinding("Axis(minValue=0,maxValue=2)") /// .With("Negative", "<Keyboard>/a") /// .With("Positive", "<Keyboard>/d"); /// /// /// /// If both axes are actuated at the same time, the behavior depends on . /// By default, neither side will win () and the result /// will be 0 (or, more precisely, the midpoint between and ). /// This can be customized to make the positive side win () /// or the negative one (). /// /// This is useful, for example, in a driving game where break should cancel out accelerate. /// By binding to the break control(s) and to the /// acceleration control(s), and setting to , /// if the break button is pressed, it will always cause the acceleration button to be ignored. /// /// The actual absolute values of and are used /// to scale and respectively. So if, for example, /// is bound to and the trigger is at a value of 0.5, then the resulting /// value is maxValue * 0.5 (the actual formula is midPoint + (maxValue - midPoint) * positive). /// [DisplayStringFormat("{negative}/{positive}")] [DisplayName("Positive/Negative Binding")] public class AxisComposite : InputBindingComposite { /// /// Binding for the axis input that controls the negative [..0] direction of the /// combined axis. /// /// /// This property is automatically assigned by the input system. /// // ReSharper disable once MemberCanBePrivate.Global // ReSharper disable once FieldCanBeMadeReadOnly.Global [InputControl(layout = "Axis")] public int negative = 0; /// /// Binding for the axis input that controls the positive [0..] direction of the /// combined axis. /// /// /// This property is automatically assigned by the input system. /// // ReSharper disable once MemberCanBePrivate.Global // ReSharper disable once FieldCanBeMadeReadOnly.Global [InputControl(layout = "Axis")] public int positive = 0; /// /// The lower bound that the axis is limited to. -1 by default. /// /// /// This value corresponds to the full actuation of the control(s) bound to . /// /// /// /// var action = new InputAction(); /// action.AddCompositeBinding("Axis(minValue=0,maxValue=2)") /// .With("Negative", "<Keyboard>/a") /// .With("Positive", "<Keyboard>/d"); /// /// /// /// /// // ReSharper disable once MemberCanBePrivate.Global // ReSharper disable once FieldCanBeMadeReadOnly.Global [Tooltip("Value to return when the negative side is fully actuated.")] public float minValue = -1; /// /// The upper bound that the axis is limited to. 1 by default. /// /// /// This value corresponds to the full actuation of the control(s) bound to . /// /// /// /// var action = new InputAction(); /// action.AddCompositeBinding("Axis(minValue=0,maxValue=2)") /// .With("Negative", "<Keyboard>/a") /// .With("Positive", "<Keyboard>/d"); /// /// /// /// /// // ReSharper disable once MemberCanBePrivate.Global // ReSharper disable once FieldCanBeMadeReadOnly.Global [Tooltip("Value to return when the positive side is fully actuated.")] public float maxValue = 1; /// /// If both the and button are actuated, this /// determines which value is returned from the composite. /// [Tooltip("If both the positive and negative side are actuated, decides what value to return. 'Neither' (default) means that " + "the resulting value is the midpoint between min and max. 'Positive' means that max will be returned. 'Negative' means that " + "min will be returned.")] public WhichSideWins whichSideWins = WhichSideWins.Neither; /// /// The value that is returned if the composite is in a neutral position, that is, if /// neither nor are actuated or if /// is set to and /// both and are actuated. /// public float midPoint => (maxValue + minValue) / 2; ////TODO: add parameters to control ramp up&down /// public override float ReadValue(ref InputBindingCompositeContext context) { var negativeValue = Mathf.Abs(context.ReadValue(negative)); var positiveValue = Mathf.Abs(context.ReadValue(positive)); var negativeIsActuated = negativeValue > Mathf.Epsilon; var positiveIsActuated = positiveValue > Mathf.Epsilon; if (negativeIsActuated == positiveIsActuated) { switch (whichSideWins) { case WhichSideWins.Negative: positiveIsActuated = false; break; case WhichSideWins.Positive: negativeIsActuated = false; break; case WhichSideWins.Neither: return midPoint; } } var mid = midPoint; if (negativeIsActuated) return mid - (mid - minValue) * negativeValue; return mid + (maxValue - mid) * positiveValue; } /// public override float EvaluateMagnitude(ref InputBindingCompositeContext context) { var value = ReadValue(ref context); if (value < midPoint) { value = Mathf.Abs(value - midPoint); return NormalizeProcessor.Normalize(value, 0, Mathf.Abs(minValue), 0); } value = Mathf.Abs(value - midPoint); return NormalizeProcessor.Normalize(value, 0, Mathf.Abs(maxValue), 0); } /// /// What happens to the value of an if both /// and are actuated at the same time. /// [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1717:OnlyFlagsEnumsShouldHavePluralNames", Justification = "False positive: `Wins` is not a plural form.")] public enum WhichSideWins { /// /// If both and are actuated, the sides cancel /// each other out and the result is 0. /// Neither = 0, /// /// If both and are actuated, the value of /// wins and is ignored. /// Positive = 1, /// /// If both and are actuated, the value of /// wins and is ignored. /// Negative = 2, } } #if UNITY_EDITOR internal class AxisCompositeEditor : InputParameterEditor { private GUIContent m_WhichAxisWinsLabel = new GUIContent("Which Side Wins", "Determine which axis 'wins' if both are actuated at the same time. " + "If 'Neither' is selected, the result is 0 (or, more precisely, " + "the midpoint between minValue and maxValue)."); public override void OnGUI() { #if UNITY_INPUT_SYSTEM_PROJECT_WIDE_ACTIONS if (!InputSystem.settings.IsFeatureEnabled(InputFeatureNames.kUseIMGUIEditorForAssets)) return; #endif target.whichSideWins = (AxisComposite.WhichSideWins)EditorGUILayout.EnumPopup(m_WhichAxisWinsLabel, target.whichSideWins); } #if UNITY_INPUT_SYSTEM_PROJECT_WIDE_ACTIONS public override void OnDrawVisualElements(VisualElement root, Action onChangedCallback) { var modeField = new EnumField(m_WhichAxisWinsLabel.text, target.whichSideWins) { tooltip = m_WhichAxisWinsLabel.tooltip }; modeField.RegisterValueChangedCallback(evt => { target.whichSideWins = (AxisComposite.WhichSideWins)evt.newValue; onChangedCallback(); }); root.Add(modeField); } #endif } #endif }