I wrote these scripts to use gaze input with Google Cardboard SDK. Please feel free to use it and let me know if there is any issue with this.
- Add InteractiveItem.cs script to every Selectable/Clickable object (Button, Toggle, Scroll bar etc).
InteractiveItem.cs
using UnityEngine;
using System.Collections;
using UnityEngine.UI;
using UnityEngine.EventSystems;
using System.Collections.Generic;
[RequireComponent(typeof(Selectable))]
public class InteractiveItem : MonoBehaviour, IPointerEnterHandler, IPointerExitHandler, IPointerClickHandler
{
public bool isEntered = false;
public Selectable _selectable;
public float GazeActivationTime = 3f;
BaseInputModule input;
float timeElapsed;
void Start ()
{
input = FindObjectOfType<BaseInputModule>();
_selectable = GetComponent<Selectable>();
}
void Update ()
{
if (!_selectable.IsInteractable())
{
ReticlePointer.Instance.SetFillAmount(0);
return;
}
if(isEntered)
{
timeElapsed += Time.deltaTime;
ReticlePointer.Instance.SetFillAmount(Mathf.Clamp(timeElapsed/GazeActivationTime,0,1));
if(timeElapsed >= GazeActivationTime)
{
timeElapsed = 0;
((GazeInputModule)input).HandleTriggerManually();
ReticlePointer.Instance.SetFillAmount(0);
isEntered = false;
}
ReticlePointer.Instance.Show();
}
else
{
timeElapsed = 0;
}
}
void OnDisable()
{
isEntered = false;
if (ReticlePointer.Instance!= null)
{
ReticlePointer.Instance.SetFillAmount(0);
}
}
#region IPointerEnterHandler implementation
public void OnPointerEnter (PointerEventData eventData)
{
if (_selectable.IsInteractable())
{
Invoke("SetEnteredTrue",0.3f); // start timer after 0.3 seconds of gaze. You can call SetEnteredTrue() directly.
}
}
void SetEnteredTrue()
{
isEntered = true;
}
#endregion
#region IPointerExitHandler implementation
public void OnPointerExit (PointerEventData eventData)
{
if (!_selectable.IsInteractable())
return;
try
{
CancelInvoke("SetEnteredTrue");
isEntered = false;
ReticlePointer.Instance.SetFillAmount(0);
}
catch (System.Exception ex)
{
Debug.LogError(ex.Message);
}
}
#endregion
#region IPointerClickHandler implementation
public void OnPointerClick (PointerEventData eventData)
{
isEntered = false;
timeElapsed = 0;
ReticlePointer.Instance.SetFillAmount(0);
}
#endregion
}
- Then this is how you place your pointer in hierarchy (Note: I am using legacy prefab GVRMain here. But you can add selected gameObject under Main Camera in whatever your prefab is):

- GazePointerRing is should be empty gameObject, attach ReticlePointer.cs to this object.
- Pointer and Fill_Image arering images in a canvas. scale them accordingly and assign them in ReticlePointer’s Inspector as reference as instructed. Fill_Image should have Image type “Fill”.
ReticlePointer.cs
using UnityEngine;
using System.Collections;
using UnityEngine.UI;
using UnityEngine.EventSystems;
public class ReticlePointer : MonoBehaviour
{
public bool hideByDefault = true;
public Image image; // assign Fill_Image here.
public float hideTimeoutPeriod = 3;
public GameObject pointerObject; // assign Pointer Image here.
public static ReticlePointer Instance;
void Awake()
{
Instance = this;
image.fillAmount = 0;
}
public void SetFillAmount(float amount)
{
image.fillAmount = amount;
}
void Update()
{
if (hideByDefault)
{
if (EventSystem.current.IsPointerOverGameObject())
{
CancelInvoke("Hide");
Show();
}
else
{
SetFillAmount(0);
Invoke("Hide",hideTimeoutPeriod);
}
}
else
{
Show();
}
}
public void Show()
{
pointerObject.SetActive(true);
}
public void Hide()
{
pointerObject.SetActive(false);
}
}
- And finally add following lines of code to GazeInputModule.cs which comes within SDK.
public void HandleTriggerManually()
{
HandleTrigger();
}
You are good to go.
You don’t need to use GVRReticle from cardboard SDK.