>
Guia 2026-05-03

Técnicas de Debug e Erros Comuns em FiveM

TDYSKY

TDYSKY

Fundador & Lead Developer na Agency Scripts

A arte de depurar scripts FiveM

A depuração de scripts FiveM é fundamentalmente diferente da depuração de aplicativos padrão. Você está lidando com uma arquitetura cliente-servidor dividida, onde o código é executado em dois tempos de execução separados, os eventos viajam pela rede e o estado do jogo muda a cada quadro. Quando algo dá errado, o erro pode ter origem no servidor, mas se manifestar no cliente ou vice-versa. Desenvolver uma abordagem sistemática para depuração economizará inúmeras horas e tornará seu fluxo de trabalho de desenvolvimento muito mais rápido. Este guia cobre as ferramentas, técnicas e padrões essenciais que os desenvolvedores profissionais do FiveM usam diariamente.

Usando declarações impressas de maneira eficaz

A ferramenta de depuração mais fundamental no FiveM é a função print(), mas usá-la de forma eficaz requer mais do que apenas descartar variáveis. Estruture sua saída de depuração com prefixos que identificam o script, o lado (cliente ou servidor) e a função onde ocorre a impressão. Use códigos de cores para destacar mensagens importantes no console. Crie um utilitário de depuração que você possa ativar e desativar sem remover linhas de depuração do seu código.

-- shared/debug.lua
local DEBUG_ENABLED = GetConvar('myresource_debug', 'false') == 'true'
local RESOURCE_NAME = GetCurrentResourceName()

function DebugLog(module, message, ...)
    if not DEBUG_ENABLED then return end

    local side = IsDuplicityVersion() and 'SERVER' else 'CLIENT'
    local formatted = type(message) == 'string' and message:format(...) or tostring(message)
    local timestamp = os.date('%H:%M:%S')

    print(('[^3%s^0][^5%s^0][^2%s^0] %s'):format(
        timestamp, RESOURCE_NAME, side .. ':' .. module, formatted
    ))
end

function DebugTable(module, tbl, depth)
    if not DEBUG_ENABLED then return end
    depth = depth or 0
    local indent = string.rep('  ', depth)

    if type(tbl) ~= 'table' then
        DebugLog(module, '%s%s', indent, tostring(tbl))
        return
    end

    for k, v in pairs(tbl) do
        if type(v) == 'table' then
            DebugLog(module, '%s%s = {', indent, tostring(k))
            DebugTable(module, v, depth + 1)
            DebugLog(module, '%s}', indent)
        else
            DebugLog(module, '%s%s = %s (%s)', indent, tostring(k), tostring(v), type(v))
        end
    end
end

Usando o utilitário de depuração

Com esse utilitário instalado, você pode adicionar log de depuração em todos os seus scripts, o que é completamente silencioso na produção. Habilite-o adicionando set myresource_debug true à configuração do seu servidor quando precisar solucionar problemas. A saída estruturada facilita a navegação pelos logs do console e a localização exata do que você está procurando.

-- server/jobs.lua
RegisterNetEvent('myresource:startJob', function(jobName)
    local src = source
    DebugLog('jobs', 'Player %d attempting to start job: %s', src, jobName)

    local playerData = GetPlayerData(src)
    DebugTable('jobs', playerData)

    if not playerData then
        DebugLog('jobs', 'ERROR: No player data found for source %d', src)
        return
    end

    if playerData.job == jobName then
        DebugLog('jobs', 'Player %d already has job %s, skipping', src, jobName)
        return
    end

    DebugLog('jobs', 'Job %s assigned to player %d successfully', jobName, src)
end)

Erros e soluções comuns do FiveM

ERRO DE SCRIPT: tentativa de indexar um valor nulo

