Spawning adalah proses menciptakan objek atau karakter dalam suatu pengalaman, dan respawning adalah proses menambahkan objek atau karakter kembali ke dalam pengalaman setelah mereka memenuhi kondisi penghapusan, seperti kesehatan karakter mencapai nol atau jatuh dari peta. Kedua proses ini penting karena memastikan pemain dapat bergabung dengan pengalaman Anda dan dapat terus bermain untuk meningkatkan keterampilan mereka.
Menggunakan pengalaman laser tag contoh sebagai referensi, bagian dari tutorial ini mengajarkan Anda bagaimana menggunakan dan menyesuaikan fitur bawaan Roblox untuk menangani spawning dan respawning, termasuk panduan scripting tentang:
- Mengonfigurasi lokasi spawn sehingga pemain hanya dapat spawn di zona spawn tim mereka.
- Menambahkan pemain baru dan karakter mereka ke dalam ronde saat mereka bergabung dengan pengalaman.
- Menyesuaikan force fields yang mencegah kerusakan saat pemain spawn dan respawn.
- Menangani status klien sehingga gameplay berfungsi dengan benar pada waktu yang tepat.
- Respawning karakter setelah mereka ditandai keluar dari ronde.
- Melakukan tindakan kecil yang beragam yang sangat penting untuk menetapkan parameter gameplay dan karakter.
Bagian ini mencakup banyak konten scripting, tetapi daripada menulis semuanya dari awal saat membuat pengalaman, ini mendorong Anda untuk memanfaatkan komponen yang ada, iterasi dengan cepat, dan mencari tahu sistem mana yang membutuhkan implementasi khusus untuk mencocokkan visi Anda. Setelah Anda menyelesaikan bagian ini, Anda akan belajar bagaimana menerapkan gameplay berbasis ronde yang melacak poin, memonitor status pemain, dan menampilkan hasil ronde.
Konfigurasi lokasi spawn
Jika Anda melakukan pengujian pengalaman saat ini, semua pemain akan secara acak spawn di objek SpawnLocation di zona spawn tim hijau, atau objek SpawnLocation di zona spawn tim merah muda. Ini menimbulkan masalah gameplay di mana pemain bisa saling menandai dalam setiap zona spawn segera setelah force field lawan mereka menghilang.
Untuk mengatasi masalah ini, pengalaman laser tag contoh mengonfigurasi kedua lokasi spawn dengan properti Neutral yang diatur ke false untuk membatasi pemain tim lawan dari spawning di zona spawn yang salah, dan properti TeamColor yang diatur ke nilai Team.Color yang sesuai dari Menetapkan Warna Tim di bagian sebelumnya dari tutorial:
- TeamBSpawn – Lokasi spawn di zona spawn tim merah muda dengan properti TeamColor diatur ke Carnation Pink.


Ketika seorang pemain bergabung dengan pengalaman, ServerScriptService ⟩ Gameplay ⟩ Rounds ⟩ spawnPlayersInMap memeriksa berapa banyak pemain yang sudah ada di setiap tim, kemudian mengembalikan tim dengan jumlah pemain paling sedikit.
local function getSmallestTeam(): Team
local teams = Teams:GetTeams()
-- Mengurutkan tim dalam urutan meningkat dari yang terkecil hingga terbesar
table.sort(teams, function(teamA: Team, teamB: Team)
return #teamA:GetPlayers() < #teamB:GetPlayers()
end)
-- Mengembalikan tim terkecil
return teams[1]
endSetelah mengetahui tim dengan jumlah pemain paling sedikit, ia mengurutkan pemain ke tim tersebut, mengatur properti Player.Neutral mereka ke false sehingga pemain hanya dapat spawn dan respawn ke lokasi spawn tim mereka, kemudian mengatur PlayerState mereka ke SelectingBlaster, yang akan Anda pelajari lebih lanjut di bagian bawah tutorial ini.
local function spawnPlayersInMap(players: { Player })
for _, player in players do
player.Team = getSmallestTeam()
player.Neutral = false
player:SetAttribute(PlayerAttribute.playerState, PlayerState.SelectingBlaster)
task.spawn(function()
player:LoadCharacter()
end)
end
endJika Anda memeriksa Workspace ⟩ World ⟩ Map ⟩ Spawns, Anda bisa melihat bahwa ada satu lokasi spawn lagi di peta: NeutralSpawn. Lokasi spawn ini unik dari yang lain karena tidak memiliki properti TeamColor yang diatur ke salah satu dari dua tim dalam pengalaman; alih-alih, lokasi spawn ini memiliki properti Neutral yang berubah tergantung pada apakah ronde aktif.
Sebagai contoh, jika ronde aktif, properti Neutral diatur ke false sehingga spawnPlayersInMap dapat mengurutkan pemain ke tim dan spawn mereka ke arena. Namun, jika ronde tidak aktif, seperti waktu antara satu ronde dan yang berikutnya, properti Neutral diatur ke true sehingga pemain dapat spawn di sana terlepas dari status tim mereka. Proses ini yang menjadikan lokasi spawn Neutral sebagai lobi yang fungsional.

