// SDK Setup|Utilities|90030 namespace VRTK { using UnityEngine; #if UNITY_EDITOR using UnityEditor; using UnityEditor.Callbacks; #endif using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; /// /// The SDK Setup describes a list of SDKs and game objects to use. /// public sealed class VRTK_SDKSetup : MonoBehaviour { public delegate void LoadEventHandler(VRTK_SDKManager sender, VRTK_SDKSetup setup); [Tooltip("Determines whether the SDK object references are automatically set to the objects of the selected SDKs. If this is true populating is done whenever the selected SDKs change.")] public bool autoPopulateObjectReferences = true; [Tooltip("A reference to the GameObject that is the user's boundary or play area, most likely provided by the SDK's Camera Rig.")] public GameObject actualBoundaries; [Tooltip("A reference to the GameObject that contains the VR camera, most likely provided by the SDK's Camera Rig Headset.")] public GameObject actualHeadset; [Tooltip("A reference to the GameObject that contains the SDK Left Hand Controller.")] public GameObject actualLeftController; [Tooltip("A reference to the GameObject that contains the SDK Right Hand Controller.")] public GameObject actualRightController; [Tooltip("A reference to the GameObject that models for the Left Hand Controller.")] public GameObject modelAliasLeftController; [Tooltip("A reference to the GameObject that models for the Right Hand Controller.")] public GameObject modelAliasRightController; public event LoadEventHandler Loaded; public event LoadEventHandler Unloaded; /// /// The info of the SDK to use to deal with all system actions. By setting this to `null` the fallback SDK will be used. /// public VRTK_SDKInfo systemSDKInfo { get { return cachedSystemSDKInfo; } set { value = value ?? VRTK_SDKInfo.Create()[0]; if (cachedSystemSDKInfo == value) { return; } #if UNITY_EDITOR DestroyImmediate(cachedSystemSDK); #else Destroy(cachedSystemSDK); #endif cachedSystemSDK = null; cachedSystemSDKInfo = new VRTK_SDKInfo(value); PopulateObjectReferences(false); } } /// /// The info of the SDK to use to utilize room scale boundaries. By setting this to `null` the fallback SDK will be used. /// public VRTK_SDKInfo boundariesSDKInfo { get { return cachedBoundariesSDKInfo; } set { value = value ?? VRTK_SDKInfo.Create()[0]; if (cachedBoundariesSDKInfo == value) { return; } #if UNITY_EDITOR DestroyImmediate(cachedBoundariesSDK); #else Destroy(cachedBoundariesSDK); #endif cachedBoundariesSDK = null; cachedBoundariesSDKInfo = new VRTK_SDKInfo(value); PopulateObjectReferences(false); } } /// /// The info of the SDK to use to utilize the VR headset. By setting this to `null` the fallback SDK will be used. /// public VRTK_SDKInfo headsetSDKInfo { get { return cachedHeadsetSDKInfo; } set { value = value ?? VRTK_SDKInfo.Create()[0]; if (cachedHeadsetSDKInfo == value) { return; } #if UNITY_EDITOR DestroyImmediate(cachedHeadsetSDK); #else Destroy(cachedHeadsetSDK); #endif cachedHeadsetSDK = null; cachedHeadsetSDKInfo = new VRTK_SDKInfo(value); PopulateObjectReferences(false); } } /// /// The info of the SDK to use to utilize the input devices. By setting this to `null` the fallback SDK will be used. /// public VRTK_SDKInfo controllerSDKInfo { get { return cachedControllerSDKInfo; } set { value = value ?? VRTK_SDKInfo.Create()[0]; if (cachedControllerSDKInfo == value) { return; } #if UNITY_EDITOR DestroyImmediate(cachedControllerSDK); #else Destroy(cachedControllerSDK); #endif cachedControllerSDK = null; cachedControllerSDKInfo = new VRTK_SDKInfo(value); PopulateObjectReferences(false); } } /// /// The selected system SDK. /// /// The currently selected system SDK. public SDK_BaseSystem systemSDK { get { if (cachedSystemSDK == null) { HandleSDKGetter("System", systemSDKInfo, VRTK_SDKManager.InstalledSystemSDKInfos); cachedSystemSDK = (SDK_BaseSystem)ScriptableObject.CreateInstance(systemSDKInfo.type); } return cachedSystemSDK; } } /// /// The selected boundaries SDK. /// /// The currently selected boundaries SDK. public SDK_BaseBoundaries boundariesSDK { get { if (cachedBoundariesSDK == null) { HandleSDKGetter("Boundaries", boundariesSDKInfo, VRTK_SDKManager.InstalledBoundariesSDKInfos); cachedBoundariesSDK = (SDK_BaseBoundaries)ScriptableObject.CreateInstance(boundariesSDKInfo.type); } return cachedBoundariesSDK; } } /// /// The selected headset SDK. /// /// The currently selected headset SDK. public SDK_BaseHeadset headsetSDK { get { if (cachedHeadsetSDK == null) { HandleSDKGetter("Headset", headsetSDKInfo, VRTK_SDKManager.InstalledHeadsetSDKInfos); cachedHeadsetSDK = (SDK_BaseHeadset)ScriptableObject.CreateInstance(headsetSDKInfo.type); } return cachedHeadsetSDK; } } /// /// The selected controller SDK. /// /// The currently selected controller SDK. public SDK_BaseController controllerSDK { get { if (cachedControllerSDK == null) { HandleSDKGetter("Controller", controllerSDKInfo, VRTK_SDKManager.InstalledControllerSDKInfos); cachedControllerSDK = (SDK_BaseController)ScriptableObject.CreateInstance(controllerSDKInfo.type); } return cachedControllerSDK; } } /// /// The VR device names used by the currently selected SDKs. /// public string[] usedVRDeviceNames { get { VRTK_SDKInfo[] infos = { systemSDKInfo, boundariesSDKInfo, headsetSDKInfo, controllerSDKInfo }; return infos.Select(info => info.description.vrDeviceName) .Distinct() .ToArray(); } } /// /// Whether it's possible to use the Setup. See `GetSimplifiedErrorDescriptions` for more info. /// public bool isValid { get { return GetSimplifiedErrorDescriptions().Length == 0; } } [SerializeField] private VRTK_SDKInfo cachedSystemSDKInfo = VRTK_SDKInfo.Create()[0]; [SerializeField] private VRTK_SDKInfo cachedBoundariesSDKInfo = VRTK_SDKInfo.Create()[0]; [SerializeField] private VRTK_SDKInfo cachedHeadsetSDKInfo = VRTK_SDKInfo.Create()[0]; [SerializeField] private VRTK_SDKInfo cachedControllerSDKInfo = VRTK_SDKInfo.Create()[0]; private SDK_BaseSystem cachedSystemSDK; private SDK_BaseBoundaries cachedBoundariesSDK; private SDK_BaseHeadset cachedHeadsetSDK; private SDK_BaseController cachedControllerSDK; /// /// The PopulateObjectReferences method populates the object references by using the currently set SDKs. /// /// Whether to ignore `autoPopulateObjectReferences` while deciding to populate. public void PopulateObjectReferences(bool force) { if (!(force || autoPopulateObjectReferences)) { return; } #if UNITY_EDITOR if (!EditorApplication.isPlaying && VRTK_SDKManager.ValidInstance()) { VRTK_SDKManager.instance.SetLoadedSDKSetupToPopulateObjectReferences(this); } #endif VRTK_SDK_Bridge.InvalidateCaches(); #if UNITY_EDITOR Undo.RecordObject(this, "Populate Object References"); #endif actualBoundaries = null; actualHeadset = null; actualLeftController = null; actualRightController = null; modelAliasLeftController = null; modelAliasRightController = null; Transform playAreaTransform = boundariesSDK.GetPlayArea(); Transform headsetTransform = headsetSDK.GetHeadset(); actualBoundaries = playAreaTransform == null ? null : playAreaTransform.gameObject; actualHeadset = headsetTransform == null ? null : headsetTransform.gameObject; actualLeftController = controllerSDK.GetControllerLeftHand(true); actualRightController = controllerSDK.GetControllerRightHand(true); modelAliasLeftController = controllerSDK.GetControllerModel(SDK_BaseController.ControllerHand.Left); modelAliasRightController = controllerSDK.GetControllerModel(SDK_BaseController.ControllerHand.Right); } /// /// The GetSimplifiedErrorDescriptions method checks the setup for errors and creates an array of error descriptions. /// /// /// The returned error descriptions handle the following cases for the current SDK infos: /// /// Its type doesn't exist anymore. /// It's a fallback SDK. /// It doesn't have its scripting define symbols added. /// It's missing its vendor SDK. /// /// Additionally the current SDK infos are checked whether they use multiple VR Devices. /// /// An array of all the error descriptions. Returns an empty array if no errors are found. public string[] GetSimplifiedErrorDescriptions() { List sdkErrorDescriptions = new List(); ReadOnlyCollection[] installedSDKInfosList = { VRTK_SDKManager.InstalledSystemSDKInfos, VRTK_SDKManager.InstalledBoundariesSDKInfos, VRTK_SDKManager.InstalledHeadsetSDKInfos, VRTK_SDKManager.InstalledControllerSDKInfos }; VRTK_SDKInfo[] currentSDKInfos = { systemSDKInfo, boundariesSDKInfo, headsetSDKInfo, controllerSDKInfo }; for (int index = 0; index < installedSDKInfosList.Length; index++) { ReadOnlyCollection installedSDKInfos = installedSDKInfosList[index]; VRTK_SDKInfo currentSDKInfo = currentSDKInfos[index]; Type baseType = VRTK_SharedMethods.GetBaseType(currentSDKInfo.type); if (baseType == null) { continue; } if (currentSDKInfo.originalTypeNameWhenFallbackIsUsed != null) { sdkErrorDescriptions.Add(string.Format("The SDK '{0}' doesn't exist anymore.", currentSDKInfo.originalTypeNameWhenFallbackIsUsed)); } else if (currentSDKInfo.description.describesFallbackSDK) { sdkErrorDescriptions.Add("A fallback SDK is used. Make sure to set a real SDK."); } else if (!installedSDKInfos.Contains(currentSDKInfo)) { sdkErrorDescriptions.Add(string.Format("The vendor SDK for '{0}' is not installed.", currentSDKInfo.description.prettyName)); } } if (usedVRDeviceNames.Except(new[] { "None" }).Count() > 1) { sdkErrorDescriptions.Add("The current SDK selection uses multiple VR Devices. It's not possible to use more than one VR Device at the same time."); } return sdkErrorDescriptions.Distinct().ToArray(); } /// /// The OnLoaded method determines when an SDK Setup has been loaded. /// /// The SDK Manager that has loaded the SDK Setup. public void OnLoaded(VRTK_SDKManager sender) { List sdkBases = new SDK_Base[] { systemSDK, boundariesSDK, headsetSDK, controllerSDK }.ToList(); sdkBases.ForEach(sdkBase => sdkBase.OnBeforeSetupLoad(this)); gameObject.SetActive(true); VRTK_SDK_Bridge.InvalidateCaches(); SetupHeadset(); SetupControllers(); boundariesSDK.InitBoundaries(); sdkBases.ForEach(sdkBase => sdkBase.OnAfterSetupLoad(this)); LoadEventHandler handler = Loaded; if (handler != null) { handler(sender, this); } } /// /// The OnUnloaded method determines when an SDK Setup has been unloaded. /// /// The SDK Manager that has unloaded the SDK Setup. public void OnUnloaded(VRTK_SDKManager sender) { List sdkBases = new SDK_Base[] { systemSDK, boundariesSDK, headsetSDK, controllerSDK }.ToList(); sdkBases.ForEach(sdkBase => sdkBase.OnBeforeSetupUnload(this)); gameObject.SetActive(false); sdkBases.ForEach(sdkBase => sdkBase.OnAfterSetupUnload(this)); LoadEventHandler handler = Unloaded; if (handler != null) { handler(sender, this); } } private void OnEnable() { #pragma warning disable 618 if (VRTK_SDKManager.ValidInstance() && !VRTK_SDKManager.instance.persistOnLoad) #pragma warning restore 618 { PopulateObjectReferences(false); } } #if UNITY_EDITOR static VRTK_SDKSetup() { //call AutoPopulateObjectReferences when the currently active scene changes #if UNITY_2018_1_OR_NEWER EditorApplication.hierarchyChanged += AutoPopulateObjectReferences; #else EditorApplication.hierarchyWindowChanged += AutoPopulateObjectReferences; #endif } [DidReloadScripts(2)] private static void AutoPopulateObjectReferences() { if (EditorApplication.isPlayingOrWillChangePlaymode) { return; } foreach (VRTK_SDKSetup setup in VRTK_SharedMethods.FindEvenInactiveComponents(true)) { setup.PopulateObjectReferences(false); } } #endif /// /// Handles the various SDK getters by logging potential errors. /// /// The SDK base type of which to handle the getter for. Must be a subclass of SDK_Base. /// The pretty name of the base SDK to use when logging errors. /// The SDK info of which the SDK getter was called. /// The installed SDK infos of which the SDK getter was called. private static void HandleSDKGetter(string prettyName, VRTK_SDKInfo info, IEnumerable installedInfos) where BaseType : SDK_Base { if (VRTK_SharedMethods.IsEditTime()) { return; } string sdkErrorDescription = GetSDKErrorDescription(prettyName, info, installedInfos); if (!string.IsNullOrEmpty(sdkErrorDescription)) { VRTK_Logger.Error(sdkErrorDescription); } } /// /// Returns an error description in case any of these are true for the current SDK info: /// /// Its type doesn't exist anymore. /// It's a fallback SDK. /// It doesn't have its scripting define symbols added. /// It's missing its vendor SDK. /// /// /// The SDK base type of which to return the error description for. Must be a subclass of SDK_Base. /// The pretty name of the base SDK to use when returning error descriptions. /// The SDK info of which to return the error description for. /// The installed SDK infos. /// An error description if there is one, else `null`. private static string GetSDKErrorDescription(string prettyName, VRTK_SDKInfo info, IEnumerable installedInfos) where BaseType : SDK_Base { Type selectedType = info.type; Type baseType = typeof(BaseType); Type fallbackType = VRTK_SDKManager.SDKFallbackTypesByBaseType[baseType]; if (selectedType == fallbackType) { return string.Format("The fallback {0} SDK is being used because there is no other {0} SDK set in the SDK Setup.", prettyName); } if (!VRTK_SharedMethods.IsTypeAssignableFrom(baseType, selectedType) || VRTK_SharedMethods.IsTypeAssignableFrom(fallbackType, selectedType)) { string description = string.Format("The fallback {0} SDK is being used despite being set to '{1}'.", prettyName, selectedType.Name); if (installedInfos.Select(installedInfo => installedInfo.type).Contains(selectedType)) { return description + " Its needed scripting define symbols are not added. You can click the GameObject with the `VRTK_SDKManager` script attached to it in Edit Mode and choose to automatically let the manager handle the scripting define symbols." + VRTK_Logger.GetCommonMessage(VRTK_Logger.CommonMessageKeys.SCRIPTING_DEFINE_SYMBOLS_NOT_FOUND); } return description + " The needed vendor SDK isn't installed."; } return null; } private void SetupHeadset() { if (actualHeadset != null && !actualHeadset.GetComponent()) { actualHeadset.AddComponent(); } } private void SetupControllers() { Action setParent = (scriptAliasGameObject, actualGameObject) => { if (scriptAliasGameObject == null) { return; } Transform scriptAliasTransform = scriptAliasGameObject.transform; Transform actualTransform = actualGameObject.transform; if (scriptAliasTransform.parent != actualTransform) { Vector3 previousScale = scriptAliasTransform.localScale; scriptAliasTransform.SetParent(actualTransform); scriptAliasTransform.localScale = previousScale; } scriptAliasTransform.localPosition = Vector3.zero; scriptAliasTransform.localRotation = Quaternion.identity; }; if (actualLeftController != null && VRTK_SDKManager.ValidInstance()) { setParent(VRTK_SDKManager.instance.scriptAliasLeftController, actualLeftController); if (actualLeftController.GetComponent() == null) { actualLeftController.AddComponent(); } } if (actualRightController != null && VRTK_SDKManager.ValidInstance()) { setParent(VRTK_SDKManager.instance.scriptAliasRightController, actualRightController); if (actualRightController.GetComponent() == null) { actualRightController.AddComponent(); } } } } }