Спроектируйте систему инвентаря для мультиплеерной игры на Unreal Engine. Игроки носят много предметов — стакающиеся расходники и уникальные предметы с состоянием (прочность, патроны), — и дизайнеры должны определять новые типы предметов без программиста и пересборки. Один и тот же тип предмета встречается во многих инвентарях, поэтому описания предметов должны быть общими, а не дублироваться на каждый экземпляр. Содержимое инвентаря должно быть авторитетным на сервере и недоверяемым клиенту (клиент не должен сам выдавать себе предметы). UI инвентаря должен обновляться при добавлении, удалении или перемещении предметов без опроса каждый кадр, а система должна масштабироваться на большие инвентари, не пересылая всё содержимое при каждом мелком изменении. Опишите, где живут состояние инвентаря и описания предметов, как UI остаётся синхронным, где живёт авторитет, и компромиссы вашего подхода.
Разместите UInventoryComponent на владельце с массивом записей предметов; описывайте предметы через UItemDataAsset и ссылайтесь по id. Реплицируйте массив записей на сервере; UI подписывается на делегат изменения и перестраивает слоты при добавлении или перемещении.
- ✗Спавнить полноценный Actor на каждый предмет вместо ссылки на лёгкое общее описание DataAsset
- ✗Опрашивать инвентарь каждый тик из UI вместо подписки на делегат изменения
- ✗Хранить состояние инвентаря на клиенте и доверять ему вместо репликации с сервера
- →Как бы вы реализовали стакание предметов и операции разделения/объединения по слотам?
- →Зачем использовать
FastArraySerializerдля массива записей вместо обычного реплицируемогоTArray?
Ключевые классы
UItemDataAsset— неизменяемое описание предмета: иконка, имя, тип, максимальный размер стака, эффекты. Создаётся дизайнером, существует в одном экземпляре.FInventoryEntry—USTRUCTсо ссылкой наUItemDataAsset(или егоid), количеством в стаке и индексом слота.UInventoryComponent—UActorComponentна владельце (Pawn/PlayerState), хранитTArray<FInventoryEntry>и предоставляет API:AddItem,RemoveItem,MoveItem.UInventoryWidget— UMG-виджет, подписан на делегатOnInventoryChanged.
Модель данных
Запись инвентаря ссылается на описание, а не копирует его. Стакающиеся предметы хранятся одной записью с полем Quantity; уникальные предметы с состоянием (прочность, патроны) — отдельной записью с дополнительным FInstanceData.
Поток данных
- Клиент нажимает «подобрать» → Server RPC на
UInventoryComponent. - Сервер валидирует (вместимость, дистанция), меняет
TArray<FInventoryEntry>. - Массив реплицируется; на клиенте срабатывает
OnRep, который вызываетOnInventoryChanged. - Виджет перестраивает слоты только по этому делегату — без опроса в
Tick.
Репликация
Массив записей должен быть Replicated. Для крупных инвентарей используйте FFastArraySerializer — он реплицирует только изменённые записи (дельты), а не весь массив целиком. Само описание UItemDataAsset не реплицируется: оно есть на всех машинах, передаётся только ссылка.
Настройка дизайнером
Каждый предмет — отдельный ассет UItemDataAsset в Content Browser. Дизайнер задаёт стак, тип, эффекты без участия программиста. Стартовый набор — массив ссылок на дефолтном UInventoryComponent.
Компромиссы
- Компонент vs. логика в Pawn: компонент переиспользуем (NPC-сундуки, торговцы), но добавляет слой косвенности.
- DataAsset vs. DataTable: DataAsset удобнее для сложных вложенных данных и ссылок на ассеты; DataTable — для плоских табличных данных и массового редактирования.