1
This repository has been archived on 2025-03-15. You can view files and clone it, but cannot push or open issues or pull requests.
2025-03-15 20:02:21 +01:00

409 lines
14 KiB
C#

// Copyright 2016 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.
// The controller is not available for versions of Unity without the
// GVR native integration.
using UnityEngine;
#if UNITY_HAS_GOOGLEVR && (UNITY_ANDROID || UNITY_EDITOR)
using UnityEngine.VR;
using System;
using System.Collections;
using Gvr.Internal;
/// Represents the controller's current connection state.
/// All values and semantics below (except for Error) are
/// from gvr_types.h in the GVR C API.
public enum GvrConnectionState {
/// Indicates that an error has occurred.
Error = -1,
/// Indicates that the controller is disconnected.
Disconnected = 0,
/// Indicates that the device is scanning for controllers.
Scanning = 1,
/// Indicates that the device is connecting to a controller.
Connecting = 2,
/// Indicates that the device is connected to a controller.
Connected = 3,
};
/// Represents the API status of the current controller state.
/// Values and semantics from gvr_types.h in the GVR C API.
public enum GvrControllerApiStatus {
/// A Unity-localized error occurred.
/// This is the only value that isn't in gvr_types.h.
Error = -1,
/// API is happy and healthy. This doesn't mean the controller itself
/// is connected, it just means that the underlying service is working
/// properly.
Ok = 0,
/// Any other status represents a permanent failure that requires
/// external action to fix:
/// API failed because this device does not support controllers (API is too
/// low, or other required feature not present).
Unsupported = 1,
/// This app was not authorized to use the service (e.g., missing permissions,
/// the app is blacklisted by the underlying service, etc).
NotAuthorized = 2,
/// The underlying VR service is not present.
Unavailable = 3,
/// The underlying VR service is too old, needs upgrade.
ApiServiceObsolete = 4,
/// The underlying VR service is too new, is incompatible with current client.
ApiClientObsolete = 5,
/// The underlying VR service is malfunctioning. Try again later.
ApiMalfunction = 6,
};
/// Represents the controller's current battery level.
/// Values and semantics from gvr_types.h in the GVR C API.
public enum GvrControllerBatteryLevel {
/// A Unity-localized error occurred.
/// This is the only value that isn't in gvr_types.h.
Error = -1,
/// The battery state is currently unreported
Unknown = 0,
/// Equivalent to 1 out of 5 bars on the battery indicator
CriticalLow = 1,
/// Equivalent to 2 out of 5 bars on the battery indicator
Low = 2,
/// Equivalent to 3 out of 5 bars on the battery indicator
Medium = 3,
/// Equivalent to 4 out of 5 bars on the battery indicator
AlmostFull = 4,
/// Equivalent to 5 out of 5 bars on the battery indicator
Full = 5,
};
#endif // UNITY_HAS_GOOGLEVR && (UNITY_ANDROID || UNITY_EDITOR)
/// Main entry point for the Daydream controller API.
///
/// To use this API, add this behavior to a GameObject in your scene, or use the
/// GvrControllerMain prefab. There can only be one object with this behavior on your scene.
///
/// This is a singleton object.
///
/// To access the controller state, simply read the static properties of this class. For example,
/// to know the controller's current orientation, use GvrController.Orientation.
public class GvrController : MonoBehaviour {
#if UNITY_HAS_GOOGLEVR && (UNITY_ANDROID || UNITY_EDITOR)
private static GvrController instance;
private static IControllerProvider controllerProvider;
private ControllerState controllerState = new ControllerState();
private IEnumerator controllerUpdate;
private WaitForEndOfFrame waitForEndOfFrame = new WaitForEndOfFrame();
/// Event handler for receiving button, track pad, and IMU updates from the controller.
public delegate void OnControllerUpdateEvent();
public event OnControllerUpdateEvent OnControllerUpdate;
public enum EmulatorConnectionMode {
OFF,
USB,
WIFI,
}
/// Indicates how we connect to the controller emulator.
[Tooltip("How to connect to the emulator: USB cable (recommended) or WIFI.")]
public EmulatorConnectionMode emulatorConnectionMode = EmulatorConnectionMode.USB;
/// Returns the arm model instance associated with the controller.
public static GvrArmModel ArmModel {
get {
return instance != null ? instance.GetComponent<GvrArmModel>() : null;
}
}
/// Returns the controller's current connection state.
public static GvrConnectionState State {
get {
return instance != null ? instance.controllerState.connectionState : GvrConnectionState.Error;
}
}
/// Returns the API status of the current controller state.
public static GvrControllerApiStatus ApiStatus {
get {
return instance != null ? instance.controllerState.apiStatus : GvrControllerApiStatus.Error;
}
}
/// Returns the controller's current orientation in space, as a quaternion.
/// The space in which the orientation is represented is the usual Unity space, with
/// X pointing to the right, Y pointing up and Z pointing forward. Therefore, to make an
/// object in your scene have the same orientation as the controller, simply assign this
/// quaternion to the GameObject's transform.rotation.
public static Quaternion Orientation {
get {
return instance != null ? instance.controllerState.orientation : Quaternion.identity;
}
}
/// Returns the controller's gyroscope reading. The gyroscope indicates the angular
/// about each of its local axes. The controller's axes are: X points to the right,
/// Y points perpendicularly up from the controller's top surface and Z lies
/// along the controller's body, pointing towards the front. The angular speed is given
/// in radians per second, using the right-hand rule (positive means a right-hand rotation
/// about the given axis).
public static Vector3 Gyro {
get {
return instance != null ? instance.controllerState.gyro : Vector3.zero;
}
}
/// Returns the controller's accelerometer reading. The accelerometer indicates the
/// effect of acceleration and gravity in the direction of each of the controller's local
/// axes. The controller's local axes are: X points to the right, Y points perpendicularly
/// up from the controller's top surface and Z lies along the controller's body, pointing
/// towards the front. The acceleration is measured in meters per second squared. Note that
/// gravity is combined with acceleration, so when the controller is resting on a table top,
/// it will measure an acceleration of 9.8 m/s^2 on the Y axis. The accelerometer reading
/// will be zero on all three axes only if the controller is in free fall, or if the user
/// is in a zero gravity environment like a space station.
public static Vector3 Accel {
get {
return instance != null ? instance.controllerState.accel : Vector3.zero;
}
}
/// If true, the user is currently touching the controller's touchpad.
public static bool IsTouching {
get {
return instance != null ? instance.controllerState.isTouching : false;
}
}
/// If true, the user just started touching the touchpad. This is an event flag (it is true
/// for only one frame after the event happens, then reverts to false).
public static bool TouchDown {
get {
return instance != null ? instance.controllerState.touchDown : false;
}
}
/// If true, the user just stopped touching the touchpad. This is an event flag (it is true
/// for only one frame after the event happens, then reverts to false).
public static bool TouchUp {
get {
return instance != null ? instance.controllerState.touchUp : false;
}
}
public static Vector2 TouchPos {
get {
return instance != null ? instance.controllerState.touchPos : Vector2.zero;
}
}
/// If true, the user is currently performing the recentering gesture. Most apps will want
/// to pause the interaction while this remains true.
public static bool Recentering {
get {
return instance != null ? instance.controllerState.recentering : false;
}
}
/// If true, the user just completed the recenter gesture. The controller's orientation is
/// now being reported in the new recentered coordinate system (the controller's orientation
/// when recentering was completed was remapped to mean "forward"). This is an event flag
/// (it is true for only one frame after the event happens, then reverts to false).
/// The headset is recentered together with the controller.
public static bool Recentered {
get {
return instance != null ? instance.controllerState.recentered : false;
}
}
/// If true, the click button (touchpad button) is currently being pressed. This is not
/// an event: it represents the button's state (it remains true while the button is being
/// pressed).
public static bool ClickButton {
get {
return instance != null ? instance.controllerState.clickButtonState : false;
}
}
/// If true, the click button (touchpad button) was just pressed. This is an event flag:
/// it will be true for only one frame after the event happens.
public static bool ClickButtonDown {
get {
return instance != null ? instance.controllerState.clickButtonDown : false;
}
}
/// If true, the click button (touchpad button) was just released. This is an event flag:
/// it will be true for only one frame after the event happens.
public static bool ClickButtonUp {
get {
return instance != null ? instance.controllerState.clickButtonUp : false;
}
}
/// If true, the app button (touchpad button) is currently being pressed. This is not
/// an event: it represents the button's state (it remains true while the button is being
/// pressed).
public static bool AppButton {
get {
return instance != null ? instance.controllerState.appButtonState : false;
}
}
/// If true, the app button was just pressed. This is an event flag: it will be true for
/// only one frame after the event happens.
public static bool AppButtonDown {
get {
return instance != null ? instance.controllerState.appButtonDown : false;
}
}
/// If true, the app button was just released. This is an event flag: it will be true for
/// only one frame after the event happens.
public static bool AppButtonUp {
get {
return instance != null ? instance.controllerState.appButtonUp : false;
}
}
// Always false in the emulator.
public static bool HomeButtonDown {
get {
return instance != null ? instance.controllerState.homeButtonDown : false;
}
}
// Always false in the emulator.
public static bool HomeButtonState {
get {
return instance != null ? instance.controllerState.homeButtonState : false;
}
}
/// If State == GvrConnectionState.Error, this contains details about the error.
public static string ErrorDetails {
get {
if (instance != null) {
return instance.controllerState.connectionState == GvrConnectionState.Error ?
instance.controllerState.errorDetails : "";
} else {
return "GvrController instance not found in scene. It may be missing, or it might "
+ "not have initialized yet.";
}
}
}
// Returns the GVR C library controller state pointer (gvr_controller_state*).
public static IntPtr StatePtr {
get {
return instance != null? instance.controllerState.gvrPtr : IntPtr.Zero;
}
}
/// If true, the user is currently touching the controller's touchpad.
public static bool IsCharging {
get {
return instance != null ? instance.controllerState.isCharging : false;
}
}
/// If true, the user is currently touching the controller's touchpad.
public static GvrControllerBatteryLevel BatteryLevel {
get {
return instance != null ? instance.controllerState.batteryLevel : GvrControllerBatteryLevel.Error;
}
}
void Awake() {
if (instance != null) {
Debug.LogError("More than one GvrController instance was found in your scene. "
+ "Ensure that there is only one GvrController.");
this.enabled = false;
return;
}
instance = this;
if (controllerProvider == null) {
controllerProvider = ControllerProviderFactory.CreateControllerProvider(this);
}
// Keep screen on here, since GvrController must be in any GVR scene in order to enable
// controller capabilities.
Screen.sleepTimeout = SleepTimeout.NeverSleep;
}
void OnDestroy() {
instance = null;
}
private void UpdateController() {
controllerProvider.ReadState(controllerState);
#if UNITY_EDITOR
// If a headset recenter was requested, do it now.
if (controllerState.recentered) {
for (int i = 0; i < Camera.allCameras.Length; i++) {
Camera cam = Camera.allCameras[i];
// Do not reset pitch, which is how it works on the device.
cam.transform.localRotation = Quaternion.Euler(cam.transform.localRotation.eulerAngles.x, 0, 0);
}
}
#endif // UNITY_EDITOR
}
void OnApplicationPause(bool paused) {
if (null == controllerProvider) return;
if (paused) {
controllerProvider.OnPause();
} else {
controllerProvider.OnResume();
}
}
void OnEnable() {
controllerUpdate = EndOfFrame();
StartCoroutine(controllerUpdate);
}
void OnDisable() {
StopCoroutine(controllerUpdate);
}
IEnumerator EndOfFrame() {
while (true) {
// This must be done at the end of the frame to ensure that all GameObjects had a chance
// to read transient controller state (e.g. events, etc) for the current frame before
// it gets reset.
yield return waitForEndOfFrame;
UpdateController();
if (OnControllerUpdate != null) {
OnControllerUpdate();
}
}
}
#endif // UNITY_HAS_GOOGLEVR && (UNITY_ANDROID || UNITY_EDITOR)
}