Voltar ao Blog

Otimização de Escalonamento em Mobile Games: Arquitetando Cidades para 1M+ de Jogadores Simultâneos

Publicado em 21 de maio de 2026
Otimização de Escalonamento em Mobile Games: Arquitetando Cidades para 1M+ de Jogadores Simultâneos Gerada com a ajuda de IA

Em resumo

Este guia técnico detalha estratégias essenciais para arquitetar cidades massivas em mobile games, focando em particionamento espacial, gerenciamento de memória e escalonamento de Backend. Exploramos técnicas como Spatial Hashing, Interest Management e streaming de assets assíncronos para suportar milhões de jogadores simultâneos em hardware mobile limitado. O conteúdo é voltado para desenvolvedores seniores que buscam otimizar o tráfego de rede e a performance de CPU/RAM utilizando ferramentas como Unreal Engine, Unity e a plataforma horizOn.

Todo desenvolvedor Multiplayer conhece o momento exato em que a arquitetura do seu mobile game racha. Você projeta um ambiente urbano vasto e detalhado. Você o testa localmente com 10 clientes simulados, e a build roda a perfeitos 60 FPS. Então, você a envia para um ambiente de produção com 1.000 jogadores simultâneos aglomerados na praça central. Em segundos, dispositivos Android de baixo custo sofrem hard-crash devido a exceções de Out-Of-Memory (OOM), o iOS Jetsam encerra agressivamente sua aplicação, e o uso de CPU do seu Dedicated Server atinge 100% enquanto tenta calcular a replicação de rede para milhares de entidades sobrepostas.

Ao construir um MMO mobile ou um mundo aberto de grande escala projetado para suportar milhões de usuários ativos, você não pode confiar nos defaults padrão da engine. O hardware mobile possui thermal throttling rigoroso e limites rígidos de memória (muitas vezes limitando seu jogo a menos de 2GB de RAM utilizável em dispositivos intermediários). Simultaneamente, seu servidor deve lidar com aglomerados densos de jogadores sem ceder.

Alcançar uma verdadeira otimização de escalonamento em mobile games requer uma abordagem de três pilares: particionamento espacial agressivo no servidor, gerenciamento de memória implacável no cliente e uma arquitetura de Backend distribuída para lidar com o enorme volume de conexões. Neste tutorial passo a passo, vamos detalhar exatamente como arquitetar cidades de grande escala para plataformas mobile.

Passo 1: Particionamento Espacial no Server-Side

O inimigo fundamental da performance do servidor em jogos multiplayer massivos é o problema O(N²). Se o seu servidor percorre cada jogador para verificar sua distância em relação a todos os outros jogadores a fim de determinar quem precisa de atualizações de rede, a matemática escala catastroficamente. 100 jogadores exigem 10.000 verificações de distância por tick. 1.000 jogadores exigem 1.000.000 de verificações. Com um tick rate de servidor de 30Hz, são 30 milhões de verificações por segundo.

Para resolver isso, devemos implementar Spatial Hashing (ou um sistema de Grid/Quadtree). Ao dividir a cidade em um grid lógico, os jogadores apenas verificam a relevância de rede contra entidades em sua célula atual e nas células imediatamente circundantes. Isso reduz nosso pesadelo O(N²) para uma busca O(1) no grid, seguida por uma verificação local pesadamente restrita.