Untuk mendemonstrasikan, jika Anda memeriksa ServerScriptService ⟩ Gameplay ⟩ Rounds ⟩ SpawnPlayersInLobby, yang berjalan di akhir ronde, Anda bisa melihat bahwa untuk setiap pemain yang diteruskan ke tabel players: { Player }, skrip:
- Mengatur properti Player.Neutral mereka ke true untuk secara otomatis mengatur ulang Player.Team mereka ke nil, memungkinkan pemain untuk respawn di lobi ketika ronde tidak aktif, karena properti Neutral dari lokasi spawn juga diatur ke true.
- Mengubah PlayerState mereka ke InLobby untuk menghapus blaster pemain dan visual antarmuka pengguna tampilan orang pertama.
Untuk informasi lebih lanjut tentang zona spawn netral dan fungsionalitasnya untuk setiap ronde, lihat Menambahkan Ronde di bagian berikut dari tutorial.
local function spawnPlayersInLobby(players: { Player })
for _, player in players do
player.Neutral = true
player:SetAttribute(PlayerAttribute.playerState, PlayerState.InLobby)
task.spawn(function()
player:LoadCharacter()
end)
end
endMenghubungkan pemain baru
Kode Luau di Studio sering bersifat berbasis acara, yang berarti bahwa skrip mendengarkan acara dari layanan Roblox, kemudian memanggil fungsi sebagai respons. Sebagai contoh, ketika menambahkan pemain baru ke pengalaman multipemain, harus ada acara yang menangani semuanya yang diperlukan agar pemain dapat terhubung dengan sukses. Dalam pengalaman laser tag contoh, acara yang sesuai adalah Players.PlayerAdded:Connect.
Players.PlayerAdded:Connect adalah bagian dari beberapa skrip dalam pengalaman. Jika Anda menggunakan pintasan Ctrl/Cmd+Shift+F dan mencari Players.PlayerAdded:Connect, hasilnya memberikan titik awal yang baik untuk memahami pengaturan awal pengalaman.

