GazeInput UI AutoClick

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