← Stanislav Deffiro
SYS 03 Data-driven content

Craft & Save System

Классика, собранная так, чтобы контент добавлял дизайнер

Инвентарь, добыча ресурсов, крафт и сохранение survival-игры в один контур. Никакой новой механики — намеренно классическая система, ценность которой в границе ответственности: C++ описывает правила, ассеты описывают содержание. Новый предмет, инструмент, одежда или рецепт добавляются из редактора обычными Data Asset и Blueprint — без строчки кода, без пересборки и без меня.

UE 5.6 · C++ Primary Data Assets SaveGame
C++ · ПРАВИЛА DATA · СОДЕРЖАНИЕ UInventoryComponent CanCraft / TryCraft UDesertSaveGame DA_Item Wood DA_Item StoneAxe DA_Recipe Cloak DA_... + n ИЗМЕНЕНИЙ В C++ НА ЕДИНИЦУ КОНТЕНТА 0
Type Inventory · Craft · Save
Модуль DesertGame
Слот сейва DesertSave
Status В работе, контур замкнут
Sec 01 Где проходит граница

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

Дизайнер работает не с кодом, а с данными — прямо в окнах свойств редактора. Новый предмет здесь не новый класс, а новый ассет. Та же линия проходит и через сохранение: сейв хранит ссылки на ассеты, а не их содержимое, поэтому правка иконки или питательности кокоса не ломает старые сохранения.

Программист · один раз
Правила стакания предметов
Проверка и списание ингредиентов
Формат и порядок сохранения
Реакция мира на удар инструментом
Дизайнер · сколько угодно
Какие предметы существуют в игре
Иконки, меши, стаки, урон
Все рецепты и их стоимость
Прочность деревьев и камней, их дроп
Sec 02 Конвейер контента

Один и тот же код обслуживает любой новый ассет

На схеме снизу средняя колонка — правила в C++. Она не меняется ни в одном сценарии: подсвечивается только то, что уже написано и просто начинает работать с новыми данными. Слева дизайнер создаёт ассет (прямо в редакторе), справа появляется игровой результат.

Это и есть весь фокус системы: контент растёт, код стоит на месте. Счётчик внизу схемы всегда показывает ноль.

Один класс на все виды предметов Ресурс, инструмент, еда и одежда — один UItemDataAsset; неиспользуемые поля просто остаются нулевыми.
Редактор не даёт ошибиться Поля восстановления открываются через EditCondition только у съедобного предмета — нерелевантных настроек дизайнер просто не видит.
Персистентность достаётся бесплатно Новый предмет сохраняется потому, что сохраняются все предметы. Новое дерево переживает перезаход потому, что этим занимается базовый класс.
ДИЗАЙНЕР · РЕДАКТОР C++ · ПРАВИЛА (НЕ МЕНЯЮТСЯ) РЕЗУЛЬТАТ В ИГРЕ ИЗМЕНЕНИЙ В C++ 0 ПЕРЕСБОРОК ПРОЕКТА 0 СТАРЫЕ СЕЙВЫ РАБОТАЮТ АССЕТОВ ДОБАВЛЕНО 0
FIG. 01 Пять реальных сценариев добавления контента · ни один не трогает код
Sec 03 Данные

UItemDataAsset

Как я и сказал один ассет — один вид предмета. Поля сгруппированы по назначению, поэтому один класс обслуживает ресурсы, инструменты, еду, одежду и все остальное.

DisplayName · Icon · WorldMesh Всегда
MaxStackSize 1–10
ItemType Ресурсы
ToolType · ToolDamage · EquipMesh Инструменты
bIsConsumable · Hunger / Thirst / Health Еда и питьё
Category · ClothEquipMode · Protection Одежда

Поля восстановления активируются в редакторе только у съедобного предмета. Дизайнер физически не видит нерелевантных настроек — а значит не заполнит их по ошибке.

URecipeDataAsset

Результат, его количество и массив пар «предмет + количество». Рецепт из одного компонента и рецепт из шести описываются одинаково и обрабатываются одним циклом.

// целиком редактируется в окне свойств
DisplayName    : "Каменный топор"
RecipeIcon     : T_Icon_StoneAxe
ResultItem     : DA_Item_StoneAxe
ResultQuantity : 1
Ingredients    : [
    { Item: DA_Item_Wood,  Quantity: 3 },
    { Item: DA_Item_Stone, Quantity: 2 }
  ]