Untuk mendemonstrasikan, buka ServerScriptService ⟩ SetupHumanoid. Perbedaan antara Player dan Character adalah kunci untuk memahami skrip ini:
- Pemain perlu memilih blaster dan ditambahkan ke papan peringkat. Karakter perlu spawn dan menerima blaster.
SetupHumanoid segera memeriksa apakah pemain memiliki karakter (baru bergabung) atau tidak (sedang respawn). Setelah menemukannya, ia memanggil onCharacterAdded(), mendapatkan model Humanoid dari karakter, dan meneruskannya ke ServerScriptService ⟩ SetupHumanoid ⟩ setupHumanoidAsync untuk kustomisasi. Setelah mengatur nilai-nilai ini, skrip kemudian menunggu kesehatan karakter mencapai nol. Anda akan belajar lebih lanjut tentang respawning di bagian ini dari tutorial.
local function setupHumanoidAsync(player: Player, humanoid: Humanoid)
humanoid.DisplayDistanceType = Enum.HumanoidDisplayDistanceType.Subject
humanoid.NameDisplayDistance = 1000
humanoid.HealthDisplayDistance = 1000
humanoid.NameOcclusion = Enum.NameOcclusion.OccludeAll
humanoid.HealthDisplayType = Enum.HumanoidHealthDisplayType.AlwaysOn
humanoid.BreakJointsOnDeath = false
humanoid.Died:Wait()
onHumanoidDied(player, humanoid)
endCatatan penting dengan skrip ini adalah bahwa propertinya sepenuhnya opsional, berarti bahwa jika Anda menghapus enam baris pertama dari fungsi, pengalaman masih berfungsi dengan baik. Alih-alih menjadi persyaratan fungsional, setiap properti memungkinkan Anda untuk membuat keputusan desain yang memenuhi tujuan gameplay Anda. Sebagai contoh:
- Jika Anda ingin nama karakter ditampilkan pada jarak yang lebih dekat, kurangi nilai Humanoid.NameDisplayDistance.
- Jika Anda hanya ingin kesehatan karakter ditampilkan jika di bawah 100%, atur Humanoid.HealthDisplayType ke DisplayWhenDamaged.
- Jika Anda ingin karakter pecah ketika kesehatan mereka mencapai 0, atur Humanoid.BreakJointsOnDeath ke True.
Jika Anda mengubah nilai-nilai properti ini, penting untuk melakukan pengujian agar Anda dapat melihat dampak pengaturan baru Anda. Anda dapat mengulang apa yang dialami pemain dalam simulasi multi-klien dengan memilih setidaknya dua karakter dalam pengujian Server & Clients dari mezzanine.

