May 08, 2024

С чего начинается лут? vol.1

Больше всего в играх я люблю лут. Еще со времен Lineage 2, когда я играл на фришках с лоу онлайном и выфармливал Б даггер на споте из 5 мобов с шансом дропа 0.0000002, я задавался вопросом почему шанс такой маленький и как с таким шансом можно выбить предмет с первого раза. Сегодня, конечно, ситуация намного понятнее и с точки зрения игрока и с точки зрения разработчика.

Создание системы генерации лута очень индивидуальная задача и сильно зависит от идеи проекта. Общего рецепта нет, но если упростить, все будет подчиняться логике:


Концепт

Определение концепта системы, как и в большинстве прочих случаев, является одним из ключевых решений. Нужно понимать все текущие и возможные точки взаимодействия с системой которую мы делаем. Конечно, идеала не существует и никто не застрахован от того, что через неделю работы над системой может прилететь правка, которая поделит всю логику на 0, но нужно постараться быть на шаг впереди с помощью понимания проекта в целом.

Например, для простых игр типа волкинг сим не обязательно придумывать что-то сложное, генерировать случайные предметы и прочее, но стоит продумать общую систему взаимодействия с предметами, чтобы можно было легко масштабировать количество и иметь возможность изменять общую логику без лишних усилий. Я упоминаю это не просто так, а потому, что уже на начинающем уровне столкнулся с чужой работой, где логика взаимодействия с предметами (в том числе лутом) была реализована индивидуально для каждого предмета. Возможно, для тех кто не знаком с принципами ООП это не покажется грубой ошибкой, но для меня это был культурный шок. А что если завтра потребуется добавить еще 10 предметов?
Этой историей я лишь хочу обозначить то, что даже над простейшей системой нужно задумываться и задавать самому себе вопросы.

При создании своей системы, я руководствовался такими принципами как:

В итоге, в голове сложился план, где я создаю UActorComponent который контролирует логику генерации лута для каждого отдельно взятого триггера. Внутри компонента я могу подстраивать шансы для выпадения требуемых предметов. Саму информацию о луте я храню в разделенных по типам DT, чтобы иметь возможность добавлять/редактировать генерируемые базы предметов.

#include <ULootComponent.h>;

// Trigger
AMonster::OnDeath(){
 GenerateLoot(1);
}

// LootComponent
ULootComponent::GenerateLoot(int GenerateAmount){
 // For each GenerateAmount unit
 for (int i; i &lt; GenerateAmount; i++){
  // Choose loot type
  // Spawn item
 }
}

Триггер

В моем случае, как триггер можно использовать любой эвент внутри Актора, система самодостаточна. Для своих экспериментов я сделал куб, при наведении камеры и нажатии E, вызывается генерация.

CharacterBP:

TriggerBP:


Генерация

Для расчета шанса выпадения лута я использовал систему весов. В открытом доступе есть куча материалов о том как она работает, но я все же уделю минутку расчетам.

Представим, что из монстра может выпасть 4 типа предметов – Оружее, Броня, Бижутерия и Расходуемые. Оружее и броня падает примерно с одинаковым шансом, а бижутерия в два раза реже. При этом самый частый дроп это обычные расходники, допустим в половине случаев. Исходя из этого составим таблицу:

Weapon20
Armor20
Jewelry10
Consumables50

Для наглядности я использовал сумму весов в 100, но для более тонких настроек можно использовать как 10 так и 100000. Используя таблицу мы фактически говорим, что в среднем на 100 вызовов генерации лута мы получим соответствующее таблице количество типов.

Реализуя гибкую систему, я складываю информацию о возможном выпадении лута в TMap состоящую из ENum типов лута и float как репрезентацию веса. При таком подходе я могу легко настраивать типы лута из единого реестра типов, а также вес для отдельных триггеров. Чтобы расчитать рандом пользуюсь следующим методом:

TMap <MyEnum, float> LootTable; // Represent moderatable loot table
int WeightSum = 100; // Float sum from LootTable simplified w/o calculations
MyEnum Key; // Result loot name represented as ENum key

int RandomRoll = FMath::RandRange(1,WeightSum); // Rolling random
float CumulativeWeight = 0.0f;

for (auto&amp; a: LootTable)
{
    CumulativeWeight += a.Value;
    // Check if the random roll is within the current cumulative weight
    if (RandomRoll &lt;= CumulativeWeight)
    {
        // Found the correct entry based on random roll
        Key = a.Key;
        break; // Exit the loop since we found the correct entry
    }
}

В результате переменная Key содержит Enum тип лута, который дальше используется для создания предмета. Переложеная на блупринты логика выглядит примерно так:

LootComponentBP weight sum:

LootComponentBP random result calculations:

Следущие итерации являются вариациями приведенных расчетов. Когда я определяю тип предмета я, таким же образом, обращаясь к следующей ENum структуре выбираю тип предмета (Sword/Dagger/Bow и т.п.). На этом моменте шанс выпадения нужного типа снаряжения начинает резко уменьшаться.

Sword50
Dagger100
Two-handed Sword10
Bow45

Например, чтобы расчитать веротяность выпадения даггера мы должны посчитать вероятность примерно так: ((LootType/WeightSum)*(LootType2/WeightSum2)) * 100 = N%
((20/100) * (100/205))*100 = 9,7% шанс выпадения даггера по данным приведенным в таблицах.

После определения типов я обращаюсь к зараннее подготовленной DT, в которую внесена базовая информация о вещи (Min-max attack/speed/defence, ассет, описание и прочее). Если в DT содержиться больше чем одна вещь искомого типа, мы также можем применить расчет веса (если структура DT предусматривает веса), но для упрощения и разнообразия я использую равноценный рандом:

Теперь шанс выпадения нужного дагера уменьшился еще в N раз количества других его видов, если они есть.

На выходе мы имеем структуру данных с информацией о базовой болванке предмета и можем продолжать манипуляции с ней.


Мой метод не кажется самым эффективным, но точно должен хорошо работать для небольших и средних игр.

Следующим шагом я собираюсь генерировать характеристики предметов в зависимости от их типа и редкости, расскажу об этом в следующей статье, а пока bp версию проекта можно найти на гитхабе:

UE-Loot-example

Self-education UE project about loot generation

< Back