1
1
// Copyright (c) Microsoft Corporation.
2
2
// Licensed under the MIT License.
3
3
4
+ using System . Collections . Generic ;
4
5
using Unity . Profiling ;
5
6
using UnityEngine ;
6
7
using UnityEngine . Events ;
7
8
using UnityEngine . InputSystem ;
9
+ using UnityEngine . XR . Interaction . Toolkit ;
8
10
using UnityPhysics = UnityEngine . Physics ;
9
11
10
12
namespace Microsoft . MixedReality . Toolkit . SpatialManipulation
11
13
{
12
14
/// <summary>
13
15
/// Tap to place is a far interaction component used to place objects on a surface.
14
16
/// </summary>
15
- [ RequireComponent ( typeof ( StatefulInteractable ) ) ]
16
17
[ AddComponentMenu ( "MRTK/Spatial Manipulation/Solvers/Tap To Place" ) ]
17
18
public class TapToPlace : Solver
18
19
{
19
- [ SerializeField ]
20
- [ Tooltip ( "The input action which is to control placement." ) ]
21
- private InputActionReference placementActionReference = null ;
22
-
23
- /// <summary>
24
- /// The input action which is to control placement.
25
- /// </summary>
26
- public InputActionReference PlacementActionReference
27
- {
28
- get => placementActionReference ;
29
- set => placementActionReference = value ;
30
- }
31
20
32
21
// todo: needed? [Space(10)]
33
22
[ SerializeField ]
@@ -237,8 +226,11 @@ public UnityEvent OnPlacingStopped
237
226
// Used to mark whether StartPlacement() is called before Start() is called.
238
227
private bool placementRequested ;
239
228
240
- // The interactable used to pick up the obect.
241
- private StatefulInteractable interactable ;
229
+ // Used to obtain list of known interactors
230
+ private XRInteractionManager interactionManager ;
231
+
232
+ // Used to cache a known set of interactor
233
+ private List < IXRInteractor > interactorsCache ;
242
234
243
235
#region MonoBehaviour Implementation
244
236
@@ -257,9 +249,6 @@ protected override void Start()
257
249
258
250
startCalled = true ;
259
251
260
- interactable = gameObject . GetComponent < StatefulInteractable > ( ) ;
261
- RegisterPickupAction ( ) ;
262
-
263
252
if ( AutoStart || placementRequested )
264
253
{
265
254
StartPlacement ( ) ;
@@ -272,8 +261,7 @@ protected override void Start()
272
261
273
262
protected override void OnDisable ( )
274
263
{
275
- UnregisterPlacementAction ( ) ;
276
- UnregisterPickupAction ( ) ;
264
+ StopPlacement ( ) ;
277
265
base . OnDisable ( ) ;
278
266
}
279
267
@@ -284,13 +272,13 @@ protected override void OnDisable()
284
272
285
273
/// <summary>
286
274
/// Start the placement of a game object without the need of the OnPointerClicked event. The game object will begin to follow the
287
- /// TrackedTargetType (Head by default) at a default distance. StopPlacement () must be called after StartPlacement() to stop the
275
+ /// TrackedTargetType (Head by default) at a default distance. StopPlacementViaPerformedAction () must be called after StartPlacement() to stop the
288
276
/// game object from following the TrackedTargetType. The game object layer is changed to IgnoreRaycast temporarily and then
289
- /// restored to its original layer in StopPlacement ().
277
+ /// restored to its original layer in StopPlacementViaPerformedAction ().
290
278
/// </summary>
291
279
public void StartPlacement ( )
292
280
{
293
- // Checking the amount of time passed between when StartPlacement or StopPlacement is called twice in
281
+ // Checking the amount of time passed between when StartPlacement or StopPlacementViaPerformedAction is called twice in
294
282
// succession. If these methods are called twice very rapidly, the object will be
295
283
// selected and then immediately unselected. If two calls occur within the
296
284
// double click timeout, then return to prevent an immediate object state switch.
@@ -299,6 +287,7 @@ public void StartPlacement()
299
287
{
300
288
return ;
301
289
}
290
+
302
291
// Get the time of this click action
303
292
LastTimeClicked = Time . time ;
304
293
@@ -333,14 +322,30 @@ public void StartPlacement()
333
322
}
334
323
335
324
private static readonly ProfilerMarker StopPlacementPerfMarker =
336
- new ProfilerMarker ( "[MRTK] TapToPlace.StopPlacement " ) ;
325
+ new ProfilerMarker ( "[MRTK] TapToPlace.StopPlacementViaPerformedAction " ) ;
337
326
338
327
/// <summary>
339
- /// Stop the placement of a game object without the need of the OnPointerClicked event.
328
+ /// Stop the placement of a game object via an action's performance.
340
329
/// </summary>
341
- public void StopPlacement ( InputAction . CallbackContext context )
330
+ private void StopPlacementViaPerformedAction ( InputAction . CallbackContext context )
342
331
{
343
- // Checking the amount of time passed between when StartPlacement or StopPlacement is called twice in
332
+ StopPlacement ( ) ;
333
+ }
334
+
335
+ /// <summary>
336
+ /// Stop the placement of a game object via an interactor's select event.
337
+ /// </summary>
338
+ private void StopPlacementViaSelect ( SelectEnterEventArgs args )
339
+ {
340
+ StopPlacement ( ) ;
341
+ }
342
+
343
+ /// <summary>
344
+ /// Stop the placement of a game object.
345
+ /// </summary>
346
+ public void StopPlacement ( )
347
+ {
348
+ // Checking the amount of time passed between when StartPlacement or StopPlacementViaPerformedAction is called twice in
344
349
// succession. If these methods are called twice very rapidly, the object will be
345
350
// selected and then immediately unselected. If two calls occur within the
346
351
// double click timeout, then return to prevent an immediate object state switch.
@@ -353,7 +358,7 @@ public void StopPlacement(InputAction.CallbackContext context)
353
358
354
359
using ( StopPlacementPerfMarker . Auto ( ) )
355
360
{
356
- // Added for code configurability to avoid multiple calls to StopPlacement in a row
361
+ // Added for code configurability to avoid multiple calls to StopPlacementViaPerformedAction in a row
357
362
if ( IsBeingPlaced )
358
363
{
359
364
// Change the game object layer back to the game object's layer on start
@@ -467,53 +472,61 @@ protected virtual void SetRotation()
467
472
/// </summary>
468
473
private void RegisterPlacementAction ( )
469
474
{
470
- InputAction placementAction = GetInputActionFromReference ( placementActionReference ) ;
471
- if ( placementAction == null )
475
+ // Refresh the registeration if they already exist
476
+ UnregisterPlacementAction ( ) ;
477
+
478
+ if ( interactionManager == null )
472
479
{
473
- Debug . Log ( "Failed to register the placement action, the action reference was null or contained no action." ) ;
474
- return ;
480
+ interactionManager = FindObjectOfType < XRInteractionManager > ( ) ;
481
+ if ( interactionManager == null )
482
+ {
483
+ Debug . LogError ( "No interaction manager found in scene. Please add an interaction manager to the scene." ) ;
484
+ }
475
485
}
476
- placementAction . performed += StopPlacement ;
477
- }
478
486
479
- /// <summary>
480
- /// Registers the event which performs pickup.
481
- /// </summary>
482
- private void RegisterPickupAction ( )
483
- {
484
- if ( interactable == null )
487
+ if ( interactorsCache == null )
485
488
{
486
- Debug . Log ( "Failed to register the pick up event. There is no StatefulInteractable set." ) ;
487
- return ;
489
+ interactorsCache = new List < IXRInteractor > ( ) ;
488
490
}
489
- interactable . OnClicked . AddListener ( StartPlacement ) ;
490
- }
491
491
492
- /// <summary>
493
- /// Unregisters the input action which performs placement.
494
- /// </summary>
495
- private void UnregisterPlacementAction ( )
496
- {
497
- InputAction placementAction = GetInputActionFromReference ( placementActionReference ) ;
498
- if ( placementAction == null )
492
+ // Try registering for the controller's "action" so object selection isn't required for placement.
493
+ // If no controller, then fallback to using object selections for placement.
494
+ interactionManager . GetRegisteredInteractors ( interactorsCache ) ;
495
+ foreach ( IXRInteractor interactor in interactorsCache )
499
496
{
500
- Debug . Log ( "Failed to unregister the placement action, the action reference was null or contained no action." ) ;
501
- return ;
497
+ if ( interactor is XRBaseControllerInteractor controllerInteractor &&
498
+ controllerInteractor . xrController is ActionBasedController actionController )
499
+ {
500
+ actionController . selectAction . action . performed += StopPlacementViaPerformedAction ;
501
+ }
502
+ else if ( interactor is IXRSelectInteractor selectInteractor )
503
+ {
504
+ selectInteractor . selectEntered . AddListener ( StopPlacementViaSelect ) ;
505
+ }
502
506
}
503
- placementAction . performed -= StopPlacement ;
504
507
}
505
508
506
509
/// <summary>
507
- /// Unregisters the event which performs pickup .
510
+ /// Unregisters the input action which performs placement .
508
511
/// </summary>
509
- private void UnregisterPickupAction ( )
512
+ private void UnregisterPlacementAction ( )
510
513
{
511
- if ( interactable = = null )
514
+ if ( interactorsCache ! = null )
512
515
{
513
- Debug . Log ( "Failed to unregister the pick up event. There is no StatefulInteractable set." ) ;
514
- return ;
516
+ foreach ( IXRInteractor interactor in interactorsCache )
517
+ {
518
+ if ( interactor is XRBaseControllerInteractor controllerInteractor &&
519
+ controllerInteractor . xrController is ActionBasedController actionController )
520
+ {
521
+ actionController . selectAction . action . performed -= StopPlacementViaPerformedAction ;
522
+ }
523
+ else if ( interactor is IXRSelectInteractor selectInteractor )
524
+ {
525
+ selectInteractor . selectEntered . RemoveListener ( StopPlacementViaSelect ) ;
526
+ }
527
+ }
528
+ interactorsCache . Clear ( ) ;
515
529
}
516
- interactable . OnClicked . RemoveListener ( StartPlacement ) ;
517
530
}
518
531
519
532
/// <summary>
0 commit comments