Contoh lain dari acara Players.PlayerAdded:Connect ada di ServerScriptService ⟩ PlayerStateHandler. Sama seperti di contoh sebelumnya, PlayerStateHandler segera memeriksa karakter. Jika pemain tidak berada di lobi, skrip mengatur atribut pemain ke status SelectingBlaster, status awal untuk ronde di mana pemain dapat memilih dari salah satu dari dua jenis blaster berbeda setelah spawn ke arena. Status ini juga mencakup force field yang mencegah pemain dari mengambil kerusakan saat mereka membuat pilihan.
local function onPlayerAdded(player: Player)
player.CharacterAdded:Connect(function()
if not player.Neutral then
player:SetAttribute(PlayerAttribute.playerState, PlayerState.SelectingBlaster)
onPlayerStateChanged(player, PlayerState.SelectingBlaster)
end
end)Satu variabel tertentu dalam PlayerStateHandler perlu dibahas: attributeChangedConnectionByPlayer. Tabel ini menyimpan semua pemain dan Connections mereka ke GetAttributeChangedSignal. Alasan untuk menyimpan koneksi ini dalam tabel adalah agar PlayerStateHandler dapat memutuskan koneksinya ketika pemain meninggalkan pengalaman. Proses ini berfungsi sebagai semacam manajemen memori untuk mencegah jumlah koneksi tumbuh semakin besar seiring waktu.
local attributeChangedConnectionByPlayer = {}
local function onPlayerAdded(player: Player)
-- Menangani semua pembaruan masa depan untuk status pemain
attributeChangedConnectionByPlayer[player] = player
:GetAttributeChangedSignal(PlayerAttribute.playerState)
:Connect(function()
local newPlayerState = player:GetAttribute(PlayerAttribute.playerState)
onPlayerStateChanged(player, newPlayerState)
end)
end
-- Putuskan koneksi atribut yang berubah saat pemain keluar
local function onPlayerRemoving(player: Player)
if attributeChangedConnectionByPlayer[player] then
attributeChangedConnectionByPlayer[player]:Disconnect()
attributeChangedConnectionByPlayer[player] = nil
end
endAnda dapat melihat bahwa kedua fungsi yang terhubung dalam onPlayerAdded() memanggil onPlayerStateChanged(). Selama pengaturan awal setelah pemain diurutkan ke tim, onPlayerAdded() mengatur PlayerState ke SelectingBlaster, sehingga pernyataan if pertama dievaluasi menjadi salah dan menonaktifkan BlasterState. Di bagian Menerapkan blaster dalam tutorial, Anda akan belajar lebih banyak tentang proses ini.
local function onPlayerStateChanged(player: Player, newPlayerState: string)
-- Status blaster adalah 'Siap' hanya jika status pemain adalah 'Bermain'
local newBlasterState = if newPlayerState == PlayerState.Playing then BlasterState.Ready else BlasterState.Disabled
-- Jadwalkan logika penghancuran force field ketika pemain mulai bermain
if newPlayerState == PlayerState.Playing then
scheduleDestroyForceField(player)
end
player:SetAttribute(PlayerAttribute.blasterStateServer, newBlasterState)
endJika Anda menambahkan breakpoint atau bahkan hanya pernyataan print(), Anda dapat melihat bahwa onPlayerStateChanged() dipanggil secara sering di seluruh pengalaman: seperti selama pengaturan awal ronde, untuk mengatur dirinya pada jalur kode utama, setelah pemain memilih blaster, dan ketika pemain kembali ke lobi, atau lokasi spawn Neutral. Selain itu, setelah pemain memilih blaster, ServerScriptService ⟩ BlasterSelectedHandler mengatur PlayerState ke Playing, dan PlayerStateHandler akhirnya dapat menghapus force field dengan memanggil scheduleDestroyForceField().
Kustomisasi force fields
Alih-alih menggunakan implementasi khusus, pengalaman laser tag contoh menggunakan kelas bawaan ForceField di Studio untuk mencegah pemain dari mengambil kerusakan saat mereka memilih blaster. Ini memastikan bahwa satu-satunya persyaratan untuk pemain agar spawn dengan force field adalah menyertakan lokasi spawn dengan properti SpawnLocation.Duration yang lebih besar dari 0. Contoh ini menggunakan nilai sewenang-wenang 9,999 untuk mengaktifkan force fields, kemudian menangani durasi aktual secara programatik di ReplicatedStorage ⟩ ForceFieldClientVisuals.
Mirip dengan setupHumanoidAsync, sebagian besar baris dalam ForceFieldClientVisuals bersifat opsional. Sebagai contoh, jika Anda mengomentari isi fungsi seperti skrip berikut, pengalaman menggunakan force field sparkling default daripada skrip heksagonal di StarterGui ⟩ ForceFieldGui.
local function onCharacterAddedAsync(character: Model)
-- local forceField = character:WaitForChild("ForceField", 3)
-- if not forceField then
-- return
-- end
-- forceField.Visible = false
-- localPlayer.PlayerGui:WaitForChild("ForceFieldGui").Enabled = true
-- forceField.Destroying:Wait()
-- localPlayer.PlayerGui.ForceFieldGui.Enabled = false
endKarena force field kustom adalah GUI dan bukan ParticleEmitter baru, skrip ForceFieldClientVisuals hanya memengaruhi visual orang pertama untuk setiap pemain, bukan visual orang ketiga ketika pemain melihat pemain lain. Visual orang ketiga tetap mempertahankan penampilan default Roblox. Untuk informasi lebih lanjut tentang memodifikasi force fields, lihat ForceField.Visible.


