Skip to content

14 — Поширені запитання

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

Короткі відповіді з посиланнями на розділ, де кожне розкрито докладно.


Перші кроки

Чи потрібно писати C++?

Ні. Предмети — це Data Assets, компоненти чіпляються на будь-який актор, кожна функція плагіна викликається з Blueprint. Навіть власний фрагмент можна написати Blueprint-класом, успадкованим від ISItemFragment.

11 — Рецепти існує на випадок, якщо ви хочете C++, а не тому, що він обов'язковий.

Чи є готовий приклад, який можна просто запустити?

Так — Content/Demo/Maps/Demo.umap (це стартова карта проєкту). Сім підбірних станцій, по одній на кожну комбінацію фрагментів, дві скрині з різними режимами кидка луту. Керування: WASD + миша, E — підібрати предмет або відкрити скриню, F — вдарити тим, що в головній руці, I — інспектор інвентаря.

Текстовий варіант тієї самої історії — Від ассета до руки гравця.

Чи потрібно успадковувати Character, PlayerState чи GameMode?

Ні. Додайте компонент на будь-який актор — і все працює. Якщо не хочете редагувати Blueprint персонажа, увімкніть автостворення в Project Settings.

Де мають лежати ассети предметів?

Будь-де у вашому контенті. Пошук іде через Asset Registry за класом, тож структура тек ваша, і переміщення ассетів нічого не ламає.

Єдине застереження — не перейменовуйте ассет після релізу: його ім'я записується у збереження гравців. DisplayName міняти можна будь-коли.

Чи можна додати плагін до проєкту, що вже в розробці?

Так. Він не вимагає конкретного базового класу, системи вводу чи структури проєкту й не додає геймплею, доки ви не створите перший предмет.

Чи все це працює в одиночній грі?

Так, і без жодного накладного витрату. В одиночній грі виклики виконуються на місці й не створюють RPC узагалі — 09 — Мультиплеєр.


Предмети та стакування

Чому два однакові предмети не об'єднуються?

Найчастіша причина: в одного з них є власні значення (міцність, заряди). Будь-яке значення в екземплярі робить копію індивідуальною — інакше пощерблений меч, злившись із новим, знищив би різницю.

Відкрийте інспектор (InvOverlay): предмет із власними значеннями має фіолетову мітку UNIQUE.

Друга причина — немає фрагмента Stackable, тож максимум стеку дорівнює 1.

Докладно — 01 — Основні поняття.

Як зробити предмет, який не стакається?

Не додавайте фрагмент Stackable. Це стан за замовчуванням.

Чи можна поєднати Stackable і Durability?

Технічно так, практично — предмет не стакуватиметься ніколи, бо Durability проставляє міцність кожній новій копії, а це робить її унікальною. Якщо вам потрібне саме таке поєднання, ви, найімовірніше, хочете чогось іншого.

Як зробити предмет із кількома використаннями, який не зникає?

Consumable із MaxCharges > 0. Предмет лишається в слоті й витрачає заряди: жезл на 10 закляттів, ліхтар із пальним, аптечка на 3 застосування. 04 — Фрагменти.

Мені потрібна здатність, якої немає серед готових фрагментів.

Напишіть свій фрагмент — у Blueprint або C++. Ваш фрагмент нічим не обмеженіший за вбудовані: вони самі є звичайними підкласами ISItemFragment. 04 — Фрагменти.

Як прикріпити до предмета власні дані?

Числові значення — у StatValues екземпляра (SetStatValue). Дані, спільні для типу, — поля вашого власного фрагмента.


Слоти й місткість

Як зробити необмежений інвентар?

MaxSlots = 0. Окремого прапорця немає — нуль і є вимикачем.

Як обмежити інвентар певними типами предметів?

AllowedItemTags — приймати лише перелічене; BlockedItemTags — відхиляти перелічене. Другий перевіряється першим і завжди виграє. 05 — Операції.

Як зробити окремі слоти під боєприпаси?

SlotRestrictions на компоненті: діапазон слотів + потрібні теги. Один інвентар може мати патронний пояс на початку й звичайні слоти далі — другий компонент не потрібен.

Як обробити ситуацію, коли предмет не вміщається?

TryAddItem повертає OutRemainder — скільки не влізло. Це нормальний результат, а не помилка: висипте решту на землю або покажіть повідомлення.

Дізнатися заздалегідь: CanAcceptItemCount(Def, Count) — чесна відповідь із урахуванням фільтрів, стеків, слотів і ваги.

Чи можна мати кілька інвентарів на одному персонажі?

Так — просто додайте кілька компонентів. Але GetInventoryFor поверне перший знайдений; якщо потрібен конкретний, зберігайте посилання явно або реалізуйте IISInventoryInterface. 11 — Рецепти.

Як увімкнути вагу?

