07 — Лут і предмети у світі¶
🇬🇧 English | 🇺🇦 Українська
Усе з цього розділу — модуль InventorySystemDemoWorld. Це те, що фізично існує на рівні:
скрині, предмети на землі, таблиці луту.
Проєкт, якому потрібні лише інвентарі на персонажах, може цей модуль не чіпати взагалі — він не тягне жодних залежностей у ядро.
Таблиця луту¶
Ассет, що описує, що може випасти. Створюється як Data Asset → ISLootTable.
Кожен запис:
| Поле | Опис |
|---|---|
Item |
що може випасти |
MinCount / MaxCount |
діапазон кількості (рівні значення = фіксована) |
DropChance |
шанс 0–1, що цей запис випаде взагалі |
Weight |
відносна вага при виборі «N предметів» |
Два способи кинути кубик¶
Той самий ассет підтримує обидві типові форми — залежить від того, який метод ви викличете:
| Метод | Поведінка | Коли брати |
|---|---|---|
GenerateLoot() |
кидає кожен запис окремо за його DropChance |
скриня з мінливим вмістом: може випасти будь-яка комбінація |
GenerateWeightedLoot(N) |
обирає рівно N записів за Weight |
бос, що завжди кидає дві речі; рідкісне має малу вагу |
DropChance ігнорується у зваженому режимі, Weight — у звичайному.
Обмеження кількості¶
| Поле | Дія |
|---|---|
MinItemsToSpawn |
якщо кидки дали менше — доберуться додаткові за вагою |
MaxItemsToSpawn |
зайве відкидається; 0 = без стелі |
MinItemsToSpawn гарантує, що скриня ніколи не буде розчаровуючою.
Наповнити інвентар одним викликом¶
LootTable->PopulateInventory(ChestInventory);
Повертає, скільки випадінь розміщено. Те, що не влізло, потрапляє в лог попередженням — щоб замалу скриню було легко помітити ще під час розробки.
Скриня, ящик, труп¶
AISDemoLootContainer — актор із інвентарем і необов'язковим наповненням із таблиці.
Він навмисно не має меша й колізії¶
Плагін не нав'язує свою систему взаємодії: гра з тригер-об'ємом, гра з лінійною трасою й гра з кліком по курсору хочуть різного, і жодна з них не хоче, щоб це вгадували за неї.
Зробіть Blueprint на основі ISLootContainer, додайте меш і колізію, які вам
потрібні, і викличте Open, коли спрацює ваша взаємодія.
OpenіClose— лише серверні, і себе не пересилають. Це єдине місце в плагіні, де так: у скрині немає власного мережевого з'єднання, тож маршрут із клієнта через неї не існує взагалі (з тієї ж причини, з якої предмети з неї беруть через інвентар гравця). Викликайте їх із серверного боку вашої взаємодії — у компонента взаємодії гравця з'єднання є, і його Server RPC саме для цього.Виклик із клієнта нічого не зламає: він відхиляється й пише в лог. Але скриня залишиться нерозкиданою — а отже, порожньою для того, хто дивиться на неї з клієнта.
Забирання предметів після відкриття цієї обережності вже не потребує:
TryTransferFromмаршрутизує себе сам.
Налаштування¶
| Поле | Опис |
|---|---|
LootTable |
чим наповнювати (порожньо = вміст задаєте вручну через StartingItems) |
bGenerateLootOnSpawn |
кинути кубик одразу, а не при першому відкритті |
bUseWeightedLoot / WeightedDropCount |
режим «рівно N предметів» |
bSingleUse |
одноразова скриня: спорожнівши, більше не наповнюється |
Коли кидається кубик¶
За замовчуванням — при першому відкритті, а не при спавні. Це навмисно:
- сотня скринь на рівні не коштує нічого, доки гравці до них не дійшли;
- двоє гравців, що відкрили одну скриню, завжди бачать однаковий вміст — кидок стався один раз, на сервері.
Увімкніть bGenerateLootOnSpawn, якщо вміст має бути відомий заздалегідь: мінімапа,
що показує вміст скрині, або тест, який його перевіряє.
Візуальні хуки¶
| Подія Blueprint | Коли |
|---|---|
BP_OnOpened / BP_OnClosed |
анімація кришки, скрип, підсвітка |
BP_OnLooted |
скриня спорожніла: пригасити свічення, замінити меш |
BP_OnLootGenerated |
кубик кинуто — можна додати щось своє (масштабування під рівень гравця, гарантований квестовий предмет) |
Забрати предмет зі скрині¶
Не викликайте інвентар скрині напряму. Запит іде через інвентар гравця:
PlayerInventory->TryTransferFrom(Container->GetContainerInventory(), SlotIndex);
Або вузлом Move Item Between Actors. Чому саме так — 09 — Мультиплеєр.
Скриня, що наповнюється знову¶
Container->ResetContainer(/*bClearExistingContents*/ true);
Для ресурсної ноди, що відновлюється, чи торговця, який поповнює асортимент уночі.
Вимкніть bSingleUse, щоб скриня не позначалася витраченою.
Предмет на землі¶
AISDemoItemPickup замикає цикл «викинути → підібрати».
Він зберігає стан предмета¶
Викидання — це не «видалити предмет, запам'ятати тип». Пікап несе сам об'єкт, тож меч, кинутий на 12/100 міцності, підбирається на 12/100 — із усіма зачаруваннями та викинутими значеннями.
Два способи створити¶
Розміщений на рівні або створений із типу — задайте ItemDefinition і ItemCount.
Копія створиться на сервері як новенька (з повною міцністю).
З наявного предмета — коли гравець щось викидає:
// Спершу забрати з інвентаря, потім віддати пікапу.
UISDemoItemInstance* Item = Inventory->GetItemAtSlot(SlotIndex);
Inventory->TryRemoveItemFromSlot(SlotIndex, Item->StackCount);
AISDemoItemPickup::SpawnForItem(this, Item, DropLocation);
Обидва рядки — серверні: пікап є реплікованим актором, тож створений на клієнті
він був би привидом, якого не бачить більше ніхто. SpawnForItem викликаний із
клієнта відмовляє й пише в лог. Викидання предмета — це Server RPC на гравцеві, який
викидає.
Налаштування¶
| Поле | Опис |
|---|---|
bDestroyOnCollected |
знищити після підбирання (типово так) |
LifeSpanSeconds |
час життя; 0 = лежить вічно |
LifeSpanSecondsварто задати в будь-якій грі, де гравці вільно щось викидають: сесія на кілька годин інакше накопичить тисячі покинутих акторів.
Підбирання¶
Викликайте на інвентарі збирача, не на самому пікапі:
CollectorInventory->TryCollect(Pickup);
Це той самий Try*, що й усі інші мутуючі методи — можна викликати звідки завгодно,
з клієнта чи з сервера, і він сам розбереться. Причина, чому саме так: у пікапа
немає власного мережевого з'єднання (його ніхто не «володіє»), тож він не може
переслати запит клієнта на сервер сам. UISDemoInventoryComponent належить гравцеві
й з'єднання має, тож запит їде через нього — той самий принцип, що й у
TryTransferFrom для скринь.
AISDemoItemPickup::TryCollect(Collector) (без інвентаря) теж існує, але це вже
низькорівневий, лише серверний виклик — використовуйте його тільки з коду,
який точно вже виконується на сервері (GameMode, інший Server RPC). З клієнтського
коду взаємодії — Blueprint чи C++ — завжди Inventory->TryCollect(Pickup).
Знаходити інвентар самому не потрібно — все вже готово. Безпечно викликати повторно: підібраний пікап відмовляє наступним, тож двоє гравців, що дотягнулися одночасно, не отримають по копії.
Якщо місця немає — пікап лишається у світі, а не знищується разом із вмістом.
Візуальний хук: BP_OnCollected.
Свій контейнер без готового класу¶
Якщо AISDemoLootContainer не пасує (свій базовий клас, своя система взаємодії) —
контейнер зробити просто: це звичайний актор із компонентом інвентаря.
UCLASS()
class AMyVendingMachine : public AMyInteractableBase
{
GENERATED_BODY()
public:
AMyVendingMachine()
{
bReplicates = true;
Stock = CreateDefaultSubobject<UISDemoInventoryComponent>(TEXT("Stock"));
Stock->MaxSlots = 12;
}
UPROPERTY(VisibleAnywhere)
TObjectPtr<UISDemoInventoryComponent> Stock;
};
Далі гравець забирає з нього так само, як зі скрині — через свій інвентар. Жодного успадкування від класів плагіна не потрібно.
Так само робиться труп: додайте компонент інвентаря персонажу й не видаляйте актор одразу після смерті.
Куди далі¶
- Правило напрямку для мережі: 09 — Мультиплеєр
- Рецепти торгівлі й крафту: 11 — Рецепти