Разработка системы энергии/выносливости GameFi

Проектируем и разрабатываем блокчейн-решения полного цикла: от архитектуры смарт-контрактов до запуска DeFi-протоколов, NFT-маркетплейсов и криптобирж. Аудит безопасности, токеномика, интеграция с существующей инфраструктурой.
Показано 1 из 1 услугВсе 1306 услуг
Разработка системы энергии/выносливости GameFi
Средняя
~3-5 рабочих дней
Часто задаваемые вопросы
Направления блокчейн-разработки
Этапы блокчейн-разработки
Последние работы
  • image_website-b2b-advance_0.png
    Разработка сайта компании B2B ADVANCE
    1258
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1170
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    873
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1092
  • image_logo-advance_0.png
    Разработка логотипа компании B2B Advance
    563
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    830

Разработка системы энергии/выносливости GameFi

Система энергии — механика ограничения игровой активности. Игрок тратит энергию на действия (битвы, фарминг, крафт), энергия восполняется со временем или через покупку. В Web2 это просто счётчик в базе данных. В Web3 это on-chain ресурс, что создаёт и возможности (tradeable энергия, verifiable regen), и проблемы (gas за каждое обновление, cheating prevention).

Правильная архитектура энергетической системы — одна из ключевых инженерных задач GameFi. Её неправильная реализация либо делает игру неиграбельной (слишком много on-chain операций), либо открывает эксплойты (бесплатная энергия через manipulation).

Ключевая проблема: time-based regen без постоянного on-chain обновления

Интуитивное решение: хранить энергию в mapping, обновлять каждую секунду. Это плохо — бесконечное количество транзакций.

Правильный подход: lazy evaluation. Храним не текущую энергию, а момент последнего изменения и значение в тот момент. Текущая энергия вычисляется on-the-fly при каждом чтении:

contract EnergySystem {
    struct EnergyState {
        uint128 storedEnergy;   // энергия на момент lastUpdate
        uint64 lastUpdateTime;
        uint64 maxEnergy;
    }
    
    mapping(address => EnergyState) private energyStates;
    
    uint256 public constant REGEN_RATE = 1e18; // 1 энергия в секунду (18 decimals)
    uint256 public constant MAX_ENERGY = 100e18;
    
    // Вычисляем текущую энергию без записи в storage
    function currentEnergy(address player) public view returns (uint256) {
        EnergyState storage state = energyStates[player];
        
        uint256 elapsed = block.timestamp - state.lastUpdateTime;
        uint256 regenerated = elapsed * REGEN_RATE;
        
        uint256 total = uint256(state.storedEnergy) + regenerated;
        uint256 max = state.maxEnergy == 0 ? MAX_ENERGY : uint256(state.maxEnergy);
        
        return total > max ? max : total;
    }
    
    // Обновляем storage только при фактическом использовании/изменении энергии
    function _updateEnergyState(address player) internal {
        EnergyState storage state = energyStates[player];
        state.storedEnergy = uint128(currentEnergy(player));
        state.lastUpdateTime = uint64(block.timestamp);
    }
    
    function spendEnergy(address player, uint256 amount) internal {
        uint256 current = currentEnergy(player);
        require(current >= amount, "Insufficient energy");
        
        _updateEnergyState(player);
        energyStates[player].storedEnergy -= uint128(amount);
    }
    
    function addEnergy(address player, uint256 amount) internal {
        _updateEnergyState(player);
        uint256 max = energyStates[player].maxEnergy == 0 
            ? MAX_ENERGY 
            : energyStates[player].maxEnergy;
        uint256 newEnergy = uint256(energyStates[player].storedEnergy) + amount;
        energyStates[player].storedEnergy = uint128(newEnergy > max ? max : newEnergy);
    }
}

Ключевой момент: currentEnergy()view функция, не тратит gas. Storage обновляется только при spendEnergy/addEnergy — то есть при реальном игровом действии. Gas экономия существенная: не нужны отдельные транзакции для regen.

Привязка к NFT: энергия персонажа

Энергия привязана к конкретному NFT, не к EOA кошельку. Это важно: игрок может иметь несколько персонажей с независимой энергией, торговать персонажами вместе с их текущей энергией.

contract CharacterEnergySystem {
    struct CharacterEnergy {
        uint128 storedEnergy;
        uint64 lastUpdate;
        uint8 tier; // tier персонажа влияет на max energy и regen
    }
    
    mapping(uint256 => CharacterEnergy) public characterEnergy; // tokenId → energy
    
    // Regen rate зависит от tier персонажа
    function regenRateForTier(uint8 tier) public pure returns (uint256) {
        if (tier == 3) return 3e18; // 3 ед/сек для legendary
        if (tier == 2) return 2e18; // 2 ед/сек для rare
        return 1e18;                // 1 ед/сек для common
    }
    
    // Max energy зависит от tier
    function maxEnergyForTier(uint8 tier) public pure returns (uint256) {
        return 100e18 + uint256(tier) * 50e18; // 100, 150, 200 для tier 1,2,3
    }
    
    function currentEnergy(uint256 tokenId) public view returns (uint256) {
        CharacterEnergy storage ce = characterEnergy[tokenId];
        uint8 tier = nftContract.getTier(tokenId);
        
        uint256 elapsed = block.timestamp - ce.lastUpdate;
        uint256 regen = elapsed * regenRateForTier(tier);
        uint256 total = uint256(ce.storedEnergy) + regen;
        uint256 max = maxEnergyForTier(tier);
        
        return total > max ? max : total;
    }
    
    // При трансфере NFT: энергия переходит с персонажем
    // (хранится в tokenId маппинге, не в owner маппинге — автоматически)
}