Задайте MaxWeight > 0 на компоненті й додайте предметам фрагмент Weight. Предмети без цього фрагмента нічого не важать.

Вага й слоти — незалежні ліміти: будь-який може стати вузьким місцем.

Чи побачить клієнт зміну MaxSlots/MaxWeight під час гри?

Так, обидва поля реплікуються. Значення, задане в Blueprint, і так приїжджає до клієнтів само собою — реплікація важлива саме для зміни в рантаймі: апгрейд рюкзака чи автостворення з нетиповим значенням у Project Settings. 05 — Операції.


Спорядження

Чому предмет не вдягається?

Три причини за частотою:

  1. У предмета немає фрагмента Equippable.
  2. Слот, який просить предмет, відсутній у AvailableSlots персонажа.
  3. Слот у фрагменті й слот у компоненті — різні теги (одруківка).

EquipList показує, які слоти є в персонажа; інспектор — які фрагменти є в предмета.

Як зробити два слоти під каблучки?

Дайте обом однаковий тег (Equipment.Slot.Accessory.Ring) і додайте його до AvailableSlots двічі… — ні, тег у контейнері унікальний. Заведіть два різні теги (...Ring.Left, ...Ring.Right) і два різні типи каблучок, або зробіть слоти взаємозамінними у своєму UI.

Чому предмет не знімається?

Найімовірніше, в інвентарі немає місця. Плагін навмисно відмовляє замість того, щоб знищити предмет: він лишається вдягненим, а виклик повертає false.

Як порахувати сумарну броню?

Equipment->GetTotalStatValue(YourArmorTag). Підсумовує стат по всьому вдягненому, читаючи фактичні значення екземплярів. 06 — Спорядження.

Чи реплікується візуальний актор зброї?

Ні — кожна машина створює свій із фрагмента Equippable. Тому меч у руці не коштує трафіку понад сам запис про слот.

Чому OnItemEquipped спрацьовує знову, коли міцність вдягненого предмета просто змінилася?

Не повинен. OnItemEquipped означає рівно «щойно вдягнули»; для зміни даних уже вдягненого предмета (міцність, заряди) є окрема подія OnEquippedItemChanged. Якщо звук вдягання лунає на кожен удар зброєю — перевірте, на яку саме подію ви підписалися. 06 — Спорядження.


Інтерфейс

Чи можна керувати інвентарем з інспектора, не пишучи код, — для швидкої перевірки?

Так. InvOverlay — не лише перегляд: кнопки Give/Fill/Use/Equip/Unequip прямо в панелі кличуть ті самі Try*, що й ваш геймплей-код, тож це чесна перевірка поведінки, а не окрема іграшкова копія.

Консольні команди (InvGive, InvList…) не працюють.

Наберіть EnableCheats перший раз — рушій створює CheatManager лише в standalone або на сервері, і без цього кожна команда відповідає «Command not recognized». Клавіша I цього обмеження не має. 13 — Відлагодження.

Клавіша I нічого не відкриває.

Перевірте GameMode Override у World Settings цієї карти. Він тихо підміняє контролер на свій, а прив'язка клавіші живе саме в AHostInventorySystemPlayerController (чи вашому аналогу) — з чужим контролером вона просто не існує.

Як намалювати сітку зі слотами?

Обходьте діапазон 0..MaxSlots-1 і питайте GetItemAtSlot(Slot) — так ви побачите й порожні клітинки. Не індексуйте GetAllEntries(): цей список розріджений, запис №N це не слот №N. 08 — Побудова інтерфейсу.

Іконка не показується.

Icon — м'яке посилання, його треба завантажити: Async Load Asset у Blueprint або StreamableManager у C++. Це зроблено навмисно, щоб тисяча типів предметів не тягла тисячу текстур у пам'ять.

UI не оновлюється.

Підпишіться на OnInventoryChanged (або на вужчі події) замість опитування щокадру. Перевірте, що підписка сталася після появи компонента — на клієнті він може прийти пізніше за віджет.

Гравець підбирає предмет — нічого не відбувається.

Прив'яжіть OnAddRejected: вона несе готовий текст причини («Інвентар повний», «Надто важко»). Без неї відмова виглядає як мовчазний баг.

Як показати міцність у тултипі?

Спитайте предмет про фрагмент і візьміть відсоток:

if (const UISFragment_Durability* Dur = Def->FindFragment<UISFragment_Durability>())
{
    Bar->SetPercent(Dur->GetDurabilityPercent(Instance));
}

Мультиплеєр

Що повертає Try* на клієнті?

true означає «запит надіслано», а не «вдалося». Сервер вирішує остаточно. Будуйте UI на подіях, а не на поверненому значенні.

Чому не працює забирання зі скрині на клієнті?

Ви, найімовірніше, викликаєте метод на інвентарі скрині. У неї немає мережевого з'єднання, тож запит не дійде до сервера.

