Skip to content

07 — Лут і предмети у світі

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

Усе з цього розділу — модуль InventorySystemWorld. Це те, що фізично існує на рівні: скрині, предмети на землі, таблиці луту.

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


Таблиця луту

Ассет, що описує, що може випасти. Створюється як 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);

Повертає, скільки випадінь розміщено. Те, що не влізло, потрапляє в лог попередженням — щоб замалу скриню було легко помітити ще під час розробки.


Скриня, ящик, труп

AISLootContainer — актор із інвентарем і необов'язковим наповненням із таблиці.

Він навмисно не має меша й колізії

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

Зробіть 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, щоб скриня не позначалася витраченою.


Предмет на землі

AISItemPickup замикає цикл «викинути → підібрати».

Він зберігає стан предмета

Викидання — це не «видалити предмет, запам'ятати тип». Пікап несе сам об'єкт, тож меч, кинутий на 12/100 міцності, підбирається на 12/100 — із усіма зачаруваннями та викинутими значеннями.

Два способи створити

Розміщений на рівні або створений із типу — задайте ItemDefinition і ItemCount. Копія створиться на сервері як новенька (з повною міцністю).

З наявного предмета — коли гравець щось викидає:

// Спершу забрати з інвентаря, потім віддати пікапу.
UISItemInstance* Item = Inventory->GetItemAtSlot(SlotIndex);
Inventory->TryRemoveItemFromSlot(SlotIndex, Item->StackCount);

AISItemPickup::SpawnForItem(this, Item, DropLocation);

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

Налаштування

Поле Опис
bDestroyOnCollected знищити після підбирання (типово так)
LifeSpanSeconds час життя; 0 = лежить вічно

LifeSpanSeconds варто задати в будь-якій грі, де гравці вільно щось викидають: сесія на кілька годин інакше накопичить тисячі покинутих акторів.

Підбирання

Викликайте на інвентарі збирача, не на самому пікапі:

CollectorInventory->TryCollect(Pickup);

Це той самий Try*, що й усі інші мутуючі методи — можна викликати звідки завгодно, з клієнта чи з сервера, і він сам розбереться. Причина, чому саме так: у пікапа немає власного мережевого з'єднання (його ніхто не «володіє»), тож він не може переслати запит клієнта на сервер сам. UISInventoryComponent належить гравцеві й з'єднання має, тож запит їде через нього — той самий принцип, що й у TryTransferFrom для скринь.

AISItemPickup::TryCollect(Collector) (без інвентаря) теж існує, але це вже низькорівневий, лише серверний виклик — використовуйте його тільки з коду, який точно вже виконується на сервері (GameMode, інший Server RPC). З клієнтського коду взаємодії — Blueprint чи C++ — завжди Inventory->TryCollect(Pickup).

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

Якщо місця немає — пікап лишається у світі, а не знищується разом із вмістом.

Візуальний хук: BP_OnCollected.


Свій контейнер без готового класу

Якщо AISLootContainer не пасує (свій базовий клас, своя система взаємодії) — контейнер зробити просто: це звичайний актор із компонентом інвентаря.

UCLASS()
class AMyVendingMachine : public AMyInteractableBase
{
    GENERATED_BODY()

public:
    AMyVendingMachine()
    {
        bReplicates = true;

        Stock = CreateDefaultSubobject<UISInventoryComponent>(TEXT("Stock"));
        Stock->MaxSlots = 12;
    }

    UPROPERTY(VisibleAnywhere)
    TObjectPtr<UISInventoryComponent> Stock;
};

Далі гравець забирає з нього так само, як зі скрині — через свій інвентар. Жодного успадкування від класів плагіна не потрібно.

Так само робиться труп: додайте компонент інвентаря персонажу й не видаляйте актор одразу після смерті.


Куди далі