Gvr 1.1 SDK¿¡¼ GazeInputÀ» ÀÌ¿ëÇØ UI¸¦ ÀÚµ¿ Ŭ¸¯Çغ¸ÀÚ.Gaze AutoClickÄ«¸Þ¶ó Áß¾ÓÀ¸·Î º¸´Â°÷À» ¸îÃÊ Áö³ª¸é ÀÚµ¿Å¬¸¯µÇ°Ô Çغ¸ÀÚ.ÀÚµ¿ Ŭ¸¯À» À§ÇÏ¿© EventSystem¿¡ "Gvr Pointer Input Module" ÄÄÆ÷³ÍÆ® ´ë½Å "Custom Gvr Pointer Input Module" ÄÄÆ÷³ÍÆ®¸¦ Ãß°¡ÇÑ´Ù. "Custom Gvr Pointer Input Module" ½ºÅ©¸³Æ®´Â GvrPointerInputModule.cs ÆÄÀÏ¿¡ Auto Click ±â´ÉÀ» Ãß°¡ ÇÏ¿´´Ù. bool GetMouseButton(int button) { //return Input.GetMouseButton(button); return gazeMouseButton; } bool GetMouseButtonDown(int button) { //return Input.GetMouseButtonDown(button); return false; } Input.GetMouseButton, Input.GetMouseButtonDownÀ» GetMouseButton, GetMouseButtonDownÀ¸·Î ¹Ù²Ù¾ú´Ù. CheckGazeMouseClick( ) ÇÔ¼ö¿¡¼ countTimer ½Ã°£ÀÌ Áö³ª¸é gazeMouseButtonÀ» true·Î ¹Ù²Û´Ù. ½½¶óÀÌ´õ¹ÙÀÇ °æ¿ì´Â °°Àº ½½¶óÀÌ´õ¹Ù¿¡ °è¼Ó À§Ä¡ ÇÏ°í ÀÖÀ¸¸é clickedMouseButtonÀ» false·Î üũÇÏ¿© ½½¶óÀÌ´õ¹Ù¿¡ Mouse Down, Click ¸Þ¼¼Áö¸¦ º¸³½´Ù. ½½¶óÀÌ´õ¹Ù¿¡ Ưº°ÇÑ Ã³¸®°¡ ÇÊ¿äÇÏÁö ¾Ê´Ù¸é clickedMouseButton º¯¼ö´Â Á¦°ÅÇÑ´Ù. void CheckGazeMouseClick( ) .......... if (selected != null && clickedMouseButton == false) { countTimer -= Time.deltaTime; bool anyKey = false; if (anyKey || countTimer < 0) { gazeMouseButton = true; clickedMouseButton = true; Slider slider = selected.GetComponent<Slider>(); if (slider) { clickedMouseButton = false; countTimer = selectTime; } } } ........... CustomGvrPointerInputModule.cs ÆÄÀÏ // The MIT License (MIT)
// // Copyright (c) 2015, Unity Technologies & Google, Inc. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.UI; #if UNITY_HAS_GOOGLEVR && (UNITY_ANDROID || UNITY_EDITOR) using UnityEngine.VR; #endif // UNITY_HAS_GOOGLEVR && (UNITY_ANDROID || UNITY_EDITOR) /// This script provides an implemention of Unity's `BaseInputModule` class, so /// that Canvas-based (_uGUI_) UI elements and 3D scene objects can be /// interacted with in a Gvr Application. /// /// This script is intended for use with either a /// 3D pointer with the Daydream Controller (Recommended for Daydream), /// or a Gaze-based-pointer (Recommended for Cardboard). /// /// To use, attach to the scene's **EventSystem** object. Be sure to move it above the /// other modules, such as _TouchInputModule_ and _StandaloneInputModule_, in order /// for the pointer to take priority in the event system. /// /// If you are using a **Canvas**, set the _Render Mode_ to **World Space**, /// and add the _GvrPointerGraphicRaycaster_ script to the object. /// /// If you'd like pointers to work with 3D scene objects, add a _GvrPointerPhysicsRaycaster_ to the main camera, /// and add a component that implements one of the _Event_ interfaces (_EventTrigger_ will work nicely) to /// an object with a collider. /// /// GvrPointerInputModule emits the following events: _Enter_, _Exit_, _Down_, _Up_, _Click_, _Select_, /// _Deselect_, _UpdateSelected_, and _GvrPointerHover_. Scroll, move, and submit/cancel events are not emitted. /// /// To use a 3D Pointer with the Daydream Controller: /// - Add the prefab GoogleVR/Prefabs/UI/GvrControllerPointer to your scene. /// - Set the parent of GvrControllerPointer to the same parent as the main camera /// (With a local position of 0,0,0). /// /// To use a Gaze-based-pointer: /// - Add the prefab GoogleVR/Prefabs/UI/GvrReticlePointer to your scene. /// - Set the parent of GvrReticlePointer to the main camera. /// [AddComponentMenu("GoogleVR/CustomGvrPointerInputModule")] public class CustomGvrPointerInputModule : BaseInputModule { /// Determines whether pointer input is active in VR Mode only (`true`), or all of the /// time (`false`). Set to false if you plan to use direct screen taps or other /// input when not in VR Mode. [Tooltip("Whether pointer input is active in VR Mode only (true), or all the time (false).")] public bool vrModeOnly = false; [HideInInspector] public Vector2 hotspot = new Vector2(0.5f, 0.5f); private PointerEventData pointerData; private Vector2 lastHeadPose; // Active state private bool isActive = false; /// Time in seconds between the pointer down and up events sent by a trigger. /// Allows time for the UI elements to make their state transitions. private const float clickTime = 0.1f; // Based on default time for a button to animate to Pressed. /// The IGvrPointer which will be responding to pointer events. private IGvrPointer pointer { get { return GvrPointerManager.Pointer; } } bool gazeMouseButton = false; static public readonly float selectTime = 2.0f; static public float countTimer { get; private set; } bool clickedMouseButton = false; /// @cond public override bool ShouldActivateModule() { bool isVrModeEnabled = !vrModeOnly; #if UNITY_HAS_GOOGLEVR && (UNITY_ANDROID || UNITY_EDITOR) isVrModeEnabled |= VRSettings.enabled; #else isVrModeEnabled |= GvrViewer.Instance.VRModeEnabled; #endif // UNITY_HAS_GOOGLEVR && (UNITY_ANDROID || UNITY_EDITOR) bool activeState = base.ShouldActivateModule() && isVrModeEnabled; if (activeState != isActive) { isActive = activeState; // Activate pointer if (pointer != null) { if (isActive) { pointer.OnInputModuleEnabled(); } } } return activeState; } /// @endcond public override void DeactivateModule() { DisablePointer(); base.DeactivateModule(); if (pointerData != null) { HandlePendingClick(); HandlePointerExitAndEnter(pointerData, null); pointerData = null; } eventSystem.SetSelectedGameObject(null, GetBaseEventData()); } public override bool IsPointerOverGameObject(int pointerId) { return pointerData != null && pointerData.pointerEnter != null; } #if false public override void Process() { // Save the previous Game Object GameObject previousObject = GetCurrentGameObject(); CastRay(); UpdateCurrentObject(previousObject); UpdateReticle(previousObject); bool isGvrTriggered = Input.GetMouseButtonDown(0); bool handlePendingClickRequired = !Input.GetMouseButton(0); #if UNITY_HAS_GOOGLEVR && (UNITY_ANDROID || UNITY_EDITOR) handlePendingClickRequired &= !GvrController.ClickButton; isGvrTriggered |= GvrController.ClickButtonDown; #endif // UNITY_HAS_GOOGLEVR && (UNITY_ANDROID || UNITY_EDITOR) // Handle input if (!Input.GetMouseButtonDown(0) && Input.GetMouseButton(0)) { HandleDrag(); } else if (Time.unscaledTime - pointerData.clickTime < clickTime) { // Delay new events until clickTime has passed. } else if (!pointerData.eligibleForClick && (isGvrTriggered || Input.GetMouseButtonDown(0))) { // New trigger action. HandleTrigger(); } else if (handlePendingClickRequired) { // Check if there is a pending click to handle. HandlePendingClick(); } } #else public override void Process() { // Save the previous Game Object GameObject gazeObjectPrevious = GetCurrentGameObject(); bool isGvrTriggered = Input.GetMouseButtonDown(0); #if UNITY_HAS_GOOGLEVR && (UNITY_ANDROID || UNITY_EDITOR) isGvrTriggered |= GvrController.ClickButtonDown; #endif // UNITY_HAS_GOOGLEVR && (UNITY_ANDROID || UNITY_EDITOR) CastRayFromGaze(); GameObject previousObject = GetCurrentGameObject(); UpdateCurrentObject(previousObject); UpdateReticle(gazeObjectPrevious); CheckGazeMouseClick(); // Handle input /*if (!GetMouseButtonDown(0) && GetMouseButton(0)) { HandleDrag(); } else */ if (Time.unscaledTime - pointerData.clickTime < clickTime) { // Delay new events until clickTime has passed. //} else if (!pointerData.eligibleForClick && (isGvrTriggered || GetMouseButtonDown(0))) { } else if (GetMouseButton(0) == true) { // New trigger action. HandleTrigger(); } else if (!isGvrTriggered && !GetMouseButton(0)) { // Check if there is a pending click to handle. HandlePendingClick(); } } #endif bool GetMouseButton(int button) { //return Input.GetMouseButton(button); return gazeMouseButton; } bool GetMouseButtonDown(int button) { //return Input.GetMouseButtonDown(button); return false; } void CheckGazeMouseClick() { gazeMouseButton = false; var go = pointerData.pointerCurrentRaycast.gameObject; var selected = ExecuteEvents.GetEventHandler<ISelectHandler>(go); var enter = ExecuteEvents.GetEventHandler<IPointerEnterHandler>(go); if (selected != enter || selected == null) { clickedMouseButton = false; countTimer = selectTime; } if (selected != null && clickedMouseButton == false) { countTimer -= Time.deltaTime; bool anyKey = false; //anyKey = (Input.GetMouseButtonDown(0) == false && Input.GetMouseButton(0) == true); if (anyKey || countTimer < 0) { gazeMouseButton = true; //Debug.Log("gazeMouseButton is true"); clickedMouseButton = true; Slider slider = selected.GetComponent<Slider>(); if (slider) { clickedMouseButton = false; countTimer = selectTime; } } } } /// @endcond private void CastRay() { Quaternion headOrientation; #if UNITY_HAS_GOOGLEVR && (UNITY_ANDROID || UNITY_EDITOR) headOrientation = InputTracking.GetLocalRotation(VRNode.Head); #else headOrientation = GvrViewer.Instance.HeadPose.Orientation; #endif // UNITY_HAS_GOOGLEVR && (UNITY_ANDROID || UNITY_EDITOR) Vector2 headPose = NormalizedCartesianToSpherical(headOrientation * Vector3.forward); if (pointerData == null) { pointerData = new PointerEventData(eventSystem); lastHeadPose = headPose; } // Cast a ray into the scene pointerData.Reset(); pointerData.position = GetPointerPosition(); eventSystem.RaycastAll(pointerData, m_RaycastResultCache); RaycastResult raycastResult = FindFirstRaycast(m_RaycastResultCache); if (raycastResult.worldPosition == Vector3.zero) { raycastResult.worldPosition = GetIntersectionPosition(pointerData.enterEventCamera, raycastResult); } pointerData.pointerCurrentRaycast = raycastResult; m_RaycastResultCache.Clear(); pointerData.delta = headPose - lastHeadPose; lastHeadPose = headPose; } private void CastRayFromGaze() { bool isGvrTriggered = Input.GetMouseButtonDown(0); Quaternion headOrientation; #if UNITY_HAS_GOOGLEVR && (UNITY_ANDROID || UNITY_EDITOR) headOrientation = InputTracking.GetLocalRotation(VRNode.Head); #else headOrientation = GvrViewer.Instance.HeadPose.Orientation; #endif // UNITY_HAS_GOOGLEVR && (UNITY_ANDROID || UNITY_EDITOR) Vector2 headPose = NormalizedCartesianToSpherical(headOrientation * Vector3.forward); if (pointerData == null) { pointerData = new PointerEventData(eventSystem); lastHeadPose = headPose; } // Cast a ray into the scene pointerData.Reset(); pointerData.position = new Vector2(hotspot.x * Screen.width, hotspot.y * Screen.height); eventSystem.RaycastAll(pointerData, m_RaycastResultCache); pointerData.pointerCurrentRaycast = FindFirstRaycast(m_RaycastResultCache); m_RaycastResultCache.Clear(); pointerData.delta = headPose - lastHeadPose; lastHeadPose = headPose; } private void UpdateCurrentObject(GameObject previousObject) { // Send enter events and update the highlight. GameObject currentObject = GetCurrentGameObject(); // Get the pointer target HandlePointerExitAndEnter(pointerData, previousObject); // Update the current selection, or clear if it is no longer the current object. var selected = ExecuteEvents.GetEventHandler<ISelectHandler>(currentObject); if (selected == eventSystem.currentSelectedGameObject) { ExecuteEvents.Execute(eventSystem.currentSelectedGameObject, GetBaseEventData(), ExecuteEvents.updateSelectedHandler); } else { eventSystem.SetSelectedGameObject(null, pointerData); } // Execute hover event. if (currentObject == previousObject) { ExecuteEvents.Execute(currentObject, pointerData, GvrExecuteEventsExtension.pointerHoverHandler); } } private void UpdateReticle(GameObject previousObject) { if (pointer == null) { return; } Camera camera = pointerData.enterEventCamera; // Get the camera GameObject currentObject = GetCurrentGameObject(); // Get the pointer target Vector3 intersectionPosition = pointerData.pointerCurrentRaycast.worldPosition; bool isInteractive = pointerData.pointerPress != null || ExecuteEvents.GetEventHandler<IPointerClickHandler>(currentObject) != null; if (currentObject == previousObject) { if (currentObject != null) { pointer.OnPointerHover(currentObject, intersectionPosition, GetLastRay(), isInteractive); } } else { if (previousObject != null) { pointer.OnPointerExit(previousObject); } if (currentObject != null) { pointer.OnPointerEnter(currentObject, intersectionPosition, GetLastRay(), isInteractive); } } } private void HandleDrag() { bool moving = pointerData.IsPointerMoving(); if (moving && pointerData.pointerDrag != null && !pointerData.dragging) { ExecuteEvents.Execute(pointerData.pointerDrag, pointerData, ExecuteEvents.beginDragHandler); pointerData.dragging = true; } // Drag notification if (pointerData.dragging && moving && pointerData.pointerDrag != null) { // Before doing drag we should cancel any pointer down state // And clear selection! if (pointerData.pointerPress != pointerData.pointerDrag) { ExecuteEvents.Execute(pointerData.pointerPress, pointerData, ExecuteEvents.pointerUpHandler); pointerData.eligibleForClick = false; pointerData.pointerPress = null; pointerData.rawPointerPress = null; } ExecuteEvents.Execute(pointerData.pointerDrag, pointerData, ExecuteEvents.dragHandler); } } private void HandlePendingClick() { if (!pointerData.eligibleForClick && !pointerData.dragging) { return; } if (pointer != null) { Camera camera = pointerData.enterEventCamera; pointer.OnPointerClickUp(); } var go = pointerData.pointerCurrentRaycast.gameObject; // Send pointer up and click events. ExecuteEvents.Execute(pointerData.pointerPress, pointerData, ExecuteEvents.pointerUpHandler); if (pointerData.eligibleForClick) { ExecuteEvents.Execute(pointerData.pointerPress, pointerData, ExecuteEvents.pointerClickHandler); } else if (pointerData.dragging) { ExecuteEvents.ExecuteHierarchy(go, pointerData, ExecuteEvents.dropHandler); ExecuteEvents.Execute(pointerData.pointerDrag, pointerData, ExecuteEvents.endDragHandler); } // Clear the click state. pointerData.pointerPress = null; pointerData.rawPointerPress = null; pointerData.eligibleForClick = false; pointerData.clickCount = 0; pointerData.clickTime = 0; pointerData.pointerDrag = null; pointerData.dragging = false; } private void HandleTrigger() { var go = pointerData.pointerCurrentRaycast.gameObject; // Send pointer down event. pointerData.pressPosition = pointerData.position; pointerData.pointerPressRaycast = pointerData.pointerCurrentRaycast; pointerData.pointerPress = ExecuteEvents.ExecuteHierarchy(go, pointerData, ExecuteEvents.pointerDownHandler) ?? ExecuteEvents.GetEventHandler<IPointerClickHandler>(go); // Save the drag handler as well pointerData.pointerDrag = ExecuteEvents.GetEventHandler<IDragHandler>(go); if (pointerData.pointerDrag != null) { ExecuteEvents.Execute(pointerData.pointerDrag, pointerData, ExecuteEvents.initializePotentialDrag); } // Save the pending click state. pointerData.rawPointerPress = go; pointerData.eligibleForClick = true; pointerData.delta = Vector2.zero; pointerData.dragging = false; pointerData.useDragThreshold = true; pointerData.clickCount = 1; pointerData.clickTime = Time.unscaledTime; if (pointer != null) { pointer.OnPointerClickDown(); } } private Vector2 NormalizedCartesianToSpherical(Vector3 cartCoords) { cartCoords.Normalize(); if (cartCoords.x == 0) cartCoords.x = Mathf.Epsilon; float outPolar = Mathf.Atan(cartCoords.z / cartCoords.x); if (cartCoords.x < 0) outPolar += Mathf.PI; float outElevation = Mathf.Asin(cartCoords.y); return new Vector2(outPolar, outElevation); } private GameObject GetCurrentGameObject() { if (pointerData != null) { return pointerData.pointerCurrentRaycast.gameObject; } return null; } private Ray GetLastRay() { if (pointerData != null) { GvrBasePointerRaycaster raycaster = pointerData.pointerCurrentRaycast.module as GvrBasePointerRaycaster; if (raycaster != null) { return raycaster.GetLastRay(); } else if (pointerData.enterEventCamera != null) { Camera cam = pointerData.enterEventCamera; return new Ray(cam.transform.position, cam.transform.forward); } } return new Ray(); } private Vector3 GetIntersectionPosition(Camera cam, RaycastResult raycastResult) { // Check for camera if (cam == null) { return Vector3.zero; } float intersectionDistance = raycastResult.distance + cam.nearClipPlane; Vector3 intersectionPosition = cam.transform.position + cam.transform.forward * intersectionDistance; return intersectionPosition; } private void DisablePointer() { if (pointer == null) { return; } GameObject currentGameObject = GetCurrentGameObject(); if (currentGameObject) { Camera camera = pointerData.enterEventCamera; pointer.OnPointerExit(currentGameObject); } pointer.OnInputModuleDisabled(); } private Vector2 GetPointerPosition() { int viewportWidth = Screen.width; int viewportHeight = Screen.height; #if UNITY_HAS_GOOGLEVR && (UNITY_ANDROID || UNITY_EDITOR) && UNITY_ANDROID // GVR native integration is supported. if (VRSettings.enabled) { viewportWidth = VRSettings.eyeTextureWidth; viewportHeight = VRSettings.eyeTextureHeight; } #endif // UNITY_HAS_GOOGLEVR && (UNITY_ANDROID || UNITY_EDITOR) && UNITY_ANDROID return new Vector2(0.5f * viewportWidth, 0.5f * viewportHeight); } } ¿ø ¸ð¾çÀÇ ½½¶óÀÌ´õ ¸¸µé±â½Ã°è ¹æÇâÀ¸·Î µ¹¾Æ°¡´Â ¿ø¸ð¾çÀÇ ½½¶óÀÌ´õ¸¦ ¸¸µé¾î º¸ÀÚ. °èÃþ ³ëµå¸¦ ¸ÕÀú º¸ÀÚ. UI ½½¶óÀÌ´õ ´ë½Å 2°³ÀÇ À̹ÌÁö·Î ¿øÇü ½½¶óÀÌ´õ¸¦ ¸¸µç´Ù. 1. MainCameraÀÇ ÀÚ½ÄÀ¸·Î Canvas¸¦ ¸¸µç´Ù. ¼³Á¤Àº ´ÙÀ½°ú °°ÀÌ ÇÑ´Ù. 2. CanvasÀÇ ÀÚ½ÄÀ¸·Î UI > Image °ÔÀÓ¿ÀºêÁ§Æ®¸¦ Ãß°¡ÇÑ´Ù. ³ëµåÀÇ À̸§Àº GazeSlider·Î ¹Ù²Û´Ù. Width, Height¸¦ °¢°¢ 20, 20À¸·Î ¼³Á¤ÇÑ´Ù. ¿©±â¼ÀÇ À̹ÌÁö´Â ½½¶óÀÌ´õÀÇ ¹é±×¶ó¿îµå À̹ÌÁö·Î »ç¿ë µÉ°ÍÀÌ´Ù. GazeSlider ½ºÅ©¸³Æ®´Â ³ªÁß¿¡ ÀÛ¼ºÈÄ Ãß°¡ ÇÒ°ÍÀÌ´Ù. ¿©±â¼´Â ±×³É ³Ñ¾î °£´Ù. 3. CanvasÀÇ ÀÚ½ÄÀ¸·Î UI > Image °ÔÀÓ¿ÀºêÁ§Æ®¸¦ Ãß°¡ÇÑ´Ù. ³ëµåÀÇ À̸§Àº fill·Î ¹Ù²Û´Ù. ¿©±â¼ÀÇ À̹ÌÁö´Â ½½¶óÀÌ´õÀÇ ÁøÇà ¹Ù ¿ªÇÒÀ» ÇÑ´Ù. "Image Type"À» Filled·Î ÇÏ°í "Fill Method"·Î ÇÏ¿© ¿øÇüÀ¸·Î ÁøÇàµÇ´Â ½½¶óÀÌ´õ¸¦ ¸¸µç´Ù. 4. GazeSlider.cs ½ºÅ©¸³Æ® ÀÛ¼º EVENTSYSTEM_TIMER µðÆÄÀÎÀÌ È°¼ºÈµÇ¸é CustomGvrPointerInputModule.cs ½ºÅ©¸³Æ®ÀÇ countTimer·Î ½Ã°£À» ÁøÇàÇÑ´Ù. CustomGvrPointerInputModule ½ºÅ©¸³Æ®´Â GvrPointerInputModule.cs¸¦ ¼öÁ¤ÇÑ ½ºÅ©¸³ÀÌ´Ù. GazeSlider.cs ÆÄÀÏ #define EVENTSYSTEM_TIMER
using UnityEngine; using System.Collections; using UnityEngine.UI; using UnityEngine.EventSystems; public class GazeSlider : MonoBehaviour { Image fillImage; bool init { get; set; } bool gazedAt; float fillTime = 2f; float timer; Coroutine fillBarRoutine; void Start () { Transform fillTrans = transform.Find("fill"); CheckNull.LogError<Transform>("fillTrans is null", fillTrans); fillImage = fillTrans.GetComponent<Image>(); CheckNull.LogError<Image>("fillImage is null", fillImage); init = true; #if EVENTSYSTEM_TIMER fillTime = CustomGvrPointerInputModule.selectTime; #endif OnEnable(); } public float Amount { get { return fillImage.fillAmount; } set { fillImage.fillAmount = value; } } public void OnEnable() { if (init == false) return; gazedAt = true; Amount = 0; fillBarRoutine = StartCoroutine(FillBar()); } public void OnDisable() { gazedAt = false; if (fillBarRoutine != null) StopCoroutine(fillBarRoutine); timer = 0f; Amount = 0f; } IEnumerator FillBar() { timer = 0f; while (timer < fillTime) { #if EVENTSYSTEM_TIMER timer = (fillTime - CustomGvrPointerInputModule.countTimer); #else timer += Time.deltaTime; #endif Amount = timer / fillTime; yield return null; if (gazedAt == true) continue; timer = 0f; yield break; } //here !!! , send event } } ½½¶óÀÌ´õ¿¡ »ç¿ëµÇ´Â À̹ÌÁö slider_background.png slider_fill.png ½½¶óÀÌ´õ Á¦¾î1. MainCameraÀÇ ÀÚ½ÄÀ¸·Î GvrReticlePointer ÇÁ¸®ÆÕÀ» Ãß°¡ÇÑ´Ù.2. °èÃþºäÀÇ GvrReticlePointer ³ëµå¿¡¼ GvrReticlePointer ÄÄÆ÷³ÍÆ®¸¦ Á¦°ÅÇÏ°í CustomGvrReticlePointer ½ºÅ©¸³Æ®¸¦ ÄÄÆ÷³ÍÆ®·Î Ãß°¡ÇÑ´Ù. 3. CustomGvrReticlePointerÀÇ "Gaze Slider Object"¸¦ ¼³Á¤ÇÑ´Ù. MainCamera > Canvas > GazeSlider¸¦ µå·¡±× ÇÑ´Ù. CustomGvrReticlePointer.cs ÆÄÀÏ // Copyright 2015 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. using UnityEngine; /// Draws a circular reticle in front of any object that the user points at. /// The circle dilates if the object is clickable. [AddComponentMenu("GoogleVR/UI/CustomGvrReticlePointer")] [RequireComponent(typeof(Renderer))] public class CustomGvrReticlePointer : GvrBasePointer { [SerializeField] GameObject gazeSliderObject; GazeSlider gazeSlider; int uiLayer; /// Number of segments making the reticle circle. public int reticleSegments = 20; /// Growth speed multiplier for the reticle/ public float reticleGrowthSpeed = 8.0f; // Private members private Material materialComp; // Current inner angle of the reticle (in degrees). private float reticleInnerAngle = 0.0f; // Current outer angle of the reticle (in degrees). private float reticleOuterAngle = 0.5f; // Current distance of the reticle (in meters). private float reticleDistanceInMeters = 10.0f; // Minimum inner angle of the reticle (in degrees). private const float kReticleMinInnerAngle = 0.0f; // Minimum outer angle of the reticle (in degrees). private const float kReticleMinOuterAngle = 0.5f; // Angle at which to expand the reticle when intersecting with an object // (in degrees). private const float kReticleGrowthAngle = 1.5f; // Minimum distance of the reticle (in meters). private const float kReticleDistanceMin = 0.45f; // Maximum distance of the reticle (in meters). private const float kReticleDistanceMax = 10.0f; // Current inner and outer diameters of the reticle, // before distance multiplication. private float reticleInnerDiameter = 0.0f; private float reticleOuterDiameter = 0.0f; protected override void Start () { base.Start(); CreateReticleVertices(); materialComp = gameObject.GetComponent<Renderer>().material; gazeSlider = gazeSliderObject.GetComponent<GazeSlider>(); uiLayer = LayerMask.NameToLayer("UI"); } void Update() { UpdateDiameters(); } /// This is called when the 'BaseInputModule' system should be enabled. public override void OnInputModuleEnabled() {} /// This is called when the 'BaseInputModule' system should be disabled. public override void OnInputModuleDisabled() {} /// Called when the user is pointing at valid GameObject. This can be a 3D /// or UI element. /// /// The targetObject is the object the user is pointing at. /// The intersectionPosition is where the ray intersected with the targetObject. /// The intersectionRay is the ray that was cast to determine the intersection. public override void OnPointerEnter(GameObject targetObject, Vector3 intersectionPosition, Ray intersectionRay, bool isInteractive) { if (targetObject.layer == uiLayer/*&& isInteractive == true*/) { gazeSliderObject.SetActive(true); SetPointerTarget(intersectionPosition, false); return; } SetPointerTarget(intersectionPosition, isInteractive); } /// Called every frame the user is still pointing at a valid GameObject. This /// can be a 3D or UI element. /// /// The targetObject is the object the user is pointing at. /// The intersectionPosition is where the ray intersected with the targetObject. /// The intersectionRay is the ray that was cast to determine the intersection. public override void OnPointerHover(GameObject targetObject, Vector3 intersectionPosition, Ray intersectionRay, bool isInteractive) { if (targetObject.layer == uiLayer/*&& isInteractive == true*/) { gazeSliderObject.SetActive(true); SetPointerTarget(intersectionPosition, false); return; } SetPointerTarget(intersectionPosition, isInteractive); } /// Called when the user's look no longer intersects an object previously /// intersected with a ray projected from the camera. /// This is also called just before **OnInputModuleDisabled** and may have have any of /// the values set as **null**. public override void OnPointerExit(GameObject targetObject) { if (targetObject.layer == uiLayer) { if(gazeSliderObject != null) gazeSliderObject.SetActive(false); } reticleDistanceInMeters = kReticleDistanceMax; reticleInnerAngle = kReticleMinInnerAngle; reticleOuterAngle = kReticleMinOuterAngle; } /// Called when a trigger event is initiated. This is practically when /// the user begins pressing the trigger. public override void OnPointerClickDown() {} /// Called when a trigger event is finished. This is practically when /// the user releases the trigger. public override void OnPointerClickUp() {} public override float GetMaxPointerDistance() { return kReticleDistanceMax; } public override void GetPointerRadius(out float innerRadius, out float outerRadius) { float min_inner_angle_radians = Mathf.Deg2Rad * kReticleMinInnerAngle; float max_inner_angle_radians = Mathf.Deg2Rad * (kReticleMinInnerAngle + kReticleGrowthAngle); innerRadius = 2.0f * Mathf.Tan(min_inner_angle_radians); outerRadius = 2.0f * Mathf.Tan(max_inner_angle_radians); } private void CreateReticleVertices() { Mesh mesh = new Mesh(); gameObject.AddComponent<MeshFilter>(); GetComponent<MeshFilter>().mesh = mesh; int segments_count = reticleSegments; int vertex_count = (segments_count+1)*2; #region Vertices Vector3[] vertices = new Vector3[vertex_count]; const float kTwoPi = Mathf.PI * 2.0f; int vi = 0; for (int si = 0; si <= segments_count; ++si) { // Add two vertices for every circle segment: one at the beginning of the // prism, and one at the end of the prism. float angle = (float)si / (float)(segments_count) * kTwoPi; float x = Mathf.Sin(angle); float y = Mathf.Cos(angle); vertices[vi++] = new Vector3(x, y, 0.0f); // Outer vertex. vertices[vi++] = new Vector3(x, y, 1.0f); // Inner vertex. } #endregion #region Triangles int indices_count = (segments_count+1)*3*2; int[] indices = new int[indices_count]; int vert = 0; int idx = 0; for (int si = 0; si < segments_count; ++si) { indices[idx++] = vert+1; indices[idx++] = vert; indices[idx++] = vert+2; indices[idx++] = vert+1; indices[idx++] = vert+2; indices[idx++] = vert+3; vert += 2; } #endregion mesh.vertices = vertices; mesh.triangles = indices; mesh.RecalculateBounds(); mesh.Optimize(); } private void UpdateDiameters() { reticleDistanceInMeters = Mathf.Clamp(reticleDistanceInMeters, kReticleDistanceMin, kReticleDistanceMax); if (reticleInnerAngle < kReticleMinInnerAngle) { reticleInnerAngle = kReticleMinInnerAngle; } if (reticleOuterAngle < kReticleMinOuterAngle) { reticleOuterAngle = kReticleMinOuterAngle; } float inner_half_angle_radians = Mathf.Deg2Rad * reticleInnerAngle * 0.5f; float outer_half_angle_radians = Mathf.Deg2Rad * reticleOuterAngle * 0.5f; float inner_diameter = 2.0f * Mathf.Tan(inner_half_angle_radians); float outer_diameter = 2.0f * Mathf.Tan(outer_half_angle_radians); reticleInnerDiameter = Mathf.Lerp(reticleInnerDiameter, inner_diameter, Time.deltaTime * reticleGrowthSpeed); reticleOuterDiameter = Mathf.Lerp(reticleOuterDiameter, outer_diameter, Time.deltaTime * reticleGrowthSpeed); materialComp.SetFloat("_InnerDiameter", reticleInnerDiameter * reticleDistanceInMeters); materialComp.SetFloat("_OuterDiameter", reticleOuterDiameter * reticleDistanceInMeters); materialComp.SetFloat("_DistanceInMeters", reticleDistanceInMeters); } private void SetPointerTarget(Vector3 target, bool interactive) { Vector3 targetLocalPosition = transform.InverseTransformPoint(target); reticleDistanceInMeters = Mathf.Clamp(targetLocalPosition.z, kReticleDistanceMin, kReticleDistanceMax); if (interactive) { reticleInnerAngle = kReticleMinInnerAngle + kReticleGrowthAngle; reticleOuterAngle = kReticleMinOuterAngle + kReticleGrowthAngle; } else { reticleInnerAngle = kReticleMinInnerAngle; reticleOuterAngle = kReticleMinOuterAngle; } } } ÇÁ¸®ÆÕÀ¸·Î ¸¸µé±âÀ§ÀÇ ¸ðµç ±â´ÉÀ» ÇÁ¸®ÆÕÀ¸·Î ¸¸µé¾î º»´Ù.1. EventSystem¿¡ CustomGvrPointerInputModule.cs ½ºÅ©¸³Æ®¸¦ Ãß°¡ÇÑ´Ù. 2. "Main Camera"ÀÇ ÀÚ½ÄÀ¸·Î ºó GameObject¸¦ ¸¸µé°í À̸§À» CustomReticlePointer·Î ¹Ù²Û´Ù. 3. CustomReticlePointerÀÇ ÀÚ½ÄÀ¸·Î GvrReticlePointer¸¦ Ãß°¡ÇÑ´Ù. 4. Canvas¸¦ ¸¸µé°í GazeSlider¸¦ ¸¸µç´Ù. (À§¿¡¼ ÀÚ¼¼È÷ ÂüÁ¶ÇÑ´Ù.) 5. GvrReticlePointer °ÔÀÓ¿ÀºêÁ§Æ®¿¡ CustomGvrReticlePointer.cs ½ºÅ©¸³Æ®¸¦ ÄÄÆ÷³ÍÆ®·Î Ãß°¡ÇÏ°í Canvas ¾Æ·¡ "GazeSlider"¸¦ "Gaze Slider Object"·Î µå·¡±×ÇÑ´Ù. 6. GvrReticlePointer °ÔÀÓ¿ÀºêÁ§Æ®¿¡¼ GvrReticlePointer ÄÄÆ÷³ÍÆ®´Â Á¦°ÅÇÑ´Ù. 7. ÇÁ·ÎÁ§Æ®ºä·Î µå·¡±×ÇÏ¿© ÇÁ¸®ÆÕÀ¸·Î ¸¸µç´Ù. ¼Ò½º: https://github.com/hisimpson/SwitchVR_GazeAutoClick |