Em jogos com mais de 50 participantes, os RemoteEvents padrão do Roblox sobrecarregam a largura de banda com a serialização pesada de tabelas.
Utilizando a biblioteca nativa `buffer` do Luau para quantizar coordenadas em inteiros de 16 bits e transmitir apenas deltas, o consumo de banda cai até 90%.
1. Gargalos dos RemoteEvents Convencionais
Limitações da serialização de tabelas no Roblox:
- Sobrecarga de chaves e floats de 64 bits gastando mais de 50 bytes por entidade.
- Saturação de pacotes com mais de 250 KB/s por jogador em servidores cheios.
- Atrasos artificiais (Bufferbloat) prejudicando a precisão de combate.
- Vantagem dos buffers Luau: Envio de bytes contíguos sem overhead de tabela.
2. Quantização de Coordenadas e Empacotamento de Bits
Compactando dados para o menor tamanho possível:
- Quantização em ponto fixo: Conversão de mapas de 2048 studs em números inteiros de 16 bits com precisão de 1 cm.
- Compressão angular: Rotações de 360 graus compactadas em 1 byte de 8 bits.
- Bitmask de estados: 8 booleanos unidos em um único byte.
- VarInt: Economia de bytes em identificadores numéricos pequenos.
--!strict
local NetcodeCompressor = {}
local WORLD_BOUNDS = 2048
local MAX_UINT16 = 65535
function NetcodeCompressor.WritePosition(buf: buffer, offset: number, pos: Vector3): number
local normX = math.clamp((pos.X + (WORLD_BOUNDS / 2)) / WORLD_BOUNDS, 0, 1)
local normY = math.clamp((pos.Y + 500) / 2000, 0, 1)
local normZ = math.clamp((pos.Z + (WORLD_BOUNDS / 2)) / WORLD_BOUNDS, 0, 1)
buffer.writeu16(buf, offset, math.floor(normX * MAX_UINT16))
buffer.writeu16(buf, offset + 2, math.floor(normY * MAX_UINT16))
buffer.writeu16(buf, offset + 4, math.floor(normZ * MAX_UINT16))
return offset + 6
end
function NetcodeCompressor.ReadPosition(buf: buffer, offset: number): (Vector3, number)
local qX = buffer.readu16(buf, offset)
local qY = buffer.readu16(buf, offset + 2)
local qZ = buffer.readu16(buf, offset + 4)
local x = (qX / MAX_UINT16) * WORLD_BOUNDS - (WORLD_BOUNDS / 2)
local y = (qY / MAX_UINT16) * 2000 - 500
local z = (qZ / MAX_UINT16) * WORLD_BOUNDS - (WORLD_BOUNDS / 2)
return Vector3.new(x, y, z), offset + 6
end
return NetcodeCompressor
3. Compressão Delta & Rastreamento de Alterações
Replicando somente atributos que mudaram:
- Keyframes a cada 2 segundos e deltas intermediários rápidos.
- Máscara de alterações (Dirty Mask) indicando campos modificados.
- Interpolação no cliente para movimentação suave.
- Ignorar entidades estáticas para zerar o tráfego desnecessário.
4. Gestão de Interesse Espacial
Distribuição dinâmica de dados por distância:
- Divisão do mundo em células de 128 studs.
- Frequência escalonada: 30Hz perto, caindo para 3Hz ao longe.
- Culling por visão de câmera para descartar dados traseiros.
- Uso de UnreliableRemoteEvent para evitar filas bloqueadas.
5. Checklist de Produção
Boas práticas para servidores de alto rendimento:
- Reutilizar buffers para evitar pausas do Garbage Collector.
- Incluir versão do protocolo no primeiro byte de cada pacote.
- Validar limites espaciais para impedir trapaças de teletransporte.
- Manter o tráfego de entrada abaixo de 35 KB/s por jogador.
Frequently Asked Questions
Por que usar buffers de Luau em vez de tabelas?
Os buffers transmitem sequências brutas de bytes sem processamento de strings, economizando 80% do tráfego.
Quando utilizar UnreliableRemoteEvents?
Para posições e rotações em tempo real onde a perda de um único pacote não prejudica a partida.
A quantização de 16 bits reduz a precisão da mira?
Não, ela mantém uma precisão de aproximadamente 1 cm, o que é imperceptível para os jogadores.