Force fields berguna karena mereka memberikan pemain cukup waktu antara spawning dan respawning tanpa perlu khawatir tentang pemain musuh; tetapi pada akhirnya mereka perlu menghilang untuk gameplay laser tag utama. Skrip yang menangani penghapusan force field ada di ReplicatedStorage ⟩ scheduleDestroyForceField, dan memeriksa tiga kondisi unik:
- Setelah pemain memilih blaster, force fields perlu bertahan cukup lama untuk memungkinkan pemain beradaptasi dengan lingkungan mereka.
- Selama waktu aklimatisasi ini, force fields tidak dapat menjadi keuntungan, sehingga mereka perlu menghilang saat pemain menembakkan blaster mereka.
- Force fields perlu menghilang ketika pemain mereset karakter mereka baik sebelum menembakkan atau sebelum force field habis.
Setiap pemeriksaan ini dalam skrip scheduleDestroyForceField memanggil endForceField() untuk kondisi ini.
-- Akhiri force field jika pemain menembak
local blasterStateAttribute = getBlasterStateAttribute()
attributeChangedConnection = player:GetAttributeChangedSignal(blasterStateAttribute):Connect(function()
local currentBlasterState = player:GetAttribute(blasterStateAttribute)
if currentBlasterState == BlasterState.Blasting then
endForceField()
end
end)
-- Akhiri force field jika pemain mereset
characterRespawnedConnection = player.CharacterRemoving:Connect(endForceField)
-- Akhiri force field setelah 8 detik
task.delay(MAX_FORCE_FIELD_TIME, endForceField)endForceField() menyertakan pernyataan if yang tampak aneh di sekitar boolean forceFieldEnded. Karena pemeriksaan berjalan secara berurutan, skrip dapat memanggil fungsi endForceField() dua atau bahkan tiga kali. Boolean forceFieldEnded memastikan bahwa fungsi hanya mencoba menghancurkan force field sekali.
local function endForceField()
if forceFieldEnded then
return
end
forceFieldEnded = true
attributeChangedConnection:Disconnect()
characterRespawnedConnection:Disconnect()
destroyForceField(player)
endTangani status klien
Sementara sebagian besar dari bagian ini berfokus pada ServerScriptService ⟩ PlayerStateHandler, ada skrip lain dengan nama yang sama di ReplicatedStorage. Alasan untuk pemisahan ini adalah arsitektur klien-server:
Klien perlu memahami informasi status pemain sehingga dapat merespons dengan tepat secara waktu nyata, seperti menampilkan elemen antarmuka pengguna yang benar, atau memungkinkan pemain untuk bergerak dan menembak.
Server memerlukan semua informasi ini agar dapat mencegah eksploit. Sebagai contoh, server juga memerlukan status pemain untuk melakukan tindakan seperti spawning dan melengkapi karakter, menonaktifkan force fields, dan menampilkan papan peringkat. Inilah sebabnya mengapa skrip ini ada di ReplicatedStorage dan bukan lokasi murni sisi klien.
Untuk melihat logika inti ini, tinjau skrip berikut di ReplicatedStorage ⟩ PlayerStateHandler yang memverifikasi status pengguna saat ini, kemudian memanggil fungsi yang sesuai yang menangani tindakan yang terkait dengan status tersebut.
local function onPlayerStateChanged(newPlayerState: string)
if newPlayerState == PlayerState.SelectingBlaster then
onSelectingBlaster()
elseif newPlayerState == PlayerState.Playing then
onPlaying()
elseif newPlayerState == PlayerState.TaggedOut then
onTaggedOut()
elseif newPlayerState == PlayerState.InLobby then
onInLobby()
else
warn(`Invalid player state ({newPlayerState})`)
end
endSemua respons acara dikelompokkan secara logis dalam skrip ini karena mereka memerlukan perilaku serupa untuk mengaktifkan atau menonaktifkan kontrol pemain, pergerakan kamera, dan lapisan UI mana yang terlihat. Sebagai contoh, selama pemilihan blaster, pemain perlu tidak rentan dan tidak dapat bergerak. Server sudah menangani force field, tetapi klien menangani pergerakan. Untuk mendemonstrasikan, jika Anda memeriksa logika untuk fungsi onSelectingBlaster(), Anda dapat melihat bahwa klien menonaktifkan pergerakan pemain saat mereka memilih blaster.
local function onSelectingBlaster()
togglePlayerCamera(true)
togglePlayerMovement(false)
setGuiExclusivelyEnabled(playerGui.PickABlasterGui)
localPlayer:SetAttribute(PlayerAttribute.blasterStateClient, BlasterState.Disabled)
endFungsi onPlaying() juga sederhana. Ini mengaktifkan pergerakan, beralih ke tampilan antarmuka pengguna (HUD) utama, mengaktifkan blaster, dan memanggil fungsi force field yang sama seperti server.
local function onPlaying()
togglePlayerMovement(true)
setGuiExclusivelyEnabled(playerGui.HUDGui)
localPlayer:SetAttribute(PlayerAttribute.blasterStateClient, BlasterState.Ready)
scheduleDestroyForceField()
endRespawn karakter
Pengalaman laser tag contoh menangani respawning karakter kembali ke ronde melalui status onTaggedOut() di ReplicatedStorage ⟩ PlayerStateHandler. Seperti status onSelectingBlaster() dan onPlaying(), onTaggedOut() memicu perilaku unik sesuai dengan perubahan pada atribut playerState. Secara spesifik, ini menonaktifkan pergerakan pemain, menampilkan UI respawn, dan menonaktifkan blaster.
local function onTaggedOut()
-- Nonaktifkan kontrol saat ditandai keluar
togglePlayerMovement(false)
togglePlayerCamera(false)
setGuiExclusivelyEnabled(playerGui.OutStateGui)
-- Nonaktifkan blaster saat ditandai keluar
localPlayer:SetAttribute(PlayerAttribute.blasterStateClient, BlasterState.Disabled)
endJika Anda ingin menguji perilaku ini, Anda dapat menekan Esc, menavigasi ke tab Pengaturan, lalu klik tombol Reset Karakter. Perhatikan bahwa ketika Anda memicu layar respawn, Anda tidak dapat bergerak, memutar kamera, atau menembakkan blaster Anda.


