Roblox
Belajar Luau 16: Keamanan Anti-Exploit dan Performa
Menghadapi exploiter dan lag di game Roblox: checklist validasi RemoteEvent, bahaya NaN, rate limiter, deteksi kecepatan tidak wajar, CollectionService dengan tag, penyebab lag umum, dan alat ukur performa.

Begitu game-mu mulai ramai, dua masalah pasti muncul: exploiter yang mencoba curang, dan lag saat pemain dan objek bertambah banyak. Pelajaran ini membahas cara menghadapi keduanya.
| File | Jenis | Diletakkan di |
|---|---|---|
modul/RateLimiter.luau |
ModuleScript RateLimiter |
ServerScriptService (di dalam folder Modul) |
modul/Validasi.luau |
ModuleScript Validasi |
ServerScriptService (di dalam folder Modul) |
1-uji-modul.luau |
Dijalankan di terminal | — |
2-rintangan-tag.server.luau |
Script | ServerScriptService |
3-anti-speed.server.luau |
Script | ServerScriptService |
Uji modulnya dulu di terminal, dari folder belajar-luau:
luau 16-keamanan-dan-performa/1-uji-modul.luau
BAGIAN A: KEAMANAN
Konsep 1: Apa yang bisa dilakukan exploiter?
Exploiter memakai program khusus untuk mengendalikan client mereka sepenuhnya:
| Bisa ✅ | Tidak bisa ❌ |
|---|---|
| Menjalankan kode apa saja di client mereka | Menjalankan kode di server |
| Memanggil RemoteEvent/RemoteFunction dengan argumen apa pun, sesering apa pun | Mengubah nilai di server secara langsung (koin, data, DataStore) |
| Mengubah WalkSpeed, JumpPower, dan posisi karakter sendiri | Membaca script di ServerScriptService atau ServerStorage |
| Membaca semua LocalScript dan ModuleScript di ReplicatedStorage | Memalsukan parameter player di OnServerEvent |
| Menghapus atau mengubah UI dan Part di layar mereka sendiri | Mengubah apa yang dilihat pemain lain, kecuali lewat Remote yang tidak aman |
Kesimpulannya: pertahananmu ada di server, terutama di setiap OnServerEvent dan OnServerInvoke.
Konsep 2: Checklist setiap RemoteEvent
Setiap kali menulis OnServerEvent atau OnServerInvoke, periksa hal-hal ini:
- Tipe argumen sudah dicek (
typeof), termasuk NaN, tak hingga, angka negatif, dan teks yang sangat panjang. Gunakan modulValidasi. - Nilai penting (harga, jumlah hadiah, damage) diambil dari server, bukan dari argumen client.
- Ada batas frekuensi (
RateLimiter) supaya Remote tidak bisa dipanggil 1000x per detik. - Syarat dicek di server: koin cukup? pemain masih hidup? pemain cukup dekat dengan toko?
- Data per pemain dibersihkan saat pemain keluar (
PlayerRemoving).
Konsep 3: Kenapa NaN berbahaya
-- Server menerima "jumlah" dari client
if jumlah < 1 or jumlah > 99 then return end -- terlihat aman...
koin.Value -= harga * jumlah -- ...tapi jumlah = 0/0 (NaN) LOLOS!
NaN ("Not a Number") selalu menghasilkan false di setiap perbandingan, sehingga lolos dari pengecekan di atas. 1-uji-modul.luau membuktikannya. Validasi.angkaBulat menangani kasus ini.
Konsep 4: Gerakan karakter
Karena posisi karakter dikendalikan client, exploiter bisa berlari sangat cepat atau berteleportasi. 3-anti-speed.server.luau mendeteksi gerakan yang tidak mungkin, lalu mengembalikan pemain ke posisi sebelumnya.
Prinsip penting: lebih baik mengembalikan posisi daripada meng-kick pemain. Pemain jujur yang lag juga bisa terdeteksi. Satu pemain jujur yang ter-kick tanpa alasan akan memberi rating buruk dan tidak kembali lagi.
Kalau game-mu punya fitur teleport sendiri, beri tahu anti-cheat lewat attribute:
player:SetAttribute("BolehTeleport", true)
character:PivotTo(tujuan)
task.wait(1)
player:SetAttribute("BolehTeleport", false)
BAGIAN B: PERFORMA
Konsep 5: CollectionService, satu script untuk semua
Di Pelajaran 7, setiap lava punya script sendiri. 50 lava berarti 50 script dengan kode yang sama, dan kalau ada bug, kamu harus memperbaikinya 50 kali.
Dengan tag, kamu cukup menandai Part dengan nama seperti Lava, dan satu script (2-rintangan-tag.server.luau) mengurus semuanya:
- Lebih ringan
- Perbaikan cukup di satu tempat
- Part baru tinggal diberi tag, tanpa perlu menyalin script
Cara memberi tag di Studio: pilih Part → di jendela Properties, gulir ke bagian Tags → klik + → ketik Lava. Bisa juga lewat View → Tag Editor untuk memberi tag ke banyak Part sekaligus.
Konsep 6: Daftar penyebab lag yang umum
| Penyebab | Solusi |
|---|---|
| Ribuan Part kecil | Gabungkan dengan Union atau MeshPart, dan hapus yang tidak terlihat |
| Part tidak di-Anchor | Part yang tidak perlu bergerak harus Anchored, karena perhitungan fisika itu berat |
Banyak while true (satu per objek) |
Satu loop untuk semua objek, seperti script koin di Pelajaran 8 |
wait() versi lama |
Selalu pakai task.wait() |
| Efek visual dikerjakan di server | Pindahkan ke client (Pelajaran 12) |
| Koneksi event tidak pernah diputus | local koneksi = x:Connect(...) lalu koneksi:Disconnect() saat tidak dipakai. Objek yang di-:Destroy() otomatis memutus koneksinya. |
| Table per pemain tidak dibersihkan | Hapus di PlayerRemoving (memory leak) |
| Peta sangat besar | Aktifkan StreamingEnabled di Workspace. Client hanya memuat area di sekitar pemain, dan ini sangat membantu pemain HP. |
⚠️ Kalau StreamingEnabled aktif, Part yang jauh dari pemain belum ada di client. LocalScript harus memakai
WaitForChild, jangan berasumsi semua Part sudah ada.
Konsep 7: Alat ukur performa
| Alat | Cara buka | Untuk apa |
|---|---|---|
| Developer Console | F9 saat bermain (juga di game sungguhan) |
Melihat error, penggunaan memori, dan jumlah instance |
| Script Performance | View → Script Performance | Script mana yang paling berat |
| MicroProfiler | Ctrl+F6 |
Analisis detail per frame (tingkat lanjut) |
| Stats | View → Stats | FPS, ping, dan memori |
Uji juga di HP sungguhan, bukan hanya di emulator Studio. HP murah jauh lebih lambat dibanding PC.
Langkah di Roblox Studio
- Di ServerScriptService, buat Folder bernama
Modul. Di dalamnya, buat 2 ModuleScript bernamaRateLimiterdanValidasi, lalu tempel isi file dari foldermodul/. - Ganti script-script di dalam Part dari Pelajaran 7:
- Hapus Script di dalam Part
Lava,PlatformRapuh, danPadPenyembuh - Beri tag
Lava,PlatformRapuh, danPadPenyembuhsesuai fungsinya - Pasang
2-rintangan-tag.server.luaudi ServerScriptService
- Hapus Script di dalam Part
- Pasang
3-anti-speed.server.luau. Untuk mengujinya, saat Play buka Command Bar dalam mode Server dan ketik:Pemindahan dari server seperti ini juga terdeteksi, karena script tidak bisa membedakan siapa yang memindahkan. Kamu akan ditarik kembali dan peringatan muncul di Output. Inilah alasan fitur teleport resmi perlu memakai attributeworkspace.NamaKamu:PivotTo(workspace.NamaKamu:GetPivot() + Vector3.new(100, 0, 0))BolehTeleport.
Di proyek
game/(Pelajaran 14), modulRateLimiterdan script rintangan sudah terpasang, dan toko sudah memakai RateLimiter. Anti-speed sengaja tidak dipasang di sana. Pasang kalau game-mu sudah mulai ramai dan ada laporan kecurangan.
Kode
modul/RateLimiter.luau
local RateLimiter = {}
RateLimiter.__index = RateLimiter
-- maks : jumlah maksimal yang diizinkan
-- detik : panjang jendela waktu
-- jam : (opsional) function yang mengembalikan waktu sekarang.
-- Bawaannya os.clock. Bisa diganti jam palsu untuk pengujian.
function RateLimiter.new(maks, detik, jam)
local self = setmetatable({}, RateLimiter)
self.maks = maks
self.detik = detik
self.jam = jam or os.clock
-- catatan[kunci] = { mulai = waktu, jumlah = n }
-- kunci biasanya objek Player, tapi boleh apa saja
self.catatan = {}
return self
end
-- Return true kalau boleh, false kalau sudah melewati batas
function RateLimiter:izinkan(kunci)
local sekarang = self.jam()
local catatan = self.catatan[kunci]
-- Belum pernah, atau jendela waktu lama sudah lewat -> mulai baru
if not catatan or sekarang - catatan.mulai >= self.detik then
self.catatan[kunci] = {mulai = sekarang, jumlah = 1}
return true
end
if catatan.jumlah < self.maks then
catatan.jumlah += 1
return true
end
return false
end
-- Panggil saat pemain keluar, supaya catatan tidak menumpuk (memory leak)
function RateLimiter:hapus(kunci)
self.catatan[kunci] = nil
end
return RateLimiter
modul/Validasi.luau
local Validasi = {}
-- Teks yang tidak kosong dan tidak terlalu panjang.
-- Teks super panjang (jutaan huruf) bisa membuat server lambat.
function Validasi.teks(nilai, panjangMaks)
return typeof(nilai) == "string"
and #nilai > 0
and #nilai <= panjangMaks
end
-- Angka bulat dalam rentang tertentu.
-- Exploiter suka mengirim angka "aneh" yang lolos pengecekan biasa:
-- NaN (0/0) -> NaN tidak sama dengan dirinya sendiri!
-- math.huge (tak hingga)
-- 1.5 (bukan bulat) -> misalnya untuk jumlah item
-- -999 (negatif) -> "beli -999 item" bisa malah MENAMBAH koin
function Validasi.angkaBulat(nilai, minimum, maksimum)
if typeof(nilai) ~= "number" then
return false
end
if nilai ~= nilai then -- cek NaN
return false
end
if nilai % 1 ~= 0 then -- cek bulat (math.huge % 1 juga gagal di sini)
return false
end
return nilai >= minimum and nilai <= maksimum
end
-- Nilai harus salah satu dari daftar yang diizinkan
function Validasi.pilihan(nilai, daftarIzin)
if typeof(nilai) ~= "string" then
return false
end
return table.find(daftarIzin, nilai) ~= nil
end
return Validasi
1-uji-modul.luau
local RateLimiter = require("./modul/RateLimiter")
local Validasi = require("./modul/Validasi")
local jumlahLulus = 0
local jumlahGagal = 0
-- Function bantuan pengujian
local function uji(nama, hasil, harapan)
if hasil == harapan then
jumlahLulus += 1
print(` ✓ {nama}`)
else
jumlahGagal += 1
print(` ✗ {nama} (dapat {hasil}, harusnya {harapan})`)
end
end
-- ------------------------------------------------------------
-- RateLimiter dengan JAM PALSU
-- Kita tidak bisa menunggu sungguhan di terminal (tidak ada task.wait),
-- jadi waktu "dipalsukan" lewat variabel yang bisa kita majukan sendiri.
-- ------------------------------------------------------------
print("RateLimiter (3x per 2 detik):")
local waktuPalsu = 0
local batas = RateLimiter.new(3, 2, function()
return waktuPalsu
end)
uji("permintaan ke-1 boleh", batas:izinkan("Budi"), true)
uji("permintaan ke-2 boleh", batas:izinkan("Budi"), true)
uji("permintaan ke-3 boleh", batas:izinkan("Budi"), true)
uji("permintaan ke-4 DITOLAK", batas:izinkan("Budi"), false)
uji("pemain lain tidak terpengaruh", batas:izinkan("Siti"), true)
waktuPalsu = 1.9
uji("1,9 detik kemudian masih ditolak", batas:izinkan("Budi"), false)
waktuPalsu = 2.0
uji("2 detik kemudian boleh lagi", batas:izinkan("Budi"), true)
batas:hapus("Budi")
uji("setelah dihapus, catatan bersih", batas.catatan["Budi"], nil)
-- ------------------------------------------------------------
-- Validasi: coba semua "serangan" yang biasa dikirim exploiter
-- ------------------------------------------------------------
print("Validasi.angkaBulat (1 sampai 99):")
uji("5 diterima", Validasi.angkaBulat(5, 1, 99), true)
uji("0 ditolak (di bawah minimum)", Validasi.angkaBulat(0, 1, 99), false)
uji("-999 ditolak", Validasi.angkaBulat(-999, 1, 99), false)
uji("1.5 ditolak (bukan bulat)", Validasi.angkaBulat(1.5, 1, 99), false)
uji("NaN ditolak", Validasi.angkaBulat(0 / 0, 1, 99), false)
uji("tak hingga ditolak", Validasi.angkaBulat(math.huge, 1, 99), false)
uji("teks '5' ditolak", Validasi.angkaBulat("5", 1, 99), false)
uji("table ditolak", Validasi.angkaBulat({}, 1, 99), false)
print("Validasi.teks (maks 20 huruf):")
uji("'Pedang' diterima", Validasi.teks("Pedang", 20), true)
uji("teks kosong ditolak", Validasi.teks("", 20), false)
uji("teks sangat panjang ditolak", Validasi.teks(string.rep("A", 100000), 20), false)
uji("nil ditolak", Validasi.teks(nil, 20), false)
print("Validasi.pilihan:")
local warnaBoleh = {"Merah", "Biru", "Hijau"}
uji("'Biru' diterima", Validasi.pilihan("Biru", warnaBoleh), true)
uji("'Emas' ditolak", Validasi.pilihan("Emas", warnaBoleh), false)
-- ------------------------------------------------------------
-- Kenapa NaN berbahaya? Lihat sendiri:
-- ------------------------------------------------------------
print("Bahaya NaN:")
local nan = 0 / 0
print(" nan < 1 ?", nan < 1, "| nan > 99 ?", nan > 99, "| nan == nan ?", nan == nan)
print(" -> NaN lolos dari pengecekan 'if jumlah < 1 or jumlah > 99 then tolak'!")
print(`\nHasil: {jumlahLulus} lulus, {jumlahGagal} gagal`)
2-rintangan-tag.server.luau
- Jenis: Script
- Letakkan di: ServerScriptService
local CollectionService = game:GetService("CollectionService")
-- ------------------------------------------------------------
-- Function bantuan
-- ------------------------------------------------------------
-- Jalankan "aturFungsi" untuk setiap objek ber-tag, termasuk yang
-- ditambahkan NANTI (misalnya Part yang dibuat script lain).
local function untukSetiapTag(tag, aturFungsi)
for _, objek in CollectionService:GetTagged(tag) do
task.spawn(aturFungsi, objek)
end
CollectionService:GetInstanceAddedSignal(tag):Connect(aturFungsi)
end
local function humanoidDari(bagian)
local model = bagian.Parent
if not model then
return nil
end
local humanoid = model:FindFirstChildOfClass("Humanoid")
if humanoid and humanoid.Health > 0 then
return humanoid
end
return nil
end
-- ------------------------------------------------------------
-- LAVA
-- ------------------------------------------------------------
untukSetiapTag("Lava", function(part)
part.Touched:Connect(function(bagian)
local humanoid = humanoidDari(bagian)
if humanoid then
humanoid.Health = 0
end
end)
end)
-- ------------------------------------------------------------
-- PLATFORM RAPUH
-- ------------------------------------------------------------
untukSetiapTag("PlatformRapuh", function(part)
local sedangBerjalan = false -- debounce milik platform INI saja
part.Touched:Connect(function(bagian)
if sedangBerjalan or not humanoidDari(bagian) then
return
end
sedangBerjalan = true
for i = 1, 10 do
part.Transparency = i / 10
task.wait(0.1)
end
part.CanCollide = false
task.wait(3)
part.Transparency = 0
part.CanCollide = true
sedangBerjalan = false
end)
end)
-- ------------------------------------------------------------
-- PAD PENYEMBUH
-- ------------------------------------------------------------
untukSetiapTag("PadPenyembuh", function(part)
local sedangCooldown = {}
part.Touched:Connect(function(bagian)
local humanoid = humanoidDari(bagian)
if not humanoid then
return
end
local karakter = humanoid.Parent
if sedangCooldown[karakter] or humanoid.Health >= humanoid.MaxHealth then
return
end
sedangCooldown[karakter] = true
humanoid.Health = math.min(humanoid.Health + 25, humanoid.MaxHealth)
task.wait(2)
sedangCooldown[karakter] = nil
end)
end)
print(`🧱 Rintangan aktif: {#CollectionService:GetTagged("Lava")} lava, `
.. `{#CollectionService:GetTagged("PlatformRapuh")} platform rapuh, `
.. `{#CollectionService:GetTagged("PadPenyembuh")} pad penyembuh`)
3-anti-speed.server.luau
- Jenis: Script
- Letakkan di: ServerScriptService
- ⚠️ Script ini sengaja TIDAK meng-kick pemain. Deteksi seperti ini
local Players = game:GetService("Players")
local INTERVAL = 0.5 -- detik antar pemeriksaan
local PENGALI_LARI = 1.6 -- harus sama dengan Lari.client.luau (Pelajaran 13)
local TOLERANSI = 1.5 -- kelonggaran untuk lag dan lompatan
local JARAK_BONUS = 4 -- stud tambahan untuk jaga-jaga
local JEDA_SETELAH_SPAWN = 3 -- detik, karena checkpoint memindahkan pemain saat spawn
-- pantauan[player] = { posisiAman, waktuCek, aktifMulai, pelanggaran }
local pantauan = {}
local function mulaiPantau(player)
pantauan[player] = {
posisiAman = nil,
waktuCek = os.clock(),
aktifMulai = os.clock() + JEDA_SETELAH_SPAWN,
pelanggaran = pantauan[player] and pantauan[player].pelanggaran or 0,
}
end
Players.PlayerAdded:Connect(function(player)
player.CharacterAdded:Connect(function()
mulaiPantau(player)
end)
end)
Players.PlayerRemoving:Connect(function(player)
pantauan[player] = nil
end)
local function periksa(player, data)
local character = player.Character
local root = character and character:FindFirstChild("HumanoidRootPart")
local humanoid = character and character:FindFirstChildOfClass("Humanoid")
if not root or not humanoid or humanoid.Health <= 0 then
return
end
local sekarang = os.clock()
local selangWaktu = sekarang - data.waktuCek
data.waktuCek = sekarang
local posisi = root.Position
-- Masih masa jeda setelah spawn, atau fitur game sedang memindahkan
-- pemain (beri attribute "BolehTeleport" = true di Player saat itu)
if sekarang < data.aktifMulai or player:GetAttribute("BolehTeleport") then
data.posisiAman = posisi
return
end
if data.posisiAman then
-- Hanya jarak MENDATAR (X dan Z). Jatuh ke bawah itu wajar.
local geser = (posisi - data.posisiAman) * Vector3.new(1, 0, 1)
local jarak = geser.Magnitude
-- Jarak maksimal yang masuk akal dalam selang waktu ini.
-- WalkSpeed dibaca dari SERVER, jadi perubahan WalkSpeed oleh
-- exploiter di client tidak ikut menaikkan batas ini.
local jarakMaks = humanoid.WalkSpeed * PENGALI_LARI * selangWaktu * TOLERANSI + JARAK_BONUS
if jarak > jarakMaks then
data.pelanggaran += 1
warn(`⚠️ {player.Name} bergerak {math.floor(jarak)} stud dalam `
.. `{string.format("%.2f", selangWaktu)} detik (maks {math.floor(jarakMaks)}). `
.. `Pelanggaran ke-{data.pelanggaran}`)
-- Kembalikan ke posisi aman, arah hadap tetap sama
character:PivotTo(CFrame.new(data.posisiAman) * root.CFrame.Rotation)
return -- posisi aman TIDAK diperbarui
end
end
data.posisiAman = posisi
end
-- Satu loop untuk SEMUA pemain (lebih ringan daripada satu loop per pemain)
while true do
task.wait(INTERVAL)
for player, data in pantauan do
periksa(player, data)
end
end
Latihan
- Validasi toko: pakai
Validasi.teks(idItem, 50)diToko.server.luaumenggantikan pengecekantypeofmanual. - Cek jarak: pemain hanya boleh membeli kalau berdiri dalam jarak 15 stud dari Part bernama
Tokodi Workspace. Petunjuk:(root.Position - toko.Position).Magnitude. - Rintangan baru: buat tag
LantaiBerduriyang mengurangi darah 20 dengan cooldown 1 detik per pemain. Tambahkan di2-rintangan-tag.server.luau. - Ukur sendiri: buat 500 Part tidak di-Anchor yang jatuh bersamaan, lalu bandingkan FPS-nya (View → Stats) dengan 500 Part yang di-Anchor.
Kunci jawaban
Coba kerjakan sendiri dulu sebelum membaca bagian ini.
Latihan 1: Validasi di toko
Di Toko.server.luau (atau 3-toko.server.luau), tambahkan di bagian atas:
local ServerScriptService = game:GetService("ServerScriptService")
local Validasi = require(ServerScriptService:WaitForChild("Modul"):WaitForChild("Validasi"))
Lalu ganti pengecekan tipe di OnServerInvoke:
local function cekPermintaan(idItem)
-- SEBELUM:
-- if typeof(idItem) ~= "string" then
-- SESUDAH: tipe DAN panjang dicek sekaligus
if not Validasi.teks(idItem, 50) then
return false, "Permintaan tidak valid"
end
return true
end
Di script aslinya, cukup ganti baris if typeof(idItem) ~= "string" then menjadi if not Validasi.teks(idItem, 50) then.
Apa bedanya? Selain mengecek tipe, Validasi.teks juga menolak teks kosong dan teks yang sangat panjang. Angka 50 dipilih karena ID item terpanjang (PenyembuhanPenuh, 16 huruf) jauh di bawahnya.
Pilihan yang lebih ketat lagi: Validasi.pilihan(idItem, daftarIdYangAda), yaitu hanya menerima ID yang memang ada di toko.
Latihan 2: Cek jarak ke toko
Di OnServerInvoke, setelah mendapatkan character (VALIDASI 3):
local JARAK_MAKS_TOKO = 15
local function dekatToko(character)
local toko = workspace:FindFirstChild("Toko")
local root = character:FindFirstChild("HumanoidRootPart")
if not toko or not root then
return false
end
local jarak = (root.Position - toko.Position).Magnitude
return jarak <= JARAK_MAKS_TOKO
end
Lalu di dalam OnServerInvoke:
if not dekatToko(character) then
return false, "Kamu harus berada di dekat toko"
end
Penjelasan: posisiA - posisiB menghasilkan Vector3 "panah" dari B ke A, dan .Magnitude adalah panjang panah itu, yaitu jarak dalam stud.
Kenapa dicek di server? Client bisa saja menyembunyikan panel toko saat jauh, tapi exploiter tetap bisa memanggil InvokeServer dari mana pun di peta. Hanya pengecekan di server yang benar-benar membatasinya.
Karena panel toko di Pelajaran 9 selalu terlihat, sebaiknya UI juga diubah supaya panel hanya muncul saat pemain dekat Part Toko. Itu untuk kenyamanan, sedangkan pengecekan server untuk keamanan.
Latihan 3: Tag LantaiBerduri
Tambahkan di 2-rintangan-tag.server.luau, sebelum baris print terakhir:
untukSetiapTag("LantaiBerduri", function(part)
local sedangCooldown = {}
part.Touched:Connect(function(bagian)
local humanoid = humanoidDari(bagian)
if not humanoid then
return
end
local karakter = humanoid.Parent
if sedangCooldown[karakter] then
return
end
sedangCooldown[karakter] = true
humanoid:TakeDamage(20)
task.wait(1)
sedangCooldown[karakter] = nil
end)
end)
Bandingkan dengan jawaban latihan 2 di Pelajaran 7: logikanya sama, tapi sekarang satu kode berlaku untuk semua Part ber-tag LantaiBerduri, berapa pun jumlahnya.
Variasi: pakai Attribute supaya setiap lantai bisa punya damage berbeda, misalnya local damage = part:GetAttribute("Damage") or 20.
Latihan 4: Mengukur performa
Script percobaan di ServerScriptService (hapus setelah selesai):
local DI_ANCHOR = false -- ganti true untuk perbandingan
local JUMLAH = 500
local folder = Instance.new("Folder")
folder.Name = "UjiPerforma"
folder.Parent = workspace
for i = 1, JUMLAH do
local part = Instance.new("Part")
part.Size = Vector3.new(2, 2, 2)
part.Position = Vector3.new(math.random(-40, 40), 50 + i * 0.5, math.random(-40, 40))
part.Anchored = DI_ANCHOR
part.Parent = folder
end
print(`{JUMLAH} Part dibuat, Anchored = {DI_ANCHOR}`)
Cara mengukur: buka View → Stats (atau Shift+F5), tekan Play, lalu catat FPS dan Physics selama beberapa detik. Ulangi dengan DI_ANCHOR = true.
Hasil yang biasanya terlihat:
- Tidak di-Anchor: saat ratusan Part jatuh dan saling bertabrakan, waktu fisika melonjak dan FPS bisa turun, terutama di komputer atau HP yang lemah. Setelah semua Part diam di tanah, beban berkurang tapi tidak hilang.
- Di-Anchor: Part melayang di tempatnya dan hampir tidak ada beban fisika. FPS stabil.
Angka persisnya berbeda-beda tergantung komputermu, jadi yang penting adalah perbandingannya.
Pelajarannya: setiap Part yang tidak perlu bergerak, seperti lantai, tembok, dekorasi, atau platform obby, harus Anchored. Kesalahan ini sangat umum di game pemula dan menjadi penyebab lag yang paling mudah diperbaiki.
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.