Este é o erro Lua mais frequente no desenvolvimento FiveM. Significa que você está tentando acessar uma propriedade ou método em uma variável que é nil. As causas mais comuns são acessar os dados do jogador antes de carregá-los, fazer referência a uma função do framework que não existe na versão que você está usando ou ler um valor de configuração com um erro de digitação na chave. Sempre verifique se há nulo antes de acessar propriedades aninhadas.

-- BAD: Will crash if GetPlayerData returns nil
local name = GetPlayerData(src).charinfo.firstname

-- GOOD: Defensive nil checks
local playerData = GetPlayerData(src)
if not playerData then
    print('[ERROR] Player data is nil for source: ' .. src)
    return
end

local charinfo = playerData.charinfo
if not charinfo then
    print('[ERROR] charinfo missing for source: ' .. src)
    return
end

local name = charinfo.firstname or 'Unknown'

ERRO DE SCRIPT: tentativa de chamar um valor nulo

Este erro ocorre quando você tenta chamar uma função que não existe. No FiveM, isso geralmente acontece quando você chama uma exportação de um recurso que ainda não foi iniciado, usa uma função de framework que foi renomeada em uma atualização ou se esquece de carregar um arquivo compartilhado em seu manifesto. Verifique seu fxmanifest.lua para ter certeza de que todos os arquivos necessários estão listados e na ordem correta.

-- Safely calling an export that might not be available
local function SafeExport(resource, exportName, ...)
    local success, result = pcall(function(...)
        return exports[resource][exportName](...)
    end, ...)

    if not success then
        print(('[^1ERROR^0] Failed to call export %s:%s - %s'):format(
            resource, exportName, tostring(result)
        ))
        return nil
    end

    return result
end

-- Usage
local inventory = SafeExport('ox_inventory', 'GetInventory', src)

O evento não foi registrado

Quando você aciona um evento para o qual nenhum script registrou um manipulador, FiveM o descarta silenciosamente, sem erros no console. Isso pode ser enlouquecedor para depurar porque tudo parece correto, mas nada acontece. Use uma função auxiliar para verificar se os eventos estão registrados antes de acioná-los e adicione log em torno dos gatilhos de eventos durante o desenvolvimento.

-- server/debug_events.lua
-- Wrap TriggerClientEvent to log when events fire
local originalTrigger = TriggerClientEvent

if GetConvar('myresource_debug', 'false') == 'true' then
    TriggerClientEvent = function(eventName, target, ...)
        print(('[^3EVENT^0] TriggerClientEvent: %s -> target: %s'):format(
            eventName, tostring(target)
        ))
        return originalTrigger(eventName, target, ...)
    end
end

Depuração NUI com DevTools

Para scripts com interfaces NUI, os Chromium DevTools integrados são inestimáveis. Abra-os com o console F8 digitando nui_devtools para acessar o inspetor completo do Chrome. Isso fornece o painel Elementos para inspecionar a estrutura do DOM, o Console para erros de JavaScript, a guia Rede para carregamento de recursos e o painel Fontes para definir pontos de interrupção. Para problemas de comunicação NUI, registre ambos os lados da ponte de mensagens.

// nui/js/debug.js
// Log all incoming NUI messages
window.addEventListener('message', (event) => {
    if (event.data && event.data.action) {
        console.log(
            '%c[NUI Received]%c ' + event.data.action,
            'background: #2dd4bf; color: #000; padding: 2px 6px; border-radius: 3px;',
            'color: #94a3b8;',
            event.data
        );
    }
});

// Wrap fetch to log NUI callbacks
const originalFetch = window.fetch;
window.fetch = function(url, options) {
    const body = options?.body ? JSON.parse(options.body) : null;
    console.log(
        '%c[NUI Callback]%c ' + url,
        'background: #8b5cf6; color: #fff; padding: 2px 6px; border-radius: 3px;',
        'color: #94a3b8;',
        body
    );
    return originalFetch.apply(this, arguments);
};

Criação de perfil com Resmon e Timing

