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
ensureresource'unuzu yeniden başlatmak verestarttek bir resource'u yeniden başlatmak için. Şunu kontrol edin:resmonresource'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:
pcallhataları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.