// 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; using System.Collections; /// Provides visual feedback for the daydream controller. [RequireComponent(typeof(Renderer))] [HelpURL("https://developers.google.com/vr/unity/reference/class/GvrControllerVisual")] public class GvrControllerVisual : MonoBehaviour, IGvrArmModelReceiver, IGvrControllerInputDeviceReceiver { [System.Serializable] public struct ControllerDisplayState { public GvrControllerBatteryLevel batteryLevel; public bool batteryCharging; public bool clickButton; public bool appButton; public bool homeButton; public bool touching; public Vector2 touchPos; } /// An array of prefabs that will be instantiated and added as children /// of the controller visual when the controller is created. Used to /// attach tooltips or other additional visual elements to the control dynamically. [SerializeField] private GameObject[] attachmentPrefabs; [SerializeField] private Color touchPadColor = new Color(200f / 255f, 200f / 255f, 200f / 255f, 1); [SerializeField] private Color appButtonColor = new Color(200f / 255f, 200f / 255f, 200f / 255f, 1); [SerializeField] private Color systemButtonColor = new Color(20f / 255f, 20f / 255f, 20f / 255f, 1); /// Determines if the displayState is set from GvrControllerInputDevice. [Tooltip("Determines if the displayState is set from GvrControllerInputDevice.")] public bool readControllerState = true; /// Used to set the display state of the controller visual. /// This can be used for tutorials that visualize the controller or other use-cases that require /// displaying the controller visual without the state being determined by controller input. /// Additionally, it can be used to preview the controller visual in the editor. /// NOTE: readControllerState must be disabled to set the display state. public ControllerDisplayState displayState; /// This is the preferred, maximum alpha value the object should have /// when it is a comfortable distance from the head. [Range(0.0f, 1.0f)] public float maximumAlpha = 1.0f; public GvrBaseArmModel ArmModel { get; set; } public GvrControllerInputDevice ControllerInputDevice { get; set; } public float PreferredAlpha{ get{ return ArmModel != null ? maximumAlpha * ArmModel.PreferredAlpha : maximumAlpha; } } public Color TouchPadColor { get { return touchPadColor; } set { touchPadColor = value; if(materialPropertyBlock != null) { materialPropertyBlock.SetColor(touchPadId, touchPadColor); } } } public Color AppButtonColor { get { return appButtonColor; } set { appButtonColor = value; if(materialPropertyBlock != null){ materialPropertyBlock.SetColor(appButtonId, appButtonColor); } } } public Color SystemButtonColor { get { return systemButtonColor; } set { systemButtonColor = value; if(materialPropertyBlock != null) { materialPropertyBlock.SetColor(systemButtonId, systemButtonColor); } } } private Renderer controllerRenderer; private MaterialPropertyBlock materialPropertyBlock; private int alphaId; private int touchId; private int touchPadId; private int appButtonId; private int systemButtonId; private int batteryColorId; private bool wasTouching; private float touchTime; // Data passed to shader, (xy) touch position, (z) touch duration, (w) battery state. private Vector4 controllerShaderData; // Data passed to shader, (x) overall alpha, (y) touchpad click duration, // (z) app button click duration, (w) system button click duration. private Vector4 controllerShaderData2; private Color currentBatteryColor; // These values control animation times for the controller buttons public const float APP_BUTTON_ACTIVE_DURATION_SECONDS = 0.111f; public const float APP_BUTTON_RELEASE_DURATION_SECONDS = 0.0909f; public const float SYSTEM_BUTTON_ACTIVE_DURATION_SECONDS = 0.111f; public const float SYSTEM_BUTTON_RELEASE_DURATION_SECONDS = 0.0909f; public const float TOUCHPAD_CLICK_DURATION_SECONDS = 0.111f; public const float TOUCHPAD_RELEASE_DURATION_SECONDS = 0.0909f; public const float TOUCHPAD_CLICK_SCALE_DURATION_SECONDS = 0.075f; public const float TOUCHPAD_POINT_SCALE_DURATION_SECONDS = 0.15f; // These values are used by the shader to control battery display // Only modify these values if you are also modifying the shader. private const float BATTERY_FULL = 0; private const float BATTERY_ALMOST_FULL = .125f; private const float BATTERY_MEDIUM = .225f; private const float BATTERY_LOW = .325f; private const float BATTERY_CRITICAL = .425f; private const float BATTERY_HIDDEN = .525f; private readonly Color GVR_BATTERY_CRITICAL_COLOR = new Color(1,0,0,1); private readonly Color GVR_BATTERY_LOW_COLOR = new Color(1,0.6823f,0,1); private readonly Color GVR_BATTERY_MED_COLOR = new Color(0,1,0.588f,1); private readonly Color GVR_BATTERY_FULL_COLOR = new Color(0,1,0.588f,1); // How much time to use as an 'immediate update'. // Any value large enough to instantly update all visual animations. private const float IMMEDIATE_UPDATE_TIME = 10f; void Awake() { Initialize(); CreateAttachments(); } void OnEnable() { GvrControllerInput.OnPostControllerInputUpdated += OnPostControllerInputUpdated; } void OnDisable() { GvrControllerInput.OnPostControllerInputUpdated -= OnPostControllerInputUpdated; } void OnValidate() { if (!Application.isPlaying) { Initialize(); OnVisualUpdate(true); } } private void OnPostControllerInputUpdated() { OnVisualUpdate(); } private void CreateAttachments() { if (attachmentPrefabs == null) { return; } for (int i = 0; i < attachmentPrefabs.Length; i++) { GameObject prefab = attachmentPrefabs[i]; GameObject attachment = Instantiate(prefab); attachment.transform.SetParent(transform, false); } } private void Initialize() { if(controllerRenderer == null) { controllerRenderer = GetComponent(); } if(materialPropertyBlock == null) { materialPropertyBlock = new MaterialPropertyBlock(); } alphaId = Shader.PropertyToID("_GvrControllerAlpha"); touchId = Shader.PropertyToID("_GvrTouchInfo"); touchPadId = Shader.PropertyToID("_GvrTouchPadColor"); appButtonId = Shader.PropertyToID("_GvrAppButtonColor"); systemButtonId = Shader.PropertyToID("_GvrSystemButtonColor"); batteryColorId = Shader.PropertyToID("_GvrBatteryColor"); materialPropertyBlock.SetColor(appButtonId, appButtonColor); materialPropertyBlock.SetColor(systemButtonId, systemButtonColor); materialPropertyBlock.SetColor(touchPadId, touchPadColor); controllerRenderer.SetPropertyBlock(materialPropertyBlock); } private void UpdateControllerState() { // Return early when the application isn't playing to ensure that the serialized displayState // is used to preview the controller visual instead of the default GvrControllerInputDevice // values. #if UNITY_EDITOR if (!Application.isPlaying) { return; } #endif if(ControllerInputDevice != null) { displayState.batteryLevel = ControllerInputDevice.BatteryLevel; displayState.batteryCharging = ControllerInputDevice.IsCharging; displayState.clickButton = ControllerInputDevice.GetButton(GvrControllerButton.TouchPadButton); displayState.appButton = ControllerInputDevice.GetButton(GvrControllerButton.App); displayState.homeButton = ControllerInputDevice.GetButton(GvrControllerButton.System); displayState.touching = ControllerInputDevice.GetButton(GvrControllerButton.TouchPadTouch); displayState.touchPos = ControllerInputDevice.TouchPos; } } private void OnVisualUpdate(bool updateImmediately = false) { // Update the visual display based on the controller state if(readControllerState) { UpdateControllerState(); } float deltaTime = Time.deltaTime; // If flagged to update immediately, set deltaTime to an arbitrarily large value // This is particularly useful in editor, but also for resetting state quickly if(updateImmediately) { deltaTime = IMMEDIATE_UPDATE_TIME; } if (displayState.clickButton) { controllerShaderData2.y = Mathf.Min(1, controllerShaderData2.y + deltaTime / TOUCHPAD_CLICK_DURATION_SECONDS); } else{ controllerShaderData2.y = Mathf.Max(0, controllerShaderData2.y - deltaTime / TOUCHPAD_RELEASE_DURATION_SECONDS); } if (displayState.appButton) { controllerShaderData2.z = Mathf.Min(1, controllerShaderData2.z + deltaTime / APP_BUTTON_ACTIVE_DURATION_SECONDS); } else{ controllerShaderData2.z = Mathf.Max(0, controllerShaderData2.z - deltaTime / APP_BUTTON_RELEASE_DURATION_SECONDS); } if (displayState.homeButton) { controllerShaderData2.w = Mathf.Min(1, controllerShaderData2.w + deltaTime / SYSTEM_BUTTON_ACTIVE_DURATION_SECONDS); } else { controllerShaderData2.w = Mathf.Max(0, controllerShaderData2.w - deltaTime / SYSTEM_BUTTON_RELEASE_DURATION_SECONDS); } // Set the material's alpha to the multiplied preferred alpha. controllerShaderData2.x = PreferredAlpha; materialPropertyBlock.SetVector(alphaId, controllerShaderData2); controllerShaderData.x = displayState.touchPos.x; controllerShaderData.y = displayState.touchPos.y; if (displayState.touching || displayState.clickButton) { if (!wasTouching) { wasTouching = true; } if(touchTime < 1) { touchTime = Mathf.Min(touchTime + deltaTime / TOUCHPAD_POINT_SCALE_DURATION_SECONDS, 1); } } else { wasTouching = false; if(touchTime > 0) { touchTime = Mathf.Max(touchTime - deltaTime / TOUCHPAD_POINT_SCALE_DURATION_SECONDS, 0); } } controllerShaderData.z = touchTime; UpdateBatteryIndicator(); materialPropertyBlock.SetVector(touchId, controllerShaderData); materialPropertyBlock.SetColor(batteryColorId, currentBatteryColor); // Update the renderer controllerRenderer.SetPropertyBlock(materialPropertyBlock); } private void UpdateBatteryIndicator() { GvrControllerBatteryLevel level = displayState.batteryLevel; bool charging = displayState.batteryCharging; switch (level) { case GvrControllerBatteryLevel.Full: controllerShaderData.w = BATTERY_FULL; currentBatteryColor = GVR_BATTERY_FULL_COLOR; break; case GvrControllerBatteryLevel.AlmostFull: controllerShaderData.w = BATTERY_ALMOST_FULL; currentBatteryColor = GVR_BATTERY_FULL_COLOR; break; case GvrControllerBatteryLevel.Medium: controllerShaderData.w = BATTERY_MEDIUM; currentBatteryColor = GVR_BATTERY_MED_COLOR; break; case GvrControllerBatteryLevel.Low: controllerShaderData.w = BATTERY_LOW; currentBatteryColor = GVR_BATTERY_LOW_COLOR; break; case GvrControllerBatteryLevel.CriticalLow: controllerShaderData.w = BATTERY_CRITICAL; currentBatteryColor = GVR_BATTERY_CRITICAL_COLOR; break; default: controllerShaderData.w = BATTERY_HIDDEN; currentBatteryColor.a = 0; break; } if (charging) { controllerShaderData.w = -controllerShaderData.w; currentBatteryColor = GVR_BATTERY_FULL_COLOR; } } public void SetControllerTexture(Texture newTexture) { controllerRenderer.material.mainTexture = newTexture; } }