Skip to content

03 — Взаємодія та фокус

🇬🇧 English | 🇺🇦 Українська

Джерело

Простими словами. Джерело — це «рука з лазерною указкою». Указка постійно показує, що перед вами (фокус), а рука бере предмет, коли ви натискаєте клавішу. У desktop-гри одна така рука прив'язана до камери, у VR — по одній на кожен контролер.

UInteractorComponent — scene component на pawn. Прив'яжіть його до камери або motion controller. При bUsePawnViewpoint=true він сам оновлює позицію з поточної камери. При false його transform визначається батьківським компонентом або кодом проєкту.

Параметр Призначення
TraceDistance Довжина променя, початково 300 см
TraceRadius 0 — line trace; більше 0 — sphere sweep
FocusUpdateHz Частота пошуку фокуса для цієї руки; початково 30 Гц. Значення 20 Гц зменшує кількість трейсів і навантаження на CPU
PoseReplicationHz Частота спільного пакета поз усіх активних рук pawn; початково 30 Гц. Значення 20 Гц зменшує мережевий трафік, але збільшує інтервал між оновленнями на сервері. Використовується значення першого активного interactor
bNearInteraction / NearRadius Пошук предмета поруч із долонею через overlap, із перевіркою видимості; зручний для VR
TraceChannel Канал блокування, початково Visibility
Filter.RequiredTags Усі потрібні Gameplay Tags, з ієрархічним зіставленням
Filter.BlockedTags Будь-який із цих Gameplay Tags забороняє ціль
Filter.RequiredActorTags Усі точні імена з Actor.Tags
GrabOffset Зсув захопленого фізичного предмета від джерела; для VR зазвичай нуль

Ціль має IInteractableInterface для звичайної дії або UInteractionGrabTargetComponent для захоплення. Готовий UInteractionManipulationComponent реалізує контракт захоплення; його можна додати до власного актора без успадкування від AInteractionPhysicsActor. Якщо поведінку утримання потрібно описати самостійно без C++, використайте Blueprint-нащадок UInteractionBlueprintGrabTargetComponent (11). Якщо інтерфейсу немає, фільтр читає TargetTags компонента. Локальна поза оновлюється кожен кадр незалежно від частот фокуса й мережі.

Відфільтрована або заблокована ціль залишається перешкодою для предметів за нею. При TraceRadius > 0 компонент викликає SweepSingleByChannel зі сферою; точку контакту додатково перевіряє LineTraceSingleByChannel, щоб не вибрати предмет за кутом. Для такого пошуку ціль має блокувати TraceChannel.

Порядок вибору цілі

RefreshFocus спершу шукає ціль для захоплення, а якщо її немає — ціль для звичайної дії. Актор підходить, якщо:

  1. реалізує IInteractableInterface або має UInteractionGrabTargetComponent;
  2. його теги проходять Filter: теги інтерфейсу (GetInteractionTags) або, без інтерфейсу, TargetTags компонента, а також Actor.Tags;
  3. CanInteract повертає true, якщо інтерфейс є;
  4. для захоплення — компонент цілі дозволяє хват (CanBeginGrip): вільне місце для руки, валідний профіль, доступна точка хвату.

Ближній пошук (bNearInteraction) знаходить усі об'єкти в сфері NearRadius навколо джерела, відкидає непридатні, для решти бере найближчу точку колізії й перевіряє до неї видимість. Перемагає найближчий кандидат. Без ближнього пошуку перемагає перший блокувальний об'єкт променя.

Актор лише з компонентом цілі, без інтерфейсу, отримує фокус тільки тоді, коли його зараз можна взяти: звичайної дії в нього немає. Тому, наприклад, зайнятий предмет іншого гравця не показує підказки.

Сервер повторює той самий пошук від своєї копії пози руки, тому ціль, яку клієнт бачив у фокусі, на сервері може не підтвердитися. Тоді OnActionResult повідомить причину відмови (13).

Фільтрація тегами

Простими словами. Теги — це наліпки на предметах, а фільтр — правило на руці: «бери лише з наліпкою Item і ніколи з наліпкою Locked». Так одна рука може брати лише квестові предмети, а інша — будь-що.

Фільтр належить джерелу (Interactor.Filter), а теги — цілі. GetInteractionTags() повертає Gameplay Tags цілі; готовий AInteractionPhysicsActor повертає свій InteractionTags. Окремо кожен актор Unreal має звичайний масив Tags із точних імен FName. Для вибору цілі потрібні всі RequiredTags, жодного з BlockedTags і всі RequiredActorTags. Порожні списки нічого не обмежують.