Викликайте на інвентарі гравця:

PlayerInventory->TryTransferFrom(ChestInventory, SlotIndex);

Це єдине місце, де мережа просочується в API — 09 — Мультиплеєр.

Підбирання предмета не працює на клієнті.

Ви, найімовірніше, викликаєте Pickup->TryCollect(...) напряму. У пікапа немає мережевого з'єднання, тож запит нікуди не їде. Викликайте на інвентарі гравця:

PlayerInventory->TryCollect(Pickup);

Той самий принцип, що й для скринь, — 07 — Лут і предмети у світі.

Container->Open() з клієнта нічого не робить.

На відміну від решти API, Open/Close на AISLootContainer не пересилають себе самі — у скрині немає з'єднання, через яке можна дістатися сервера. Викликайте їх із серверного боку вашої взаємодії (Server RPC на компоненті взаємодії гравця; забирання предметів після відкриття такої обережності вже не потребує — TryTransferFrom маршрутизує себе сам). 07 — Лут і предмети у світі.

Чи бачать інші гравці мій інвентар?

Так, вміст реплікується всім спостерігачам актора-власника. Це наслідок того, що той самий компонент обслуговує і рюкзак гравця, і скриню, вміст якої мають бачити всі.

Якщо потрібна сувора приватність — приховуйте дані у своєму UI.

Де краще тримати інвентар — на PlayerState чи на Pawn?

PlayerState — інвентар переживає смерть і respawn (типовий рюкзак). Pawn — інвентар гине з тілом (extraction-шутери, рогалики, лутання трупів).

Чи треба щось міняти для одиночної гри?

Ні. Той самий код і та сама карта працюють в обох режимах без гілок.


Збереження

Як зберегти інвентар?

ExportState() віддає звичайну структуру — покладіть її у свій USaveGame. Назад — ImportState(). 10 — Збереження.

Чи зберігається міцність?

Так. Зберігаються всі значення екземпляра, і при завантаженні хуки створення не виконуються — іржавий меч завантажиться іржавим.

Що буде, якщо предмет видалили з гри в патчі?

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

Чому ImportState нічого не робить?

Це серверний метод. На клієнті він ігнорується з попередженням у лог.

Чи зберігається спорядження окремо від інвентаря?

Так — UISEquipmentComponent має власні ExportState()/ImportState(), незалежні від інвентарних. Кладіть обидві структури у свій USaveGame.


Розширення

Як змінити поведінку інвентаря глобально?

Успадкуйтеся від UISInventoryComponent і вкажіть свій клас у Project Settings → Game → Inventory System → Inventory Component Class.

Чому мій фрагмент не зберігає значення між використаннями?

Тому що ви пишете в поле фрагмента, а він один на всю гру — спільний для кожної копії предмета. Змінний стан належить екземпляру:

Instance->SetStatValue(MyTag, Value);   // так
MyField = Value;                        // ні - змінює ассет для всіх

Це головне правило фрагментів — 04 — Фрагменти.

Як зробити свій контейнер, не успадковуючись від AISLootContainer?

Додайте UISInventoryComponent на свій актор — це і є контейнер. Готовий клас лише додає таблицю луту й стан «відкрито/спорожнено». 07 — Лут і предмети у світі.

Як гравцеві щось видати з довільного місця коду?

UISInventoryBlueprintLibrary::GiveItemTo(Player, ItemDef, Count);

Сама знайде інвентар, поверне, скільки реально влізло.

Я задаю теги в конструкторі свого класу, а слот чи фільтр виявляється порожнім.

Класична пастка часу ініціалізації: конструктор CDO нативного класу може виконатися до того, як FInventorySystemModule::StartupModule зареєструє теку плагіна з тегами, тож RequestGameplayTag того разу мовчки поверне порожній тег. Перенесіть запит тегів у BeginPlay — там рушій уже повністю запущений. Із soft-посиланнями на самі ассети предметів (TSoftObjectPtr) та сама історія: конструюйте посилання в конструкторі скільки завгодно, а LoadSynchronous() кличте вже в BeginPlay.

Додав свій тег напряму у .ini-файл, а він не з'являється.

Формат ніяк не перевіряється й тому ламається мовчки: секція має бути [/Script/GameplayTags.GameplayTagsList] (не GameplayTagsSettings), а записи — GameplayTagList=(Tag="...") без провідного +. Обидві помилки дають порожній список тегів без жодного повідомлення в лозі. Найнадійніше — редагувати теги через Project Settings → GameplayTags у редакторі: він сам пише правильний формат. 02 — Підключення.

Хочу зробити підбірний об'єкт, який не є AISItemPickup.

Реалізуйте IISCollectibleInterface (метод один — TryGiveContents) на своєму акторі. UISInventoryComponent::TryCollect побачить його так само, як і звичайний пікап. 12 — Довідник API.