Munculkan adalah proses menciptakan objek atau karakter dalam permainan, dan respawn adalah proses menambahkan objek atau karakter kembali ke dalam permainan setelah mereka memenuhi kondisi penghapusan, seperti kesehatan karakter mencapai nol atau jatuh dari peta. Kedua proses ini penting karena memastikan pemain dapat bergabung dengan permainan Anda, dan dapat terus bermain untuk meningkatkan keterampilan mereka.
Menggunakan permainan laser tag contoh sebagai referensi, bagian tutorial ini mengajarkan Anda bagaimana menggunakan dan menyesuaikan fitur bawaan Roblox untuk menangani munculkan dan respawn, termasuk panduan scripting tentang:
- Mengonfigurasi lokasi spawn sehingga pemain hanya dapat muncul di zona spawn tim mereka.
- Menambahkan pemain baru dan karakter mereka ke dalam ronde saat mereka bergabung dengan permainan.
- Menyesuaikan force field yang mencegah kerusakan saat pemain muncul dan respawn.
- Menangani status klien sehingga gameplay berfungsi dengan benar pada waktu yang tepat.
- Respawn karakter setelah mereka ditandai keluar dari ronde.
- Melakukan tindakan kecil yang penting untuk mengatur gameplay dan parameter karakter.
Bagian ini mencakup banyak konten scripting, tetapi alih-alih menulis semuanya dari awal saat membuat permainan, ini mendorong Anda untuk memanfaatkan komponen yang ada, beriterasi dengan cepat, dan mencari tahu sistem mana yang memerlukan implementasi khusus untuk mencocokkan visi Anda. Setelah Anda menyelesaikan bagian ini, Anda akan belajar bagaimana menerapkan gameplay berbasis ronde yang melacak poin, memantau status pemain, dan menampilkan hasil ronde.
Konfigurasi lokasi spawn
Jika Anda melakukan pengujian permainan sekarang, semua pemain akan muncul secara acak di objek SpawnLocation di zona spawn tim hijau, atau objek SpawnLocation di zona spawn tim pink. Ini menimbulkan masalah gameplay di mana pemain dapat menandai satu sama lain di dalam setiap zona spawn segera setelah force field lawan mereka menghilang.
Untuk mengatasi masalah ini, permainan laser tag contoh mengonfigurasi kedua lokasi spawn dengan properti Neutral yang diatur ke false untuk membatasi pemain tim lawan dari muncul 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 pink dengan properti TeamColor yang diatur ke Carnation Pink.


Ketika seorang pemain bergabung dengan permainan, 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()
-- Urutkan tim dalam urutan menaik dari yang terkecil hingga terbesar
table.sort(teams, function(teamA: Team, teamB: Team)
return #teamA:GetPlayers() < #teamB:GetPlayers()
end)
-- Kembalikan tim terkecil
return teams[1]
endSetelah mengetahui tim dengan jumlah pemain paling sedikit, ia mengurutkan pemain ke dalam tim tersebut, mengatur properti Player.Neutral mereka ke false sehingga pemain hanya dapat muncul dan respawn ke lokasi spawn tim mereka, kemudian mengatur PlayerState mereka ke SelectingBlaster, yang akan Anda pelajari lebih lanjut nanti di tutorial.
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 dapat 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 permainan; sebaliknya, 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 dalam tim dan memunculkan mereka ke arena. Namun, jika ronde tidak aktif, seperti waktu antara satu ronde dan yang berikutnya, properti Neutral diatur ke true sehingga pemain dapat muncul di sana terlepas dari status tim mereka. Proses ini yang menjadikan lokasi spawn Neutral sebagai lobi fungsional.