Implementando um Spatial Hash Grid (Exemplo em C#)

Aqui está uma implementação altamente eficiente de um Spatial Hash Grid 2D em C# que você pode adaptar para Unity, Godot (via C#) ou um servidor de Backend customizado para gerenciar a proximidade de entidades sem percorrer todo o estado do mundo.

using System.Collections.Generic;
using UnityEngine;

public class SpatialHashGrid
{
    private readonly float _cellSize;
    private readonly Dictionary<Vector2Int, HashSet<uint>> _grid;

    public SpatialHashGrid(float cellSize = 50f)
    {
        _cellSize = cellSize;
        _grid = new Dictionary<Vector2Int, HashSet<uint>>();
    }

    // Convert a world position to a grid coordinate
    private Vector2Int GetCellCoordinate(Vector3 position)
    {
        return new Vector2Int(
            Mathf.FloorToInt(position.x / _cellSize),
            Mathf.FloorToInt(position.z / _cellSize)
        );
    }

    // Add or update a player's position in the grid
    public void UpdateEntityPosition(uint entityId, Vector3 oldPosition, Vector3 newPosition)
    {
        Vector2Int oldCell = GetCellCoordinate(oldPosition);
        Vector2Int newCell = GetCellCoordinate(newPosition);

        if (oldCell != newCell)
        {
            if (_grid.ContainsKey(oldCell))
            {
                _grid[oldCell].Remove(entityId);
            }

            if (!_grid.ContainsKey(newCell))
            {
                _grid[newCell] = new HashSet<uint>();
            }
            _grid[newCell].Add(entityId);
        }
    }

    // Retrieve all entities in the immediate vicinity (9 cells)
    public List<uint> GetEntitiesInProximity(Vector3 position)
    {
        List<uint> nearbyEntities = new List<uint>();
        Vector2Int centerCell = GetCellCoordinate(position);

        // Loop through the 3x3 grid around the player
        for (int x = -1; x <= 1; x++)
        {
            for (int y = -1; y <= 1; y++)
            {
                Vector2Int cellToCheck = new Vector2Int(centerCell.x + x, centerCell.y + y);
                if (_grid.TryGetValue(cellToCheck, out HashSet<uint> entitiesInCell))
                {
                    nearbyEntities.AddRange(entitiesInCell);
                }
            }
        }

        return nearbyEntities;
    }
}

Ao rotear sua lógica de replicação de rede através do GetEntitiesInProximity, seu servidor apenas calcula distâncias exatas para as poucas dezenas de jogadores ativamente próximos uns dos outros, reduzindo drasticamente a carga de CPU e permitindo que seu servidor lide confortavelmente com milhares de conexões simultâneas na mesma instância.

Passo 2: Gerenciamento de Interesse de Rede (Interest Management)

Mesmo com o Spatial Hashing resolvendo o gargalo de CPU do servidor, você ainda tem um problema de largura de banda (bandwidth). Redes móveis (4G/5G) são inerentemente instáveis, propensas a alto jitter e possuem limitações estritas de largura de banda. Enviar dados de 50 jogadores próximos a cada tick inundará o socket buffer do cliente mobile, levando a desyncs extremos.

O Interest Management (ou Relevância de Rede) é a prática de priorizar o que é enviado pela rede. Um jogador a 2 metros de distância envolvido em um combate requer 30 atualizações por segundo. Um jogador a 40 metros de distância caminhando em uma rua diferente precisa de apenas 2 atualizações por segundo.

Sobrescrevendo a Relevância de Rede (Exemplo em C++ para Unreal Engine)

Na Unreal Engine, você pode assumir o controle disso sobrescrevendo a função IsNetRelevantFor. Isso permite filtrar (cull) o tráfego de rede agressivamente com base na linha de visão (line-of-sight) e camadas de distância.

bool ACityPlayerCharacter::IsNetRelevantFor(const AActor* RealViewer, const AActor* ViewTarget, const FVector& SrcLocation) const
{
    // 1. Always relevant to ourselves
    if (RealViewer == this || ViewTarget == this)
    {
        return true;
    }

    // 2. Calculate squared distance (faster than exact distance)
    const float DistanceSquared = FVector::DistSquared(SrcLocation, GetActorLocation());

    // 3. Absolute Cull Distance (e.g., 10,000 units = 100 meters)
    const float MaxRelevancyDistSq = 100000000.0f; 
    if (DistanceSquared > MaxRelevancyDistSq)
    {
        return false;
    }

    // 4. Dynamic Network Update Frequency based on distance
    // If they are far away, we lower how often we send data
    if (DistanceSquared > 25000000.0f) // 50 meters
    {
        NetUpdateFrequency = 2.0f; // 2 updates a second
    }
    else
    {
        NetUpdateFrequency = 30.0f; // 30 updates a second
    }

    return Super::IsNetRelevantFor(RealViewer, ViewTarget, SrcLocation);
}

Ao escalar sua NetUpdateFrequency dinamicamente com base na distância, você pode reduzir a largura de banda de saída do servidor em mais de 70%, preservando o plano de dados móveis do jogador e evitando picos de latência.

Passo 3: Limites de Memória no Client-Side e Asset Streaming

Servidores têm muita RAM; celulares não. Um iPhone 13 possui 4GB de memória unificada. O sistema operacional iOS geralmente reserva cerca de 1,5GB a 2GB disso. Seu jogo deve caber inteiramente nos 2GB restantes. Se você carregar uma cidade inteira de grande escala na memória de uma só vez, o SO encerrará instantaneamente a aplicação.

Para sobreviver nesse ambiente, sua cidade deve ser dividida em chunks e carregada de forma assíncrona (streamed).

  • Hierarchical Level of Detail (HLODs): Em vez de renderizar 50 prédios individuais em um quarteirão distante (resultando em 3.000 draw calls), você deve fazer o bake de todo esse quarteirão em uma única mesh estática com um atlas de textura unificado. Isso reduz as draw calls para geometria distante de milhares para exatamente uma.
  • Sistemas de Addressable Assets: Nunca use referências diretas (hard references) em seus assets de dados primários. Se um jogador nasce no Distrito A, o cliente deve usar carregamento assíncrono (ex: Addressables do Unity ou PrimaryAssetLabels do Unreal) para baixar ou carregar apenas as texturas e meshes necessárias para o Distrito A. O Distrito B deve ser rigorosamente removido da RAM.
  • Compressão de Textura: Dependa exclusivamente de ASTC (Adaptive Scalable Texture Compression) para mobile. Ele permite footprints de blocos altamente variáveis, oferecendo controle granular sobre memória vs. qualidade visual em cada textura.

Passo 4: Arquitetura de Backend Distribuída e Server Sharding

Uma metrópole massiva não pode rodar em uma única máquina física. Ao projetar uma cidade em escala MMO, o mundo deve ser fisicamente dividido em múltiplas instâncias de servidor (shards ou nós). Quando um jogador atravessa uma ponte do Nó do Centro para o Nó da Periferia, sua conexão de cliente e estado de mundo devem ser transferidos de forma transparente entre dois processos de servidor completamente diferentes.

Construir isso por conta própria exige a configuração de clusters de Kubernetes orquestrados por sistemas como Agones, sharding de banco de dados com Redis para passar o estado do jogador entre os nós do servidor e load balancers UDP customizados para transferências de conexão contínuas. Projetar isso de forma robusta para que os jogadores não percam itens durante a transição é uma tarefa hercúlea — facilmente 4 a 6 meses de trabalho dedicado de DevOps para uma equipe de engenharia sênior.

Boas Práticas para Worldbuilding de Cidades Mobile

Para garantir que sua cidade escale perfeitamente para milhões de usuários totais enquanto mantém altas taxas de quadros em dispositivos econômicos, siga rigorosamente estas regras arquitetônicas:

Erros Comuns a Evitar

  • A Armadilha do RPC Flooding: Desenvolvedores frequentemente disparam Remote Procedure Calls (RPCs) do servidor para o cliente para efeitos visuais (como uma faísca saindo de uma batida de carro). Não faça isso. O servidor deve apenas replicar o estado do carro (ex: bIsCrashed = true). O cliente deve observar independentemente essa mudança de estado via um OnRep/property hook e disparar o VFX da faísca localmente. Isso economiza uma quantidade massiva de largura de banda de rede.
  • Vazamento de Memória em Transições de Zona: Ao descarregar um chunk de cidade no mobile, garanta que você está forçando o Garbage Collection ou descarregando manualmente os asset bundles. Se você deixar apenas alguns Megabytes de texturas órfãs na memória cada vez que um jogador se move entre zonas, ele inevitavelmente sofrerá um crash após 20 minutos de jogo.

Conclusão

Alcançar uma verdadeira otimização de escalonamento em mobile games é um ato de equilíbrio. Requer lutar por cada megabyte de RAM no cliente, regular estritamente a relevância de rede e distribuir a carga do servidor entre nós de Backend escalonáveis. Ao implementar Spatial Hashing, frequências de atualização dinâmicas e streaming assíncrono de assets, você pode construir cidades massivas e vivas que rodam suavemente mesmo em hardware mobile de anos atrás.


Fonte: Designing Large-Scale Mobile Game Cities: Production, Optimization, & Worldbuilding Expertise