// Copyright 2017 Google Inc. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #if UNITY_ANDROID && !UNITY_EDITOR #define RUNNING_ON_ANDROID_DEVICE #endif // UNITY_ANDROID && !UNITY_EDITOR namespace GoogleVR.Demos { using UnityEngine; using UnityEngine.UI; using System; #if UNITY_2017_2_OR_NEWER using UnityEngine.XR; #else using XRSettings = UnityEngine.VR.VRSettings; #endif // UNITY_2017_2_OR_NEWER public class DemoInputManager : MonoBehaviour { private const string MESSAGE_CANVAS_NAME = "MessageCanvas"; private const string MESSAGE_TEXT_NAME = "MessageText"; private const string LASER_GAMEOBJECT_NAME = "Laser"; private const string CONTROLLER_CONNECTING_MESSAGE = "Controller connecting..."; private const string CONTROLLER_DISCONNECTED_MESSAGE = "Controller disconnected"; private const string CONTROLLER_SCANNING_MESSAGE = "Controller scanning..."; private const string NON_GVR_PLATFORM = "Please select a supported Google VR platform via 'Build Settings > Android | iOS > Switch Platform'\n"; private const string VR_SUPPORT_NOT_CHECKED = "Please make sure 'Player Settings > Virtual Reality Supported' is checked\n"; private const string EMPTY_VR_SDK_WARNING_MESSAGE = "Please add 'Daydream' or 'Cardboard' under 'Player Settings > Virtual Reality SDKs'\n"; // Java class, method, and field constants. private const int ANDROID_MIN_DAYDREAM_API = 24; private const string FIELD_SDK_INT = "SDK_INT"; private const string PACKAGE_BUILD_VERSION = "android.os.Build$VERSION"; private const string PACKAGE_DAYDREAM_API_CLASS = "com.google.vr.ndk.base.DaydreamApi"; private const string METHOD_IS_DAYDREAM_READY = "isDaydreamReadyPlatform"; private bool isDaydream = false; private int activeControllerPointer = 0; private static GvrControllerHand[] AllHands = { GvrControllerHand.Right, GvrControllerHand.Left, }; [Tooltip("Reference to GvrControllerMain")] public GameObject controllerMain; public static string CONTROLLER_MAIN_PROP_NAME = "controllerMain"; [Tooltip("Reference to GvrControllerPointers")] public GameObject[] controllerPointers; public static string CONTROLLER_POINTER_PROP_NAME = "controllerPointers"; [Tooltip("Reference to GvrReticlePointer")] public GameObject reticlePointer; public static string RETICLE_POINTER_PROP_NAME = "reticlePointer"; public GameObject messageCanvas; public Text messageText; #if !RUNNING_ON_ANDROID_DEVICE public enum EmulatedPlatformType { Daydream, Cardboard } [Tooltip("Emulated GVR Platform")] public EmulatedPlatformType gvrEmulatedPlatformType = EmulatedPlatformType.Daydream; public static string EMULATED_PLATFORM_PROP_NAME = "gvrEmulatedPlatformType"; #else // Running on an Android device. private GvrSettings.ViewerPlatformType viewerPlatform; #endif // !RUNNING_ON_ANDROID_DEVICE void Start() { if (messageCanvas == null) { messageCanvas = transform.Find(MESSAGE_CANVAS_NAME).gameObject; if (messageCanvas != null) { messageText = messageCanvas.transform.Find(MESSAGE_TEXT_NAME).GetComponent(); } } // Message canvas will be enabled later when there's a message to display. messageCanvas.SetActive(false); #if !RUNNING_ON_ANDROID_DEVICE if (playerSettingsHasDaydream() || playerSettingsHasCardboard()) { // The list is populated with valid VR SDK(s), pick the first one. gvrEmulatedPlatformType = (XRSettings.supportedDevices[0] == GvrSettings.VR_SDK_DAYDREAM) ? EmulatedPlatformType.Daydream : EmulatedPlatformType.Cardboard; } isDaydream = (gvrEmulatedPlatformType == EmulatedPlatformType.Daydream); #else // Running on an Android device. viewerPlatform = GvrSettings.ViewerPlatform; // First loaded device in Player Settings. string vrDeviceName = XRSettings.loadedDeviceName; if (vrDeviceName != GvrSettings.VR_SDK_CARDBOARD && vrDeviceName != GvrSettings.VR_SDK_DAYDREAM) { Debug.LogErrorFormat("Loaded device was '{0}', must be one of '{1}' or '{2}'", vrDeviceName, GvrSettings.VR_SDK_DAYDREAM, GvrSettings.VR_SDK_CARDBOARD); return; } // On a non-Daydream ready phone, fall back to Cardboard if it's present in the list of // enabled VR SDKs. // On a Daydream-ready phone, go into Cardboard mode if it's the currently-paired viewer. if ((!IsDeviceDaydreamReady() && playerSettingsHasCardboard()) || (IsDeviceDaydreamReady() && playerSettingsHasCardboard() && GvrSettings.ViewerPlatform == GvrSettings.ViewerPlatformType.Cardboard)) { vrDeviceName = GvrSettings.VR_SDK_CARDBOARD; } isDaydream = (vrDeviceName == GvrSettings.VR_SDK_DAYDREAM); #endif // !RUNNING_ON_ANDROID_DEVICE SetVRInputMechanism(); } // Runtime switching enabled only in-editor. void Update() { UpdateStatusMessage(); // Scan all devices' buttons for button down, and switch the singleton pointer // to the controller the user last clicked. int newPointer = activeControllerPointer; if (controllerPointers.Length > 1 && controllerPointers[1] != null) { // Buttons that can trigger pointer switching. GvrControllerButton buttonMask = GvrControllerButton.App | GvrControllerButton.TouchPadButton; GvrTrackedController trackedController1 = controllerPointers[1].GetComponent(); foreach (var hand in AllHands) { GvrControllerInputDevice device = GvrControllerInput.GetDevice(hand); if (device.GetButtonDown(buttonMask)) { // Match the button to our own controllerPointers list. if (device == trackedController1.ControllerInputDevice) { newPointer = 1; } else { newPointer = 0; } break; } } } if (newPointer != activeControllerPointer) { activeControllerPointer = newPointer; SetVRInputMechanism(); } #if !RUNNING_ON_ANDROID_DEVICE UpdateEmulatedPlatformIfPlayerSettingsChanged(); if ((isDaydream && gvrEmulatedPlatformType == EmulatedPlatformType.Daydream) || (!isDaydream && gvrEmulatedPlatformType == EmulatedPlatformType.Cardboard)) { return; } isDaydream = (gvrEmulatedPlatformType == EmulatedPlatformType.Daydream); SetVRInputMechanism(); #else // Running on an Android device. // Viewer type switched at runtime. if (!IsDeviceDaydreamReady() || viewerPlatform == GvrSettings.ViewerPlatform) { return; } isDaydream = (GvrSettings.ViewerPlatform == GvrSettings.ViewerPlatformType.Daydream); viewerPlatform = GvrSettings.ViewerPlatform; SetVRInputMechanism(); #endif // !RUNNING_ON_ANDROID_DEVICE } public bool IsCurrentlyDaydream() { return isDaydream; } public static bool playerSettingsHasDaydream() { string[] playerSettingsVrSdks = XRSettings.supportedDevices; return Array.Exists(playerSettingsVrSdks, element => element.Equals(GvrSettings.VR_SDK_DAYDREAM)); } public static bool playerSettingsHasCardboard() { string[] playerSettingsVrSdks = XRSettings.supportedDevices; return Array.Exists(playerSettingsVrSdks, element => element.Equals(GvrSettings.VR_SDK_CARDBOARD)); } #if !RUNNING_ON_ANDROID_DEVICE private void UpdateEmulatedPlatformIfPlayerSettingsChanged() { if (!playerSettingsHasDaydream() && !playerSettingsHasCardboard()) { return; } // Player Settings > VR SDK list may have changed at runtime. The emulated platform // may not have been manually updated if that's the case. if (gvrEmulatedPlatformType == EmulatedPlatformType.Daydream && !playerSettingsHasDaydream()) { gvrEmulatedPlatformType = EmulatedPlatformType.Cardboard; } else if (gvrEmulatedPlatformType == EmulatedPlatformType.Cardboard && !playerSettingsHasCardboard()) { gvrEmulatedPlatformType = EmulatedPlatformType.Daydream; } } #endif // !RUNNING_ON_ANDROID_DEVICE #if RUNNING_ON_ANDROID_DEVICE // Running on an Android device. private static bool IsDeviceDaydreamReady() { // Check API level. using (var version = new AndroidJavaClass(PACKAGE_BUILD_VERSION)) { if (version.GetStatic(FIELD_SDK_INT) < ANDROID_MIN_DAYDREAM_API) { return false; } } // API level > 24, check whether the device is Daydream-ready.. AndroidJavaObject androidActivity = null; try { androidActivity = GvrActivityHelper.GetActivity(); } catch (AndroidJavaException e) { Debug.LogError("Exception while connecting to the Activity: " + e); return false; } AndroidJavaClass daydreamApiClass = new AndroidJavaClass(PACKAGE_DAYDREAM_API_CLASS); if (daydreamApiClass == null || androidActivity == null) { return false; } return daydreamApiClass.CallStatic(METHOD_IS_DAYDREAM_READY, androidActivity); } #endif // RUNNING_ON_ANDROID_DEVICE private void UpdateStatusMessage() { if (messageText == null || messageCanvas == null) { return; } #if !UNITY_ANDROID && !UNITY_IOS messageText.text = NON_GVR_PLATFORM; messageCanvas.SetActive(true); return; #else #if UNITY_EDITOR if (!UnityEditor.PlayerSettings.virtualRealitySupported) { messageText.text = VR_SUPPORT_NOT_CHECKED; messageCanvas.SetActive(true); return; } #endif // UNITY_EDITOR bool isVrSdkListEmpty = !playerSettingsHasCardboard() && !playerSettingsHasDaydream(); if (!isDaydream) { if (messageCanvas.activeSelf) { messageText.text = EMPTY_VR_SDK_WARNING_MESSAGE; messageCanvas.SetActive(isVrSdkListEmpty); } return; } string vrSdkWarningMessage = isVrSdkListEmpty ? EMPTY_VR_SDK_WARNING_MESSAGE : ""; string controllerMessage = ""; GvrPointerGraphicRaycaster graphicRaycaster = messageCanvas.GetComponent(); GvrControllerInputDevice dominantDevice = GvrControllerInput.GetDevice(GvrControllerHand.Dominant); GvrConnectionState connectionState = dominantDevice.State; // This is an example of how to process the controller's state to display a status message. switch (connectionState) { case GvrConnectionState.Connected: break; case GvrConnectionState.Disconnected: controllerMessage = CONTROLLER_DISCONNECTED_MESSAGE; messageText.color = Color.white; break; case GvrConnectionState.Scanning: controllerMessage = CONTROLLER_SCANNING_MESSAGE; messageText.color = Color.cyan; break; case GvrConnectionState.Connecting: controllerMessage = CONTROLLER_CONNECTING_MESSAGE; messageText.color = Color.yellow; break; case GvrConnectionState.Error: controllerMessage = "ERROR: " + dominantDevice.ErrorDetails; messageText.color = Color.red; break; default: // Shouldn't happen. Debug.LogError("Invalid controller state: " + connectionState); break; } messageText.text = string.Format("{0}\n{1}", vrSdkWarningMessage, controllerMessage); if (graphicRaycaster != null) { graphicRaycaster.enabled = !isVrSdkListEmpty || connectionState != GvrConnectionState.Connected; } messageCanvas.SetActive(isVrSdkListEmpty || (connectionState != GvrConnectionState.Connected)); #endif // !UNITY_ANDROID && !UNITY_IOS } private void SetVRInputMechanism() { SetGazeInputActive(!isDaydream); SetControllerInputActive(isDaydream); } private void SetGazeInputActive(bool active) { if (reticlePointer == null) { return; } reticlePointer.SetActive(active); // Update the pointer type only if this is currently activated. if (!active) { return; } GvrReticlePointer pointer = reticlePointer.GetComponent(); if (pointer != null) { GvrPointerInputModule.Pointer = pointer; } } private void SetControllerInputActive(bool active) { if (controllerMain != null) { controllerMain.SetActive(active); } if (controllerPointers == null || controllerPointers.Length <= activeControllerPointer) { return; } controllerPointers[activeControllerPointer].SetActive(active); // Update the pointer type only if this is currently activated. if (!active) { return; } GvrLaserPointer pointer = controllerPointers[activeControllerPointer].GetComponentInChildren(true); if (pointer != null) { GvrPointerInputModule.Pointer = pointer; } } } }