Уже в проекте
DA_Item_Wood DA_Item_Stone DA_Item_Metal DA_Coconut DA_Item_StoneAxe DA_Pickaxe DA_Sword DA_Cloack DA_Recipe_StoneAxe DA_Cloak_Recipe

Третий тип данных, UResourceNodeData, описывает дерево или залежь: требуемый инструмент, прочность, выпадающий предмет. Проверка соответствия инструмента — одна строка сравнения ToolType с RequiredTool: дизайнер выбирает «Axe» в выпадающем списке, и правило начинает действовать.

Sec 04 Демонстрация

Две записи из проекта: как контент создаётся в редакторе и как состояние переживает перезаход.

REC 01 Data Assets в редакторе · создание и правка предмета без кода
REC 02 Сохранение и загрузка · состояние мира, инвентаря и лута
Sec 05 Сохранение

Две стратегии для двух видов объектов

Центральное решение системы, и оно следует прямо из того, как Unreal именует акторов. У расставленного на карте дерева имя стабильно между запусками — достаточно запомнить факт «уничтожено», и актор сам удалит себя в BeginPlay. У выпавшего лута имя генерируется по порядку спавна и в следующей сессии будет другим — такие объекты сохраняются целиком и пересоздаются.

Различает их флаг bIsRuntimeDrop, выставляемый в момент спавна. Без этого разделения возможна тихая ошибка: сгенерированное имя случайно совпадает с именем расставленного объекта, и предмет исчезает при загрузке без всяких сообщений.

Инвентарь и хотбар 16 + 4 ячейки · ссылка + кол-во
Одежда ClothSlot
Игрок трансформ · HP · стамина · голод · жажда
Ресурсные узлы список имён срубленных
Выпавший лут предмет + кол-во + трансформ
Мир фаза суток и прогресс внутри неё
До загрузки уровня Списки уничтоженного Иначе деревья успеют появиться до проверки.
PHASE 01
В BeginPlay Инвентарь, атрибуты, время суток После инициализации по умолчанию.
PHASE 02
Следующий кадр Выпавший лут Когда все акторы уровня уже готовы.
PHASE 03

Порядок критичен: BeginPlay у разных акторов вызывается независимо, и часть состояния перетирается значениями по умолчанию, если применить её слишком рано.

FIG. 02 Порядок восстановления состояния при загрузке
Связь крафта и сохранения

Игрок рубит дерево топором, подбирает три доски, крафтит плащ, надевает его и жмёт P. В файл попадут имя срубленного дерева, инвентарь после списания досок, ссылка на DA_Cloack в слоте одежды и фаза суток. При загрузке дерево не вернётся, плащ окажется надет, а защита от жары снова начнёт действовать — потому что она вычисляется из поля Protection того же ассета.

Sec 06 Практика без программиста

Реальные сценарии. Ни один не требует правки C++ или пересборки проекта.

CASE 01

Новый ресурс и рецепт из него

  1. Создать UItemDataAsset: имя, иконка, меш, стак.
  2. Создать рецепт, указать результат и ингредиенты.
  3. Добавить рецепт в AvailableRecipes виджета крафта.
Рецепт немедленно появится в интерфейсе, будет проверяться, списывать ингредиенты и сохраняться.
CASE 02

Новое оружие или инструмент

  1. Выставить ToolType и ToolDamage.
  2. Указать EquipMesh — меш в руке.
  3. При необходимости создать рецепт.
Инструмент сразу начнёт взаимодействовать с узлами: совпал ToolType с RequiredTool — удары наносятся.
CASE 03

Новая одежда с игровым эффектом

  1. CategoryCloth.
  2. Overlay для накидки или BodySwap для замены тела.
  3. Скелетный меш со скелетом персонажа.
  4. ProtectionHeat или Cold.
Самый показательный пункт: защита от жары или холода включается выбором из выпадающего списка.
CASE 04

Новый вид добываемого объекта

  1. UResourceNodeData: прочность, инструмент, дроп, разброс.
  2. Blueprint на базе AResourceNode с этим ассетом.
  3. Расставить по карте.
Новые узлы автоматически попадают в систему сохранения — отслеживание разрушения живёт в базовом классе.
ГДЕ ГРАНИЦЫ ГИБКОСТИ

Перечисления EItemType, EToolType и EClothProtection заданы в C++: новый вид материала или принципиально новый класс инструмента — это правка кода. Внутри существующих категорий предметов можно создавать сколько угодно. Массив AvailableRecipes тоже заполняется вручную — осознанное упрощение, которое стоит заменить автоматическим сбором ассетов через Asset Manager, и тогда третий шаг из CASE 01 исчезнет совсем.