Belajar Lua FiveM dari Nol: Membuat Script Pertama
Tutorial Lua FiveM dari nol: dasar Lua, fxmanifest.lua, client dan server script, command, event, thread, dan pola keamanan dasar lewat resource pertamamu.
Hampir semua script di server roleplay FiveM ditulis dengan Lua. Kabar baiknya, Lua termasuk bahasa yang ringkas dan mudah dipelajari. Di tutorial ini kamu akan membuat resource pertamamu dari nol: memahami struktur folder, menulis fxmanifest.lua, membuat command di sisi client, berkomunikasi dengan server lewat event, dan menghindari kesalahan keamanan yang paling sering dilakukan pemula.
Kamu tidak perlu framework apa pun untuk mengikuti tutorial ini. Server dengan recipe CFX Default sudah cukup. Belum punya server? Ikuti dulu Cara Membuat Server FiveM Sendiri dari Nol.
Dasar Lua yang perlu kamu tahu
FiveM memakai Lua 5.4 (diaktifkan dengan lua54 'yes' di manifest). Berikut sintaks dasar yang akan sering kamu pakai:
-- Komentar satu baris
-- Variabel: selalu pakai local kecuali benar-benar perlu global
local nama = "Budi"
local umur = 21
local aktif = true
-- Table: satu-satunya struktur data di Lua (bisa jadi array atau object)
local pekerjaan = { "polisi", "ems", "mekanik" } -- array, index mulai dari 1
local pemain = { nama = "Budi", uang = 500 } -- key-value
print(pekerjaan[1]) -- polisi
print(pemain.uang) -- 500
-- Fungsi
local function sapa(siapa)
return "Halo, " .. siapa -- .. untuk menggabungkan string
end
-- Percabangan
if umur >= 18 then
print(sapa(nama))
elseif umur > 12 then
print("Remaja")
else
print("Anak-anak")
end
-- Perulangan
for i, job in ipairs(pekerjaan) do
print(i, job)
end
for key, value in pairs(pemain) do
print(key, value)
endDua hal yang sering membingungkan developer dari bahasa lain: index array di Lua dimulai dari 1, dan "tidak sama dengan" ditulis ~=, bukan !=.
Client, server, dan shared
Konsep paling penting di FiveM adalah pemisahan kode:
- Client script berjalan di komputer setiap pemain. Dipakai untuk hal yang terlihat dan dirasakan pemain: UI, input, efek, spawn kendaraan lokal, dan pemanggilan native GTA.
- Server script berjalan di server. Dipakai untuk logika yang harus dipercaya: uang, inventory, database, dan izin.
- Shared script dimuat di keduanya. Biasanya berisi konfigurasi.
Aturan emas: client tidak bisa dipercaya. Cheater bisa menjalankan kode Lua sendiri di client dan memicu event apa pun. Semua keputusan penting harus divalidasi di server.
Langkah 1: Buat folder resource
Di folder resources/ servermu, buat struktur berikut:
resources/
└── [lokal]/
└── noctis_belajar/
├── fxmanifest.lua
├── config.lua
├── client.lua
└── server.luaNama folder (noctis_belajar) adalah nama resource. Folder [lokal] hanya kategori untuk merapikan resource buatanmu sendiri.
Langkah 2: Tulis fxmanifest.lua
Manifest memberi tahu FXServer file apa saja yang harus dimuat dan di mana:
fx_version 'cerulean'
game 'gta5'
lua54 'yes'
author 'Nama Kamu'
description 'Resource belajar pertama'
version '1.0.0'
shared_script 'config.lua'
client_script 'client.lua'
server_script 'server.lua'Penjelasan lengkap setiap field ada di dokumentasi resmi Resource manifest.
Langkah 3: Konfigurasi di config.lua
Config = {}
Config.KendaraanDiizinkan = {
adder = true,
sultan = true,
faggio = true,
}
Config.JedaKendaraan = 30 -- detikConfig sengaja dibuat global supaya bisa dibaca dari client maupun server.
Langkah 4: Command pertama di client
Kita buat dua command: /koordinat untuk menampilkan posisi pemain, dan /mobil untuk memunculkan kendaraan.
-- client.lua
local function notif(teks)
BeginTextCommandThefeedPost('STRING')
AddTextComponentSubstringPlayerName(teks)
EndTextCommandThefeedPostTicker(false, true)
end
RegisterCommand('koordinat', function()
local ped = PlayerPedId()
local coords = GetEntityCoords(ped)
local heading = GetEntityHeading(ped)
local teks = ('x: %.2f, y: %.2f, z: %.2f, h: %.2f'):format(coords.x, coords.y, coords.z, heading)
print(teks)
notif(teks)
end, false)
RegisterCommand('mobil', function(_, args)
local model = (args[1] or 'adder'):lower()
if not Config.KendaraanDiizinkan[model] then
return notif('Kendaraan tidak diizinkan.')
end
local hash = joaat(model)
if not IsModelInCdimage(hash) or not IsModelAVehicle(hash) then
return notif('Model tidak ditemukan.')
end
RequestModel(hash)
local batas = GetGameTimer() + 5000
while not HasModelLoaded(hash) do
if GetGameTimer() > batas then
return notif('Gagal memuat model.')
end
Wait(10)
end
local ped = PlayerPedId()
local coords = GetEntityCoords(ped)
local veh = CreateVehicle(hash, coords.x, coords.y, coords.z, GetEntityHeading(ped), true, false)
SetPedIntoVehicle(ped, veh, -1)
SetModelAsNoLongerNeeded(hash)
notif(('Kendaraan %s dimunculkan.'):format(model))
end, false)Beberapa hal yang perlu diperhatikan:
- Fungsi seperti
PlayerPedId,GetEntityCoords, danCreateVehicledisebut native. Daftar lengkapnya ada di FiveM Native Reference. - Model harus dimuat dulu dengan
RequestModelsebelum dipakai, lalu dilepas denganSetModelAsNoLongerNeededsupaya tidak memakan memori. - Loop menunggu model diberi batas waktu supaya tidak berputar selamanya.
Catatan: spawn kendaraan dari client seperti ini cocok untuk belajar. Di server produksi, pembuatan entity penting sebaiknya dikendalikan dari server agar tidak bisa disalahgunakan.
Langkah 5: Komunikasi client ke server dengan event
Sekarang kita tambahkan jeda (cooldown) yang dicek di server, sehingga pemain tidak bisa memunculkan kendaraan terus-menerus. Client meminta izin ke server, server memutuskan, lalu membalas.
-- server.lua
local terakhirSpawn = {}
RegisterNetEvent('noctis_belajar:mintaMobil', function(model)
local src = source -- simpan source segera, nilainya bisa berubah setelah Wait
if type(model) ~= 'string' or not Config.KendaraanDiizinkan[model] then
return
end
local sekarang = os.time()
if terakhirSpawn[src] and sekarang - terakhirSpawn[src] < Config.JedaKendaraan then
TriggerClientEvent('noctis_belajar:hasil', src, false, 'Tunggu sebentar sebelum memunculkan kendaraan lagi.')
return
end
terakhirSpawn[src] = sekarang
print(('[noctis_belajar] %s memunculkan %s'):format(GetPlayerName(src), model))
TriggerClientEvent('noctis_belajar:hasil', src, true, model)
end)
AddEventHandler('playerDropped', function()
terakhirSpawn[source] = nil
end)Lalu ubah client supaya command /mobil hanya mengirim permintaan, dan spawn dilakukan saat server menyetujui:
-- client.lua (ganti RegisterCommand 'mobil' sebelumnya)
local function spawnKendaraan(model)
local hash = joaat(model)
RequestModel(hash)
local batas = GetGameTimer() + 5000
while not HasModelLoaded(hash) do
if GetGameTimer() > batas then return notif('Gagal memuat model.') end
Wait(10)
end
local ped = PlayerPedId()
local coords = GetEntityCoords(ped)
local veh = CreateVehicle(hash, coords.x, coords.y, coords.z, GetEntityHeading(ped), true, false)
SetPedIntoVehicle(ped, veh, -1)
SetModelAsNoLongerNeeded(hash)
end
RegisterCommand('mobil', function(_, args)
TriggerServerEvent('noctis_belajar:mintaMobil', (args[1] or 'adder'):lower())
end, false)
RegisterNetEvent('noctis_belajar:hasil', function(ok, data)
if not ok then return notif(data) end
spawnKendaraan(data)
notif(('Kendaraan %s dimunculkan.'):format(data))
end)Perhatikan pola keamanannya: server tidak percaya nilai yang dikirim client. Tipe data dicek, model dicocokkan dengan whitelist, dan cooldown disimpan di server. Pola yang sama berlaku untuk hal yang jauh lebih sensitif seperti uang dan item.
Langkah 6: Thread dan Wait
Kadang kamu butuh kode yang berjalan berulang, misalnya mengecek apakah pemain berada di dekat suatu titik. FiveM menyediakan CreateThread:
local titik = vector3(215.76, -810.12, 30.73)
CreateThread(function()
while true do
local jarak = #(GetEntityCoords(PlayerPedId()) - titik)
if jarak < 20.0 then
DrawMarker(1, titik.x, titik.y, titik.z - 1.0, 0, 0, 0, 0, 0, 0, 1.5, 1.5, 0.5, 120, 80, 255, 150, false, false, 2, false, nil, nil, false)
Wait(0) -- dekat: gambar marker setiap frame
else
Wait(1000) -- jauh: cukup cek sekali per detik
end
end
end)Inilah sumber lag paling umum di server FiveM: loop dengan Wait(0) yang berjalan terus walaupun tidak ada yang perlu dikerjakan. Selalu sesuaikan jeda dengan kebutuhan. Cara mengukur dampaknya dibahas di Cara Menggunakan Resmon dan Profiler FiveM.
Langkah 7: Jalankan dan tes resource
Tambahkan baris berikut ke server.cfg:
ensure noctis_belajarAtau tanpa restart server, ketik di console server atau live console txAdmin:
refresh
ensure noctis_belajarSetelah mengubah kode, cukup jalankan restart noctis_belajar. Masuk ke server, lalu coba /koordinat dan /mobil sultan. Output print dari client muncul di console F8, sedangkan dari server muncul di console server.
Kesalahan umum pemula
- Lupa
local. Variabel global bisa bentrok dengan resource lain dan sulit di-debug. - Memakai
sourcesetelahWait. Simpan dulu ke variabel lokal di baris pertama handler. - Mempercayai data dari client. Jangan pernah membuat event seperti
beriUang(jumlah)yang langsung dieksekusi tanpa cek di server. - Loop
Wait(0)di mana-mana. Gunakan jeda dinamis seperti contoh di atas. - Salah tempat file. Native client tidak tersedia di server, dan sebaliknya. Cek kolom "side" di native reference.
Langkah berikutnya
- Pelajari framework roleplay: QBCore vs ESX vs Qbox.
- Buat pekerjaan sendiri: Cara Membuat Sistem Job Custom di QBCore.
- Sinkronkan data antar pemain dengan State Bags FiveM.
- Bangun antarmuka modern dengan NUI FiveM, React, dan TypeScript.
Referensi resmi yang layak kamu bookmark: Scripting Manual FiveM dan Lua 5.4 Reference Manual.
Butuh server untuk development?
Development lebih nyaman kalau kamu punya server staging terpisah dari server utama, sehingga eksperimen tidak mengganggu pemain. Hosting FiveM Noctis menyediakan VDS Mini (2 vCPU, 4 GB RAM) yang cocok untuk development dan staging, dengan core AMD Ryzen khusus, NVMe, root penuh, dan anti-DDoS. Untuk eksperimen yang lebih ringan, ada juga Cloud VPS di Noctis Cloud. Punya pertanyaan soal scripting? Gabung ke Discord Noctis.