Roblox
Belajar Luau 09: Client vs Server, RemoteEvent, dan UI
Memahami client dan server di Roblox, aturan emas jangan percaya data dari client, RemoteEvent dan RemoteFunction, ModuleScript, serta membuat UI koin, tombol hadiah, dan panel toko.

Sampai pelajaran 08, semua script berjalan di server. Sekarang kita belajar sisi client (komputer atau HP pemain), cara keduanya berkomunikasi, dan cara membuat UI (tombol dan teks di layar).
| Script | Jenis | Diletakkan di |
|---|---|---|
| Script 1: UI koin | LocalScript | StarterPlayer → StarterPlayerScripts |
| Script 2: hadiah (server) | Script | ServerScriptService |
| Script 2: hadiah (client) | LocalScript | StarterPlayer → StarterPlayerScripts |
KonfigToko |
ModuleScript bernama KonfigToko |
ReplicatedStorage |
| Script 3: toko (server) | Script | ServerScriptService |
| Script 3: toko (client) | LocalScript | StarterPlayer → StarterPlayerScripts |
Pelajaran ini membutuhkan script leaderstats dari pelajaran 08, karena kita memakai Koin.
Konsep 1: server dan client
┌──────────────────────────┐
│ SERVER │ ← komputer milik Roblox
│ Script (.server.luau) │ SATU untuk semua pemain
│ Data asli: koin, stage │ Yang DIPERCAYA
└────────────┬─────────────┘
RemoteEvent / │ RemoteFunction
┌──────────────────┼──────────────────┐
┌───────┴───────┐ ┌───────┴───────┐ ┌───────┴───────┐
│ CLIENT Budi │ │ CLIENT Siti │ │ CLIENT Andi │ ← HP/PC pemain
│ LocalScript │ │ LocalScript │ │ LocalScript │ Bisa DIRETAS
│ UI, kamera, │ │ │ │ │ (exploit)
│ input │ │ │ │ │
└───────────────┘ └───────────────┘ └───────────────┘
Server (Script) |
Client (LocalScript) |
|
|---|---|---|
| Berjalan di | Server Roblox | Perangkat masing-masing pemain |
| Bisa mengakses | Semua: data, DataStore, pembelian | UI, kamera, keyboard/sentuhan, Players.LocalPlayer |
| Perubahannya terlihat oleh | Semua pemain | Hanya pemain itu sendiri |
| Bisa dipercaya? | ✅ Ya | ❌ Tidak. Exploiter bisa mengubah apa saja di client. |
Konsep 2: aturan emas keamanan
Jangan pernah percaya data dari client.
Exploiter bisa memanggil RemoteEvent-mu dengan data apa pun. Karena itu:
| ❌ Salah (bisa dicurangi) | ✅ Benar |
|---|---|
| Client: "beri saya 1000 koin" | Client: "saya klik tombol hadiah". Server yang menentukan jumlah koinnya. |
| Client: "beli Pedang, harganya 0" | Client: "beli Pedang". Server yang mengecek harga dari tabel miliknya sendiri. |
| Server percaya tanpa batas waktu | Server menerapkan cooldown, karena exploiter bisa menekan tombol 1000× per detik |
| Server langsung memakai input | Server mengecek tipe data (typeof) dan memastikan nilainya masuk akal |
Konsep 3: jembatan komunikasi
| Objek | Arah | Kegunaan |
|---|---|---|
RemoteEvent |
Client → Server: :FireServer() diterima oleh .OnServerEvent |
"Saya klik tombol" |
RemoteEvent |
Server → Client: :FireClient(player) diterima oleh .OnClientEvent |
"Tampilkan notifikasi ini" |
RemoteFunction |
Client → Server → balasan ke client: :InvokeServer() dan .OnServerInvoke |
"Beli item" lalu "berhasil/gagal" |
Pada pelajaran ini, RemoteEvent dan RemoteFunction dibuat oleh script server di ReplicatedStorage, lalu client menunggunya dengan WaitForChild. Jadi kamu tidak perlu membuatnya manual di Explorer.
Konsep 4: ModuleScript
ModuleScript adalah script yang tidak berjalan sendiri. Isinya dipinjam oleh script lain dengan require(). Kegunaannya untuk menyimpan data atau function yang dipakai bersama. ModuleScript di ReplicatedStorage bisa dibaca oleh server dan client.
Di pelajaran ini, KonfigToko berisi daftar item dan harga. Client memakainya untuk menampilkan tombol, sedangkan server memakainya untuk mengecek harga. Karena hanya ada satu sumber data, harga yang tampil dan harga yang dipotong tidak mungkin berbeda.
Konsep 5: UI
UI di Roblox tersusun seperti ini:
PlayerGui (milik setiap pemain)
└── ScreenGui ← "kanvas" layar
├── TextLabel ← teks
├── TextButton ← tombol
└── Frame ← kotak / wadah
└── UICorner ← membuat sudut membulat
Ukuran dan posisi UI memakai UDim2:
UDim2.fromScale(0.5, 0.1)artinya 50% lebar layar dan 10% tinggi layar. Pakai ini supaya UI pas di HP maupun PC.UDim2.fromOffset(200, 50)artinya 200×50 piksel tetap.
AnchorPoint menentukan titik acuan posisi. Vector2.new(0.5, 0.5) berarti titik tengah elemen.
Pada pelajaran ini UI dibuat dengan kode supaya mudah disalin. Untuk game sungguhan, UI biasanya disusun manual di StarterGui lewat editor visual, lalu diatur oleh script. Keduanya sama-sama benar.
Langkah di Roblox Studio
- Pastikan script leaderstats dari pelajaran 08 sudah terpasang.
- LocalScript: di Explorer, buka StarterPlayer, arahkan kursor ke StarterPlayerScripts, klik +, lalu pilih LocalScript (bukan Script).
- ModuleScript: di ReplicatedStorage, klik + lalu pilih ModuleScript. Ganti namanya menjadi
KonfigToko, persis sama. - Pasang setiap kode di bawah sesuai tabel di atas, lalu tekan Play.
Yang akan terlihat:
- Kotak 🪙 Koin: 0 di kiri atas, yang ikut berubah saat mengambil koin
- Tombol 🎁 Hadiah di kiri bawah: +10 koin dengan cooldown 30 detik, disertai notifikasi
- Panel Toko di kanan: Sepatu Cepat dan Penyembuhan Penuh
Script 1: menampilkan jumlah koin (LocalScript)
Diletakkan di StarterPlayerScripts. Players.LocalPlayer hanya ada di LocalScript.
local Players = game:GetService("Players")
-- LocalPlayer = pemain yang menjalankan script ini (HANYA ada di LocalScript)
local player = Players.LocalPlayer
-- PlayerGui = tempat semua UI milik pemain ini
local playerGui = player:WaitForChild("PlayerGui")
-- 1. ScreenGui: "kanvas" untuk UI
local screenGui = Instance.new("ScreenGui")
screenGui.Name = "UIKoin"
screenGui.ResetOnSpawn = false -- UI tidak dibuat ulang saat pemain mati
-- 2. Kotak latar
local kotak = Instance.new("Frame")
kotak.Name = "Kotak"
kotak.AnchorPoint = Vector2.new(0, 0)
kotak.Position = UDim2.fromScale(0.02, 0.12) -- 2% dari kiri, 12% dari atas
kotak.Size = UDim2.fromScale(0.16, 0.07)
kotak.BackgroundColor3 = Color3.fromRGB(30, 30, 40)
kotak.BackgroundTransparency = 0.2
kotak.Parent = screenGui
-- Sudut membulat
local sudut = Instance.new("UICorner")
sudut.CornerRadius = UDim.new(0.25, 0)
sudut.Parent = kotak
-- 3. Teks
local label = Instance.new("TextLabel")
label.Name = "TeksKoin"
label.Size = UDim2.fromScale(1, 1) -- penuhi kotak
label.BackgroundTransparency = 1 -- tanpa latar (pakai latar kotak)
label.Font = Enum.Font.GothamBold
label.TextScaled = true -- ukuran huruf menyesuaikan kotak
label.TextColor3 = Color3.fromRGB(255, 215, 0)
label.Text = "🪙 Koin: ..."
label.Parent = kotak
-- Padding supaya teks tidak menempel ke tepi
local padding = Instance.new("UIPadding")
padding.PaddingLeft = UDim.new(0.08, 0)
padding.PaddingRight = UDim.new(0.08, 0)
padding.PaddingTop = UDim.new(0.15, 0)
padding.PaddingBottom = UDim.new(0.15, 0)
padding.Parent = label
screenGui.Parent = playerGui
-- 4. Hubungkan ke data koin
local leaderstats = player:WaitForChild("leaderstats")
local koin = leaderstats:WaitForChild("Koin")
local function perbaruiTeks()
label.Text = `🪙 Koin: {koin.Value}`
end
perbaruiTeks() -- tampilkan nilai awal
-- .Changed pada IntValue dipanggil setiap kali Value berubah.
-- Server yang mengubah nilainya, lalu Roblox otomatis mengirim
-- nilai baru ke client, dan event ini terpicu.
koin.Changed:Connect(perbaruiTeks)
Script 2: hadiah gratis dengan cooldown
Server (Script di ServerScriptService)
Parameter pertama OnServerEvent selalu player yang mengirim. Nilai ini diisi otomatis oleh Roblox dan tidak bisa dipalsukan. Client tidak mengirim jumlah koin; server yang menentukannya.
local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local JUMLAH_HADIAH = 10
local COOLDOWN = 30 -- detik
-- 1. Buat RemoteEvent di ReplicatedStorage supaya client bisa melihatnya
local remoteMintaHadiah = Instance.new("RemoteEvent")
remoteMintaHadiah.Name = "MintaHadiah" -- client -> server
remoteMintaHadiah.Parent = ReplicatedStorage
local remoteNotifikasi = Instance.new("RemoteEvent")
remoteNotifikasi.Name = "Notifikasi" -- server -> client
remoteNotifikasi.Parent = ReplicatedStorage
-- 2. Catat kapan terakhir kali setiap pemain mengambil hadiah.
-- Dictionary: [player] = waktu (detik)
local waktuTerakhir = {}
-- 3. Saat client mengirim "MintaHadiah".
-- Parameter PERTAMA selalu "player" yang mengirim, diisi otomatis oleh
-- Roblox dan TIDAK BISA dipalsukan. Client tidak mengirim jumlah koin,
-- karena SERVER yang menentukan jumlahnya.
remoteMintaHadiah.OnServerEvent:Connect(function(player)
-- os.clock() = jumlah detik sejak server menyala (untuk mengukur selisih waktu)
local sekarang = os.clock()
local terakhir = waktuTerakhir[player]
-- VALIDASI: cek cooldown DI SERVER.
-- Tombol di client juga dinonaktifkan, tapi itu hanya tampilan.
-- Exploiter bisa memanggil FireServer langsung tanpa menekan tombol.
if terakhir and sekarang - terakhir < COOLDOWN then
local sisa = math.ceil(COOLDOWN - (sekarang - terakhir))
remoteNotifikasi:FireClient(player, `Tunggu {sisa} detik lagi`)
return
end
local leaderstats = player:FindFirstChild("leaderstats")
if not leaderstats then
return
end
waktuTerakhir[player] = sekarang
leaderstats.Koin.Value += JUMLAH_HADIAH
-- Kirim pesan HANYA ke pemain ini
remoteNotifikasi:FireClient(player, `🎁 Kamu mendapat {JUMLAH_HADIAH} koin!`)
end)
-- 4. Bersihkan catatan saat pemain keluar, supaya dictionary tidak
-- terus membesar (memory leak).
Players.PlayerRemoving:Connect(function(player)
waktuTerakhir[player] = nil
end)
Client (LocalScript di StarterPlayerScripts)
local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local player = Players.LocalPlayer
local playerGui = player:WaitForChild("PlayerGui")
-- RemoteEvent dibuat oleh script server, jadi tunggu sampai ada
local remoteMintaHadiah = ReplicatedStorage:WaitForChild("MintaHadiah")
local remoteNotifikasi = ReplicatedStorage:WaitForChild("Notifikasi")
local COOLDOWN_TAMPILAN = 30 -- hanya untuk tampilan, yang menentukan tetap server
-- ------------------------------------------------------------
-- Membuat UI
-- ------------------------------------------------------------
local screenGui = Instance.new("ScreenGui")
screenGui.Name = "UIHadiah"
screenGui.ResetOnSpawn = false
local tombol = Instance.new("TextButton")
tombol.Name = "TombolHadiah"
tombol.AnchorPoint = Vector2.new(0, 1) -- acuan: pojok kiri bawah
tombol.Position = UDim2.fromScale(0.02, 0.95)
tombol.Size = UDim2.fromScale(0.14, 0.08)
tombol.BackgroundColor3 = Color3.fromRGB(46, 160, 67)
tombol.Font = Enum.Font.GothamBold
tombol.TextScaled = true
tombol.TextColor3 = Color3.new(1, 1, 1)
tombol.Text = "🎁 Hadiah"
tombol.Parent = screenGui
Instance.new("UICorner").Parent = tombol
-- Label notifikasi di tengah atas, awalnya tersembunyi
local notif = Instance.new("TextLabel")
notif.Name = "Notifikasi"
notif.AnchorPoint = Vector2.new(0.5, 0)
notif.Position = UDim2.fromScale(0.5, 0.05)
notif.Size = UDim2.fromScale(0.4, 0.06)
notif.BackgroundColor3 = Color3.fromRGB(20, 20, 20)
notif.BackgroundTransparency = 0.3
notif.Font = Enum.Font.GothamMedium
notif.TextScaled = true
notif.TextColor3 = Color3.new(1, 1, 1)
notif.Visible = false
notif.Parent = screenGui
Instance.new("UICorner").Parent = notif
screenGui.Parent = playerGui
-- ------------------------------------------------------------
-- Tombol diklik -> kirim permintaan ke server
-- ------------------------------------------------------------
local bisaDiklik = true
-- .Activated berfungsi untuk klik mouse DAN sentuhan di HP
-- (lebih baik daripada MouseButton1Click)
tombol.Activated:Connect(function()
if not bisaDiklik then
return
end
bisaDiklik = false
-- Hanya mengirim "saya klik". TIDAK mengirim jumlah koin.
remoteMintaHadiah:FireServer()
tombol.BackgroundColor3 = Color3.fromRGB(90, 90, 90)
tombol.Text = "⏳ Tunggu"
task.wait(COOLDOWN_TAMPILAN)
tombol.BackgroundColor3 = Color3.fromRGB(46, 160, 67)
tombol.Text = "🎁 Hadiah"
bisaDiklik = true
end)
-- ------------------------------------------------------------
-- Server mengirim notifikasi -> tampilkan selama 3 detik
-- ------------------------------------------------------------
local nomorNotif = 0
remoteNotifikasi.OnClientEvent:Connect(function(pesan)
nomorNotif += 1
local nomorIni = nomorNotif
notif.Text = pesan
notif.Visible = true
task.wait(3)
-- Hanya sembunyikan kalau belum ada notifikasi yang lebih baru.
-- Tanpa ini, notifikasi baru bisa ikut tersembunyi oleh timer lama.
if nomorIni == nomorNotif then
notif.Visible = false
end
end)
KonfigToko (ModuleScript di ReplicatedStorage)
Efek item (apa yang terjadi setelah dibeli) tidak ditulis di sini, tetapi di script server, karena modul di ReplicatedStorage bisa dibaca client (dan exploiter).
local KonfigToko = {
-- Urutan array = urutan tombol di layar
{
id = "SepatuCepat",
nama = "👟 Sepatu Cepat",
harga = 20,
deskripsi = "Lari lebih cepat sampai mati",
},
{
id = "PenyembuhanPenuh",
nama = "❤️ Sembuh Penuh",
harga = 5,
deskripsi = "Darah langsung penuh",
},
}
-- Function bantuan: cari item berdasarkan id
function KonfigToko.cari(id)
for _, item in ipairs(KonfigToko) do
if item.id == id then
return item
end
end
return nil
end
-- ModuleScript WAJIB me-return sesuatu.
-- Nilai inilah yang didapat oleh require(...)
return KonfigToko
Script 3: toko dengan RemoteFunction
Server (Script di ServerScriptService)
Perhatikan empat lapis validasi sebelum koin dipotong.
local ReplicatedStorage = game:GetService("ReplicatedStorage")
-- require = "pinjam" isi ModuleScript
local KonfigToko = require(ReplicatedStorage:WaitForChild("KonfigToko"))
-- RemoteFunction: client bertanya, server MENJAWAB (return)
local remoteBeli = Instance.new("RemoteFunction")
remoteBeli.Name = "BeliItem"
remoteBeli.Parent = ReplicatedStorage
-- EFEK setiap item. Disimpan di server saja, jadi client tidak bisa
-- melihat atau mengubahnya. Kuncinya sama dengan "id" di KonfigToko.
local EFEK_ITEM = {
SepatuCepat = function(player, humanoid)
humanoid.WalkSpeed = 28 -- normalnya 16
end,
PenyembuhanPenuh = function(player, humanoid)
humanoid.Health = humanoid.MaxHealth
end,
}
-- Function ini dipanggil saat client memanggil remoteBeli:InvokeServer(idItem).
-- Nilai yang di-return dikirim kembali ke client.
remoteBeli.OnServerInvoke = function(player, idItem)
-- VALIDASI 1: tipe data. Exploiter bisa mengirim apa saja:
-- angka, table, nil. Pastikan memang teks.
if typeof(idItem) ~= "string" then
return false, "Permintaan tidak valid"
end
-- VALIDASI 2: item benar-benar ada di toko.
-- Harga diambil dari KonfigToko (milik server), BUKAN dari client.
local item = KonfigToko.cari(idItem)
local efek = EFEK_ITEM[idItem]
if not item or not efek then
return false, "Item tidak ditemukan"
end
-- VALIDASI 3: pemain hidup dan punya character
local character = player.Character
local humanoid = character and character:FindFirstChildOfClass("Humanoid")
if not humanoid or humanoid.Health <= 0 then
return false, "Kamu harus hidup untuk membeli"
end
-- VALIDASI 4: koin cukup. Nilai Koin di SERVER yang dipakai.
local leaderstats = player:FindFirstChild("leaderstats")
if not leaderstats then
return false, "Data belum siap"
end
local koin = leaderstats.Koin
if koin.Value < item.harga then
return false, `Koin kurang {item.harga - koin.Value}`
end
-- Semua aman: potong koin, LALU beri efek
koin.Value -= item.harga
efek(player, humanoid)
print(`{player.Name} membeli {item.id} seharga {item.harga}`)
return true, `Berhasil membeli {item.nama}!`
end
Client (LocalScript di StarterPlayerScripts)
Tombol dibuat otomatis untuk setiap item di KonfigToko. Yang dikirim ke server hanya ID item, bukan harga.
local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local player = Players.LocalPlayer
local playerGui = player:WaitForChild("PlayerGui")
-- Modul yang SAMA dengan yang dipakai server
local KonfigToko = require(ReplicatedStorage:WaitForChild("KonfigToko"))
local remoteBeli = ReplicatedStorage:WaitForChild("BeliItem")
-- ------------------------------------------------------------
-- Panel toko
-- ------------------------------------------------------------
local screenGui = Instance.new("ScreenGui")
screenGui.Name = "UIToko"
screenGui.ResetOnSpawn = false
local panel = Instance.new("Frame")
panel.Name = "Panel"
panel.AnchorPoint = Vector2.new(1, 0.5) -- acuan: tengah kanan
panel.Position = UDim2.fromScale(0.98, 0.5)
panel.Size = UDim2.fromScale(0.2, 0.4)
panel.BackgroundColor3 = Color3.fromRGB(30, 30, 40)
panel.BackgroundTransparency = 0.15
panel.Parent = screenGui
Instance.new("UICorner").Parent = panel
-- UIListLayout menyusun isi panel otomatis dari atas ke bawah
local susunan = Instance.new("UIListLayout")
susunan.Padding = UDim.new(0.03, 0)
susunan.SortOrder = Enum.SortOrder.LayoutOrder
susunan.HorizontalAlignment = Enum.HorizontalAlignment.Center
susunan.Parent = panel
local padding = Instance.new("UIPadding")
padding.PaddingTop = UDim.new(0.04, 0)
padding.Parent = panel
local judul = Instance.new("TextLabel")
judul.LayoutOrder = 0
judul.Size = UDim2.fromScale(0.9, 0.15)
judul.BackgroundTransparency = 1
judul.Font = Enum.Font.GothamBold
judul.TextScaled = true
judul.TextColor3 = Color3.new(1, 1, 1)
judul.Text = "🛒 Toko"
judul.Parent = panel
local pesan = Instance.new("TextLabel")
pesan.LayoutOrder = 999 -- selalu paling bawah
pesan.Size = UDim2.fromScale(0.9, 0.12)
pesan.BackgroundTransparency = 1
pesan.Font = Enum.Font.Gotham
pesan.TextScaled = true
pesan.TextColor3 = Color3.fromRGB(200, 200, 200)
pesan.Text = ""
pesan.Parent = panel
-- ------------------------------------------------------------
-- Satu tombol untuk setiap item di KonfigToko (loop, pelajaran 3)
-- ------------------------------------------------------------
local sedangMembeli = false
local function buatTombol(urutan, item)
local tombol = Instance.new("TextButton")
tombol.Name = item.id
tombol.LayoutOrder = urutan
tombol.Size = UDim2.fromScale(0.9, 0.2)
tombol.BackgroundColor3 = Color3.fromRGB(60, 110, 200)
tombol.Font = Enum.Font.GothamBold
tombol.TextScaled = true
tombol.TextColor3 = Color3.new(1, 1, 1)
tombol.Text = `{item.nama} 🪙{item.harga}`
tombol.Parent = panel
Instance.new("UICorner").Parent = tombol
tombol.Activated:Connect(function()
if sedangMembeli then
return
end
sedangMembeli = true
pesan.Text = "Memproses..."
-- InvokeServer MENUNGGU sampai server menjawab (return).
-- Yang dikirim hanya ID item, BUKAN harga.
local berhasil, teks = remoteBeli:InvokeServer(item.id)
pesan.TextColor3 = if berhasil
then Color3.fromRGB(120, 255, 120)
else Color3.fromRGB(255, 120, 120)
pesan.Text = teks
sedangMembeli = false
end)
end
for urutan, item in ipairs(KonfigToko) do
buatTombol(urutan, item)
end
screenGui.Parent = playerGui
Coba "jadi exploiter"
Supaya paham kenapa validasi di server itu penting, lakukan ini saat Play berjalan:
- Saat Play, Explorer menampilkan sudut pandang Client (cek tombol Current: Client di tab Test).
- Di Explorer, buka Players → kamu → leaderstats → Koin, lalu ubah Value menjadi 9999 di Properties. Label koin di layar ikut berubah menjadi 9999.
- Coba beli Sepatu Cepat. Pembelian gagal karena koin kurang, sebab di server koinmu masih jumlah aslinya. Perubahan di client tidak sampai ke server.
- Klik Current: Client untuk pindah ke Server, lalu lihat nilai Koin di sana. Angkanya tetap angka asli.
Inilah alasan semua data penting harus diurus oleh server.
Error yang sering muncul
| Masalah | Penyebab |
|---|---|
| LocalScript tidak jalan sama sekali | LocalScript diletakkan di Workspace atau ServerScriptService. LocalScript hanya berjalan di StarterPlayerScripts, StarterGui, StarterPack, atau di dalam Character. |
LocalPlayer bernilai nil |
Memakai Players.LocalPlayer di Script biasa. Properti ini hanya ada di LocalScript. |
KonfigToko is not a valid member of ReplicatedStorage |
ModuleScript belum dibuat, atau namanya salah |
Requested module experienced an error while loading |
Ada error di dalam ModuleScript. Periksa Output. |
| UI hilang setelah mati | ResetOnSpawn masih true |
Latihan
- Tampilan Stage: tambahkan label 🚩 Stage: 0 di bawah label koin.
- Item baru: tambahkan item
LompatTinggi(harga 15) diKonfigTokoyang membuathumanoid.JumpPower = 75. Ingat, efeknya ditulis di server. Catatan: JumpPower hanya berpengaruh kalauhumanoid.UseJumpPower = true. - Hitung mundur di tombol: setelah hadiah diambil, ubah teks tombol menjadi
⏳ 29,⏳ 28, dan seterusnya sampai bisa diklik lagi. - Tantangan keamanan: di script toko server, apa yang terjadi kalau pengecekan
typeof(idItem) ~= "string"dihapus, lalu exploiter mengirim sebuah table sebagaiidItem? Jelaskan dengan kata-katamu sendiri.
Kunci jawaban
Coba kerjakan sendiri dulu sebelum membaca bagian ini.
Latihan 1: label Stage
Tambahkan di paling bawah script 1 (UI koin):
-- Salin kotak koin, lalu sesuaikan
local kotakStage = kotak:Clone()
kotakStage.Name = "KotakStage"
kotakStage.Position = UDim2.fromScale(0.02, 0.20) -- sedikit di bawah kotak koin
kotakStage.Parent = screenGui
local labelStage = kotakStage.TeksKoin
labelStage.Name = "TeksStage"
labelStage.TextColor3 = Color3.fromRGB(120, 200, 255)
local stage = leaderstats:WaitForChild("Stage")
local function perbaruiStage()
labelStage.Text = `🚩 Stage: {stage.Value}`
end
perbaruiStage()
stage.Changed:Connect(perbaruiStage)
Trik :Clone(): daripada menulis ulang 20 baris pembuatan UI, salin kotak yang sudah ada beserta isinya (sudut membulat, padding), lalu ubah yang berbeda saja.
Latihan 2: item LompatTinggi
Di KonfigToko, tambahkan item baru di dalam table, sejajar dengan item lain:
{
id = "LompatTinggi",
nama = "🦘 Lompat Tinggi",
harga = 15,
deskripsi = "Lompat jauh lebih tinggi sampai mati",
},
Di script toko server, tambahkan efeknya di EFEK_ITEM:
local EFEK_ITEM = {
-- ... item lain ...
LompatTinggi = function(player, humanoid)
humanoid.UseJumpPower = true -- pakai JumpPower, bukan JumpHeight
humanoid.JumpPower = 75 -- normalnya 50
end,
}
Yang tidak perlu diubah: script client. Tombol baru muncul otomatis karena client membuat tombol dengan loop dari KonfigToko. Inilah keuntungan memakai ModuleScript bersama.
Kenapa efeknya di server? Kalau efek ditulis di modul yang bisa dibaca client, exploiter bisa melihat semua efek. Lebih penting lagi, server yang memutuskan kapan efek diberikan, yaitu hanya setelah koin dipotong.
Latihan 3: hitung mundur di tombol
Di script hadiah client, ganti bagian task.wait(COOLDOWN_TAMPILAN):
tombol.Activated:Connect(function()
if not bisaDiklik then
return
end
bisaDiklik = false
remoteMintaHadiah:FireServer()
tombol.BackgroundColor3 = Color3.fromRGB(90, 90, 90)
for sisa = COOLDOWN_TAMPILAN, 1, -1 do
tombol.Text = `⏳ {sisa}`
task.wait(1)
end
tombol.BackgroundColor3 = Color3.fromRGB(46, 160, 67)
tombol.Text = "🎁 Hadiah"
bisaDiklik = true
end)
Ini memakai for dengan langkah -1 dari pelajaran 03.
Latihan 4: tantangan keamanan
Pertanyaan: apa yang terjadi kalau pengecekan typeof(idItem) ~= "string" dihapus, lalu exploiter mengirim table?
Jawaban jujurnya: di script ini, tidak terjadi kerusakan. Coba telusuri:
KonfigToko.cari(idTable)membandingkanitem.id == idTable. Teks tidak pernah sama dengan table, jadi hasilnyanil.EFEK_ITEM[idTable]juganil.- Script mengembalikan "Item tidak ditemukan".
Lalu kenapa pengecekan tipe tetap penting?
- Kode berubah. Bulan depan kamu mungkin menambah
print("Membeli " .. idItem)atauidItem:lower(). Dengan table, keduanya error. Error diOnServerInvokememang tidak mematikan server, tetapi memenuhi Output dan bisa membuat logika berhenti di tengah jalan, misalnya koin sudah dipotong tetapi efek belum diberikan. - Jenis serangan lain. Teks sepanjang jutaan huruf bisa membuat server lambat kalau diproses dengan loop. Itu sebabnya pelajaran 16 juga membatasi panjang teks.
- Pertahanan berlapis. Lebih aman menolak semua yang tidak terduga di pintu masuk daripada berharap setiap baris di bawahnya kebetulan aman.
Kesimpulan: validasi tipe di awal setiap Remote adalah kebiasaan, bukan reaksi terhadap bug tertentu.
Diskusi (0)
Kami menyimpan nama (opsional), komentar, dan hash teknis (bukan alamat IP asli) untuk mencegah spam. Cookie fungsional dipakai agar Anda dapat mengedit atau menghapus komentar sendiri dari browser ini. Untuk permintaan penghapusan, gunakan halaman kontak.
Belum ada komentar. Mulai diskusinya.