13 — Мережева модель¶
🇬🇧 English | 🇺🇦 Українська
Розділ пояснює, як InteractionSystem працює в мережевій грі: що вирішує клієнт, що перевіряє сервер, які дані передаються і чому предмет може бути відхилено чи відпущено. Основи налаштування описані в 04 — Мультиплеєр та VR; тут — внутрішня механіка, потрібна для діагностики й власних розширень. Незнайомі терміни пояснено у словнику.
Простими словами. Клієнт — гравець, який бачить світ і просить дозволу. Сервер — суддя, який має остаточну версію світу. Гравець одразу бачить свою підказку й рух предмета у власній руці, але хто що тримає, записує лише суддя.
Хто що вирішує¶
| Що | Де вирішується | Як дізнаються інші машини |
|---|---|---|
| Фокус, підсвітка, підказки | Локально, на машині гравця | Не реплікуються |
| Пози камери й рук | Клієнт повідомляє, сервер перевіряє кожну руку окремо | Ненадійний RPC ServerUpdatePoses і копія пакета в кожному запиті; іншим клієнтам — реплікована трансформація interactor |
| Захоплення, використання, відпускання | Сервер | Надійні RPC, replicated HeldGrip, ClientActionResult з причиною |
| Право на предмет | Сервер, UInteractionClaimSubsystem |
Сам реєстр не реплікується; результат видно через HeldGrip і snapshot предмета |
| Рух, масштаб, прикріплення предмета | Сервер; власник розв'язує рух у своїй руці сам (Direct) або передбачає (Physics) |
Replicated snapshot UInteractionManipulationComponent |
| Фізика, втикання, удари, шкода | Сервер | Snapshot, multicast для звуку й ефектів |
RPC розміщені на UInteractorComponent pawn, яким володіє клієнт. Предметам не потрібно передавати network ownership.
Шлях запиту захоплення¶
- Клієнт.
RequestGrabперевіряє, що компонент активний, pawn керується локально, трекінг надійний і рука вільна. Компонент збирає пакет поз усіх активних рук pawn, шукає ціль тим самим трасуванням, що й фокус, запам'ятовує її як очікувану, збільшуєLocalRequestIdі надсилає надійнийServerRequestAction(Target, Grab, RequestId, Poses). - Сервер. Перевіряє умови в порядку з таблиці причин відмови і повертає першу причину, що спрацювала. Якщо всі перевірки пройдено,
UInteractionClaimSubsystem::TryClaimрезервує ціль за рукою, сервер записуєHeldGrip = {Target, RequestId}і викликаєBeginGripцілі. ЯкщоBeginGripповертаєfalse, резерв іHeldGripскасовуються, а клієнт отримуєUnavailable. Ціль зIInteractableInterfaceотримуєOnGrab, коли її бере перша рука. - Відповідь. На кожен запит, що дійшов до активного на сервері компонента, сервер відповідає надійним
ClientActionResult(Target, Action, bAccepted, RequestId, Reason), аHeldGripдоходить реплікацією. Якщо компонент на сервері неактивний, запит ігнорується без відповіді. - Клієнт. Відмова очищає очікувану ціль.
OnRep_HeldGripочищає її лише для того самогоRequestId, тому запізніла відповідь на старий запит не скасовує новий. ПодіяOnActionResultотримує результат і причину. - Предмет.
UInteractionManipulationComponentна сервері викликаєOnGripStarted, перебудовує опору хвату й одразу надсилає snapshot.
Причини відмови¶
Кожна відповідь сервера містить EInteractionRejectReason. Сервер перевіряє умови в такому порядку й повертає першу, що не виконалась:
| Причина | Коли |
|---|---|
Cooldown |
Від попереднього прийнятого запиту цього джерела минуло менше MinActionInterval |
InvalidTarget |
Ціль не існує або в мережевій грі не реплікується |
InvalidPose |
Пакет поз некоректний або поза джерела, що надсилає дію, не пройшла перевірку (відстань, видимість від очей pawn, таймаут) |
HandBusy |
Рука вже тримає предмет або запит застарів |
Claimed |
Ціль тримає чи резервує інший pawn |
NotReachable |
Повторне трасування сервера не дістало ціль, хоча її можна було б узяти |
Unavailable |
Ціль відмовила: захоплення вимкнене, немає місця, немає звичайної дії або BeginGrip повернув false |
None означає, що запит прийнято. Причину бачить подія OnActionResult і панель налагодження (15). Як перетворити причини на зрозумілі гравцеві повідомлення — урок 3.
Звичайна дія¶
RequestInteraction має два варіанти:
- рука тримає предмет — ціллю стає цей предмет без трасування, сервер викликає
Useцілі захоплення, а маніпуляція — подіюOnUsed; - рука вільна — сервер повторює трасування для
Interactі викликаєInteraction, якщо ціль реалізуєIInteractableInterfaceі її ніхто не резервує.
Відпускання і кидок¶
RequestRelease(Linear, Angular) спершу відкидає нескінченні значення. Клієнт надсилає ServerRelease з тим RequestId, яким отримав предмет. Сервер виконує відпускання лише тоді, коли ціль і RequestId збігаються з HeldGrip. Якщо поза руки не пройшла перевірку або трекінг ненадійний, навмисне відпускання стає TrackingLost, і кидка не буде.
Під час навмисного відпускання (Released) сервер обрізає швидкості до MaxThrowSpeed і MaxAngularSpeed та передає їх EndGrip. Маніпуляція застосовує їх лише тоді, коли предмет відпускає остання рука, профіль має bAllowThrow=true, а тіло після відпускання симулюється. Ціль з інтерфейсом отримує OnRelease з причиною й обмеженими швидкостями. Швидкість обчислює ваш pawn у світових координатах; VR-pawn демо згладжує різницю позицій долоні між кадрами.
Пакет поз¶
Пакет — це масив до восьми записів, по одному на кожен активний UInteractorComponent pawn:
| Поле | Значення |
|---|---|
Source |
Посилання на interactor цього pawn |
Sequence |
Номер зразка цього джерела; сервер застосовує лише новіші за останній прийнятий |
Position |
Світова позиція, квантована до 0.1 см |
Orientation |
Світовий поворот |
bTracked |
Чи надійна поза пристрою зараз |
Хто надсилає. Періодичний пакет надсилає перший активний interactor pawn на клієнті з частотою свого PoseReplicationHz. Той самий пакет додається до кожного запиту дії та відпускання. Оскільки кожне джерело нумерує власні зразки, серверу байдуже, хто саме надіслав пакет: якщо на клієнті вимкнути першу руку, пакети почне надсилати наступна, і нумерація не зіб'ється.
Перевірка пакета. Пакет відкидається цілком лише тоді, коли він зіпсований: у pawn немає контролера, записів нуль або понад вісім, запис посилається на чуже або дубльоване джерело, або в пакеті немає джерела, що надсилає дію.
Перевірка кожного зразка. Решта перевірок стосується кожної руки окремо:
- джерело активне на сервері, а
Sequenceновіший за останній прийнятий; інакше зразок пропускається без змін; - для
bTracked=true: координати скінченні, позиція не далі заMaxOriginDistanceвід pawn; - пряма від очей pawn (
GetActorEyesViewPoint) до позиції не перетинає геометрію, що блокуєTraceChannelджерела. Pawn і предмети в його руках ігноруються. Для ближнього хвату (bNearInteraction) допускається дотик до цілі захоплення ближче заNearRadius(не більше 30 см).
Прийнятий зразок переміщує серверну копію руки. Відхилений зразок позначає лише цю руку: вона зберігає останню прийняту позу, її дії відхиляються з InvalidPose, а відпускання не кидає предмет. Друга рука працює далі незалежно.
Простими словами. Якщо ліва рука сховалася за дверима, суддя не вірить лише лівій руці. Гвинтівка в правій руці залишається в правій руці.
Що бачать інші гравці. Відносна трансформація interactor реплікується всім, крім власника (COND_SkipOwner): власник не отримує корекцій своєї руки, а інші бачать серверну позу.
Таймаути, трекінг і примусове відпускання¶
Сервер відпускає предмет без кидка в таких випадках:
| Умова | Причина |
|---|---|
| pawn утратив контролер | Cancelled |
від віддаленого джерела довше за PoseTimeout не надходить жодного зразка |
TrackingLost |
bTracked=false триває довше за TrackingLossGrace |
TrackingLost |
CancelGrip(Reason) |
Reason; Released замінюється на Cancelled |
Deactivate або EndPlay interactor |
Cancelled |
ціль стала невалідною: не проходить ValidateSetup, точка хвату зникла, захоплення вимкнули через SetGrabEnabled, компонент неактивний |
Cancelled |
у режимі Physics тіло відстало від бажаної пози на BreakDistance більше, ніж на своєму найближчому підході |
Cancelled |
Таймаут рахує саме тишу: відхилені зразки теж підтверджують, що рука на зв'язку. Recentered і TargetInvalid призначені для вашого коду: викликайте CancelGrip(Recentered) перед телепортом або recenter, щоб старі швидкості не стали кидком.
Ідентичність запитів¶
Кожен RequestGrab отримує новий LocalRequestId. Сервер приймає лише номери, новіші за останній прийнятий для цієї руки, і зберігає ціль разом із номером у HeldGrip атомарно. Відпускання мусить збігтися з обома значеннями. Тому:
- запізніле відпускання попереднього предмета не відпускає новий;
- повторна доставка старого запиту не захоплює предмет удруге;
- очікування відповіді (
HasRequestedGrip()) блокує новий фокус і новийRequestGrabцієї руки.
Стан предмета на клієнтах¶
UInteractionManipulationComponent реплікує одну структуру State: налаштування, руки й точки хвату, кількість рук, опору хвату, позу з масштабом, відставання фізичного тіла, ознаку симуляції, прикріплення, зовнішній драйвер і хід ручки. Поле Revision змінюється, коли треба повторно застосувати топологію хвату, прикріплення чи фізику.
- Компактна поза. Позиція квантується до 0.01 см, масштаб — до 0.01 (обидва
FVector_NetQuantize100), поворот передається стиснутимFRotator. Зміни, менші за 0.01 см, 0.01° чи 0.001 масштабу, не надсилаються. - Push model. Сервер позначає
Stateзміненим лише тоді, коли знімок справді змінився. Якщо push model увімкнено (net.IsPushModelEnabled=1і збірка зWITH_PUSH_MODEL, див. Продуктивність), незмінний знімок навіть не порівнюється під час реплікації. Без push model поведінка та сама, лише сервер порівнює знімок щоразу. - Клієнти не симулюють фізику предмета: вони плавно наближають позу до серверної з
RemoteInterpolationSpeed. - Власник розв'язує позу від власних рук: для
Direct— завжди, дляPhysics— поки серверне тіло не відстає більше ніж на половинуOwnerPredictionTolerance; далі він плавно переходить до серверної пози (08). - Прикріплений предмет на клієнті слідує за батьківським компонентом. Якщо посилання на батька не реплікується, клієнт і далі отримує світову позу зі snapshot.
- Гравець, що приєднався пізніше, отримує повний snapshot і
HeldGrip, тож бачить поточних власників і пози. - Важливі зміни викликають
FlushNetDormancyіForceNetUpdate. Не робіть рухомі предмети dormant.
Ролі мережі¶
| Режим | Особливості |
|---|---|
| Dedicated server | Усі перевірки й таймаути, як описано вище |
| Listen server | Pawn хоста — локальний authority: дії виконуються без RPC, OnActionResult спрацьовує локально, таймауту пози для хоста немає |
| Одиночна гра | Як у хоста listen-сервера; перевірки пози, відстані й інтервалу дій також виконуються |
| AI-pawn на сервері | IsLocallyControlled() повертає true: фокус шукається з частотою FocusUpdateHz, дії виконуються напряму, OnActionResult спрацьовує на сервері. Якщо AI не потрібен фокус, зменште FocusUpdateHz; деактивація компонента відпускає утримуваний предмет |
Вимоги до акторів¶
- Pawn має
Replicates=true;UInteractorComponentреплікується сам. - Ціль у мережевій грі має
Replicates=true, а її колізія блокуєTraceChannel. - Ціль маніпуляції —
Movable; вона або не реплікується, або єUInteractionMeshComponent. - Не вмикайте
ReplicateMovementна акторах ізUInteractionManipulationComponent: компонент вимикає його сам, щоб рух мав одного «письменника». - Компоненти, до яких прикріплюють предмети, мають бути доступні для реплікації.
bGrabEnabledіbCanBeGrabbedреплікуються: змінюйте їх на сервері.
Що перевіряє сервер, а що — ні¶
Сервер перевіряє межі пози, видимість від очей pawn, інтервал між діями, повторне трасування, теги й CanInteract, реєстр захоплень, номери запитів і зразків, скінченність і межі швидкостей. Конфігурація сервера має пріоритет над клієнтською.
Сервер не може перевірити, куди саме гравець навів руку в межах допустимої зони, і не перевіряє напрямок кидка. Це обмеження будь-якої моделі з клієнтським трекінгом: геометричні межі звужують можливості читера, але не є повною античит-системою.
Автоматичні тести мережі¶
Тести InteractionSystem.Network.* запускають dedicated server і клієнтів в одному процесі PIE: конкуренцію за предмет і повний цикл захоплення, закриту перешкодою вільну руку, локально вимкнену першу руку, реплікацію дозволу на захоплення й передбачення фізичного предмета під затримкою пакетів. Перелік — у 04. Окремі машини, втрати пакетів і late join перевіряйте вручну, наприклад консольними командами Net PktLag=100 і Net PktLoss=5 у PIE, з панеллю налагодження.