04 — Мультиплеєр та VR¶
🇬🇧 English | 🇺🇦 Українська
Серверний шлях¶
Простими словами. Клієнт лише просить: «дай мені цей ящик». Сервер, як суддя, перевіряє, чи гравець справді поруч, чи бачить ящик, чи ящик не в чужих руках, і тільки тоді дозволяє. Тому чесний гравець бачить підказку одразу, а читер не дотягнеться до предмета через стіну.
RPC розміщені на компоненті pawn, яким володіє клієнт. Предмети не передають network ownership заради взаємодії. У мережевій грі ціль повинна мати Replicates=true.
Сервер перевіряє інтервал запитів, ціль, пакет поз, зайнятість руки й реєстр захоплень. Потім він повторює пошук цілі, перевірки тегів, CanInteract за наявності інтерфейсу та CanBeginGrip компонента цілі. Відповідь приходить у OnActionResult разом із причиною відмови (EInteractionRejectReason): таблиця причин — у 13, а зрозумілі гравцеві повідомлення — у уроці 3.
Дві руки одного pawn можуть тримати один предмет, якщо MaximumHands=2; два різні pawn — не можуть. Запит release має збігатися із серверними HeldGrip.Target і HeldGrip.RequestId. Запізніле відпускання попереднього захоплення не звільняє нове. Механізм резервування описано нижче.
Мережеві межі¶
Межі задаються для всього проєкту в Project Settings → Plugins → Interaction System (UInteractionSystemSettings, файл Config/DefaultGame.ini). Окремий InteractorComponent може перевизначити будь-яке значення: увімкніть прапорець біля параметра в Details (bOverrideMaxOriginDistance тощо) і задайте власне.
| Параметр | Початкове значення | Що обмежує |
|---|---|---|
MaxOriginDistance |
500 см | Відстань від pawn до повідомленої пози камери чи руки; враховуйте довжину camera boom |
MinActionInterval |
0.08 с | Мінімальний час між прийнятими запитами одного джерела |
PoseTimeout |
0.75 с | Скільки сервер чекає на будь-яку позу віддаленого джерела, перш ніж відпустити його предмет |
MaxThrowSpeed |
2500 см/с | Найбільша лінійна швидкість кидка |
MaxAngularSpeed |
3600 градусів/с | Найбільша кутова швидкість кидка |
Фактичні значення джерела повертають GetMaxOriginDistance, GetMinActionInterval, GetPoseTimeout, GetMaxThrowSpeed і GetMaxAngularSpeed; їх також показує панель налагодження. Змінюйте межі узгоджено на сервері й клієнтах: остаточне рішення завжди за сервером.
; Config/DefaultGame.ini of your project
[/Script/InteractionSystem.InteractionSystemSettings]
MaxOriginDistance=650
PoseTimeout=1.0
Сервер обрізає завеликі швидкості. При втраті контролера, таймауті пози, деактивації або EndPlay джерела предмет відпускається без кидка. Фізика, пошкодження, руйнування та втикання списа обчислюються на сервері.
Пози рук¶
Клієнт регулярно надсилає серверу пакет поз усіх рук pawn, а також додає його до кожного запиту. Кожна рука має власний номер зразка, і сервер перевіряє кожну руку окремо: відстань від pawn і видимість від очей pawn. Якщо вільна рука опинилася за перешкодою, сервер відкидає лише її зразок, а предмет у другій руці залишається. Пакет як ціле відкидається, лише коли він зіпсований: чужа рука, повтори, понад вісім записів.
Простими словами. Ви надсилаєте судді окремі фото кожної руки. Якщо одне фото розмите, суддя не вірить лише йому — фото другої руки й надалі зараховується.
Повний вміст пакета, перевірки й таймаути — у 13 — Мережева модель.
Дані про клієнтський напрямок залишаються даними клієнта. Геометричні межі та повторний трейс обмежують допустиму дію, але не є повною античит-системою.
Реєстр захоплень UInteractionClaimSubsystem¶
Простими словами. Реєстр — це гардероб із номерками. Хто перший отримав номерок на предмет, той його й тримає; другий гравець отримає відмову
Claimed, доки номерок не повернуть.
UInteractionClaimSubsystem — автоматичний subsystem кожного ігрового UWorld. Він є єдиним серверним реєстром права на предмет: для кожної цілі зберігаються pawn-власник, одна або дві руки цього pawn і, за потреби, батьківська пов'язана дія. Реєстр використовує ключі, що не продовжують життя акторів, очищає знищені руки та цілі й ніколи не стає клієнтським джерелом істини. Клієнти дізнаються про результат через OnActionResult, HeldGrip і реплікований стан компонента маніпуляції.
Наприклад, стандартний спис має MaximumHands=1. Коли дві руки одночасно надсилають RequestGrab, перша допустима заявка резервує його; друга отримує відмову. Для гвинтівки з MaximumHands=2 друга рука того самого pawn може приєднатися. Рука іншого гравця відхиляється незалежно від вільного місця. RequestInteraction руки, що вже тримає предмет, викликає його Use; стороннє джерело не може виконати звичайну дію над зарезервованою ціллю.
| Метод | Призначення |
|---|---|
TryClaim(Target, Interactor, MaximumHands) |
На сервері додає руку того самого pawn; ліміт — одна або дві руки, інший pawn завжди отримує false |
FindClaimant(Target) / FindHands(Target) |
Перша рука або всі руки, що тримають ціль |
FindOwningPawn(Target) / IsClaimed(Target) |
Власник і факт резервування, зокрема пов'язаного без активної руки |
GetClaimedTargets() / FindLinkedParent(Target) |
Усі зайняті цілі; батьківський предмет пов'язаного резервування, наприклад лук для стріли |
ReleaseClaim(Target, Interactor) |
Знімає лише цю руку; друга рука залишається власником і стає primary |
ReserveLinked(Target, Parent, Pawn) |
Резервує стрілу, поршень чи іншу дочірню ціль за тим самим pawn і батьківським предметом; цикли заборонені |
ReleaseLinked(Target, Parent) / ForgetTarget(Target) |
Завершує пов'язану дію та рекурсивно очищає залежні резервування |
Запити (Find…, IsClaimed, GetClaimedTargets) доступні і в Blueprint, але лише на сервері. Змінюйте реєстр тільки через запити interactor: компонент узгоджує claim, HeldGrip і callbacks. При відпусканні сервер викликає EndGrip компонента предмета і знімає лише claim цієї руки. Маніпуляція перебудовує опору для руки, що залишилася; остання рука запускає політику відпускання. Пов'язане резервування стріли може зберігатися без рук до зняття з тятиви.
if (UInteractionClaimSubsystem* Claims = GetWorld()->GetSubsystem<UInteractionClaimSubsystem>())
{
UInteractorComponent* Hand = Claims->FindClaimant(Spear);
}
Дозвіл на захоплення¶
UInteractionManipulationComponent::bGrabEnabled і AInteractionPhysicsActor::bCanBeGrabbed реплікуються: достатньо змінити їх на сервері, і клієнти перестануть показувати підказку. Для маніпуляції використовуйте Set Grab Enabled: вимкнення одразу безпечно відпускає поточні руки.
Дві VR-руки¶
- На replicated pawn створіть два motion controller компоненти.
- Додайте по
InteractorComponentпід кожен контролер. ВимкнітьUse Pawn Viewpoint, залиштеGrabOffset=(0,0,0). - Налаштуйте forward axis джерела вздовж локального +X. За потреби додайте проміжний scene component для корекції орієнтації.
- Викликайте
RequestGrabвід grip/input action відповідної руки. ДляRequestReleaseвикористайте відфільтровану лінійну та кутову швидкості tracked controller у світових координатах. - Замініть текстуру
Eна потрібний символ контролера або використайте власний HUD.
FocusUpdateHz і PoseReplicationHz налаштовуються окремо. Обидва мають типовий рівень 30 Гц: перший керує пошуком фокуса, другий — частотою пакета поз обох рук. Значення 20 Гц зменшує CPU, RPC і мережевий трафік, але робить наведення та віддалену руку менш плавними; утримуваний предмет усе одно розв'язується кожен кадр. Авторитетна поза руки реплікується іншим клієнтам, власник не отримує корекції свого tracked transform.
Предмет у руці власника рухається без затримки мережі в обох режимах: Direct власник розв'язує сам, а Physics — передбачає. Коли серверне тіло впирається в перешкоду далі за OwnerPredictionTolerance (10 см), власник плавно переходить до серверної пози й бачить, що предмет зупинила стіна (08). Саме захоплення не передбачається: предмет опиняється в руці після відповіді сервера.
VR locomotion, tracking origin, реплікація тіла й Enhanced Input bindings визначаються проєктом. Передавайте актуальність пози через SetTrackingValid: коротка втрата утримує останню позу протягом TrackingLossGrace, довша безпечно завершує хват. Перед teleport/recenter викличте CancelGrip(Recentered), щоб старі швидкості не стали кидком. Реальний HMD не є частиною автоматичних тестів.
Повний VR-приклад¶
Детальна схема профілів, socket/component/free anchors, двох рук, лука, просторової панелі, маховика та посадки поршня описана в 08 — VR-маніпуляції. Карта /InteractionSystem/Demo/VRLab/Maps/VRLab працює з OpenXR і має desktop-емулятор двох рук, тому її можна перевірити без гарнітури. Для Quest 2 використовуйте той самий pawn із MotionControllerComponent, EHMDTrackingOrigin::LocalFloor і Enhanced Input; перед випуском перевірте Android-збірку на своєму пристрої.
Перевірка мережі¶
Найшвидший спосіб побачити, що відбувається, — панель налагодження: на клієнті PIE введіть EnableCheats, потім InteractionOverlay. Для затримки й втрат пакетів використовуйте Net PktLag=150 і Net PktLoss=5.
Автоматичні тести запускають dedicated server і клієнтів в одному процесі PIE:
| Тест | Що перевіряє |
|---|---|
Network.TwoClientsGrabMoveReleaseAndEmbed |
Одночасну конкуренцію за предмет, реплікацію власника, рух віддаленої руки, відпускання, втикання списа, рух разом із поверхнею й повторне захоплення |
Network.ObstructedFreeHandKeepsOtherGrip |
Закрита перешкодою вільна рука не змушує сервер упустити предмет в іншій руці |
Network.ClientDeactivatedSourceKeepsPoses |
Локально вимкнена перша рука не зупиняє оновлення поз другої |
Network.GrabPermissionReplicates |
Вимкнений на сервері дозвіл на захоплення доходить до клієнта |
Network.OwnerPredictsPhysicsGrip |
Власник бачить фізичний предмет у своїй руці одразу, навіть із затримкою пакетів |
Network.PushModelReplicatesManipulation |
З увімкненим push model другий клієнт бачить рух і відпускання чужого предмета |
Demo.SpearMeshThrowAndRetrieval |
Фізичний цикл із mesh SpearRange; пропускається, якщо демо-контент видалено |
Це не замінює перевірки різних машин, високої затримки, втрат пакетів і late join у вашій грі.