A game about forced loneliness, made by TACStudios
1using System;
2using System.ComponentModel;
3using UnityEngine.InputSystem.Controls;
4using UnityEngine.InputSystem.Layouts;
5using UnityEngine.InputSystem.Utilities;
6
7#if UNITY_EDITOR
8using UnityEditor;
9using UnityEngine.InputSystem.Editor;
10using UnityEngine.UIElements;
11#endif
12
13////TODO: add support for ramp up/down
14
15namespace UnityEngine.InputSystem.Composites
16{
17 /// <summary>
18 /// A 2D planar motion vector computed from an up+down button pair and a left+right
19 /// button pair.
20 /// </summary>
21 /// <remarks>
22 /// This composite allows to grab arbitrary buttons from a device and arrange them in
23 /// a D-Pad like configuration. Based on button presses, the composite will return a
24 /// normalized direction vector (normalization can be turned off via <see cref="mode"/>).
25 ///
26 /// Opposing motions cancel each other out. This means that if, for example, both the left
27 /// and right horizontal button are pressed, the resulting horizontal movement value will
28 /// be zero.
29 ///
30 /// <example>
31 /// <code>
32 /// // Set up WASD style keyboard controls.
33 /// action.AddCompositeBinding("2DVector")
34 /// .With("Up", "<Keyboard>/w")
35 /// .With("Left", "<Keyboard>/a")
36 /// .With("Down", "<Keyboard>/s")
37 /// .With("Right", "<Keyboard>/d");
38 /// </code>
39 /// </example>
40 /// </remarks>
41 /// <seealso cref="Vector3Composite"/>
42 [DisplayStringFormat("{up}/{left}/{down}/{right}")] // This results in WASD.
43 [DisplayName("Up/Down/Left/Right Composite")]
44 public class Vector2Composite : InputBindingComposite<Vector2>
45 {
46 /// <summary>
47 /// Binding for the button that represents the up (that is, <c>(0,1)</c>) direction of the vector.
48 /// </summary>
49 /// <remarks>
50 /// This property is automatically assigned by the input system.
51 /// </remarks>
52 // ReSharper disable once MemberCanBePrivate.Global
53 // ReSharper disable once FieldCanBeMadeReadOnly.Global
54 [InputControl(layout = "Axis")] public int up;
55
56 /// <summary>
57 /// Binding for the button represents the down (that is, <c>(0,-1)</c>) direction of the vector.
58 /// </summary>
59 /// <remarks>
60 /// This property is automatically assigned by the input system.
61 /// </remarks>
62 // ReSharper disable once MemberCanBePrivate.Global
63 // ReSharper disable once FieldCanBeMadeReadOnly.Global
64 [InputControl(layout = "Axis")] public int down;
65
66 /// <summary>
67 /// Binding for the button represents the left (that is, <c>(-1,0)</c>) direction of the vector.
68 /// </summary>
69 /// <remarks>
70 /// This property is automatically assigned by the input system.
71 /// </remarks>
72 // ReSharper disable once MemberCanBePrivate.Global
73 // ReSharper disable once FieldCanBeMadeReadOnly.Global
74 [InputControl(layout = "Axis")] public int left;
75
76 /// <summary>
77 /// Binding for the button that represents the right (that is, <c>(1,0)</c>) direction of the vector.
78 /// </summary>
79 /// <remarks>
80 /// This property is automatically assigned by the input system.
81 /// </remarks>
82 [InputControl(layout = "Axis")] public int right;
83
84 [Obsolete("Use Mode.DigitalNormalized with 'mode' instead")]
85 public bool normalize = true;
86
87 /// <summary>
88 /// How to synthesize a <c>Vector2</c> from the values read from <see cref="up"/>, <see cref="down"/>,
89 /// <see cref="left"/>, and <see cref="right"/>.
90 /// </summary>
91 /// <value>Determines how X and Y of the resulting <c>Vector2</c> are formed from input values.</value>
92 /// <remarks>
93 /// <example>
94 /// <code>
95 /// var action = new InputAction();
96 ///
97 /// // DigitalNormalized composite (the default). Turns gamepad left stick into
98 /// // control equivalent to the D-Pad.
99 /// action.AddCompositeBinding("2DVector(mode=0)")
100 /// .With("up", "<Gamepad>/leftStick/up")
101 /// .With("down", "<Gamepad>/leftStick/down")
102 /// .With("left", "<Gamepad>/leftStick/left")
103 /// .With("right", "<Gamepad>/leftStick/right");
104 ///
105 /// // Digital composite. Turns gamepad left stick into control equivalent
106 /// // to the D-Pad except that diagonals will not be normalized.
107 /// action.AddCompositeBinding("2DVector(mode=1)")
108 /// .With("up", "<Gamepad>/leftStick/up")
109 /// .With("down", "<Gamepad>/leftStick/down")
110 /// .With("left", "<Gamepad>/leftStick/left")
111 /// .With("right", "<Gamepad>/leftStick/right");
112 ///
113 /// // Analog composite. In this case results in setup that behaves exactly
114 /// // the same as leftStick already does. But you could use it, for example,
115 /// // to swap directions by binding "up" to leftStick/down and "down" to
116 /// // leftStick/up.
117 /// action.AddCompositeBinding("2DVector(mode=2)")
118 /// .With("up", "<Gamepad>/leftStick/up")
119 /// .With("down", "<Gamepad>/leftStick/down")
120 /// .With("left", "<Gamepad>/leftStick/left")
121 /// .With("right", "<Gamepad>/leftStick/right");
122 /// </code>
123 /// </example>
124 /// </remarks>
125 public Mode mode;
126
127 /// <inheritdoc />
128 public override Vector2 ReadValue(ref InputBindingCompositeContext context)
129 {
130 var mode = this.mode;
131
132 if (mode == Mode.Analog)
133 {
134 var upValue = context.ReadValue<float>(up);
135 var downValue = context.ReadValue<float>(down);
136 var leftValue = context.ReadValue<float>(left);
137 var rightValue = context.ReadValue<float>(right);
138
139 return DpadControl.MakeDpadVector(upValue, downValue, leftValue, rightValue);
140 }
141
142 var upIsPressed = context.ReadValueAsButton(up);
143 var downIsPressed = context.ReadValueAsButton(down);
144 var leftIsPressed = context.ReadValueAsButton(left);
145 var rightIsPressed = context.ReadValueAsButton(right);
146
147 // Legacy. We need to reference the obsolete member here so temporarily
148 // turn of the warning.
149 #pragma warning disable CS0618
150 if (!normalize) // Was on by default.
151 mode = Mode.Digital;
152 #pragma warning restore CS0618
153
154 return DpadControl.MakeDpadVector(upIsPressed, downIsPressed, leftIsPressed, rightIsPressed, mode == Mode.DigitalNormalized);
155 }
156
157 /// <inheritdoc />
158 public override float EvaluateMagnitude(ref InputBindingCompositeContext context)
159 {
160 var value = ReadValue(ref context);
161 return value.magnitude;
162 }
163
164 /// <summary>
165 /// Determines how a <c>Vector2</c> is synthesized from part controls.
166 /// </summary>
167 public enum Mode
168 {
169 /// <summary>
170 /// Part controls are treated as analog meaning that the floating-point values read from controls
171 /// will come through as is (minus the fact that the down and left direction values are negated).
172 /// </summary>
173 Analog = 2,
174
175 /// <summary>
176 /// Part controls are treated as buttons (on/off) and the resulting vector is normalized. This means
177 /// that if, for example, both left and up are pressed, instead of returning a vector (-1,1), a vector
178 /// of roughly (-0.7,0.7) (that is, corresponding to <c>new Vector2(-1,1).normalized</c>) is returned instead.
179 /// The resulting 2D area is diamond-shaped.
180 /// </summary>
181 DigitalNormalized = 0,
182
183 /// <summary>
184 /// Part controls are treated as buttons (on/off) and the resulting vector is not normalized. This means
185 /// that if, for example, both left and up are pressed, the resulting vector is (-1,1) and has a length
186 /// greater than 1. The resulting 2D area is box-shaped.
187 /// </summary>
188 Digital = 1
189 }
190 }
191
192 #if UNITY_EDITOR
193 internal class Vector2CompositeEditor : InputParameterEditor<Vector2Composite>
194 {
195 private GUIContent m_ModeLabel = new GUIContent("Mode",
196 "How to synthesize a Vector2 from the inputs. Digital "
197 + "treats part bindings as buttons (on/off) whereas Analog preserves "
198 + "floating-point magnitudes as read from controls.");
199
200 public override void OnGUI()
201 {
202#if UNITY_INPUT_SYSTEM_PROJECT_WIDE_ACTIONS
203 if (!InputSystem.settings.IsFeatureEnabled(InputFeatureNames.kUseIMGUIEditorForAssets)) return;
204#endif
205 target.mode = (Vector2Composite.Mode)EditorGUILayout.EnumPopup(m_ModeLabel, target.mode);
206 }
207
208#if UNITY_INPUT_SYSTEM_PROJECT_WIDE_ACTIONS
209 public override void OnDrawVisualElements(VisualElement root, Action onChangedCallback)
210 {
211 var modeField = new EnumField(m_ModeLabel.text, target.mode)
212 {
213 tooltip = m_ModeLabel.tooltip
214 };
215
216 modeField.RegisterValueChangedCallback(evt =>
217 {
218 target.mode = (Vector2Composite.Mode)evt.newValue;
219 onChangedCallback();
220 });
221
222 root.Add(modeField);
223 }
224
225#endif
226 }
227 #endif
228}