// 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();
}
}
}
}
}