Мобильный 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-приложения.







