Tilt Five™ Unity API  1.4.1
TiltFiveManager2.cs
Go to the documentation of this file.
1 /*
2  * Copyright (C) 2020-2023 Tilt Five, Inc.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 using System.Collections.Generic;
17 using UnityEngine;
18 
19 #if UNITY_2019_1_OR_NEWER && INPUTSYSTEM_AVAILABLE
20 using UnityEngine.InputSystem;
21 using UnityEngine.InputSystem.Users;
22 #endif
23 
24 using TiltFive;
25 using TiltFive.Logging;
26 
27 namespace TiltFive
28 {
29 
33  [DisallowMultipleComponent]
34 #if !UNITY_2019_1_OR_NEWER || !INPUTSYSTEM_AVAILABLE
35  // Workaround to enable inputs to be collected before other scripts execute their Update() functions.
36  // This is unnecessary if we're using the Input System's OnBeforeUpdate() to collect fresh inputs.
37  [DefaultExecutionOrder(-500)]
38 #else
39  // If the Input System's OnBeforeUpdate is available, set TiltFiveManager's execution order to be very late.
40  // This is desirable in two similar scenarios:
41  // - Our Update() executes last, providing the freshest pose data possible to any scripts using LateUpdate().
42  // - Our LateUpdate() executes last, providing the freshest pose data possible before we render to the glasses.
43  [DefaultExecutionOrder(500)]
44 #endif
45  public class TiltFiveManager2 : TiltFive.SingletonComponent<TiltFiveManager2>, ISceneInfo
46  {
51  {
52  get
53  {
54  if(allPlayerSettings[0] == null)
55  {
57  }
58  return allPlayerSettings[0];
59  }
60  }
61 
66  {
67  get
68  {
69  if (allPlayerSettings[1] == null)
70  {
72  }
73  return allPlayerSettings[1];
74  }
75  }
76 
81  {
82  get
83  {
84  if (allPlayerSettings[2] == null)
85  {
87  }
88  return allPlayerSettings[2];
89  }
90  }
91 
96  {
97  get
98  {
99  if (allPlayerSettings[3] == null)
100  {
102  }
103  return allPlayerSettings[3];
104  }
105  }
106 
108 
109  public uint supportedPlayerCount = 3;
110 
115 
120 
125 
126 #if UNITY_EDITOR
130  public EditorSettings2 editorSettings = new EditorSettings2();
131  public PlayerIndex selectedPlayer => editorSettings.selectedPlayer;
132 
133  private HashSet<GameBoard> renderedGameboards = new HashSet<GameBoard>();
134 #endif
135 
136  private bool needsDriverUpdateNotifiedOnce = false;
137  private bool needsDriverUpdateErroredOnce = false;
138 
139  private static bool upgradeInProgress = false;
140 
141 #if UNITY_2019_1_OR_NEWER && INPUTSYSTEM_AVAILABLE
147  private int[] playerIndexMapping = {0,1,2,3};
148 #endif
149 
153  protected override void Awake()
154  {
155  base.Awake();
156 
157  // Apply log settings
160 
161  // Store graphics settings
162  graphicsSettings.applicationTargetFramerate = Application.targetFrameRate;
163  graphicsSettings.applicationVSyncCount = QualitySettings.vSyncCount;
164 
165  if (!SystemControl.SetPlatformContext())
166  {
167  Log.Warn("Failed to set application context.");
168  enabled = false;
169  }
170 
171  if (!SystemControl.SetApplicationInfo())
172  {
173  Log.Warn("Failed to send application info to the T5 Service.");
174  enabled = false;
175  }
176 
177  // Initialize the player settings if necessary
178  for (int i = 0; i < allPlayerSettings.Length; i++)
179  {
180  var currentPlayerSettings = allPlayerSettings[i];
181  if(currentPlayerSettings == null)
182  {
183  allPlayerSettings[i] = new PlayerSettings() { PlayerIndex = (PlayerIndex) i + 1 };
184  }
185  }
186  }
187 
188 
189 #if UNITY_2019_1_OR_NEWER && INPUTSYSTEM_AVAILABLE
193  private void OnBeforeUpdate()
194  {
195 #if UNITY_EDITOR
196  if (UnityEditor.EditorApplication.isPaused)
197  {
198  return;
199  }
200 #endif
201  if (Player.scanningForPlayers)
202  {
203  return;
204  }
205 
207  Player.ScanForNewPlayers(); // Should only be executed once per frame
208  Wand.GetLatestInputs(); // Should only be executed once per frame
209 
210  // OnBeforeUpdate can get called multiple times per frame. Unity seems to not properly utilize the camera positions for rendering
211  // if they are updated after Late Update and before render, causing a disparity between render pose and camera position leading to
212  // shaky displays in the headset. To avoid this, we prevent updating the camera positions during the BeforeRender Input State.
213  if (UnityEngine.InputSystem.LowLevel.InputState.currentUpdateType != UnityEngine.InputSystem.LowLevel.InputUpdateType.BeforeRender)
214  {
215  Update();
216  }
217  }
218 #endif
219 
223  void Update()
224  {
225 #if !UNITY_2019_1_OR_NEWER || !INPUTSYSTEM_AVAILABLE
227  Player.ScanForNewPlayers(); // Should only be executed once per frame
228  Wand.GetLatestInputs(); // Should only be executed once per frame
229 #endif
231  Display.ApplyGraphicsSettings(graphicsSettings);
232 
233  for (int i = 0; i < supportedPlayerCount; i++)
234  {
235  var playerSettings = allPlayerSettings[i];
236  if (playerSettings != null)
237  {
238  Player.Update(playerSettings, spectatorSettings);
239  }
240  }
241 
242  var spectatedPlayer = spectatorSettings.spectatedPlayer;
243  if (Glasses.TryGetPreviewPose(spectatedPlayer, out var spectatedPlayerPose))
244  {
245  spectatorSettings.spectatorCamera?.transform.SetPositionAndRotation(
246  spectatedPlayerPose.position,
247  spectatedPlayerPose.rotation);
248  }
249 
250 #if UNITY_2019_1_OR_NEWER && INPUTSYSTEM_AVAILABLE
251  var devices = InputUser.GetUnpairedInputDevices();
252  if (devices.Count > 0)
253  {
254  foreach (InputDevice dev in devices)
255  {
256  if (dev is WandDevice)
257  {
258  var headPoseRoot = Glasses.GetPoseRoot(((WandDevice)dev).playerIndex);
259 
260  if (headPoseRoot != null)
261  {
262  var playerInput = headPoseRoot.GetComponentInChildren<PlayerInput>();
263 
264  if (playerInput != null && playerInput.user.valid)
265  {
266  Log.Warn($"Unpaired Wand Device [{((WandDevice)dev).ControllerIndex}] found and paired to Player [{((WandDevice)dev).playerIndex}].");
267  InputUser.PerformPairingWithDevice(dev, playerInput.user);
268  playerInput.user.ActivateControlScheme("XR");
269  }
270  }
271  }
272  }
273  }
274 #endif
275  }
276 
277 
281  void LateUpdate()
282  {
283  // Trackables should be updated just before rendering occurs,
284  // after all Update() calls are completed.
285  // This allows any Game Board movements to be finished before we base the
286  // Glasses/Wand poses off of its pose, preventing perceived jittering.
287  for (int i = 0; i < supportedPlayerCount; i++)
288  {
289  var playerSettings = allPlayerSettings[i];
290  if (playerSettings != null)
291  {
292  Player.Update(playerSettings, spectatorSettings);
293  }
294  }
295  }
296 
308  public bool NeedsDriverUpdate()
309  {
311  {
312  try
313  {
314  ServiceCompatibility compatibility = SystemControl.GetServiceCompatibility();
315  bool needsUpdate = compatibility == ServiceCompatibility.Incompatible;
316 
317  if (needsUpdate)
318  {
320  {
321  Log.Warn("Incompatible Tilt Five service. Please update driver package.");
323  }
324  }
325  else
326  {
327  // Not incompatible. Reset the incompatibility warning.
329  }
330  return needsUpdate;
331  }
332  catch (System.DllNotFoundException e)
333  {
334  Log.Info(
335  "Could not connect to Tilt Five plugin for compatibility check: {0}",
336  e.Message);
338  }
339  catch (System.Exception e)
340  {
341  Log.Error(e.Message);
343  }
344  }
345 
346  // Failed to communicate with Tilt Five plugin at some point, so don't know whether
347  // an update is needed or not. Just say no.
348  return false;
349  }
350 
357  public bool TryGetPlayerSettings(PlayerIndex playerIndex, out PlayerSettings playerSettings)
358  {
359  switch (playerIndex)
360  {
361  case PlayerIndex.One:
362  playerSettings = playerOneSettings;
363  return true;
364  case PlayerIndex.Two:
365  playerSettings = playerTwoSettings;
366  return true;
367  case PlayerIndex.Three:
368  playerSettings = playerThreeSettings;
369  return true;
370  case PlayerIndex.Four:
371  playerSettings = playerFourSettings;
372  return true;
373  default:
374  playerSettings = null;
375  return false;
376  }
377  }
378 
379 #if UNITY_2019_1_OR_NEWER && INPUTSYSTEM_AVAILABLE
380  internal void RefreshInputDevicePairings()
381  {
382  foreach (WandDevice wand in Input.wandDevices)
383  {
384  PlayerInput playerInput = null;
385  if (wand != null)
386  {
387  playerInput = PlayerInput.GetPlayerByIndex(playerIndexMapping[(int)wand.playerIndex - 1]);
388  if (playerInput != null)
389  {
390  InputUser.PerformPairingWithDevice(wand, playerInput.user);
391  }
392  }
393  }
394  foreach (GlassesDevice glasses in Input.glassesDevices)
395  {
396  PlayerInput playerInput = null;
397  if(glasses != null)
398  {
399  playerInput = PlayerInput.GetPlayerByIndex(playerIndexMapping[(int)glasses.PlayerIndex - 1]);
400  if (playerInput != null)
401  {
402  InputUser.PerformPairingWithDevice(glasses, playerInput.user);
403  }
404  }
405  }
406  }
407 
408  internal void ReassignPlayerIndexMapping(int[] mapping)
409  {
410  if(mapping.Length != 4)
411  {
412  throw new System.ArgumentException("Invalid player index mapping argument - mapping should be 4 values long");
413  }
414  for (var i = 0; i < mapping.Length; i++)
415  {
416  if(mapping[i] < 0)
417  {
418  throw new System.ArgumentException("Invalid player index mapping argument - mapping should contain positive values only");
419  }
420  for (var j = 0; j < i; j++)
421  {
422  if (mapping[i] == mapping[j])
423  {
424  throw new System.ArgumentException("Invalid player index mapping argument - mapping should contain no duplicates");
425  }
426  }
427  }
428  playerIndexMapping = mapping;
429  RefreshInputDevicePairings();
430  }
431 #endif //UNITY_2019_1_OR_NEWER && INPUTSYSTEM_AVAILABLE
432 
433 
434 
438  private void OnEnable()
439  {
440  try
441  {
442  // TODO: change this to something in the settings drawer once that exists
443  NativePlugin.SetMaxDesiredGlasses((byte)GlassesSettings.MAX_SUPPORTED_GLASSES_COUNT);
444  }
445  catch (System.DllNotFoundException e)
446  {
447  Log.Info(
448  "Could not connect to Tilt Five plugin for setting max glasses: {0}",
449  e.Message);
450  }
451  catch (System.Exception e)
452  {
453  Log.Error(e.Message);
454  }
455 
456  // Initialize the player settings if necessary
457  for (int i = 0; i < allPlayerSettings.Length; i++)
458  {
459  var currentPlayerSettings = allPlayerSettings[i];
460  if (currentPlayerSettings == null)
461  {
463  }
464  }
465 
466  for (int i = 0; i < supportedPlayerCount; i++)
467  {
468  var playerSettings = allPlayerSettings[i];
469  if (playerSettings != null)
470  {
471  Player.Reset(playerSettings, spectatorSettings);
472  }
473  }
474 
475 #if UNITY_2019_1_OR_NEWER && INPUTSYSTEM_AVAILABLE
476  InputSystem.onBeforeUpdate += OnBeforeUpdate;
477 #endif
478  }
479 
480  private void OnDisable()
481  {
482 #if UNITY_2019_1_OR_NEWER && INPUTSYSTEM_AVAILABLE
483  InputSystem.onBeforeUpdate -= OnBeforeUpdate;
484  //Input.OnDisable();
485 #endif
486  Player.OnDisable();
487  }
488 
489  private void OnDestroy()
490  {
491  Player.OnDisable();
492  }
493 
494  private void OnApplicationQuit()
495  {
496  OnDisable();
497  }
498 
499  // There's a longstanding bug where UnityPluginUnload isn't called.
500  // - https://forum.unity.com/threads/unitypluginunload-never-called.414066/
501  // - https://gamedev.stackexchange.com/questions/200118/unity-native-plugin-unitypluginload-is-called-but-unitypluginunload-is-not
502  // - https://issuetracker.unity3d.com/issues/unitypluginunload-is-never-called-in-a-standalone-build
503  // Work around this by invoking it via Application.quitting.
504  private static void Quit()
505  {
506  try
507  {
508  NativePlugin.UnloadWorkaround();
509  }
510  catch (System.DllNotFoundException)
511  {
512  // nothing to report on quit if the plugin isn't present
513  }
514  catch (System.Exception e)
515  {
516  Log.Error(e.Message);
517  }
518  }
519 
520  [RuntimeInitializeOnLoadMethod]
521  private static void RunOnStart()
522  {
523  Application.quitting += Quit;
524  }
525 
527  {
528  // Warn developers if they've left the spectatorCamera field empty
529  // TiltFiveManager2's custom inspector should already warn them in the editor, but this warns them again at runtime.
531  {
532  Log.Warn("No spectator camera detected in TiltFiveManager2's spectator settings. A spectator camera is required.");
533  }
534 
535  // Make sure that the spectated player isn't set to a player index higher than what TiltFiveManager2 supports
536  var highestSupportedPlayer = (PlayerIndex)supportedPlayerCount;
537  if (spectatorSettings.spectatedPlayer > highestSupportedPlayer)
538  {
539  Log.Warn($"Invalid spectatorSettings.spectatedPlayer [{spectatorSettings.spectatedPlayer}]. TiltFiveManager2 currently only supports up to Player {highestSupportedPlayer}.");
540  spectatorSettings.spectatedPlayer = highestSupportedPlayer;
541  }
542  }
543 
544 #if UNITY_EDITOR
545 
549  void OnValidate()
550  {
551  // Don't do any validation if we're in the middle of copying settings.
553  {
554  return;
555  }
556 
559 
564 
566 
567  for (int i = 0; i < allPlayerSettings.Length; i++)
568  {
569  var playerSettings = allPlayerSettings[i];
570  if (playerSettings != null)
571  {
572  Player.Validate(playerSettings);
573  playerSettings.glassesSettings.glassesMirrorMode = spectatorSettings.glassesMirrorMode;
574  }
575  }
577  }
578 
582  void OnDrawGizmos()
583  {
584  if (!enabled)
585  {
586  return;
587  }
588 
589  renderedGameboards.Clear();
590 
591  for (int i = 0; i < supportedPlayerCount; i++)
592  {
593  var playerSettings = allPlayerSettings[i];
594  if (playerSettings != null)
595  {
596  var currentGameboard = playerSettings.gameboardSettings.currentGameBoard;
597  if (!renderedGameboards.Contains(currentGameboard))
598  {
599  renderedGameboards.Add(currentGameboard);
600  Player.DrawGizmos(playerSettings);
601  }
602  }
603  }
604  }
605 
606  public static void CreateFromTiltFiveManager(TiltFiveManager tiltFiveManager)
607  {
608  var parentGameObject = tiltFiveManager.gameObject;
609 
610  // Ideally, we only want one TiltFiveManager2 in the scene.
611  // If the developer clicks the upgrade button repeatedly, we don't want to keep creating more of them.
612  // In this scenario, ask the developer whether they'd like to overwrite the settings on the existing
613  // TiltFiveManager2 with the TiltFiveManager's settings.
614  var isTiltFiveManager2AlreadyPresent = parentGameObject.TryGetComponent<TiltFiveManager2>(out var existingTiltFiveManager2);
615  var confirmationDialogTitle = "Existing TiltFiveManager2 detected";
616  var confirmationDialogText = $"The GameObject \"{parentGameObject.name}\" already has a TiltFiveManager2 component." +
617  System.Environment.NewLine + System.Environment.NewLine +
618  "Overwrite the existing TiltFiveManager2 component values?" +
619  System.Environment.NewLine + System.Environment.NewLine +
620  "Warning: This cannot be undone via Edit > Undo (Ctrl+Z)";
621  var confirmButtonLabel = "Overwrite";
622  var cancelButtonLabel = "Cancel";
623  var overwriteExistingTiltFiveManager2 = isTiltFiveManager2AlreadyPresent
624  && UnityEditor.EditorUtility.DisplayDialog(confirmationDialogTitle, confirmationDialogText, confirmButtonLabel, cancelButtonLabel);
625 
626  if(isTiltFiveManager2AlreadyPresent && !overwriteExistingTiltFiveManager2)
627  {
628  Debug.Log($"Aborted attempt to upgrade TiltFiveManager.");
629  return;
630  }
631 
632  upgradeInProgress = true;
633 
634  TiltFiveManager2 tiltFiveManager2 = overwriteExistingTiltFiveManager2
635  ? existingTiltFiveManager2
636  : parentGameObject.AddComponent<TiltFiveManager2>();
637 
638  // Disable the old TiltFiveManager.
639  tiltFiveManager.enabled = false;
640 
641  // Default to supporting a single player, just like TiltFiveManager did.
642  tiltFiveManager2.supportedPlayerCount = 1;
643 
644  // Copy the various settings objects from TiltFiveManager to playerOneSettings.
645  tiltFiveManager2.playerOneSettings.glassesSettings = tiltFiveManager.glassesSettings.Copy();
646 
647  tiltFiveManager2.playerOneSettings.scaleSettings = tiltFiveManager.scaleSettings.Copy();
648 
649  tiltFiveManager2.playerOneSettings.gameboardSettings = tiltFiveManager.gameBoardSettings.Copy();
650 
651  tiltFiveManager2.playerOneSettings.leftWandSettings = tiltFiveManager.leftWandSettings.Copy();
652  tiltFiveManager2.playerOneSettings.rightWandSettings = tiltFiveManager.rightWandSettings.Copy();
653 
654  // Emulate TiltFiveManager, which used a single camera internally for eye camera cloning and onscreen previews
655  tiltFiveManager2.spectatorSettings.spectatorCamera = tiltFiveManager.glassesSettings.cameraTemplate;
656 
657  // Copy TiltFiveManager's GlassesSettings' mirror mode, which has moved to SpectatorSettings for TiltFiveManager2
658  tiltFiveManager2.spectatorSettings.glassesMirrorMode = tiltFiveManager.glassesSettings.glassesMirrorMode;
659 
660  // For the sake of thoroughness, let's copy the old log settings, too.
661  tiltFiveManager2.logSettings = tiltFiveManager.logSettings.Copy();
662 
663  upgradeInProgress = false;
664 
665  var resultText = overwriteExistingTiltFiveManager2
666  ? $"Successfully overwrote component values on the existing TiltFiveManager2 component attached to \"{parentGameObject.name}\" using the old TiltFiveManager component values."
667  : $"Successfully attached a new TiltFiveManager2 component to \"{parentGameObject.name}\" and imported the old TiltFiveManager component values.";
668  Debug.Log($"{resultText}{System.Environment.NewLine}The old TiltFiveManager has been disabled - it can safely be removed.");
669  }
670 
671 #endif
672 
673  #region ISceneInfo Implementation
674 
675  public float GetScaleToUWRLD_UGBD()
676  {
678  }
679 
680  public Pose GetGameboardPose()
681  {
683  }
684 
685  public Camera GetEyeCamera()
686  {
687  return Glasses.GetLeftEye(PlayerIndex.One);
688  }
689 
691  {
692  return supportedPlayerCount;
693  }
694 
695  public bool IsActiveAndEnabled()
696  {
697  return isActiveAndEnabled;
698  }
699 
700  #endregion ISceneInfo Implementation
701  }
702 
703 }
Vector3 gameBoardRotation
The game board rotation or focal rotational offset.
Vector3 gameBoardCenter
The game board position or focal position offset.
GameBoard currentGameBoard
The game board is the window into the game world, as well as the origin about which the glasses/wand ...
float gameBoardScale
The game board's scale multiplies the perceived size of objects in the scene.
The Glasses API and runtime.
Definition: Glasses.cs:35
static Camera GetLeftEye(PlayerIndex playerIndex)
Gets the left eye camera for the specified player's glasses.
Definition: Glasses.cs:268
static bool TryGetPreviewPose(PlayerIndex playerIndex, out Pose pose)
Attempts to get the position and orientation of the specified player's glasses, smoothed for on-scree...
Definition: Glasses.cs:233
static GameObject GetPoseRoot(PlayerIndex playerIndex)
Gets the pose root GameObject for the specified player.
Definition: Glasses.cs:254
GlassesSettings encapsulates all configuration data used by the Glasses' tracking runtime to compute ...
Camera cameraTemplate
The camera used as a template for creating the eye cameras at runtime.
GlassesMirrorMode glassesMirrorMode
GraphicsSettings encapsulates configuration data related to the project's graphics settings,...
Provides access to Wand inputs
Definition: Input.cs:31
The Logger.
Definition: Log.cs:42
static void Warn(string m, params object[] list)
WARN logging function call.
Definition: Log.cs:166
static void Info(string m, params object[] list)
INFO logging function call.
Definition: Log.cs:140
static void Error(string m, params object[] list)
ERROR logging function call.
Definition: Log.cs:127
static int LogLevel
Gets or sets the logging level.
Definition: Log.cs:68
static string TAG
Gets or sets the logging tag.
Definition: Log.cs:58
Provides access to player settings and functionality.
Definition: Player.cs:31
ScaleSettings scaleSettings
GameBoardSettings gameboardSettings
static uint MAX_SUPPORTED_PLAYERS
WandSettings leftWandSettings
GlassesSettings glassesSettings
WandSettings rightWandSettings
float GetScaleToUWRLD_UGBD(float gameboardScale)
PlayerIndex spectatedPlayer
The player that will have their perspective mirrored on screen.
GlassesMirrorMode glassesMirrorMode
The spectator camera will display content on screen depending on the mirroring mode....
Camera spectatorCamera
The camera used for rendering the onscreen preview
The Tilt Five manager.
LogSettings logSettings
The log settings.
PlayerSettings[] allPlayerSettings
bool NeedsDriverUpdate()
Check if a driver update is needed.
SpectatorSettings spectatorSettings
The spectator camera's runtime configuration data.
void OnEnable()
Called when the GameObject is enabled.
override void Awake()
Awake this instance.
void Update()
Update this instance.
GraphicsSettings graphicsSettings
Project-wide graphics settings related to Tilt Five.
PlayerSettings playerFourSettings
The fourth player's runtime configuration data.
PlayerSettings playerOneSettings
The first player's runtime configuration data.
bool TryGetPlayerSettings(PlayerIndex playerIndex, out PlayerSettings playerSettings)
Gets the player settings for the specified player.
void LateUpdate()
Update this instance after all components have finished executing their Update() functions.
PlayerSettings playerTwoSettings
The second player's runtime configuration data.
PlayerSettings playerThreeSettings
The third player's runtime configuration data.
The Tilt Five manager.
LogSettings logSettings
The log settings.
GameBoardSettings gameBoardSettings
The game board runtime configuration data.
WandSettings rightWandSettings
The wand runtime configuration data for the right hand wand.
ScaleSettings scaleSettings
The scale conversion runtime configuration data.
GlassesSettings glassesSettings
The glasses runtime configuration data.
WandSettings leftWandSettings
The wand runtime configuration data for the left hand wand.
The Wand API and runtime.
Definition: Wand.cs:56
Definition: Log.cs:21
ServiceCompatibility
Whether the running service is compatible.
PlayerIndex
The Player index (e.g. Player One, Player Two, etc)