Roblox
Belajar Luau 10: DataStore untuk Menyimpan Data Pemain
Menyimpan progres pemain Roblox dengan DataStore: GetAsync dan SetAsync, pcall dan percobaan ulang, tiga aturan wajib agar data tidak tertimpa, session data, BindToClose, dan autosave.

Sampai sekarang, koin dan stage hilang setiap kali pemain keluar. Di pelajaran ini kita menyimpannya ke DataStore, yaitu database milik Roblox, supaya progres pemain tetap ada saat mereka kembali.
Ini salah satu bagian terpenting dan paling berisiko dalam game Roblox. Kalau salah, pemain bisa kehilangan progres (dan berhenti main), atau koin hasil pembelian Robux mereka hilang.
| File | Jenis | Diletakkan di |
|---|---|---|
1-data-pemain.server.luau |
Script | ServerScriptService |
⚠️ Script ini MENGGANTIKAN
1-leaderstats.server.luaudari Pelajaran 8. Hapus script leaderstats lama dari ServerScriptService. Kalau keduanya terpasang bersamaan, akan ada dua folder leaderstats dan hasilnya kacau. Script koin dan checkpoint dari Pelajaran 8 tetap dipakai.
Konsep 1: Cara kerja DataStore
DataStore mirip dictionary raksasa di internet:
DataStore "DataPemain_v1"
├── "Pemain_12345678" → { Koin = 350, Stage = 7, Mati = 21, Kunjungan = 4 }
├── "Pemain_87654321" → { Koin = 90, Stage = 2, Mati = 3, Kunjungan = 1 }
└── ...
- Kunci memakai UserId pemain, bukan nama, karena pemain bisa ganti nama sedangkan UserId tidak pernah berubah.
- Nilai bisa berupa angka, teks, boolean, atau table (pelajaran 5). Yang tidak bisa disimpan adalah Instance, Vector3, Color3, dan function.
| Function | Kegunaan |
|---|---|
dataStore:GetAsync(kunci) |
Membaca data. Hasilnya nil kalau pemain baru. |
dataStore:SetAsync(kunci, nilai) |
Menulis atau menimpa data |
dataStore:UpdateAsync(kunci, function) |
Membaca lalu menulis dengan aman, untuk data yang bisa diubah dari beberapa tempat |
Konsep 2: DataStore BISA GAGAL, jadi selalu pakai pcall
DataStore berkomunikasi lewat internet. Server Roblox bisa sedang sibuk, atau kamu terlalu sering menyimpan (ada batas jumlah permintaan per menit). Kalau gagal tanpa pcall, script akan berhenti dengan error.
-- pcall = "protected call": jalankan function, tangkap error-nya
local berhasil, hasil = pcall(function()
return dataStore:GetAsync("Pemain_123")
end)
if berhasil then
-- hasil = data (atau nil kalau pemain baru)
else
-- hasil = pesan error
end
Konsep 3: Tiga aturan wajib
- Gagal memuat, JANGAN menyimpan. Kalau data gagal dimuat lalu pemain bermain dengan data kosong (0 koin), menyimpan saat keluar akan menimpa data asli mereka. Script ini mengeluarkan (kick) pemain dengan pesan sopan, supaya mereka masuk lagi dan mencoba memuat ulang.
- Simpan di beberapa momen: saat pemain keluar (
PlayerRemoving), saat server mati (BindToClose), dan secara berkala (autosave) untuk jaga-jaga kalau server crash. - Pakai template data. Kalau nanti kamu menambah data baru, misalnya
Gems, pemain lama otomatis mendapat nilai awalnya dari template.
Konsep 4: Session data
Script ini menyimpan data setiap pemain di sebuah table selama mereka bermain (dataSesi[player]). Nilai yang perlu tampil di papan skor dicerminkan ke leaderstats, sedangkan data tersembunyi seperti Kunjungan cukup ada di table. Saat menyimpan, nilai terbaru dari leaderstats disalin kembali ke table, lalu seluruh table disimpan.
Langkah di Roblox Studio
DataStore hanya bisa dipakai di game yang sudah dipublikasikan, dan Studio perlu diberi izin:
- Publikasikan game: File → Publish to Roblox, lalu isi nama game. Game boleh tetap private.
- Buka Home → Game Settings → Security, lalu aktifkan Enable Studio Access to API Services. Klik Save.
- Hapus script leaderstats dari Pelajaran 8 di ServerScriptService.
- Tambahkan Script baru di ServerScriptService, lalu tempel isi
1-data-pemain.server.luau.
Uji coba
- Tekan Play, kumpulkan beberapa koin, dan injak satu atau dua checkpoint.
- Tekan Stop. Di Output akan muncul
💾 Data ... tersimpan. - Tekan Play lagi. Koin dan stage masih ada, dan kamu langsung muncul di checkpoint terakhir.
Data di Studio dan di game sungguhan sama, karena keduanya memakai DataStore yang sama. Untuk mereset datamu saat menguji, ubah nama DataStore di script, misalnya
DataPemain_v1menjadiDataPemain_v2. Jangan lakukan ini setelah game dirilis, karena semua pemain akan kehilangan data mereka.
Kode
1-data-pemain.server.luau
- Jenis: Script
- Letakkan di: ServerScriptService
- ⚠️ MENGGANTIKAN 1-leaderstats.server.luau dari Pelajaran 8.
local Players = game:GetService("Players")
local DataStoreService = game:GetService("DataStoreService")
-- ------------------------------------------------------------
-- KONFIGURASI
-- ------------------------------------------------------------
-- Nama DataStore. JANGAN diganti setelah game dirilis,
-- karena semua pemain akan kehilangan data.
local dataStore = DataStoreService:GetDataStore("DataPemain_v1")
-- Template data pemain baru. Tambahkan data baru di sini.
local DATA_AWAL = {
Koin = 0,
Stage = 0,
Mati = 0,
Kunjungan = 0, -- data tersembunyi (tidak tampil di papan skor)
}
-- Data yang ditampilkan di papan skor, sesuai urutan kolom
local KOLOM_LEADERSTATS = {"Koin", "Stage", "Mati"}
local INTERVAL_AUTOSAVE = 120 -- detik
local MAKS_PERCOBAAN = 3
-- ------------------------------------------------------------
-- DATA SESI: data setiap pemain selama mereka bermain
-- dataSesi[player] = { Koin = ..., Stage = ..., ... }
-- ------------------------------------------------------------
local dataSesi = {}
local function kunciData(player)
return "Pemain_" .. player.UserId
end
-- Menjalankan operasi DataStore, mencoba lagi kalau gagal.
-- Return: berhasil (boolean), hasil
local function cobaBeberapaKali(operasi)
for percobaan = 1, MAKS_PERCOBAAN do
local berhasil, hasil = pcall(operasi)
if berhasil then
return true, hasil
end
warn(`DataStore gagal (percobaan {percobaan}/{MAKS_PERCOBAAN}): {hasil}`)
if percobaan < MAKS_PERCOBAAN then
task.wait(percobaan * 2) -- tunggu makin lama: 2, 4 detik
end
end
return false, nil
end
-- ------------------------------------------------------------
-- MEMUAT DATA
-- ------------------------------------------------------------
local function muatData(player)
local berhasil, tersimpan = cobaBeberapaKali(function()
return dataStore:GetAsync(kunciData(player))
end)
if not berhasil then
return nil -- GAGAL: berbeda dengan "pemain baru"!
end
-- Mulai dari salinan template, lalu timpa dengan data tersimpan.
-- Dengan cara ini, data baru di template otomatis ada untuk pemain lama.
local data = table.clone(DATA_AWAL)
if type(tersimpan) == "table" then
for kunci, nilai in pairs(tersimpan) do
data[kunci] = nilai
end
end
return data
end
-- ------------------------------------------------------------
-- MENYIMPAN DATA
-- ------------------------------------------------------------
local function simpanData(player)
local data = dataSesi[player]
if not data then
return -- data tidak pernah dimuat -> JANGAN simpan (aturan 1)
end
-- Salin nilai terbaru dari leaderstats ke table data
local leaderstats = player:FindFirstChild("leaderstats")
if leaderstats then
for _, nama in ipairs(KOLOM_LEADERSTATS) do
local nilai = leaderstats:FindFirstChild(nama)
if nilai then
data[nama] = nilai.Value
end
end
end
local berhasil = cobaBeberapaKali(function()
dataStore:SetAsync(kunciData(player), data)
end)
if berhasil then
print(`💾 Data {player.Name} tersimpan: Koin {data.Koin}, Stage {data.Stage}`)
else
warn(`❌ Data {player.Name} GAGAL disimpan!`)
end
end
-- ------------------------------------------------------------
-- MEMBUAT LEADERSTATS dari data
-- ------------------------------------------------------------
local function buatLeaderstats(player, data)
local leaderstats = Instance.new("Folder")
leaderstats.Name = "leaderstats"
for _, nama in ipairs(KOLOM_LEADERSTATS) do
local nilai = Instance.new("IntValue")
nilai.Name = nama
nilai.Value = data[nama]
nilai.Parent = leaderstats
end
leaderstats.Parent = player
return leaderstats
end
-- Menghitung kematian (sama seperti Pelajaran 8)
local function pantauKematian(player, character)
local humanoid = character:WaitForChild("Humanoid")
humanoid.Died:Connect(function()
local leaderstats = player:FindFirstChild("leaderstats")
if leaderstats then
leaderstats.Mati.Value += 1
end
end)
end
-- ------------------------------------------------------------
-- PEMAIN MASUK
-- ------------------------------------------------------------
local function saatPemainMasuk(player)
local data = muatData(player)
-- Pemain bisa saja keluar SELAMA data dimuat (bisa beberapa detik)
if not player:IsDescendantOf(Players) then
return
end
if not data then
-- ATURAN 1: gagal memuat -> keluarkan pemain, jangan biarkan
-- bermain dengan data kosong yang nanti menimpa data asli.
player:Kick("Gagal memuat datamu. Datamu aman, silakan masuk lagi.")
return
end
data.Kunjungan += 1
dataSesi[player] = data
buatLeaderstats(player, data)
print(`📂 Data {player.Name} dimuat (kunjungan ke-{data.Kunjungan})`)
-- Pantau kematian untuk character sekarang DAN yang akan datang.
-- Character pertama bisa saja sudah muncul sebelum data selesai dimuat.
player.CharacterAdded:Connect(function(character)
pantauKematian(player, character)
end)
if player.Character then
pantauKematian(player, player.Character)
end
-- Kalau pemain sudah punya stage, respawn supaya langsung muncul
-- di checkpoint terakhir (diurus script checkpoint Pelajaran 8).
if data.Stage > 0 then
player:LoadCharacter()
end
end
-- ------------------------------------------------------------
-- PEMAIN KELUAR
-- ------------------------------------------------------------
local function saatPemainKeluar(player)
simpanData(player)
dataSesi[player] = nil -- bersihkan memori
end
Players.PlayerAdded:Connect(saatPemainMasuk)
Players.PlayerRemoving:Connect(saatPemainKeluar)
for _, player in ipairs(Players:GetPlayers()) do
task.spawn(saatPemainMasuk, player)
end
-- ------------------------------------------------------------
-- SERVER MATI (game ditutup / update / Stop di Studio)
-- Roblox memberi waktu maksimal 30 detik untuk menyelesaikan ini.
-- ------------------------------------------------------------
game:BindToClose(function()
local sisa = 0
for _, player in ipairs(Players:GetPlayers()) do
sisa += 1
-- task.spawn = jalankan BERSAMAAN, tidak antre satu per satu
task.spawn(function()
simpanData(player)
sisa -= 1
end)
end
-- Tunggu sampai semua selesai (maksimal sekitar 25 detik)
local mulai = os.clock()
while sisa > 0 and os.clock() - mulai < 25 do
task.wait(0.5)
end
end)
-- ------------------------------------------------------------
-- AUTOSAVE: jaga-jaga kalau server crash
-- ------------------------------------------------------------
task.spawn(function()
while true do
task.wait(INTERVAL_AUTOSAVE)
for _, player in ipairs(Players:GetPlayers()) do
task.spawn(simpanData, player)
end
end
end)
Error yang sering muncul
| Pesan | Penyebab |
|---|---|
You must publish this place to the web to access DataStore |
Game belum dipublikasikan (langkah 1) |
StudioAccessToApisNotAllowed |
Langkah 2 belum dilakukan |
DataStore request was added to queue |
Terlalu sering menyimpan. Jangan menyimpan setiap kali koin berubah. |
| Ada 2 kolom Koin di papan skor | Script leaderstats Pelajaran 8 belum dihapus |
| Data tidak tersimpan saat Stop di Studio | Terkadang Studio mati terlalu cepat. BindToClose di script ini membantu, tapi untuk hasil pasti, uji di game sungguhan. |
Untuk game yang serius
Script di pelajaran ini sudah cukup untuk belajar dan game kecil. Game besar menghadapi masalah tambahan seperti session locking: pemain pindah server sangat cepat sehingga dua server menulis data yang sama. Developer profesional biasanya memakai modul komunitas ProfileStore yang sudah menangani semua ini. Pelajari setelah kamu paham isi script ini, karena modul seperti itu jauh lebih mudah dipakai kalau kamu mengerti apa yang dikerjakannya.
Latihan
- Data baru: tambahkan
Gems = 0keDATA_AWALdan tampilkan di leaderstats. Perhatikan bahwa pemain lama otomatis mendapat Gems = 0. - Waktu bermain: simpan total menit bermain (
MenitBermain). Petunjuk: catatos.time()saat masuk, lalu hitung selisihnya saat menyimpan. - Bonus kunjungan: setiap kunjungan ke-5, 10, 15, dan seterusnya, pemain mendapat 50 koin. Pakai
%(pelajaran 1). - Pertanyaan: kenapa kita tidak menyimpan data setiap kali koin bertambah? Jelaskan dengan kata-katamu sendiri.
Kunci jawaban
Coba kerjakan sendiri dulu sebelum membaca bagian ini.
Semua perubahan ada di 1-data-pemain.server.luau.
Latihan 1: Gems
Cukup ubah dua baris konfigurasi:
local DATA_AWAL = {
Koin = 0,
Stage = 0,
Mati = 0,
Kunjungan = 0,
Gems = 0, -- baru
}
local KOLOM_LEADERSTATS = {"Koin", "Stage", "Mati", "Gems"}
Kenapa pemain lama otomatis punya Gems = 0? Di muatData, data selalu dimulai dari salinan template (table.clone(DATA_AWAL)), lalu ditimpa dengan data tersimpan. Data lama tidak punya kunci Gems, jadi nilai dari template (0) tetap dipakai. Tidak ada data pemain yang rusak.
Latihan 2: Menit bermain
local DATA_AWAL = {
Koin = 0,
Stage = 0,
Mati = 0,
Kunjungan = 0,
MenitBermain = 0,
}
-- Kapan terakhir kali waktu bermain DIHITUNG untuk setiap pemain
local waktuTerakhirDihitung = {}
Di saatPemainMasuk, setelah dataSesi[player] = data:
waktuTerakhirDihitung[player] = os.time()
Di simpanData, sebelum cobaBeberapaKali(...):
local sekarang = os.time()
local mulai = waktuTerakhirDihitung[player]
if mulai then
data.MenitBermain += (sekarang - mulai) / 60
waktuTerakhirDihitung[player] = sekarang -- PENTING: reset titik awal
end
Di saatPemainKeluar, setelah dataSesi[player] = nil:
waktuTerakhirDihitung[player] = nil
Jebakan utamanya: simpanData dipanggil berkali-kali (autosave setiap 2 menit, lalu saat keluar). Kalau kamu selalu menghitung dari waktu masuk, setiap autosave menambahkan seluruh waktu sejak masuk lagi, sehingga pemain yang bermain 10 menit tercatat 30 menit atau lebih. Karena itu titik awalnya di-reset setiap kali dihitung.
os.time() menghitung dalam detik, jadi dibagi 60. Hasilnya bisa pecahan (misalnya 12.5 menit). Untuk ditampilkan, pakai math.floor(data.MenitBermain).
Latihan 3: Bonus kunjungan
Di saatPemainMasuk, setelah data.Kunjungan += 1 dan sebelum buatLeaderstats(...):
if data.Kunjungan % 5 == 0 then
data.Koin += 50
print(`🎁 {player.Name} kunjungan ke-{data.Kunjungan}, bonus 50 koin!`)
end
Kenapa sebelum buatLeaderstats? Leaderstats dibuat dari isi data. Kalau bonus ditambahkan ke data setelah leaderstats dibuat, papan skor tidak berubah, lalu saat disimpan nilai dari leaderstats menimpa data.Koin, sehingga bonusnya hilang.
Latihan 4: Kenapa tidak menyimpan setiap kali koin bertambah?
- Ada batas jumlah permintaan. Roblox membatasi berapa kali sebuah server boleh menulis ke DataStore per menit (jumlahnya bergantung pada banyaknya pemain di server). Koin bisa bertambah puluhan kali per menit per pemain. Dengan 20 pemain, batas itu cepat terlampaui, permintaan masuk antrean, lalu gagal, termasuk penyimpanan yang benar-benar penting.
- Lambat. Setiap penyimpanan adalah permintaan lewat internet yang butuh waktu.
- Tidak perlu. Data sudah ada di memori server (
dataSesi). Yang penting adalah data tersimpan sebelum hilang: saat pemain keluar, saat server mati, dan secara berkala (autosave) untuk jaga-jaga kalau server crash.
Pengecualian: transaksi Robux (Pelajaran 11) layak dicatat segera, karena pemain membayar uang sungguhan.
Discussion (0)
We store your optional name, your comment, and technical hashes (not your raw IP address) to prevent spam. A functional cookie lets you edit or delete your own comments from this browser. To request deletion, use the contact page.
No comments yet. Start the discussion.