08 — VR-маніпуляції та профілі¶
🇬🇧 English | 🇺🇦 Українська
Цей розділ описує реалізацію InteractionSystem 1.0 для Unreal Engine 5.8. Один UInteractionManipulationComponent обслуговує гвинтівку, сокиру, бінокль, двигун, просторову панель, колесо дверей і будь-який інший актор. Профіль — UInteractionManipulationProfile або InlineSettings — визначає рух, кількість рук, масштабування, pivot, фізику та обмеження.
Як читається профіль¶
MaximumHands=2 дозволяє другій руці приєднатися тільки після першої і тільки з того самого pawn. GripSelection має три режими: Free приймає долоню без авторської точки, Authored вимагає валідну точку, PreferAuthored використовує точку, якщо вона доступна, інакше бере вільну долоню. Alignment=Align при першому захопленні суміщає authored anchor із долонею, а для вільного хвату — корінь предмета з долонею; KeepOffset зберігає фактичну позу. У простому InteractionLab кубики використовують Align, щоб опинятися поблизу камери після натискання E. У VR Lab авторські профілі з KeepOffset не змінюють позу предмета раптово.
Movement=Direct підставляє вирішену трансформацію щокадру. Movement=Physics створює один серверний PhysicsHandle; при відпусканні профіль може залишити тіло в симуляції та передати обмежені швидкості. Фізичний режим не поєднується з масштабуванням: це захищає Chaos від конфлікту масштабу й фізичної маси. Інші клієнти отримують атомарний snapshot і згладжують його RemoteInterpolationSpeed.
Простими словами.
Direct— предмет «приклеєний» до руки: де рука, там і предмет, навіть якщо рука пройшла крізь стіну.Physics— предмет на пружині: рука тягне його, а стіна зупиняє. Для меча, що має впиратися в щит, обирайтеPhysics; для бінокля —Direct.
BreakDistance (100 см) рахується від найближчого положення, якого тіло досягало після захоплення. Предмет, узятий здалеку, спершу «підлітає» до руки, і хват не рветься. Якщо ж тіло застрягло, а рука відійшла ще на BreakDistance, хват безпечно завершується.
Профілі VR Lab лежать у /InteractionSystem/Demo/VRLab/Profiles і є готовими відправними точками:
| Профіль | Ключові значення | Предмети демо |
|---|---|---|
DA_RigidObject |
Movement=Physics, MaximumHands=2, GripSelection=Authored, KeepOffset |
гвинтівка, сокира |
DA_SymmetricObject |
MaximumHands=2, Authored, TwoHandPivot=Midpoint |
бінокль |
DA_InspectObject |
MaximumHands=2, bAllowScaling, Midpoint, без симуляції й кидка після відпускання |
двигун |
DA_SpatialPanel |
Authored, bAllowScaling, Midpoint, масштаб 0.6–1.8, без симуляції й кидка |
просторова панель |
DA_SingleHand |
MaximumHands=1, решта типова |
поршень, стріли |
DA_BowBody |
MaximumHands=1, Authored, без симуляції й кидка |
лук |
DA_RotaryHandle |
MaximumHands=1, Constraint=Rotary, 0–720°, без симуляції й кидка |
колесо бункера |
Типові значення FInteractionManipulationSettings: Direct, Free, KeepOffset, MaximumHands=2, Primary, масштаб 0.25–4, bSimulateOnRelease=true, bAllowThrow=true, жорсткість 3000/2500, демпфування 200/200, BreakDistance=100 см, Constraint=None, вісь X, хід 0–720. Повний перелік полів — у довіднику API. Профіль перевіряється перед кожним хватом (ValidateSetup): масштабування вимагає Direct, двох рук і відсутності обмеження; обмеження — Direct і однієї руки. Невалідний профіль не дає взяти предмет, а активний хват безпечно завершується.
Дворучний solver зберігає відстань, frame пари, поворот і масштаб. TwoHandPivot=Primary зберігає primary-hand як опору для гвинтівки; Midpoint масштабує й обертає навколо середини для бінокля або панелі. Межі MinimumScale і MaximumScale застосовуються симетрично. Коли руки майже збігаються, solver тимчасово утримує останню валідну позу, а після розходження перебудовує baseline — це не створює випадкового стрибка на 180°.
Фізичний режим у руці власника¶
Гравець, який тримає предмет із Movement=Physics, бачить його у своїй руці одразу, без затримки мережі: клієнт передбачає позу тим самим розв'язувачем, що й сервер. Сервер повідомляє, наскільки його тіло відстає від бажаної пози (BodySeparation). Поки відставання менше половини OwnerPredictionTolerance (типово 10 см), власник бачить своє передбачення; далі він плавно переходить до серверної пози, тож упертий у стіну предмет зупиняється і в його руці. Інші гравці завжди бачать серверну позу.
| Властивість | Початково | Призначення |
|---|---|---|
bPredictPhysicsForOwner |
увімкнено | Передбачати фізичний предмет у руці власника |
OwnerPredictionTolerance |
10 см | Відставання серверного тіла, після якого власник бачить серверну позу |
Коли компонент оновлюється¶
Компонент тікає щокадру, поки предмет тримають, рухає зовнішній драйвер, тіло симулюється й не спить або клієнт ще наближає позу до серверної. Нерухомий предмет перевіряє стан раз на IdleUpdateInterval (0.5 с) і прокидається одразу, щойно його трансформація змінилася чи фізичне тіло прокинулося. Тому сотні предметів, що лежать, майже не навантажують game thread.
Пересувати предмет з gameplay-коду можна звичайними функціями (SetActorLocation, SetActorRotation): компонент помітить зміну й передасть її клієнтам. SyncTransform() на сервері надсилає нову позу негайно, не чекаючи наступного тіку.
Увімкнення та вимкнення захоплення¶
SetGrabEnabled(false) на сервері забороняє нові хвати й безпечно відпускає поточні руки; SetGrabEnabled(true) знову дозволяє брати. Значення bGrabEnabled реплікується, тож клієнти одразу перестають показувати підказку. Так у VR Lab колесо бункера стає недоступним, поки двері відчинені.
Повернення на місце¶
ResetManipulation() на сервері звільняє руки й повертає предмет «додому»:
| Предмет | Куди повертається |
|---|---|
| Вільний | У HomeTransform — позу на початку гри |
| Ручка з обмеженням | У нульовий хід свого обмеження |
| Іменована частина актора | Назад під свого батька, у початкову відносну позу |
HomeTransform запам'ятовується в BeginPlay. Якщо предмет створили й перемістили пізніше, задайте дім на сервері через SetHomeTransform; поточне значення повертає GetHomeTransform.
Сокети, компоненти та їх відсутність¶
UInteractionGripPointComponent має Source=Socket або Source=Component. Для socket anchor задайте SocketName, а за потреби й SocketComponentName — точне ім'я компонента з цим socket; порожнє SocketComponentName означає батьківський компонент самої точки. SocketOffset коригує позу відносно socket без зміни mesh. Для component anchor достатньо окремого scene component з локальною трансформацією. Якщо socket перейменований, mesh не завантажений або компонент видалений, точка стає невалідною і grip завершується без переходу до origin. bFallbackToComponent дозволяє явно погодити безпечний fallback.
Точку вибирає найвищий Priority; серед рівних перемагає найближча до долоні. Долоня має бути не далі за Radius (20 см). AllowedHand обмежує точку лівою чи правою рукою, bAllowFirstHand і bAllowSecondHand визначають, чи може точка почати хват або лише додати підтримувальну руку. Точку, яку вже зайняла інша рука, повторно не обрано.
Для предмета без сокетів оберіть GripSelection=Free і залиште authored points порожніми. Interactor бере GetGripTransform() долоні; анімована skeletal hand, mesh руки або спеціальний socket не потрібні. Це однаковий шлях для Quest 2 і desktop-емулятора. Не покладайтеся на автоматичний origin: він часто знаходиться всередині корпусу й дає неочікуване вирівнювання.
Приклад для гвинтівки: Grip_Main на прикладі SM_Rifle є socket першої руки, Grip_Support — socket другої. Для бінокля в demo використані два component points, тому цей сценарій показує роботу без socket. Для довільного Blueprint достатньо додати component, призначити profile і не змінювати core-код.
Пов'язані предмети: лук і стріла¶
UInteractionBowComponent не є окремим типом захоплення. Лук має одну руку; друга рука тримає стрілу як звичайний UInteractionManipulationComponent. Коли хвіст стріли входить у NockRadius, bow перевіряє, що обидві руки належать тому самому pawn, викликає ReserveLinked(Arrow, Bow, Pawn) і тимчасово передає рух стріли зовнішньому bow driver.
Демонстраційний лук має суцільний вигнутий силует із плечей, що перекриваються, і дві візуальні ділянки тятиви. Їхні кінці збігаються з точками на кінцях плечей (-15, 0, ±65) у локальних координатах. Рухома середина тятиви лишається пов'язаною з поточним натягом, але видима геометрія не визначає фізику пострілу.
Друга рука рухається відносно поточної опори тятиви. Бічний вихід за MaximumLateralOffset скасовує встановлення. Відпускання менш ніж на MinimumShotFraction повертає тятиву без пострілу; intentional release обчислює швидкість на сервері й викликає OnShot. Втрата tracking, CancelGrip, знищення стріли або відпускання лука очищують linked reservation без кидка.
Фіксовані ручки та посадки¶
Constraint=Rotary накопичує signed angle навколо ConstraintAxis, відкидає tracking-стрибки від 90° і обмежує результат MinimumValue..MaximumValue. У VR Lab червоне трипроменеве колесо має діапазон 0..720°, тобто два повні оберти з можливістю перехоплювати обід; після цього двері відкриваються й колесо стає зовнішньо керованим разом із полотном. Constraint=Linear аналогічно проєктує долоню на одну вісь; хід вимірюється у світових сантиметрах незалежно від масштабу компонента.
Рухома частина всередині актора¶
TargetComponentName дозволяє рухати не весь актор, а один його компонент: шухляду шафи, кришку скрині, важіль пульта. Така частина залишається під своїм батьком і під час хвату, і після відпускання. Рамка обмеження прив'язана до батька: якщо шафа їде на платформі, шухляда їде з нею й далі висувається відносно шафи, а накопичений хід зберігається між хватами. Покроковий приклад — урок 4.
Сама частина має бути Movable і не реплікуватися окремо (або бути UInteractionMeshComponent): її рух передає snapshot маніпуляції.
У VRLabBP полотно має окремий компонент DoorPanel, зміщений від кореня до середини дверей: корінь залишається бічною віссю петлі. Після розблокування Blueprint звільняє хват колеса через CancelAllGrips, призначає двері зовнішнім драйвером через SetExternalDriver і плавно повертає корінь. C++-версія у VRLab натомість викликає SetGrabEnabled(false): це і звільняє руку, і забороняє брати колесо, поки двері відчинені. SetExternalPose передає нову світову позу колеса зі збереженим відступом від дверей; snapshot маніпуляції доставляє її клієнтам. Обидва методи зовнішнього руху викликаються на сервері. Таку схему можна використати для ручки, приладу чи іншого предмета, який після дії має рухатися разом з основною збіркою.
Геометрія демонстраційного маховика складається з коротких дотичних секцій гладкого кільця, центральної ступиці й трьох спиць від центра до обода. Генератор розвертає секції з урахуванням знака Roll у UE, щоб обід не виглядав зубчастим, а спиці точно сходилися у ступиці.
UInteractionDockComponent — універсальна посадкова опора. Вона приймає вільну деталь із потрібним Actor Tag, перевіряє відстань, орієнтацію і видимість, фіксує її на власній трансформації, а після повторного захоплення автоматично знімає attachment. Саму опору можна приєднати до компонента або його socket стандартними засобами UE. У демо поршень має Interaction.EnginePiston, сідає у двигун і рухається разом із ним.
Удар і UI¶
UInteractionStrikeComponent перевіряє швидкість ребра, sweep між попередньою та поточною позою, нормаль підходу і contact episode. Швидкість рахується за час між двома позами предмета, а не за один кадр сервера: віддалена рука рухає предмет лише з приходом пакета поз, тому кадри без руху пропускаються й не завищують швидкість удару. Подія не наносить damage сама: гра вирішує, чи розрубати підготовлену мішень, відтворити звук або подати haptic feedback. У VR Lab три валідні удари сокири відкривають підготовлені половини поліна.
World-space panel — звичайний дворукий actor із незалежним WidgetComponent. Коли рука тримає frame, вона не одночасно натискає кнопку: action capture належить interactor. Інша рука або окремий pointer може натиснути TEST POINTER; reset викликає серверне очищення усіх demo-станів.
Запуск VR Lab¶
Покрокове керування всіма предметами, включно з луком і стрілою, наведене в 10 — Як брати та використовувати предмети в демо. Там також описані вибір долоні, пошук предмета в емуляторі й повторна спроба після невдалого захоплення.
- Відкрийте
/InteractionSystem/Demo/VRLab/Maps/VRLabі натисніть Play. - Без HMD автоматично вмикається емулятор: WASD/миша — рух і огляд; утримання LMB/RMB — ліва/права рука;
1/2вибирає долоню для редагування;Alt+мишапереміщує її вбік/угору, колесо миші — вперед/назад;Z/CіR/Fобертають її;Q/Eвиконує use/UI.Tab/Backspaceпереходять між станціями,Homeскидає demo. - Quest Touch уже прив'язаний у
IMC_VRLab: Grip — тримати, Trigger — use/UI, A — наступна станція, X — попередня, B — reset. Pawn має два OpenXR motion controllers і вибирає HMD-режим автоматично, якщоbForceEmulation=false. Станційний teleport скидає активні grips перед переміщенням. Безперервний рух thumbstick у цьому демо не реалізований. - Перевірте, що для кожного фізичного target є проста collision і лише один movement writer. Не додавайте паралельно
ReplicateMovementдо actor, яким керує manipulation snapshot.
Без шолома для гвинтівки або бінокля перейдіть Tab/Backspace до потрібної станції, підійдіть так, щоб модель була перед вами, і наведіть на основне руків'я. Утримуйте RMB для правої руки: предмет залишиться в першій руці, поки кнопка натиснута. Натисніть 1, підведіть ліву долоню до другої точки через Alt+миша і колесо (за потреби поверніть Z/C, R/F), потім утримуйте LMB. Рух двох долонь обертає предмет; гвинтівка й бінокль зберігають розмір. Для масштабування перевірте наступні станції з двигуном і панеллю. Відпускання LMB прибирає підтримувальну руку, відпускання RMB — останню. Локальні сфери показують положення обох долонь.
У своєму проєкті увімкніть OpenXR та Enhanced Input, призначте AInteractionVRGameMode і зареєструйте IMC_VRLab у Enhanced Input → Default Mapping Contexts до початку XR-сесії. Pawn додає context до локального input subsystem. Для Quest 2 налаштуйте Android SDK згідно з вимогами вашої інсталяції UE 5.8 і перевірте збірку та комфорт на самому пристрої.
Режим пристрою визначається лише на машині гравця після отримання керування pawn. PawnClientRestart повторно підключає локальний input, якщо possession надійшов після BeginPlay; owned RPC повідомляє серверу, чи потрібен ближній VR-пошук або промінь емулятора. Відсутність HMD на dedicated server не змінює режим рук VR-клієнта.
Карта, meshes, material instances, profiles і input assets уже входять до плагіна. Відкрийте їх у Content Browser з увімкненим Show Plugin Content, щоб змінити приклад під свою гру. Gameplay реалізований у модулях плагіна; для запуску демонстрації додаткові скрипти не потрібні.