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 — Операції.
Спорядження¶
Чому предмет не вдягається?¶
Три причини за частотою:
- У предмета немає фрагмента
Equippable. - Слот, який просить предмет, відсутній у
AvailableSlotsперсонажа. - Слот у фрагменті й слот у компоненті — різні теги (одруківка).
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.