Penting untuk dicatat bahwa skrip ini tidak benar-benar respawn karakter, ia hanya menghentikan mereka dari bertindak dan memberikan umpan balik visual kepada pemain bahwa server sedang respawn karakter mereka. Untuk mendemonstrasikan, jika Anda memeriksa ServerScriptService ⟩ SetupHumanoid ⟩ setupHumanoidAsync ⟩ onHumanoidDied, skrip mengatur PlayerState ke TaggedOut (sebenarnya memberi tahu ReplicatedStorage ⟩ PlayerStateHandler), dan menambahkan beberapa indikator visual. Logika aktual dari respawning adalah perilaku bawaan Roblox.
Ketika pemain respawn kembali ke ronde, mereka respawn di lokasi spawn tim mereka sesuai dengan properti SpawnLocation.TeamColor. Untuk menyesuaikan waktu respawn, Anda dapat menambahkan baris berikut di bagian atas SetupHumanoid. Untuk mempelajari lebih lanjut tentang teknik ini, lihat Players.RespawnTime.
local Players = game:GetService("Players")
Players.RespawnTime = 10 -- baris baru, dalam detikPengaturan kecil lainnya
Sebagai bagian dari pengaturan awal, pengalaman laser tag contoh juga melakukan beberapa langkah kecil, tetapi penting:
Pengalaman ini mencakup skrip kosong bernama StarterPlayer ⟩ StarterCharacterScripts ⟩ Health yang menonaktifkan regenerasi kesehatan default Roblox. Untuk penjelasan tentang perilaku properti ini, lihat Humanoid.Health.
Pengalaman ini menggunakan kamera orang pertama dengan mengatur properti StarterPlayer.CameraMode.LockFirstPerson. Perhatikan bahwa jika Anda ingin membiarkan pengguna berubah antara kamera orang pertama dan ketiga, Anda harus mengubah properti secara programatik daripada hanya mengatur sekali di Studio, dan memodifikasi kontrol dan UI untuk mengimbangi perubahan perspektif.
Pengalaman ini menggunakan papan peringkat bawaan Roblox dengan unit "poin", yang diperoleh pemain setiap kali mereka menandai pemain lain keluar. Anda dapat melihat konfigurasinya di ServerScriptService ⟩ SetupLeaderboard, tetapi Papan Peringkat Dalam Pengalaman menawarkan gambaran penuh. Perhatikan bahwa onPlayerTagged menambahkan poin ke papan peringkat, yang akan Anda pelajari di Tambahkan ronde dan Deteksi pukulan.
Sekarang bahwa pemain dapat spawn, memilih blaster, dan mengarahkannya dari sudut pandang orang pertama, bagian berikutnya mengajarkan Anda tentang skrip di balik menciptakan gameplay berbasis ronde.