Bloga Geri Dön
Guide7 dk. okuma

FiveM Hata Ayıklama Teknikleri: Lua ve JS İçinde Hataları Hızlıca Bulun

FiveM script'lerindeki hataları daha hızlı ayıklayın. Oyuncular fark etmeden sorunları çözmek için sunucu logları, client print'leri, txAdmin araçları, profiler ipuçları ve kanıtlanmış iş akışları.

Agency Scripts

Agency Scripts Kurucusu ve Baş Geliştiricisi

FiveM Scriptlerinde Hata Ayıklama Sanatı

FiveM script'lerinde hata ayıklamak, standart uygulamalarda hata ayıklamaktan temelde farklıdır. Kodun iki ayrı runtime üzerinde çalıştığı, event'lerin ağ üzerinden iletildiği ve oyun durumunun her karede değiştiği ayrık bir client-server mimarisiyle çalışırsınız. Bir şeyler ters gittiğinde hata sunucu kaynaklı olup client tarafında ortaya çıkabilir ya da tam tersi yaşanabilir. Hata ayıklama konusunda sistematik bir yaklaşım geliştirmek, size sayısız saat kazandıracak ve geliştirme iş akışınızı önemli ölçüde hızlandıracaktır. Bu rehber, profesyonel FiveM geliştiricilerinin her gün kullandığı temel araçları, teknikleri ve kalıpları kapsar.

Print İfadelerini Etkili Kullanma

FiveM içerisindeki en temel hata ayıklama aracı print() function, ancak bunu etkili bir şekilde kullanmak yalnızca değişkenleri yazdırmaktan daha fazlasını gerektirir. Hata ayıklama çıktınızı script'i, tarafı (client veya server) ve yazdırmanın gerçekleştiği function'ı tanımlayan öneklerle yapılandırın. Önemli iletilerin konsolda öne çıkmasını sağlamak için renk kodları kullanın. Kodunuzdan hata ayıklama satırlarını kaldırmadan açıp kapatabileceğiniz bir debug aracı oluşturun.

-- 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

Hata Ayıklama Yardımcı Programını Kullanma

Bu araç hazır olduğunda, script'leriniz genelinde canlı ortamda tamamen sessiz çalışan debug logları ekleyebilirsiniz. Bunu ekleyerek etkinleştirin: set myresource_debug true sorun gidermeniz gerektiğinde sunucu yapılandırmanıza. Yapılandırılmış çıktı, konsol günlüklerinde grep ile arama yapmayı ve tam olarak aradığınızı bulmayı kolaylaştırır.

-- 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)

Sık Karşılaşılan FiveM Hataları ve Çözümleri

SCRIPT ERROR: attempt to index a nil value

Bu, FiveM geliştirmede en sık karşılaşılan Lua hatasıdır. Şu durumda olan bir değişkendeki bir özelliğe veya metoda erişmeye çalıştığınız anlamına gelir: nil. En yaygın nedenler; oyuncu verilerine yüklenmeden önce erişmek, kullandığınız sürümde bulunmayan bir framework fonksiyonuna başvurmak veya anahtarda yazım hatası olan bir yapılandırma değerini okumaktır. İç içe geçmiş özelliklere erişmeden önce daima nil kontrolü yapın.

-- 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'

SCRIPT ERROR: attempt to call a nil value

Bu hata, var olmayan bir işlevi çağırmaya çalıştığınızda meydana gelir. FiveM içinde bu durum genellikle henüz başlatılmamış bir kaynaktan bir export çağırdığınızda, bir güncellemede adı değiştirilmiş bir framework işlevini kullandığınızda veya manifest dosyanızda paylaşılan bir dosyayı yüklemeyi unuttuğunuzda gerçekleşir. Şunu kontrol edin: fxmanifest.lua gerekli tüm dosyaların listelendiğinden ve doğru sırada olduğundan emin olmak için.

-- 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)

Event kaydedilmedi

