using UnityEngine; using UnityEngine.Serialization; using System.Collections; using System; #if UNITY_2017_2_OR_NEWER using UnityEngine.XR; #endif namespace LIV.SDK.Unity { [System.Flags] public enum INVALIDATION_FLAGS : uint { NONE = 0, HMD_CAMERA = 1, STAGE = 2, MR_CAMERA_PREFAB = 4, EXCLUDE_BEHAVIOURS = 8 } /// /// The LIV SDK provides a spectator view of your application. /// /// /// It contextualizes what the user feels & experiences by capturing their body directly inside your world! /// Thanks to our software, creators can film inside your app and have full control over the camera. /// With the power of out-of-engine compositing, a creator can express themselves freely without limits; /// as a real person or an avatar! /// /// /// /// public class StartFromScriptExample : MonoBehaviour /// { /// [SerializeField] Camera _hmdCamera; /// [SerializeField] Transform _stage; /// [SerializeField] Transform _stageTransform; /// [SerializeField] Camera _mrCameraPrefab; /// LIV.SDK.Unity.LIV _liv; /// private void OnEnable() /// { /// _liv = gameObject.AddComponent(); /// _liv.HMDCamera = _hmdCamera; /// _liv.stage = _stage; /// _liv.stageTransform = _stageTransform; /// _liv.MRCameraPrefab = _mrCameraPrefab; /// } /// private void OnDisable() /// { /// if (_liv == null) return; /// Destroy(_liv); /// _liv = null; /// } /// } /// /// [HelpURL("https://liv.tv/sdk-unity-docs")] [AddComponentMenu("LIV/LIV")] public class LIV : MonoBehaviour { /// /// triggered when the LIV SDK is activated by the LIV App and enabled by the game. /// public System.Action onActivate = null; /// /// triggered before the Mixed Reality camera is about to render. /// public System.Action onPreRender = null; /// /// triggered before the LIV SDK starts rendering background image. /// public System.Action onPreRenderBackground = null; /// /// triggered after the LIV SDK starts rendering background image. /// public System.Action onPostRenderBackground = null; /// /// triggered before the LIV SDK starts rendering the foreground image. /// public System.Action onPreRenderForeground = null; /// /// triggered after the LIV SDK starts rendering the foreground image. /// public System.Action onPostRenderForeground = null; /// /// triggered after the Mixed Reality camera has finished rendering. /// public System.Action onPostRender = null; /// /// triggered when the LIV SDK is deactivated by the LIV App or disabled by the game. /// public System.Action onDeactivate = null; [Tooltip("This is the topmost transform of your VR rig.")] [FormerlySerializedAs("TrackedSpaceOrigin")] [SerializeField] Transform _stage = null; /// /// This is the topmost transform of your VR rig. /// /// /// When implementing VR locomotion(teleporting, joystick, etc), /// this is the GameObject that you should move around your scene. /// It represents the centre of the user’s playspace. /// public Transform stage { get { return _stage == null ? transform.parent : _stage; } set { if (value == null) { Debug.LogWarning("LIV: Stage cannot be null!"); } if (_stage != value) { _stageCandidate = value; _invalidate = (INVALIDATION_FLAGS)SDKUtils.SetFlag((uint)_invalidate, (uint)INVALIDATION_FLAGS.STAGE, true); } } } [Obsolete("Use stage instead")] public Transform trackedSpaceOrigin { get { return stage; } set { stage = value; } } public Matrix4x4 stageLocalToWorldMatrix { get { return (stage != null) ? stage.localToWorldMatrix : Matrix4x4.identity; } } public Matrix4x4 stageWorldToLocalMatrix { get { return (stage != null) ? stage.worldToLocalMatrix : Matrix4x4.identity; } } [Tooltip("This transform is an additional wrapper to the user’s playspace.")] [FormerlySerializedAs("StageTransform")] [SerializeField] Transform _stageTransform = null; /// /// This transform is an additional wrapper to the user’s playspace. /// /// /// It allows for user-controlled transformations for special camera effects & transitions. /// If a creator is using a static camera, this transformation can give the illusion of camera movement. /// public Transform stageTransform { get { return _stageTransform; } set { _stageTransform = value; } } [Tooltip("This is the camera responsible for rendering the user’s HMD.")] [FormerlySerializedAs("HMDCamera")] [SerializeField] Camera _HMDCamera = null; /// /// This is the camera responsible for rendering the user’s HMD. /// /// /// The LIV SDK, by default clones this object to match your application’s rendering setup. /// You can use your own camera prefab should you want to! /// public Camera HMDCamera { get { return _HMDCamera; } set { if (value == null) { Debug.LogWarning("LIV: HMD Camera cannot be null!"); } if (_HMDCamera != value) { _HMDCameraCandidate = value; _invalidate = (INVALIDATION_FLAGS)SDKUtils.SetFlag((uint)_invalidate, (uint)INVALIDATION_FLAGS.HMD_CAMERA, true); } } } [Tooltip("Camera prefab for customized rendering.")] [FormerlySerializedAs("MRCameraPrefab")] [SerializeField] Camera _MRCameraPrefab = null; /// /// Camera prefab for customized rendering. /// /// /// By default, LIV uses the HMD camera as a reference for the Mixed Reality camera. /// It is cloned and set up as a Mixed Reality camera.This approach works for most apps. /// However, some games can experience issues because of custom MonoBehaviours attached to this camera. /// You can use a custom camera prefab for those cases. /// public Camera MRCameraPrefab { get { return _MRCameraPrefab; } set { if (_MRCameraPrefab != value) { _MRCameraPrefabCandidate = value; _invalidate = (INVALIDATION_FLAGS)SDKUtils.SetFlag((uint)_invalidate, (uint)INVALIDATION_FLAGS.MR_CAMERA_PREFAB, true); } } } [Tooltip("This option disables all standard Unity assets for the Mixed Reality rendering.")] [FormerlySerializedAs("DisableStandardAssets")] [SerializeField] bool _disableStandardAssets = false; /// /// This option disables all standard Unity assets for the Mixed Reality rendering. /// /// /// Unity’s standard assets can interfere with the alpha channel that LIV needs to composite MR correctly. /// public bool disableStandardAssets { get { return _disableStandardAssets; } set { _disableStandardAssets = value; } } [Tooltip("The layer mask defines exactly which object layers should be rendered in MR.")] [FormerlySerializedAs("SpectatorLayerMask")] [SerializeField] LayerMask _spectatorLayerMask = ~0; /// /// The layer mask defines exactly which object layers should be rendered in MR. /// /// /// You should use this to hide any in-game avatar that you’re using. /// LIV is meant to include the user’s body for you! /// Certain HMD-based effects should be disabled here too. /// Also, this can be used to render special effects or additional UI only to the MR camera. /// Useful for showing the player’s health, or current score! /// public LayerMask spectatorLayerMask { get { return _spectatorLayerMask; } set { _spectatorLayerMask = value; } } [Tooltip("This is for removing unwanted scripts from the cloned MR camera.")] [FormerlySerializedAs("ExcludeBehaviours")] [SerializeField] string[] _excludeBehaviours = new string[] { "AudioListener", "Collider", "SteamVR_Camera", "SteamVR_Fade", "SteamVR_ExternalCamera" }; /// /// This is for removing unwanted scripts from the cloned MR camera. /// /// /// By default, we remove the AudioListener, Colliders and SteamVR scripts, as these are not necessary for rendering MR! /// The excluded string must match the name of the MonoBehaviour. /// public string[] excludeBehaviours { get { return _excludeBehaviours; } set { if (_excludeBehaviours != value) { _excludeBehavioursCandidate = value; _invalidate = (INVALIDATION_FLAGS)SDKUtils.SetFlag((uint)_invalidate, (uint)INVALIDATION_FLAGS.EXCLUDE_BEHAVIOURS, true); } } } /// /// Recovers corrupted alpha channel when using post-effects. /// /// [Tooltip("Recovers corrupted alpha channel when using post-effects.")] [FormerlySerializedAs("FixPostEffectsAlpha")] [SerializeField] private bool _fixPostEffectsAlpha = false; public bool fixPostEffectsAlpha { get { return _fixPostEffectsAlpha; } set { _fixPostEffectsAlpha = value; } } /// /// Is the curret LIV SDK setup valid. /// public bool isValid { get { if (_invalidate != INVALIDATION_FLAGS.NONE) return false; if (_HMDCamera == null) { Debug.LogError("LIV: HMD Camera is a required parameter!"); return false; } if (_stage == null) { Debug.LogWarning("LIV: Tracked space origin should be assigned!"); } if (_spectatorLayerMask == 0) { Debug.LogWarning("LIV: The spectator layer mask is set to not show anything. Is this correct?"); } return true; } } bool _isActive = false; /// /// Is the LIV SDK currently active. /// public bool isActive { get { return _isActive; } } private bool _isReady { get { return isValid && _enabled && SDKBridge.IsActive; } } private SDKRender _render = null; /// /// Script responsible for the MR rendering. /// public SDKRender render { get { return _render; } } private bool _wasReady = false; private INVALIDATION_FLAGS _invalidate = INVALIDATION_FLAGS.NONE; private Transform _stageCandidate = null; private Camera _HMDCameraCandidate = null; private Camera _MRCameraPrefabCandidate = null; private string[] _excludeBehavioursCandidate = null; private bool _enabled = false; private Coroutine _waitForEndOfFrameCoroutine; void OnEnable() { _enabled = true; UpdateSDKReady(); } void Update() { UpdateSDKReady(); Invalidate(); } void OnDisable() { _enabled = false; UpdateSDKReady(); } IEnumerator WaitForUnityEndOfFrame() { while (Application.isPlaying && enabled) { yield return new WaitForEndOfFrame(); if (isActive) { _render.Render(); } } } void UpdateSDKReady() { bool ready = _isReady; if (ready != _wasReady) { OnSDKReadyChanged(ready); _wasReady = ready; } } void OnSDKReadyChanged(bool value) { if (value) { OnSDKActivate(); } else { OnSDKDeactivate(); } } void OnSDKActivate() { Debug.Log("LIV: Compositor connected, setting up Mixed Reality!"); SubmitSDKOutput(); CreateAssets(); StartRenderCoroutine(); _isActive = true; if (onActivate != null) onActivate.Invoke(); } void OnSDKDeactivate() { Debug.Log("LIV: Compositor disconnected, cleaning up Mixed Reality."); if (onDeactivate != null) onDeactivate.Invoke(); StopRenderCoroutine(); DestroyAssets(); _isActive = false; } void CreateAssets() { DestroyAssets(); _render = new SDKRender(this); } void DestroyAssets() { if (_render != null) { _render.Dispose(); _render = null; } } void StartRenderCoroutine() { StopRenderCoroutine(); _waitForEndOfFrameCoroutine = StartCoroutine(WaitForUnityEndOfFrame()); } void StopRenderCoroutine() { if (_waitForEndOfFrameCoroutine != null) { StopCoroutine(_waitForEndOfFrameCoroutine); _waitForEndOfFrameCoroutine = null; } } void SubmitSDKOutput() { SDKApplicationOutput output = SDKApplicationOutput.empty; output.supportedFeatures = FEATURES.BACKGROUND_RENDER | FEATURES.FOREGROUND_RENDER | FEATURES.OVERRIDE_POST_PROCESSING | FEATURES.FIX_FOREGROUND_ALPHA; output.sdkID = SDKConstants.SDK_ID; output.sdkVersion = SDKConstants.SDK_VERSION; output.engineName = SDKConstants.ENGINE_NAME; output.engineVersion = Application.unityVersion; output.applicationName = Application.productName; output.applicationVersion = Application.version; output.graphicsAPI = SystemInfo.graphicsDeviceType.ToString(); #if UNITY_2017_2_OR_NEWER output.xrDeviceName = XRSettings.loadedDeviceName; #endif SDKBridge.SubmitApplicationOutput(output); } void Invalidate() { if (SDKUtils.ContainsFlag((uint)_invalidate, (uint)INVALIDATION_FLAGS.STAGE)) { _stage = _stageCandidate; _stageCandidate = null; _invalidate = (INVALIDATION_FLAGS)SDKUtils.SetFlag((uint)_invalidate, (uint)INVALIDATION_FLAGS.STAGE, false); } if (SDKUtils.ContainsFlag((uint)_invalidate, (uint)INVALIDATION_FLAGS.HMD_CAMERA)) { _HMDCamera = _HMDCameraCandidate; _HMDCameraCandidate = null; _invalidate = (INVALIDATION_FLAGS)SDKUtils.SetFlag((uint)_invalidate, (uint)INVALIDATION_FLAGS.HMD_CAMERA, false); } if (SDKUtils.ContainsFlag((uint)_invalidate, (uint)INVALIDATION_FLAGS.MR_CAMERA_PREFAB)) { _MRCameraPrefab = _MRCameraPrefabCandidate; _MRCameraPrefabCandidate = null; _invalidate = (INVALIDATION_FLAGS)SDKUtils.SetFlag((uint)_invalidate, (uint)INVALIDATION_FLAGS.MR_CAMERA_PREFAB, false); } if (SDKUtils.ContainsFlag((uint)_invalidate, (uint)INVALIDATION_FLAGS.EXCLUDE_BEHAVIOURS)) { _excludeBehaviours = _excludeBehavioursCandidate; _excludeBehavioursCandidate = null; _invalidate = (INVALIDATION_FLAGS)SDKUtils.SetFlag((uint)_invalidate, (uint)INVALIDATION_FLAGS.EXCLUDE_BEHAVIOURS, false); } } } }