Untuk mendemonstrasikan, jika Anda memeriksa ServerScriptService ⟩ Gameplay ⟩ Rounds ⟩ SpawnPlayersInLobby, yang dijalankan di akhir ronde, Anda dapat melihat bahwa untuk setiap pemain yang dimasukkan ke dalam 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 lokasi spawn juga diatur ke true.
- Mengubah PlayerState mereka menjadi InLobby untuk menghapus blaster pemain dan visual UI tampilan orang pertama.
Untuk informasi lebih lanjut tentang zona spawn netral dan fungsionalitasnya untuk setiap ronde, lihat Menambahkan Ronde di bagian berikutnya 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 kali berbasis peristiwa, yang berarti bahwa skrip mendengarkan peristiwa dari layanan Roblox, kemudian memanggil fungsi sebagai respons. Sebagai contoh, saat menambahkan pemain baru ke permainan multipemain, harus ada peristiwa yang menangani semua yang diperlukan agar pemain dapat terhubung dengan sukses. Dalam permainan laser tag contoh, peristiwa yang sesuai adalah Players.PlayerAdded:Connect.
Players.PlayerAdded:Connect adalah bagian dari beberapa skrip dalam permainan. Jika Anda menggunakan pintasan Ctrl/Cmd+Shift+F dan mencari Players.PlayerAdded:Connect, hasilnya memberikan titik awal yang baik untuk memahami pengaturan awal permainan.

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 muncul dan menerima blaster.
SetupHumanoid segera memeriksa apakah pemain memiliki karakter (baru bergabung) atau tidak (sedang respawn). Setelah menemukan salah satu, ia memanggil onCharacterAdded(), mendapatkan model Humanoid dari karakter, dan meneruskannya ke ServerScriptService ⟩ SetupHumanoid ⟩ setupHumanoidAsync untuk penyesuaian. Setelah mengatur nilai-nilai ini, skrip kemudian menunggu kesehatan karakter mencapai nol. Anda akan belajar lebih lanjut tentang respawn nanti 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, yang berarti bahwa jika Anda menghapus enam baris pertama dari fungsi, permainan 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 hancur ketika kesehatan mereka mencapai 0, atur Humanoid.BreakJointsOnDeath ke True.
Jika Anda mengubah nilai-nilai properti ini, penting untuk melakukan pengujian permainan sehingga Anda dapat melihat dampak dari pengaturan baru Anda. Anda dapat mereplikasi apa yang dialami pemain dalam simulasi multi-klien dengan memilih setidaknya dua karakter dalam pengujian Server & Clients dari mezzanine.

Contoh lain dari peristiwa Players.PlayerAdded:Connect ada di ServerScriptService ⟩ PlayerStateHandler. Sama seperti pada 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 dua jenis blaster yang berbeda setelah muncul di arena. Status ini juga mencakup force field yang mencegah pemain dari menerima 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 layak dibahas: attributeChangedConnectionByPlayer. Tabel ini menyimpan semua pemain dan Connections mereka ke GetAttributeChangedSignal. Alasan untuk menyimpan koneksi ini dalam tabel adalah agar PlayerStateHandler dapat memutuskan koneksi tersebut ketika pemain meninggalkan permainan. Proses ini berfungsi sebagai semacam manajemen memori untuk mencegah jumlah koneksi tumbuh semakin besar seiring waktu.
local attributeChangedConnectionByPlayer = {}
local function onPlayerAdded(player: Player)
-- Tangani semua pembaruan masa depan ke status pemain
attributeChangedConnectionByPlayer[player] = player
:GetAttributeChangedSignal(PlayerAttribute.playerState)
:Connect(function()
local newPlayerState = player:GetAttribute(PlayerAttribute.playerState)
onPlayerStateChanged(player, newPlayerState)
end)
end
-- Putuskan koneksi dari atribut yang berubah ketika pemain meninggalkan
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 dalam tim, onPlayerAdded() mengatur PlayerState ke SelectingBlaster, sehingga pernyataan if pertama dievaluasi menjadi false dan menonaktifkan BlasterState. Di bagian Implementasikan blaster dari tutorial, Anda akan mempelajari lebih banyak detail 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 permainan: 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().
Sesuaikan force field
Alih-alih menggunakan implementasi khusus, permainan laser tag contoh menggunakan kelas bawaan ForceField di Studio untuk mencegah pemain menerima kerusakan saat mereka memilih blaster mereka. Ini memastikan bahwa satu-satunya persyaratan bagi pemain untuk muncul 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 field, kemudian menangani durasi aktual secara programatis di ReplicatedStorage ⟩ ForceFieldClientVisuals.
Mirip dengan setupHumanoidAsync, sebagian besar baris dalam ForceFieldClientVisuals adalah opsional. Sebagai contoh, jika Anda mengomentari isi fungsi seperti skrip berikut, permainan menggunakan force field berkilau default alih-alih 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 daripada ParticleEmitter baru, skrip ForceFieldClientVisuals hanya mempengaruhi visual tampilan orang pertama untuk setiap pemain, bukan visual tampilan orang ketiga ketika pemain melihat pemain lain. Visual tampilan orang ketiga mempertahankan penampilan default Roblox. Untuk informasi lebih lanjut tentang memodifikasi force field, lihat ForceField.Visible.


