В феврале, я проходил курс по С++ для Unreal Engine в контексте VR. В это же время я начал параллельно писать основу для VR проекта с нуля, но в какой то момент, понял насколько много вещей нужно учесть и протестировать, прежде чем двигаться дальше.
Я закомментировал весь написанный код и перешел на VR Expansion Plugin. В этом плагине уже решено множество проблем, как синхронизация камеры HMD и капсулы персонажа, поведение капсулы когда ты движешься физически упираешься в стену и так далее. Я до сих пор разбираю код этого плагина. Плагин поддерживает разные версии Unreal Engine. Всё доступно в репозитории бесплатно: https://github.com/mordentral/VRExpansionPlugin
Я полностью перенес код VR Expansion Plugin в свой проект, чтобы в последующем модифицировать его. Так как я базируюсь на коде этого плагина, я не буду описывать туториалы "как с нуля построить VR проект", вроде бы звучит логичным "покажи как перенести код из VR Expansion Plugin и модифицировать его", но у меня столько времени нет.
Статьи будут выходить, в статьях я буду стараться передавать модульный функционал. Показывая пример как его можно применить на простом примере. Так как контекст всех проектов разный, основываясь на моем примере вы можете применить его у себя (нейронки в помощь, грузите ей "свой контекст" и "мой пример", смотрите как можно интегрировать и надо ли оно вам вобще)
А сегодня поговорим про перевод персонажа в спринт с помощью махов рук. Такая механика применяется например в Blade and Sorcery или Battle Talent.
Функции вероятнее всего будут приложены скриншотами, а скопировать код можно будет из https://gist.github.com/8bitsage
Концепция механики Arm Swinging
Идея проста: когда игрок двигается вперед и активно машет руками (как при беге), персонаж автоматически переходит в режим спринта. Это позволяет:
Освободить кнопки контроллера для других действий.
Сделать передвижение более иммерсивным.
Связать физическую активность игрока со скоростью персонажа в игре.
Архитектура решения
Шаг 1: Определение переменных
В заголовочном файле (h файл) класса персонажа (например, AVRPlayerCharacter.h) добавляем необходимые переменные:
// === Настраиваемые параметры ===
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Movement|ArmSwing",
meta = (ClampMin = "3.0", ClampMax = "15.0"))
float ArmSwingThreshold = 7.0f;
// Порог активации спринта в сантиметрах
// Определяет минимальное суммарное расстояние, которое должны пройти
// оба контроллера за один цикл проверки для активации спринта
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Movement|ArmSwing",
meta = (ClampMin = "0.01", ClampMax = "0.2"))
float CheckFrequency = 0.02f;
// Частота проверки движения контроллеров в секундах
// Меньшее значение = более отзывчивая механика, но больше нагрузка
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Movement")
float SprintSpeed = 600.0f;
// Скорость персонажа при спринте (см/с)
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Movement")
float RunSpeed = 400.0f;
// Скорость персонажа при беге (см/с)
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Movement")
float WalkSpeed = 200.0f;
// Скорость персонажа при ходьбе (см/с)
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Movement",
meta = (ClampMin = "0.1", ClampMax = "0.5"))
float MinStickMagnitudeForRun = 0.3f;
// Минимальное отклонение стика для перехода от ходьбы к бегу
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Movement",
meta = (ClampMin = "0.5", ClampMax = "0.9"))
float MinForwardRatioForSprint = 0.7f;
// Минимальная доля движения вперед для возможности спринта
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Movement",
meta = (ClampMin = "0.3", ClampMax = "0.7"))
float MaxLateralRatioForSprint = 0.5f;
// Максимальная доля бокового движения для возможности спринта
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Movement",
meta = (ClampMin = "0.1", ClampMax = "1.0"))
float BackwardSpeedMultiplier = 0.7f;
// Множитель скорости при движении назад
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Movement",
meta = (ClampMin = "0.1", ClampMax = "1.0"))
float SidewaysSpeedMultiplier = 0.8f;
// Множитель скорости при движении вбок
// === Внутренние переменные ===
FVector LastLeftControllerPos;
// Предыдущая позиция левого контроллера для расчета дельты
FVector LastRightControllerPos;
// Предыдущая позиция правого контроллера для расчета дельты
FTimerHandle ArmSwingTimerHandle;
// Хэндл таймера для периодической проверки движения
bool bIsArmSwinging = false;
// Флаг текущего состояния: true если игрок машет руками достаточно интенсивно
void CheckArmSwing();
void SetupPlayerInputComponent(UInputComponent* PlayerInputComponent);
void MoveCharacter(const FInputActionValue& Value);
void HandleMovementInput(const FVector2D& InputValue);
Логика определения скорости:
Слабое отклонение стика (< MinStickMagnitudeForRun) — всегда ходьба, независимо от махов руками
Сильное отклонение стика (≥ MinStickMagnitudeForRun): Движение вперед (ForwardRatio > MinForwardRatioForSprint и боковое отклонение < MaxLateralRatioForSprint):С махами руками → Спринт Без махов руками → Бег Движение назад или вбок → Только бег (спринт невозможен)
Такой подход предотвращает нереалистичный спринт при движении назад или боком, делая механику более естественной. Все пороговые значения можно настроить в редакторе для точной подгонки под ваш проект.
Ссылки на документацию:
Шаг 2: Реализация детекции махов руками
В файле реализации (AVRPlayerCharacter.cpp) создаем функцию проверки:
Как это работает:
Функция вызывается каждые CheckFrequency секунд (по умолчанию 0.02с = 50 раз в секунду) Настройте для оптимальной производительности.
Измеряется расстояние, пройденное каждым контроллером с последнего вызова
Если суммарное расстояние превышает ArmSwingThreshold, считается что игрок "бежит"
Шаг 3: Инициализация системы
В методе BeginPlay() запускаем периодическую проверку:
Ссылки на документацию:
Шаг 4: Интеграция с Enhanced Input System
Теперь нужно связать механику с системой ввода. Сначала настраиваем привязку action'а:
Затем реализуем функцию обработки ввода:
Шаг 5: Управление скоростью персонажа
Важно понимать! В моем примере применение скорости персонажа в зависимости от направления взгляда камеры. Например, стик отодвинут вперед (мы бежим вперед), повернули голову на 90 градусов, мы по прежнему бежим вперед, только уже в ту сторону, когда повернулась голова.
Иногда делают реализации, когда вектор движения применяется от направления капсулы. Направление "вперед" для ввода с джойстика определяется направлением "вперед" капсулы персонажа (или его тела), а не тем, куда смотрит HMD (голова). Игрок может свободно осматриваться головой, не меняя направления движения. При этом повороты самой капсулы (тела персонажа) обычно управляются отдельно, например, правым стиком (snap turn/smooth turn) или физическим поворотом игрока, если включено bUseControllerRotationYaw для персонажа и соответствующая логика в PlayerController.
Вернемся к моему примеру. Основная логика применения скорости находится в HandleMovementInput:
Ссылки на документацию:
Настройка в редакторе
1. Создание Input Action
Создайте новый Input Action IA_MoveCharacter
Установите Value Type: Axis2D
В Input Mapping Context привяжите к левому стику VR контроллера
Укажите персонажу Input Mapping Context
2. Настройка параметров движения
В Blueprint персонажа или в Details панели вы можете настроить все параметры:Параметры махов руками:
Sprint Speed — скорость при спринте
Run Speed — скорость при беге
Walk Speed — скорость при ходьбе
Min Stick Magnitude For Run — минимальное отклонение стика для бега
Min Forward Ratio For Sprint — доля движения вперед для спринта
Max Lateral Ratio For Sprint — максимальная доля бокового движения для спринта
Backward Speed Multiplier — коэффициент замедления при движении назад (0.7 = 70% от обычной скорости)
Sideways Speed Multiplier — коэффициент замедления при движении вбок (0.8 = 80% от обычной скорости)
Демонстрация этой логики на моем проекте:
Заключение
Эта механика делает передвижение в VR более естественным и погружающим. Игроку не нужно думать о кнопках — достаточно начать активнее двигать руками, как при реальном беге. Демонстрация примера показана упрощенно, модифицируйте код под конкретные нужды проекта, все ключевые параметры доступны для настройки прямо в редакторе.