Разработка системы инвентаря мобильной игры

TRUETECH занимается разработкой, поддержкой и обслуживанием мобильных приложений iOS, Android, PWA. Имеем большой опыт и экспертизу для публикации мобильных приложений в популярные маркеты Google Play, App Store, Amazon, AppGallery и другие.
Разработка и поддержка любых видов мобильных приложений:
Информационные и развлекательные мобильные приложения
Новостные приложения, игры, справочники, онлайн-каталоги, погодные, фитнес и здоровье, туристические, образовательные, социальные сети и мессенджеры, квиз, блоги и подкасты, форумы, агрегаторы
Мобильные приложения электронной коммерции
Интернет-магазины, B2B-приложения, маркетплейсы, онлайн-обменники, кэшбэк-сервисы, биржи, дропшиппинг-платформы, программы лояльности, доставка еды и товаров, платежные системы
Мобильные приложения для управления бизнес-процессами
CRM-системы, ERP-системы, управление проектами, инструменты для команды продаж, учет финансов, управление производством, логистика и доставка, управление персоналом, системы мониторинга данных
Мобильные приложения электронных услуг
Доски объявлений, онлайн-школы, онлайн-кинотеатры, платформы предоставления электронных услуг, платформы кешбека, видеохостинги, тематические порталы, платформы онлайн-бронирования и записи, платформы онлайн-торговли

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

Предлагаемые услуги
Показано 1 из 1 услугВсе 1735 услуг
Разработка системы инвентаря мобильной игры
Средняя
~3-5 рабочих дней
Часто задаваемые вопросы
Наши компетенции:
Этапы разработки
Последние работы
  • image_mobile-applications_feedme_467_0.webp
    Разработка мобильного приложения для компании FEEDME
    756
  • image_mobile-applications_xoomer_471_0.webp
    Разработка мобильного приложения для компании XOOMER
    624
  • image_mobile-applications_rhl_428_0.webp
    Разработка мобильного приложения для компании RHL
    1052
  • image_mobile-applications_zippy_411_0.webp
    Разработка мобильного приложения для компании ZIPPY
    947
  • image_mobile-applications_affhome_429_0.webp
    Разработка мобильного приложения для компании Affhome
    862
  • image_mobile-applications_flavors_409_0.webp
    Разработка мобильного приложения для компании FLAVORS
    445

Разработка системы инвентаря мобильной игры

Инвентарь — это не просто список предметов. Это транзакционная система с бизнес-правилами: нельзя потратить больше монет, чем есть; нельзя взять два уникальных предмета; стак должен корректно объединяться. Ошибки здесь стоят денег: дублированные предметы из-за race condition, отрицательная валюта из-за несинхронизированных запросов — это реальные баги в продакшене.

Структура данных инвентаря

@Entity(tableName = "inventory_items")
data class InventoryItemEntity(
    @PrimaryKey val instanceId: String,    // уникальный ID каждого предмета
    val playerId: String,
    val itemDefinitionId: String,          // ссылка на шаблон предмета
    val quantity: Int,                     // для стакируемых
    val durability: Int? = null,           // для предметов с прочностью
    val enchantments: String = "[]",       // JSON array дополнительных свойств
    val acquiredAt: Long,
    val slotIndex: Int? = null             // для экипированных предметов
)

// Определение предмета (статичные данные — загружаются из assets)
data class ItemDefinition(
    val id: String,
    val name: String,
    val type: ItemType,                    // WEAPON, ARMOR, CONSUMABLE, CURRENCY
    val isStackable: Boolean,
    val maxStackSize: Int = 1,
    val isUnique: Boolean = false,         // нельзя иметь больше одного
    val maxQuantity: Int = Int.MAX_VALUE
)

Разделение на InstanceData (то, что уникально для каждого предмета у игрока) и ItemDefinition (шаблон предмета) — классический паттерн. Шаблоны грузим из JSON в assets при старте, не кладём в БД — они не меняются в runtime.

Транзакционные операции

Race condition при пополнении кошелька — классическая проблема. Два параллельных запроса «добавить 100 монет» оба читают текущее значение 500, оба пишут 600. Должно быть 700.

