-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathLocomotionSchemeManager.cs
More file actions
524 lines (471 loc) · 21.7 KB
/
LocomotionSchemeManager.cs
File metadata and controls
524 lines (471 loc) · 21.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
using System;
using System.Collections.Generic;
using System.ComponentModel;
using UnityEngine.InputSystem;
namespace UnityEngine.XR.Interaction.Toolkit.Examples
{
/// <summary>
/// Use this class as a central manager to configure locomotion control schemes and configuration preferences.
/// </summary>
/// <remarks>
/// Input bindings will often overlap between different locomotion methods, and this class can be used to
/// set binding masks which are used to determine which bindings of an action to enable and which to ignore.
///
/// <example>
/// Teleport (Input Action)
/// (1) Binding <XRController>{LeftHand}/PrimaryButton (Use in control scheme "Generic XR")
/// (2) Binding <XRController>{LeftHand}/Primary2DAxis (Use in control scheme "Noncontinuous Move")
/// Move (Input Action)
/// (3) Binding <XRController>{LeftHand}/Primary2DAxis (Use in control scheme "Continuous Move")
///
/// Set <see cref="baseControlScheme"/>="Generic XR"
/// Set <see cref="noncontinuousControlScheme"/>="Noncontinuous Move"
/// Set <see cref="continuousControlScheme"/>="Continuous Move"
/// Set <see cref="actions"/> to be both input actions (Teleport and Move).
///
/// When <see cref="moveScheme"/>=<see cref="MoveScheme.Noncontinuous"/>,
/// bindings (1) and (2) will be enabled, but binding (3) will be disabled.
///
/// When <see cref="moveScheme"/>=<see cref="MoveScheme.Continuous"/>,
/// bindings (1) and (3) will be enabled, but binding (2) will be disabled.
/// </example>
/// </remarks>
public class LocomotionSchemeManager : MonoBehaviour
{
/// <summary>
/// Sets which movement control scheme to use.
/// </summary>
/// <seealso cref="moveScheme"/>
public enum MoveScheme
{
/// <summary>
/// Use noncontinuous movement control scheme.
/// </summary>
Noncontinuous,
/// <summary>
/// Use continuous movement control scheme.
/// </summary>
Continuous,
}
/// <summary>
/// Sets which turn style of locomotion to use.
/// </summary>
/// <seealso cref="turnStyle"/>
public enum TurnStyle
{
/// <summary>
/// Use snap turning to rotate the direction you are facing by snapping by a specified angle.
/// </summary>
Snap,
/// <summary>
/// Use continuous turning to smoothly rotate the direction you are facing by a specified speed.
/// </summary>
Continuous,
}
/// <summary>
/// Sets which orientation the forward direction of continuous movement is relative to.
/// </summary>
/// <seealso cref="moveForwardSource"/>
/// <seealso cref="ContinuousMoveProviderBase.forwardSource"/>
public enum MoveForwardSource
{
/// <summary>
/// Use to continuously move in a direction based on the head orientation.
/// </summary>
Head,
/// <summary>
/// Use to continuously move in a direction based on the left hand orientation.
/// </summary>
LeftHand,
/// <summary>
/// Use to continuously move in a direction based on the right hand orientation.
/// </summary>
RightHand,
}
[SerializeField]
[Tooltip("Controls which movement control scheme to use.")]
MoveScheme m_MoveScheme;
/// <summary>
/// Controls which movement control scheme to use.
/// </summary>
/// <seealso cref="MoveScheme"/>
public MoveScheme moveScheme
{
get => m_MoveScheme;
set
{
SetMoveScheme(value);
m_MoveScheme = value;
}
}
[SerializeField]
[Tooltip("Controls which turn style of locomotion to use.")]
TurnStyle m_TurnStyle;
/// <summary>
/// Controls which turn style of locomotion to use.
/// </summary>
/// <seealso cref="TurnStyle"/>
public TurnStyle turnStyle
{
get => m_TurnStyle;
set
{
SetTurnStyle(value);
m_TurnStyle = value;
}
}
[SerializeField]
[Tooltip("Controls which orientation the forward direction of continuous movement is relative to.")]
MoveForwardSource m_MoveForwardSource;
/// <summary>
/// Controls which orientation the forward direction of continuous movement is relative to.
/// </summary>
/// <seealso cref="MoveForwardSource"/>
public MoveForwardSource moveForwardSource
{
get => m_MoveForwardSource;
set
{
SetMoveForwardSource(value);
m_MoveForwardSource = value;
}
}
[SerializeField]
[Tooltip("Input action assets associated with locomotion to affect when the active movement control scheme is set." +
" Can use this list by itself or together with the Action Maps list to set control scheme masks by Asset or Map.")]
List<InputActionAsset> m_ActionAssets;
/// <summary>
/// Input action assets associated with locomotion to affect when the active movement control scheme is set.
/// Can use this list by itself or together with the Action Maps list to set control scheme masks by Asset or Map.
/// </summary>
/// <seealso cref="actionMaps"/>
public List<InputActionAsset> actionAssets
{
get => m_ActionAssets;
set => m_ActionAssets = value;
}
[SerializeField]
[Tooltip("Input action maps associated with locomotion to affect when the active movement control scheme is set." +
" Can use this list together with the Action Assets list to set control scheme masks by Map instead of the whole Asset.")]
List<string> m_ActionMaps;
/// <summary>
/// Input action maps associated with locomotion to affect when the active movement control scheme is set.
/// Can use this list together with the Action Assets list to set control scheme masks by Map instead of the whole Asset.
/// </summary>
/// <seealso cref="actionAssets"/>
public List<string> actionMaps
{
get => m_ActionMaps;
set => m_ActionMaps = value;
}
[SerializeField]
[Tooltip("Input actions associated with locomotion to affect when the active movement control scheme is set." +
" Can use this list to select exactly the actions to affect instead of setting control scheme masks by Asset or Map.")]
List<InputActionReference> m_Actions;
/// <summary>
/// Input actions associated with locomotion that are affected by the active movement control scheme.
/// Can use this list to select exactly the actions to affect instead of setting control scheme masks by Asset or Map.
/// </summary>
/// <seealso cref="actionAssets"/>
/// <seealso cref="actionMaps"/>
public List<InputActionReference> actions
{
get => m_Actions;
set => m_Actions = value;
}
[SerializeField]
[Tooltip("Name of an input control scheme that defines the grouping of bindings that should remain enabled when applying any movement control scheme." +
" Control schemes are created and named in the Input Actions window. The other movement control schemes are applied additively to this scheme." +
" Can be an empty string, which means only bindings that match the specified movement control scheme will be enabled.")]
string m_BaseControlScheme;
/// <summary>
/// Name of an input control scheme that defines the grouping of bindings that should remain enabled when applying any movement control scheme.
/// Control schemes are created and named in the Input Actions window. The other movement control schemes are applied additively to this scheme.
/// Can be an empty string, which means only bindings that match the specified movement control scheme will be enabled.
/// </summary>
public string baseControlScheme
{
get => m_BaseControlScheme;
set => m_BaseControlScheme = value;
}
[SerializeField]
[Tooltip("Name of an input control scheme that defines the grouping of bindings that should remain enabled when applying the noncontinuous movement control scheme." +
" Control schemes are created and named in the Input Actions window. Can be an empty string, which means only bindings that match the" +
" base control scheme will be enabled.")]
string m_NoncontinuousControlScheme;
/// <summary>
/// Name of an input control scheme that defines the grouping of bindings that should remain enabled when applying the noncontinuous movement control scheme.
/// Control schemes are created and named in the Input Actions window. Can be an empty string, which means only bindings that match the
/// base control scheme will be enabled.
/// </summary>
public string noncontinuousControlScheme
{
get => m_NoncontinuousControlScheme;
set => m_NoncontinuousControlScheme = value;
}
[SerializeField]
[Tooltip("Name of an input control scheme that defines the grouping of bindings that should remain enabled when applying the continuous movement control scheme." +
" Control schemes are created and named in the Input Actions window. Can be an empty string, which means only bindings that match the" +
" base control scheme will be enabled.")]
string m_ContinuousControlScheme;
/// <summary>
/// Name of an input control scheme that defines the grouping of bindings that should remain enabled when applying the continuous movement control scheme.
/// Control schemes are created and named in the Input Actions window. Can be an empty string, which means only bindings that match the
/// base control scheme will be enabled.
/// </summary>
public string continuousControlScheme
{
get => m_ContinuousControlScheme;
set => m_ContinuousControlScheme = value;
}
[SerializeField]
[Tooltip("Stores the locomotion provider for continuous movement.")]
ContinuousMoveProviderBase m_ContinuousMoveProvider;
/// <summary>
/// Stores the locomotion provider for continuous movement.
/// </summary>
/// <seealso cref="ContinuousMoveProviderBase"/>
public ContinuousMoveProviderBase continuousMoveProvider
{
get => m_ContinuousMoveProvider;
set => m_ContinuousMoveProvider = value;
}
[SerializeField]
[Tooltip("Stores the locomotion provider for continuous turning.")]
ContinuousTurnProviderBase m_ContinuousTurnProvider;
/// <summary>
/// Stores the locomotion provider for continuous turning.
/// </summary>
/// <seealso cref="ContinuousTurnProviderBase"/>
public ContinuousTurnProviderBase continuousTurnProvider
{
get => m_ContinuousTurnProvider;
set => m_ContinuousTurnProvider = value;
}
[SerializeField]
[Tooltip("Stores the locomotion provider for snap turning.")]
SnapTurnProviderBase m_SnapTurnProvider;
/// <summary>
/// Stores the locomotion provider for snap turning.
/// </summary>
/// <seealso cref="SnapTurnProviderBase"/>
public SnapTurnProviderBase snapTurnProvider
{
get => m_SnapTurnProvider;
set => m_SnapTurnProvider = value;
}
[SerializeField]
[Tooltip("Stores the \"Head\" Transform used with continuous movement when inputs should be relative to head orientation (usually the main camera).")]
Transform m_HeadForwardSource;
/// <summary>
/// Stores the "Head" <see cref="Transform"/> used with continuous movement when inputs should be relative to head orientation (usually the main camera).
/// </summary>
public Transform headForwardSource
{
get => m_HeadForwardSource;
set => m_HeadForwardSource = value;
}
[SerializeField]
[Tooltip("Stores the \"Left Hand\" Transform used with continuous movement when inputs should be relative to the left hand's orientation.")]
Transform m_LeftHandForwardSource;
/// <summary>
/// Stores the "Left Hand" <see cref="Transform"/> used with continuous movement when inputs should be relative to the left hand's orientation.
/// </summary>
public Transform leftHandForwardSource
{
get => m_LeftHandForwardSource;
set => m_LeftHandForwardSource = value;
}
[SerializeField]
[Tooltip("Stores the \"Right Hand\" Transform used with continuous movement when inputs should be relative to the right hand's orientation.")]
Transform m_RightHandForwardSource;
/// <summary>
/// Stores the "Right Hand" <see cref="Transform"/> used with continuous movement when inputs should be relative to the right hand's orientation.
/// </summary>
public Transform rightHandForwardSource
{
get => m_RightHandForwardSource;
set => m_RightHandForwardSource = value;
}
void OnEnable()
{
SetMoveScheme(m_MoveScheme);
SetTurnStyle(m_TurnStyle);
SetMoveForwardSource(m_MoveForwardSource);
}
void OnDisable()
{
ClearBindingMasks();
}
void SetMoveScheme(MoveScheme scheme)
{
switch (scheme)
{
case MoveScheme.Noncontinuous:
SetBindingMasks(m_NoncontinuousControlScheme);
if (m_ContinuousMoveProvider != null)
{
m_ContinuousMoveProvider.enabled = false;
}
break;
case MoveScheme.Continuous:
SetBindingMasks(m_ContinuousControlScheme);
if (m_ContinuousMoveProvider != null)
{
m_ContinuousMoveProvider.enabled = true;
}
break;
default:
throw new InvalidEnumArgumentException(nameof(scheme), (int)scheme, typeof(MoveScheme));
}
}
void SetTurnStyle(TurnStyle style)
{
switch (style)
{
case TurnStyle.Snap:
if (m_ContinuousTurnProvider != null)
{
m_ContinuousTurnProvider.enabled = false;
}
if (m_SnapTurnProvider != null)
{
// TODO: If the Continuous Turn and Snap Turn providers both use the same
// action, then disabling the first provider will cause the action to be
// disabled, so the action needs to be enabled, which is done by forcing
// the OnEnable() of the second provider to be called.
// ReSharper disable Unity.InefficientPropertyAccess
m_SnapTurnProvider.enabled = false;
m_SnapTurnProvider.enabled = true;
// ReSharper restore Unity.InefficientPropertyAccess
m_SnapTurnProvider.enableTurnLeftRight = true;
}
break;
case TurnStyle.Continuous:
if (m_SnapTurnProvider != null)
{
m_SnapTurnProvider.enableTurnLeftRight = false;
}
if (m_ContinuousTurnProvider != null)
{
m_ContinuousTurnProvider.enabled = true;
}
break;
default:
throw new InvalidEnumArgumentException(nameof(style), (int)style, typeof(TurnStyle));
}
}
void SetMoveForwardSource(MoveForwardSource forwardSource)
{
if (m_ContinuousMoveProvider == null)
{
Debug.LogError($"Cannot set forward source to {forwardSource}," +
$" the reference to the {nameof(ContinuousMoveProviderBase)} is missing or the object has been destroyed.", this);
return;
}
switch (forwardSource)
{
case MoveForwardSource.Head:
m_ContinuousMoveProvider.forwardSource = m_HeadForwardSource;
break;
case MoveForwardSource.LeftHand:
m_ContinuousMoveProvider.forwardSource = m_LeftHandForwardSource;
break;
case MoveForwardSource.RightHand:
m_ContinuousMoveProvider.forwardSource = m_RightHandForwardSource;
break;
default:
throw new InvalidEnumArgumentException(nameof(forwardSource), (int)forwardSource, typeof(MoveForwardSource));
}
}
void SetBindingMasks(string controlSchemeName)
{
foreach (var actionReference in m_Actions)
{
if (actionReference == null)
continue;
var action = actionReference.action;
if (action == null)
{
Debug.LogError($"Cannot set binding mask on {actionReference} since the action could not be found.", this);
continue;
}
// Get the (optional) base control scheme and the control scheme to apply on top of base
var baseInputControlScheme = FindControlScheme(m_BaseControlScheme, actionReference);
var inputControlScheme = FindControlScheme(controlSchemeName, actionReference);
action.bindingMask = GetBindingMask(baseInputControlScheme, inputControlScheme);
}
if (m_ActionMaps.Count > 0 && m_ActionAssets.Count == 0)
{
Debug.LogError($"Cannot set binding mask on action maps since no input action asset references have been set.", this);
}
foreach (var actionAsset in m_ActionAssets)
{
if (actionAsset == null)
continue;
// Get the (optional) base control scheme and the control scheme to apply on top of base
var baseInputControlScheme = FindControlScheme(m_BaseControlScheme, actionAsset);
var inputControlScheme = FindControlScheme(controlSchemeName, actionAsset);
if (m_ActionMaps.Count == 0)
{
actionAsset.bindingMask = GetBindingMask(baseInputControlScheme, inputControlScheme);
continue;
}
foreach (var mapName in m_ActionMaps)
{
var actionMap = actionAsset.FindActionMap(mapName);
if (actionMap == null)
{
Debug.LogError($"Cannot set binding mask on \"{mapName}\" since the action map not be found in '{actionAsset}'.", this);
continue;
}
actionMap.bindingMask = GetBindingMask(baseInputControlScheme, inputControlScheme);
}
}
}
void ClearBindingMasks()
{
SetBindingMasks(string.Empty);
}
InputControlScheme? FindControlScheme(string controlSchemeName, InputActionReference action)
{
if (action == null)
throw new ArgumentNullException(nameof(action));
if (string.IsNullOrEmpty(controlSchemeName))
return null;
var asset = action.asset;
if (asset == null)
{
Debug.LogError($"Cannot find control scheme \"{controlSchemeName}\" for '{action}' since it does not belong to an {nameof(InputActionAsset)}.", this);
return null;
}
return FindControlScheme(controlSchemeName, asset);
}
InputControlScheme? FindControlScheme(string controlSchemeName, InputActionAsset asset)
{
if (asset == null)
throw new ArgumentNullException(nameof(asset));
if (string.IsNullOrEmpty(controlSchemeName))
return null;
var scheme = asset.FindControlScheme(controlSchemeName);
if (scheme == null)
{
Debug.LogError($"Cannot find control scheme \"{controlSchemeName}\" in '{asset}'.", this);
return null;
}
return scheme;
}
static InputBinding? GetBindingMask(InputControlScheme? baseInputControlScheme, InputControlScheme? inputControlScheme)
{
if (inputControlScheme.HasValue)
{
return baseInputControlScheme.HasValue
? InputBinding.MaskByGroups(baseInputControlScheme.Value.bindingGroup, inputControlScheme.Value.bindingGroup)
: InputBinding.MaskByGroup(inputControlScheme.Value.bindingGroup);
}
return baseInputControlScheme.HasValue
? InputBinding.MaskByGroup(baseInputControlScheme.Value.bindingGroup)
: (InputBinding?)null;
}
}
}