Além do monitoramento básico resmon, você pode criar instrumentação de tempo precisa em seus scripts. Meça quanto tempo levam operações específicas e registre avisos quando excederem limites aceitáveis. Isto é especialmente importante para consultas de banco de dados, cálculos complexos e loops que processam muitas entidades.

-- shared/profiler.lua
local Profiler = {}

function Profiler.Start(label)
    return {
        label = label,
        startTime = GetGameTimer()
    }
end

function Profiler.Stop(timer, warnThresholdMs)
    local elapsed = GetGameTimer() - timer.startTime
    warnThresholdMs = warnThresholdMs or 5

    if elapsed >= warnThresholdMs then
        print(('[^1PERF WARNING^0] %s took %dms (threshold: %dms)'):format(
            timer.label, elapsed, warnThresholdMs
        ))
    elseif GetConvar('myresource_debug', 'false') == 'true' then
        print(('[^2PERF^0] %s completed in %dms'):format(timer.label, elapsed))
    end

    return elapsed
end

-- Usage in a server event
RegisterNetEvent('myresource:heavyOperation', function(data)
    local timer = Profiler.Start('heavyOperation')

    -- ... expensive processing ...
    local result = ProcessLargeDataSet(data)

    Profiler.Stop(timer, 10) -- warn if over 10ms
end)

Depuração de saco de estado

As bolsas estaduais são um recurso poderoso, mas às vezes confuso. Quando os valores do pacote de estado não são atualizados conforme o esperado, geralmente é porque você os está configurando na entidade errada, o manipulador não está capturando o nome do pacote correto ou a replicação está atrasada. Crie um comando de inspetor de bagagem de estado que despeje todo o estado de uma determinada entidade.

-- server/debug_statebags.lua
RegisterCommand('debugstate', function(source, args)
    local targetId = tonumber(args[1])
    if not targetId then
        print('Usage: debugstate [playerId]')
        return
    end

    local playerPed = GetPlayerPed(targetId)
    if playerPed == 0 then
        print('Player not found: ' .. targetId)
        return
    end

    local entityState = Player(targetId).state
    print(('[^3STATE BAGS^0] Player %d:'):format(targetId))

    -- Print known state keys (state bags don't have an iterator)
    local keysToCheck = {'job', 'gang', 'duty', 'dead', 'phone', 'inventory'}
    for _, key in ipairs(keysToCheck) do
        local val = entityState[key]
        if val ~= nil then
            print(('  %s = %s (%s)'):format(key, tostring(val), type(val)))
        end
    end
end, true)

Lista de verificação essencial para depuração

  • Verifique ambos os consoles.Sempre verifique se há erros no console do servidor (txAdmin ou terminal) e no console do cliente (F8). Um erro de um lado geralmente explica o comportamento incorreto do outro.
  • Verifique o estado do recurso.Use ensure para reiniciar seu recurso e restart para reiniciar um único recurso. Verifique resmon para ter certeza de que o recurso está realmente em execução.
  • Teste em um ambiente limpo.Desative outros scripts que interagem com os mesmos sistemas. Muitos bugs vêm de conflitos entre recursos, e não de bugs em um único script.
  • Leia o rastreamento da pilha de erros com atenção.Os rastreamentos de pilha Lua mostram o arquivo exato e o número da linha. Leia-os de baixo para cima para entender a cadeia de chamadas que levou ao erro.
  • Use pcall para operações arriscadas.Envolva consultas de banco de dados, chamadas de exportação e decodificação JSON em pcall para capturar erros normalmente, em vez de deixá-los travar seu script.
  • Versão de suas configurações.Quando os jogadores relatarem bugs, pergunte qual versão eles estão executando. Muitos problemas decorrem de arquivos de configuração desatualizados após uma atualização.

Partilhar este artigo

Pronto para melhorar o teu servidor?

Explora os nossos scripts FiveM premium na loja Agency Scripts ou junta-te à nossa comunidade no Discord para suporte e atualizações.