Force field berguna karena memberikan pemain cukup waktu antara muncul dan respawn tanpa perlu khawatir tentang pemain musuh, tetapi 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 field perlu bertahan cukup lama untuk memungkinkan pemain beradaptasi dengan lingkungan mereka.
- Selama waktu aklimatisasi ini, force field tidak dapat menjadi keuntungan, jadi mereka perlu menghilang segera setelah pemain menembakkan blaster mereka.
- Force field perlu menghilang ketika pemain mereset karakter mereka baik sebelum menembak atau sebelum force field habis waktu.
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() mencakup pernyataan if yang tampaknya aneh di sekitar boolean forceFieldEnded. Karena pemeriksaan dijalankan 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 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 real-time, seperti menampilkan elemen antarmuka pengguna yang tepat, atau memungkinkan pemain bergerak dan menembak.
Server membutuhkan semua informasi yang sama ini sehingga dapat mencegah eksploitasi. Sebagai contoh, server juga membutuhkan status pemain untuk melakukan tindakan seperti memunculkan dan melengkapi karakter, menonaktifkan force field, dan menampilkan papan peringkat. Inilah sebabnya skrip ini ada di ReplicatedStorage dan bukan lokasi yang sepenuhnya di 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 sesuai untuk 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 peristiwa dikelompokkan secara logis dalam skrip ini karena mereka memerlukan perilaku serupa dalam mengaktifkan atau menonaktifkan kontrol pemain, gerakan kamera, dan lapisan UI mana yang terlihat. Sebagai contoh, selama pemilihan blaster, pemain perlu baik tidak dapat terluka dan tidak dapat bergerak. Server sudah menangani force field, tetapi klien menangani gerakan. Untuk mendemonstrasikan, jika Anda memeriksa logika untuk fungsi onSelectingBlaster(), Anda dapat melihat bahwa klien menonaktifkan gerakan pemain saat mereka memilih blaster.
local function onSelectingBlaster()
togglePlayerCamera(true)
togglePlayerMovement(false)
setGuiExclusivelyEnabled(playerGui.PickABlasterGui)
localPlayer:SetAttribute(PlayerAttribute.blasterStateClient, BlasterState.Disabled)
endFungsi onPlaying() juga cukup sederhana. Ini mengaktifkan gerakan, beralih ke tampilan antarmuka utama (HUD), mengaktifkan blaster, dan memanggil fungsi force field yang sama seperti di server.
local function onPlaying()
togglePlayerMovement(true)
setGuiExclusivelyEnabled(playerGui.HUDGui)
localPlayer:SetAttribute(PlayerAttribute.blasterStateClient, BlasterState.Ready)
scheduleDestroyForceField()
endRespawn karakter
Permainan laser tag contoh menangani respawn karakter kembali ke dalam ronde melalui status onTaggedOut() di ReplicatedStorage ⟩ PlayerStateHandler. Seperti status onSelectingBlaster() dan onPlaying(), onTaggedOut() memicu perilaku unik sesuai dengan perubahan atribut playerState. Secara khusus, ini menonaktifkan gerakan 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 mengklik 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, itu 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 respawn adalah perilaku bawaan Roblox.
Ketika pemain respawn kembali ke dalam 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 Miscellaneous
Sebagai bagian dari pengaturan awal, permainan laser tag contoh juga melakukan beberapa langkah kecil, tetapi penting:
Permainan ini menyertakan skrip kosong bernama StarterPlayer ⟩ StarterCharacterScripts ⟩ Health yang menonaktifkan regenerasi kesehatan default Roblox. Untuk penjelasan tentang perilaku properti ini, lihat Humanoid.Health.
Permainan ini menggunakan kamera tampilan orang pertama dengan mengatur properti StarterPlayer.CameraMode.LockFirstPerson. Perhatikan bahwa jika Anda ingin membiarkan pengguna beralih antara kamera tampilan orang pertama dan ketiga, Anda harus mengubah properti secara programatis daripada hanya mengaturnya sekali di Studio, dan memodifikasi kontrol serta UI untuk mengimbangi perubahan perspektif.
Permainan ini menggunakan papan peringkat bawaan Roblox dengan unit "poin", yang diperoleh pemain setiap kali mereka menandai pemain lain keluar. Anda dapat melihat konfigurasi di ServerScriptService ⟩ SetupLeaderboard, tetapi Papan Peringkat Dalam Permainan menawarkan gambaran lengkap. Perhatikan bahwa onPlayerTagged menambahkan poin ke papan peringkat, yang akan Anda pelajari di Tambahkan ronde dan Deteksi hit.
Sekarang pemain dapat muncul, memilih blaster, dan mengarahkannya dari sudut pandang orang pertama, bagian berikutnya mengajarkan Anda tentang skrip di balik menciptakan gameplay berbasis ronde.