Hiçbir script'in bir işleyici kaydetmediği bir eventi tetiklediğinizde, FiveM konsolda hiçbir hata vermeden bunu sessizce düşürür. Bu durum hata ayıklamayı çıldırtıcı hale getirebilir çünkü her şey doğru görünür ama hiçbir şey olmaz. Event'leri tetiklemeden önce kayıtlı olduklarını doğrulamak için bir yardımcı fonksiyon kullanın ve geliştirme sırasında event tetikleyicilerinizin etrafına log kaydı ekleyin.

-- 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

DevTools ile NUI Hata Ayıklama

NUI arayüzlü scriptler için yerleşik Chromium DevTools paha biçilmezdir. F8 konsolunu açıp şunu yazarak başlatın: nui_devtools tam Chrome denetleyicisine erişmek için. Bu size DOM yapısını incelemek için Elements panelini, JavaScript hataları için Console'u, resource yüklemeleri için Network sekmesini ve kesme noktaları ayarlamak için Sources panelini sağlar. NUI iletişim sorunlarında, mesaj köprüsünün her iki tarafını da günlüğe kaydedin.

// 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);
};

Resmon ve Zamanlama ile Profil Çıkarma

Temelin ötesinde resmon izleme ile scriptlerinize hassas zamanlama ölçümleri entegre edebilirsiniz. Belirli işlemlerin ne kadar sürdüğünü ölçün ve kabul edilebilir eşikleri aştıklarında uyarı günlükleri (log) kaydedin. Bu durum özellikle veritabanı sorguları, karmaşık hesaplamalar ve çok sayıda entity işleyen döngüler için önemlidir.

-- 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)

State Bag Hata Ayıklama

State bag'ler güçlü ancak bazen kafa karıştırıcı bir özelliktir. State bag değerleri beklendiği gibi güncellenmediğinde, bunun nedeni genellikle onları yanlış entity üzerinde ayarlıyor olmanız, handler'ın doğru bag adını yakalamaması veya çoğaltmanın gecikmesidir. Belirli bir entity için tüm durumları döken bir state bag denetleyici komutu oluşturun.

-- 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)

Temel Hata Ayıklama Kontrol Listesi

  • Her iki konsolu da kontrol edin. Hatalar için her zaman hem sunucu konsolunu (txAdmin veya terminal) hem de istemci konsolunu (F8) kontrol edin. Bir taraftaki hata genellikle diğer taraftaki bozuk davranışı açıklar.
  • Resource durumunu doğrulayın. Kullanın ensure resource'unuzu yeniden başlatmak ve restart tek bir resource'u yeniden başlatmak için. Şunu kontrol edin: resmon resource'un gerçekten çalıştığından emin olmak için.
  • Temiz bir ortamda test edin. Aynı sistemlerle etkileşime giren diğer scriptleri devre dışı bırakın. Çoğu hata, tek bir scriptteki hatalardan ziyade kaynaklar arasındaki çakışmalardan kaynaklanır.
  • Hata stack trace çıktısını dikkatlice okuyun. Lua stack trace'leri size tam dosya ve satır numarasını gösterir. Hataya yol açan çağrı zincirini anlamak için bunları aşağıdan yukarıya doğru okuyun.
  • Riskli işlemler için pcall kullanın. Veritabanı sorgularını, export çağrılarını ve JSON çözümlemelerini şunun içine alın: pcall hataların scriptinizi çökertmesine izin vermek yerine onları sorunsuzca yakalamak için.
  • Yapılandırmalarınızı sürümleyin. Oyuncular hata bildirdiğinde hangi sürümü çalıştırdıklarını sorun. Birçok sorun, güncelleme sonrasında güncel olmayan yapılandırma dosyalarından kaynaklanır.

Başlamaya Hazır mısınız?

Scriptleri mağazamızdan edinin veya destek, güncellemeler ve gelecek yeniliklere göz atmak için Discord'a katılın.