Energy token: tradeable энергия

Более сложная модель: отдельный ERC-20 токен как энергия, которую можно купить/продать.

// ENERGY_TOKEN: ERC-20 с burn при использовании
contract EnergyToken is ERC20, AccessControl {
    bytes32 public constant GAME_ROLE = keccak256("GAME_ROLE");
    
    // Game контракт минтит при regen, сжигает при использовании
    function mintRegen(address player, uint256 amount) external onlyRole(GAME_ROLE) {
        _mint(player, amount);
    }
    
    function burnForAction(address player, uint256 amount) external onlyRole(GAME_ROLE) {
        _burn(player, amount);
    }
}

Trade-off ERC-20 энергии: игроки могут купить энергию на DEX → pay-to-win риск. Если это допустимо в вашей модели — ERC-20 энергия даёт экономическую ценность. Если нет — энергия должна быть non-transferable (не токен, а internal accounting).

Anti-cheat: защита от манипуляций

Problem: flash-refill через re-org или timestamp manipulation

Майнер (в PoW) или validator (в PoS) технически может манипулировать block.timestamp в небольших пределах (~15 секунд). Для энергетической системы это незначительно.

Более реальная угроза: off-chain server атаки. Если game server выдаёт подписи для игровых действий — игрок может replay старую подпись.

// Защита через signed action с nonce
struct GameAction {
    uint256 characterId;
    uint256 actionType;
    uint256 energyCost;
    uint256 nonce;       // одноразовый, инкрементальный
    uint256 deadline;    // транзакция должна быть подана до
}

mapping(address => uint256) public actionNonces; // текущий nonce игрока

function executeAction(
    GameAction calldata action,
    bytes calldata serverSignature
) external {
    // Проверяем подпись game server
    bytes32 digest = _hashTypedData(action);
    address signer = ECDSA.recover(digest, serverSignature);
    require(signer == GAME_SERVER_SIGNER, "Invalid signature");
    
    // Проверяем nonce (защита от replay)
    require(action.nonce == actionNonces[msg.sender], "Invalid nonce");
    actionNonces[msg.sender]++;
    
    // Проверяем deadline
    require(block.timestamp <= action.deadline, "Expired");
    
    // Списываем энергию
    spendEnergy(action.characterId, action.energyCost);
    
    _processAction(action);
}

Cooldown механизм

Некоторые действия требуют cooldown независимо от энергии:

mapping(uint256 => mapping(uint8 => uint256)) public lastActionTime; // charId → actionType → timestamp

uint256 public constant BOSS_FIGHT_COOLDOWN = 4 hours;

modifier withCooldown(uint256 charId, uint8 actionType, uint256 cooldown) {
    require(
        block.timestamp >= lastActionTime[charId][actionType] + cooldown,
        "Action on cooldown"
    );
    _;
    lastActionTime[charId][actionType] = block.timestamp;
}

function fightBoss(uint256 charId) external withCooldown(charId, ACTION_BOSS, BOSS_FIGHT_COOLDOWN) {
    spendEnergy(charId, BOSS_FIGHT_ENERGY_COST);
    // ...
}

Экономическая модель: sink и source

Энергетическая система работает как регулятор экономики. Важно балансировать:

Sources (откуда берётся энергия):

  • Regen over time (бесплатно, ограничено max)
  • Purchase за game token (sink для токена)
  • Staking NFT более высокого tier → бонусный regen
  • Daily login reward (один раз в 24ч)

Sinks (куда уходит энергия):

  • Боевые действия
  • Фарминг ресурсов
  • Крафт предметов
  • PvP ставки

Если sources >> sinks — инфляция энергии, игра теряет смысл. Если sinks >> sources — игроки уходят из-за невозможности играть без постоянных платежей.

Параметр Рекомендации
Regen rate Заполнение с 0 до max за 8–12 часов
Max energy 1–3 игровых сессии по 2–3 часа
Premium refill Не более 2–3 полных refill в день
Tier multiplier Max 2x–3x, не больше

Стек и интеграция

Контракты: Solidity 0.8.x, packed structs (uint128/uint64/uint8 для экономии storage). Foundry для тестов с vm.warp() для проверки regen логики.

// Foundry тест regen механики
function test_energyRegenOverTime() public {
    uint256 tokenId = 1;
    
    // Тратим всю энергию
    vm.prank(player);
    game.spendAllEnergy(tokenId);
    assertEq(energy.currentEnergy(tokenId), 0);
    
    // Пропускаем 50 секунд
    vm.warp(block.timestamp + 50);
    
    // Проверяем regen (tier 1: 1 ед/сек)
    assertEq(energy.currentEnergy(tokenId), 50e18);
    
    // Пропускаем ещё 200 секунд — должны упереться в max (100)
    vm.warp(block.timestamp + 200);
    assertEq(energy.currentEnergy(tokenId), 100e18); // capped at max
}

Ориентиры по срокам

Базовая система (lazy regen, spend на действия, cooldowns): 2–3 недели. Полная система (tier-based regen, ERC-20 energy token, DEX интеграция, anti-cheat signed actions, dashboard аналитики): 5–7 недель.