Наприклад, рука має брати лише предмети для завдання. На руці встановіть RequiredTags = {Interaction.Item}, BlockedTags = {Interaction.Locked} і RequiredActorTags = {QuestPickup}. На списі задайте InteractionTags = {Interaction.Item} та додайте QuestPickup до Actor → Tags. Спис пройде фільтр. Якщо додати йому Interaction.Locked, він перестане вибиратися; якщо прибрати QuestPickup, результат буде таким самим. Вбудовані теги Interaction.Item і Interaction.Locked оголошені в InteractionGameplayTags.h.

Еквівалентне налаштування в C++ після створення компонентів:

Interactor->Filter.RequiredTags.AddTag(InteractionGameplayTags::Item);
Interactor->Filter.BlockedTags.AddTag(InteractionGameplayTags::Locked);
Interactor->Filter.RequiredActorTags.Add(TEXT("QuestPickup"));

Spear->InteractionTags.AddTag(InteractionGameplayTags::Item);
Spear->Tags.Add(TEXT("QuestPickup"));

Gameplay Tags мають ієрархію: потрібний Interaction.Item також збігається з дочірнім тегом на кшталт Interaction.Item.Spear, якщо ви зареєстрували його в проєкті. Actor.Tags — звичайні імена: QuestPickup і QuestPickup.Special різні. Фільтр перевіряється і локально для фокуса, і повторно на сервері перед дією. Відхилений предмет усе одно перекриває предмети за ним, якщо його collision блокує TraceChannel.

Контракт цілі

Метод Де виконується Відповідальність
CanInteract Локальний запит і повторно сервер Дозволити дію; реалізуйте без побічних ефектів
GetInteractionTags Локально й на сервері Повернути семантичні теги
Interaction Сервер Відкрити двері, натиснути кнопку або інша ігрова дія
OnFocusStart / OnFocusEnd Локальний спостерігач Показати або прибрати візуальний відгук
OnGrab(Interactor) Сервер, коли першу руку прийнято Повідомити gameplay про підбір; рухом керує компонент цілі
OnRelease(Interactor, Reason, LinearVelocity, AngularVelocity) Сервер, коли відпустила остання рука Повідомити gameplay про відпускання; Reason = Released означає навмисне відпускання, швидкості вже обмежені сервером

OnGrab і OnRelease викликає ядро для будь-якої цілі з інтерфейсом, а не лише для готових акторів. Події інтерфейсу — Interaction, OnFocusStart, OnFocusEnd, OnGrab, OnRelease — можна лише реалізувати, але не викликати з Blueprint: так клієнт не обійде серверну перевірку. Перевіряйте допустимість у CanInteract та CanBeginGrip. Для власного компонента цілі реалізуйте BeginGrip/EndGrip; для готового manipulation використовуйте OnGripStarted, OnGripEnded та OnUsed, залишаючи одного виконавця руху.

GetFocusedActor повертає одну поточну ціль на джерело. Перехід A → B надсилає кінець A перед початком B. OnFocusChanged підходить для HUD; OnActionResult повідомляє результат серверного запиту разом із причиною відмови (EInteractionRejectReason). Локальний фокус не означає, що сервер уже дозволив дію; як пояснити гравцеві відмову — у уроці 3.

Один UInteractorComponent керує однією рукою: під час захоплення або очікування відповіді сервера він прибирає фокус з інших предметів. CanFocusNewTarget() показує, чи може ця рука зараз вибрати нову ціль. У VR друга, вільна рука має власний компонент і може вибирати незалежно. Після відпускання перша рука знову шукає цілі.

Візуальний відгук

AInteractionMasterItem додає proximity sphere, текстові маркери та опціональні billboard-текстури. Встановіть NearbyIcon.Sprite і FocusIcon.Sprite, щоб замінити текст. Billboard сам повертається до камери, зокрема у стереорежимі.

Для підсвітки призначте FocusOverlayMaterial. За потреби увімкніть bUseCustomDepthFocus; видимий outline вимагає post-process матеріалу проєкту. Без нього custom depth сам по собі нічого не малює. Попередні overlay і stencil відновлюються після завершення фокуса.

Фокус двох рук підраховується окремо: завершення фокуса однієї руки не прибирає підсвітку іншої. Вбудоване представлення спільне для локальних гравців одного процесу. Для незалежного split-screen UI використайте OnFocusChanged та власні widgets кожного LocalPlayer.

Proximity sphere використовує overlap із locally controlled pawn. Увімкніть Generate Overlap Events на capsule персонажа. Маркер близькості з'являється, тільки якщо хоча б одна місцева рука вільна; він не дає права взаємодіяти крізь стіни.