Мобільний VR без контролерів — єдиний спосіб взаємодії — напрямок погляду. Здається, просто дивись на кнопку — вона натискається. На практиці неправильно реалізований gaze дратує користувача швидше за будь-який інший UI-паттерн. За роки роботи ми виробили стек та підходи, що гарантують комфортну взаємодію. Наш досвід — 5+ років у мобільній VR-розробці, понад 20 проєктів з gaze-інтерфейсом. Ми пропонуємо готове рішення під ключ: від raycast до прогрес-індикатора. Базову систему реалізуємо за 3–5 днів, повну — за 1–2 тижні. Згідно з документацією Google Cardboard, gaze-based interaction є стандартним методом введення для мобільного VR без контролерів.
Як працює raycast з центру погляду?
Gaze визначається напрямком погляду камери. Ray пускається з позиції камери вперед по Camera.main.transform.forward:
void FixedUpdate() {
Ray gazeRay = new Ray(Camera.main.transform.position,
Camera.main.transform.forward);
if (Physics.Raycast(gazeRay, out RaycastHit hit, maxGazeDistance, interactableLayer)) {
var target = hit.collider.GetComponent<IGazeTarget>();
if (target != null) {
HandleGazeHit(target, hit.point);
} else {
HandleGazeMiss();
}
} else {
HandleGazeMiss();
}
}
FixedUpdate() замість Update() — стабільна частота викликів не прив'язана до FPS. На слабких пристроях при просіданнях до 30 FPS Update() дає нерівномірний відгук. Шар interactableLayer — обов'язково: raycast по всій сцені дорого, і користувач не повинен випадково активувати невидимі колайдери.
Як реалізувати reticle (курсор погляду)?
Reticle — візуальний індикатор точки погляду. Розміщується в world space на поверхні об'єкта під поглядом. Відстань динамічна: reticle «прилипає» до хітпойнту.
void UpdateReticle(Vector3 hitPoint, Vector3 hitNormal) {
reticleTransform.position = hitPoint + hitNormal * RETICLE_OFFSET;
reticleTransform.rotation = Quaternion.LookRotation(-hitNormal);
// Масштаб постійний у кутових одиницях (constant apparent size)
float dist = Vector3.Distance(Camera.main.transform.position, hitPoint);
reticleTransform.localScale = Vector3.one * dist * ANGULAR_SIZE;
}
Зазначимо: коли об'єкта під поглядом немає — reticle на дефолтній відстані (3–5 метрів). Не ховаємо його: користувач завжди повинен бачити, куди дивиться.
Dwell-активація та прогрес-індикатор
Користувач дивиться на об'єкт N секунд — відбувається активація. Оптимальний час dwell: 1.2–2.0 секунди. Менше 1 секунди — випадкові активації при огляді сцени, більше 2 секунд — втомлює. Прогрес має бути помітним. Кільце, що заповнюється навколо reticle — стандарт:
public class GazeDwellController : MonoBehaviour {
[SerializeField] private float dwellTime = 1.5f;
[SerializeField] private Image progressRing;
private float dwellProgress = 0f;
private IGazeTarget currentTarget;
private bool isActivated = false;
public void OnGazeEnter(IGazeTarget target) {
currentTarget = target;
dwellProgress = 0f;
isActivated = false;
progressRing.gameObject.SetActive(true);
}
public void OnGazeStay() {
if (isActivated) return;
dwellProgress += Time.deltaTime / dwellTime;
progressRing.fillAmount = dwellProgress;
if (dwellProgress >= 1f) {
isActivated = true;
currentTarget?.OnGazeActivate();
StartCoroutine(ResetAfterDelay(0.5f));
}
}
public void OnGazeExit() {
currentTarget = null;
progressRing.gameObject.SetActive(false);
dwellProgress = 0f;
}
}
Після активації — короткий cooldown перед наступною активацією того самого об'єкта (0.5–1.0 сек). Інакше користувач не встигає прибрати погляд і кнопка «натискається» двічі.
Hover-стан: зворотний зв'язок до активації
Зазначимо: коли користувач дивиться на об'єкт, але dwell ще не завершено, потрібен негайний візуальний зворотний зв'язок. Об'єкт повинен якось відреагувати при OnGazeEnter — до завершення часу активації. Варіанти:
- Підсвітка: зміна emission кольору матеріалу
- Масштаб: об'єкт трохи збільшується (
0.05fвистачає) - Анімація: іконка реагує на погляд
- Звуковий сигнал: короткий click на початку dwell
Без цього користувач не розуміє, чи «бачить» його застосування.
Cardboard button як підтвердження
У Cardboard є фізична кнопка (магнітний тригер). Додаємо її як альтернативний метод активації замість dwell — для досвідчених користувачів це швидше та зручніше:
// Cardboard SDK trigger event
void Update() {
if (CardboardInput.GetButtonDown()) {
TriggerCurrentGazeTarget();
}
}
Кнопка — не заміна dwell, а доповнення. Не всі корпуси Cardboard мають робочу магнітну кнопку.
Які типові помилки виникають при gaze-взаємодії?
Занадто маленький колайдер інтерактивного об'єкта — користувач «промахується» повз кнопку. Колайдер має бути на 10–20% більший за видимий об'єкт. Швидкість погляду (кутова швидкість) впливає на точність: при швидкому огляді поріг швидкості скидає dwell. Reticle тремтить через гіроскоп — Lerp з часом 20 мс усуває тремтіння. Використовуйте окремий шар для raycast, щоб уникнути випадкових активацій та заощадити продуктивність.
| Помилка | Рішення |
|---|---|
| Занадто маленький колайдер | Збільшити колайдер на 10–20% відносно візуалу |
| Випадкові активації | Поріг кутової швидкості голови при старті dwell |
| Тремтіння reticle | Lerp позиції reticle з time ~20ms |
Порівняння методів активації
| Параметр | Dwell-активація | Cardboard кнопка |
|---|---|---|
| Готовність на всіх пристроях | Так | Ні (не у всіх працює) |
| Втомлюваність | Середня | Низька |
| Точність | Добра | Висока |
| Налаштування часу | Так (1.2–2.0 с) | Ні |
Що входить у роботу
- Документація архітектури gaze-системи
- Вихідний код: raycast, reticle, dwell controller, hover-стан
- Інтеграція з Cardboard SDK та альтернативними тригерами
- Тестування на пристроях (iOS/Android) з різними версіями Cardboard
- Підтримка 2 тижні після здачі
Процес роботи
- Аналіз інтерактивних елементів: типи об'єктів, сценарії взаємодії.
- Реалізація raycast-системи з правильними шарами та колайдерами.
- Reticle в world space з constant apparent size.
- Dwell controller з прогрес-індикатором, hover-станом, cooldown.
- Cardboard button як альтернативний тригер.
- Тестування комфорту: час dwell, розміри кнопок, зворотний зв'язок.
Орієнтири за термінами
Базова gaze interaction система з reticle та dwell — 3–5 днів. Повноцінна система з кількома типами інтерактивних об'єктів, анімаціями, звуком та налаштовуваними параметрами — 1–2 тижні.
Гарантуємо комфортну взаємодію. Сертифіковані розробники Unity та Android/iOS. Зв'яжіться з нами, щоб обговорити ваш проєкт — реалізуємо gaze-інтерфейс для вашого мобільного VR-застосування.