@Dao
interface InventoryDao {
    // Атомарное добавление к стеку — не читаем и не пишем отдельно
    @Query("""
        UPDATE inventory_items
        SET quantity = MIN(quantity + :amount, :maxStackSize)
        WHERE instance_id = :instanceId AND player_id = :playerId
    """)
    suspend fun incrementQuantity(instanceId: String, playerId: String,
                                   amount: Int, maxStackSize: Int): Int

    // Атомарное списание с проверкой — не даёт уйти в минус
    @Query("""
        UPDATE inventory_items
        SET quantity = quantity - :amount
        WHERE instance_id = :instanceId AND player_id = :playerId
          AND quantity >= :amount
    """)
    suspend fun decrementQuantity(instanceId: String, playerId: String, amount: Int): Int
    // Возвращает количество обновлённых строк — если 0, значит не хватило

    @Transaction
    suspend fun transferItem(fromPlayerId: String, toPlayerId: String,
                              instanceId: String): Boolean {
        val updated = updateOwner(instanceId, fromPlayerId, toPlayerId)
        return updated > 0
    }
}

decrementQuantity возвращает количество затронутых строк. Если 0 — операция не прошла из-за нехватки ресурсов. Никакого read-check-write — одна атомарная SQL-операция.

Серверная валидация

Локальный инвентарь — для отображения. Всё, что касается реальной монетизации (покупки, расход gems, получение за реальные деньги), обязательно проходит валидацию на сервере:

class InventoryRepository(
    private val localDao: InventoryDao,
    private val api: InventoryApi
) {
    suspend fun spendGems(amount: Int, reason: String): Result<Unit> {
        return try {
            // Сервер проверяет баланс, списывает, возвращает новое состояние
            val serverState = api.spendGems(SpendGemsRequest(amount, reason))

            // Синхронизируем локальное состояние с сервером
            localDao.updateCurrencyBalance(
                playerId = serverState.playerId,
                gems = serverState.newGemsBalance
            )
            Result.success(Unit)
        } catch (e: InsufficientFundsException) {
            Result.failure(e)
        }
    }
}

Оптимистичный update на клиенте с rollback при ошибке — только для некритичных операций. Для монетизации — всегда server-first.

Сортировка и фильтрация

Инвентарь из 500 предметов нельзя держать весь в памяти и фильтровать на клиенте. Room Paging 3:

@Dao
interface InventoryDao {
    @Query("""
        SELECT * FROM inventory_items
        WHERE player_id = :playerId
          AND (:typeFilter IS NULL OR item_type = :typeFilter)
          AND (:searchQuery IS NULL OR item_name LIKE '%' || :searchQuery || '%')
        ORDER BY
          CASE :sortBy WHEN 'rarity' THEN rarity_value ELSE acquired_at END DESC
    """)
    fun pagingSource(playerId: String, typeFilter: String?, searchQuery: String?,
                     sortBy: String): PagingSource<Int, InventoryItemEntity>
}

// ViewModel
val inventoryItems = Pager(PagingConfig(pageSize = 20)) {
    dao.pagingSource(playerId, selectedType, searchQuery, sortBy)
}.flow.cachedIn(viewModelScope)

Paging 3 загружает по 20 предметов при скролле — никакого лага при большом инвентаре.

Drag-and-drop перестановка слотов

В Jetpack Compose через reorderable библиотеку (burnoutcrew/reorderable) или через detectDragGesturesAfterLongPress. Ключевой момент — применять новый порядок оптимистично в UI сразу, а в БД — батчем после drop:

fun onItemDropped(fromIndex: Int, toIndex: Int) {
    // Оптимистично обновляем список в памяти
    val newList = _inventoryState.value.toMutableList().apply {
        add(toIndex, removeAt(fromIndex))
    }
    _inventoryState.value = newList

    // Сохраняем новый порядок в БД батчем
    viewModelScope.launch(Dispatchers.IO) {
        dao.updateSlotIndices(newList.mapIndexed { index, item ->
            SlotUpdate(item.instanceId, index)
        })
    }
}

Разработка системы инвентаря с транзакционными операциями, серверной валидацией и Paging: 2–4 недели. Стоимость рассчитывается индивидуально.