Spawnowanie to proces tworzenia obiektu lub postaci w grze, a respawnowanie to proces dodawania obiektu lub postaci z powrotem do gry po spełnieniu warunku usunięcia, takiego jak osiągnięcie zera zdrowia postaci lub spadnięcie z mapy. Oba procesy są ważne, ponieważ zapewniają, że gracze mogą dołączyć do twojej gry i kontynuować grę, aby poprawić swoje umiejętności.
Używając przykładowej gry w laser tag jako odniesienia, ta sekcja samouczka nauczy cię, jak korzystać z wbudowanych funkcji Roblox do obsługi spawnowania i respawnowania, w tym wskazówki dotyczące skryptów dotyczących:
- Konfigurowania lokalizacji spawnu, aby gracze mogli spawnować się tylko w strefie spawnu swojej drużyny.
- Dodawania nowych graczy i ich postaci do rundy, gdy dołączają do gry.
- Dostosowywania pól siłowych, które zapobiegają obrażeniom, gdy gracze się spawnowują i respawnują.
- Obsługi stanu klienta, aby rozgrywka działała poprawnie w odpowiednim czasie.
- Respawnowania postaci po tym, jak zostaną wyeliminowane z rundy.
- Wykonywania małych, różnorodnych działań, które są kluczowe dla ustawienia parametrów rozgrywki i postaci.
Ta sekcja zawiera wiele treści dotyczących skryptów, ale zamiast pisać wszystko od podstaw podczas tworzenia gry, zachęca cię do wykorzystania istniejących komponentów, szybkiego iterowania i ustalenia, które systemy wymagają niestandardowej implementacji, aby pasowały do twojej wizji. Po ukończeniu tej sekcji nauczysz się, jak wdrożyć rozgrywkę opartą na rundach, która śledzi punkty, monitoruje stan gracza i wyświetla wyniki rund.
Konfiguracja lokalizacji spawnu
Jeśli teraz przetestujesz grę, wszyscy gracze będą losowo spawnować się albo w obiekcie SpawnLocation w strefie spawnu drużyny zielonej, albo w obiekcie SpawnLocation w strefie spawnu drużyny różowej. Stwarza to problem z rozgrywką, w którym gracze mogą oznaczać się nawzajem w każdej strefie spawnu, gdy tylko zniknie pole siłowe przeciwnika.
Aby zwalczyć ten problem, przykładowa gra w laser tag konfiguruje obie lokalizacje spawnu z właściwością Neutral ustawioną na false, aby ograniczyć graczy przeciwnej drużyny od spawnowania się w niewłaściwej strefie spawnu, oraz właściwością TeamColor ustawioną na odpowiadającą wartość Team.Color z Przypisz kolory drużyn w poprzedniej sekcji samouczka:
- TeamASpawn – Lokalizacja spawnu w strefie spawnu drużyny zielonej z właściwością TeamColor ustawioną na Mint.
- TeamBSpawn – Lokalizacja spawnu w strefie spawnu drużyny różowej z właściwością TeamColor ustawioną na Carnation Pink.


Gdy gracz dołącza do gry, ServerScriptService ⟩ Gameplay ⟩ Rounds ⟩ spawnPlayersInMap sprawdza, ilu graczy jest już w każdej drużynie, a następnie zwraca drużynę z najmniejszą liczbą graczy.
local function getSmallestTeam(): Team
local teams = Teams:GetTeams()
-- Sortuj drużyny w porządku rosnącym od najmniejszej do największej
table.sort(teams, function(teamA: Team, teamB: Team)
return #teamA:GetPlayers() < #teamB:GetPlayers()
end)
-- Zwróć najmniejszą drużynę
return teams[1]
endGdy już zna drużynę z najmniejszą liczbą graczy, sortuje gracza do tej drużyny, ustawia jego właściwość Player.Neutral na false, aby gracz mógł spawnować się i respawnować tylko w lokalizacji spawnu swojej drużyny, a następnie ustawia jego PlayerState na SelectingBlaster, o czym dowiesz się więcej później w samouczku.
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
endJeśli zbadujesz Workspace ⟩ World ⟩ Map ⟩ Spawns, możesz zobaczyć, że na mapie znajduje się jeszcze jedna lokalizacja spawnu: NeutralSpawn. Ta lokalizacja spawnu różni się od innych, ponieważ nie ma ustawionej właściwości TeamColor na jedną z dwóch drużyn w grze; zamiast tego, ta lokalizacja spawnu ma właściwość Neutral, która zmienia się w zależności od tego, czy runda jest aktywna.
Na przykład, jeśli runda jest aktywna, właściwość Neutral jest ustawiona na false, aby spawnPlayersInMap mogło sortować graczy do drużyn i spawnować ich na arenie. Jednak jeśli runda nie jest aktywna, na przykład w czasie między jedną rundą a drugą, właściwość Neutral jest ustawiona na true, aby gracze mogli się tam spawnować niezależnie od statusu drużyny. Ten proces sprawia, że lokalizacja spawnu Neutral jest funkcjonalnym lobby.

Aby to zobrazować, jeśli zbadujesz ServerScriptService ⟩ Gameplay ⟩ Rounds ⟩ SpawnPlayersInLobby, który działa na końcu rundy, możesz zobaczyć, że dla każdego gracza, który jest przekazywany do tabeli players: { Player }, skrypt:
- Ustawia ich właściwość Player.Neutral na true, aby automatycznie zresetować ich Player.Team na nil, co pozwala graczowi respawnować się w lobby, gdy runda nie jest aktywna, ponieważ właściwość Neutral lokalizacji spawnu jest również ustawiona na true.
- Zmienia ich PlayerState na InLobby, aby usunąć blaster gracza i wizualizacje interfejsu użytkownika w trybie pierwszej osoby.
Aby uzyskać więcej informacji na temat strefy neutralnego spawnu i jej funkcjonalności dla każdej rundy, zobacz Dodawanie rund w następnej sekcji samouczka.
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
endŁączenie nowych graczy
Kod Luau w Studio jest często oparty na zdarzeniach, co oznacza, że skrypty nasłuchują zdarzeń z usługi Roblox, a następnie wywołują funkcję w odpowiedzi. Na przykład, gdy dodajesz nowych graczy do gry wieloosobowej, musi istnieć zdarzenie, które obsługuje wszystko, co jest potrzebne, aby gracze mogli się pomyślnie połączyć. W przykładowej grze w laser tag to odpowiadające zdarzenie to Players.PlayerAdded:Connect.
Players.PlayerAdded:Connect jest częścią wielu skryptów w grze. Jeśli użyjesz skrótu Ctrl/Cmd+Shift+F i wyszukasz Players.PlayerAdded:Connect, wyniki stanowią dobry punkt wyjścia do zrozumienia początkowej konfiguracji gry.

Aby to zobrazować, otwórz ServerScriptService ⟩ SetupHumanoid. Różnica między Player a Character jest kluczowa dla zrozumienia tego skryptu:
- Gracze muszą wybrać blaster i zostać dodani do tablicy wyników. Postacie muszą się spawnować i otrzymać blaster.
SetupHumanoid natychmiast sprawdza, czy gracz ma postać (właśnie dołączył) czy nie (respawn). Po znalezieniu jednej z nich wywołuje onCharacterAdded(), uzyskuje model Humanoid z postaci i przekazuje go do ServerScriptService ⟩ SetupHumanoid ⟩ setupHumanoidAsync w celu dostosowania. Po ustawieniu tych wartości skrypt czeka, aż zdrowie postaci osiągnie zero. Dowiesz się więcej o respawnowaniu później w tej sekcji samouczka.
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)
endWażna uwaga dotycząca tego skryptu jest taka, że właściwości są całkowicie opcjonalne, co oznacza, że jeśli usuniesz pierwsze sześć linii funkcji, gra nadal działa poprawnie. Zamiast być wymaganiami funkcjonalnymi, każda właściwość pozwala podejmować decyzje projektowe, które spełniają twoje cele rozgrywki. Na przykład:
- Jeśli chcesz, aby nazwy postaci były wyświetlane z mniejszych odległości, zmniejsz wartość Humanoid.NameDisplayDistance.
- Jeśli chcesz, aby zdrowie postaci było wyświetlane tylko wtedy, gdy jest poniżej 100%, ustaw Humanoid.HealthDisplayType na DisplayWhenDamaged.
- Jeśli chcesz, aby postacie rozpadały się, gdy ich zdrowie osiągnie 0, ustaw Humanoid.BreakJointsOnDeath na True.
Jeśli zmienisz wartości tych właściwości, ważne jest, aby przetestować grę, aby zobaczyć wpływ nowych ustawień. Możesz odtworzyć to, co gracze doświadczają w symulacji wielu klientów, wybierając co najmniej dwie postacie w teście Server & Clients z mezzaniny.

Innym przykładem zdarzenia Players.PlayerAdded:Connect jest w ServerScriptService ⟩ PlayerStateHandler. Podobnie jak w poprzednim przykładzie, PlayerStateHandler natychmiast sprawdza, czy postać istnieje. Jeśli gracz nie jest w lobby, skrypt ustawia atrybut gracza na stan SelectingBlaster, początkowy stan rundy, w której gracze mogą wybierać spośród dwóch różnych typów blasterów po spawnowaniu się na arenie. Ten stan obejmuje również pole siłowe, które zapobiega obrażeniom graczy podczas dokonywania wyboru.
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)Jedna szczególna zmienna w PlayerStateHandler zasługuje na omówienie: attributeChangedConnectionByPlayer. Ta tabela przechowuje wszystkich graczy i ich Connections do GetAttributeChangedSignal. Powód przechowywania tego połączenia w tabeli polega na tym, że PlayerStateHandler może rozłączyć je, gdy gracz opuszcza grę. Ten proces służy jako rodzaj zarządzania pamięcią, aby zapobiec nieustannemu wzrostowi liczby połączeń w czasie.
local attributeChangedConnectionByPlayer = {}
local function onPlayerAdded(player: Player)
-- Obsłuż wszystkie przyszłe aktualizacje stanu gracza
attributeChangedConnectionByPlayer[player] = player
:GetAttributeChangedSignal(PlayerAttribute.playerState)
:Connect(function()
local newPlayerState = player:GetAttribute(PlayerAttribute.playerState)
onPlayerStateChanged(player, newPlayerState)
end)
end
-- Rozłącz z połączeniem zmiany atrybutu, gdy gracz opuszcza
local function onPlayerRemoving(player: Player)
if attributeChangedConnectionByPlayer[player] then
attributeChangedConnectionByPlayer[player]:Disconnect()
attributeChangedConnectionByPlayer[player] = nil
end
endMożesz zobaczyć, że obie połączone funkcje w onPlayerAdded() wywołują onPlayerStateChanged(). Podczas początkowej konfiguracji, gdy gracz sortuje się do drużyny, onPlayerAdded() ustawia PlayerState na SelectingBlaster, więc pierwsze if ocenia się jako fałsz i wyłącza BlasterState. W późniejszej sekcji Wdrożenie blasterów dowiesz się więcej szczegółów na temat tego procesu.
local function onPlayerStateChanged(player: Player, newPlayerState: string)
-- Stan blastera jest 'Gotowy' tylko wtedy, gdy stan gracza to 'Gra'
local newBlasterState = if newPlayerState == PlayerState.Playing then BlasterState.Ready else BlasterState.Disabled
-- Zaplanuj logikę usunięcia pola siłowego, gdy gracz zacznie grać
if newPlayerState == PlayerState.Playing then
scheduleDestroyForceField(player)
end
player:SetAttribute(PlayerAttribute.blasterStateServer, newBlasterState)
endJeśli dodasz punkty przerwania lub nawet po prostu instrukcję print(), możesz zobaczyć, że onPlayerStateChanged() jest wywoływane często w całej grze: na przykład podczas początkowej konfiguracji rundy, aby ustawić się na głównym kodzie, po tym, jak gracz wybierze blaster, oraz gdy gracz wraca do lobby lub lokalizacji spawnu Neutral. Ponadto, po tym, jak gracz wybierze blaster, ServerScriptService ⟩ BlasterSelectedHandler ustawia PlayerState na Playing, a PlayerStateHandler może w końcu usunąć pole siłowe, wywołując scheduleDestroyForceField().
Dostosowywanie pól siłowych
Zamiast używać niestandardowej implementacji, przykładowa gra w laser tag korzysta z wbudowanej klasy ForceField w Studio, aby zapobiec obrażeniom graczy podczas wyboru blastera. Zapewnia to, że jedynym wymaganiem dla graczy, aby spawnować się z polem siłowym, jest uwzględnienie lokalizacji spawnu z właściwością SpawnLocation.Duration, która jest większa niż 0. Przykład używa arbitralnej wartości 9,999, aby włączyć pola siłowe, a następnie obsługuje rzeczywistą długość programowo w ReplicatedStorage ⟩ ForceFieldClientVisuals.
Podobnie jak w setupHumanoidAsync, większość linii w ForceFieldClientVisuals jest opcjonalna. Na przykład, jeśli skomentujesz zawartość funkcji, jak poniższy skrypt, gra używa domyślnego błyszczącego pola siłowego zamiast heksagonalnego skryptu w 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
endPonieważ niestandardowe pole siłowe jest interfejsem GUI, a nie nowym ParticleEmitter, skrypt ForceFieldClientVisuals wpływa tylko na wizualizacje w trybie pierwszej osoby dla każdego gracza, nie na wizualizacje w trybie trzeciej osoby, gdy gracze patrzą na innych graczy. Wizualizacje w trybie trzeciej osoby zachowują domyślny wygląd Roblox. Aby uzyskać więcej informacji na temat modyfikowania pól siłowych, zobacz ForceField.Visible.


Pola siłowe są przydatne, ponieważ dają graczom wystarczająco dużo czasu między spawnowaniem a respawnowaniem, nie martwiąc się o wrogich graczy, ale w końcu muszą zniknąć, aby umożliwić główną rozgrywkę w laser tag. Skrypt, który obsługuje usuwanie pól siłowych, znajduje się w ReplicatedStorage ⟩ scheduleDestroyForceField, i sprawdza trzy unikalne warunki:
- Po tym, jak gracze wybiorą blaster, pola siłowe muszą trwać wystarczająco długo, aby gracze mogli przyzwyczaić się do otoczenia.
- W tym czasie aklimatyzacji pola siłowe nie mogą być przewagą, więc muszą zniknąć w momencie, gdy gracz wystrzeli swój blaster.
- Pola siłowe muszą zniknąć, gdy gracze resetują swoje postacie, zarówno przed wystrzałem, jak i przed upływem czasu pola siłowego.
Każde z tych sprawdzeń w skrypcie scheduleDestroyForceField wywołuje endForceField() dla tych warunków.
-- Zakończ pole siłowe, jeśli gracz wystrzeli
local blasterStateAttribute = getBlasterStateAttribute()
attributeChangedConnection = player:GetAttributeChangedSignal(blasterStateAttribute):Connect(function()
local currentBlasterState = player:GetAttribute(blasterStateAttribute)
if currentBlasterState == BlasterState.Blasting then
endForceField()
end
end)
-- Zakończ pole siłowe, jeśli gracz resetuje
characterRespawnedConnection = player.CharacterRemoving:Connect(endForceField)
-- Zakończ pole siłowe po 8 sekundach
task.delay(MAX_FORCE_FIELD_TIME, endForceField)endForceField() zawiera pozornie dziwne if w okół boolean forceFieldEnded. Ponieważ sprawdzenia są wykonywane sekwencyjnie, skrypt może wywołać funkcję endForceField() dwa lub nawet trzy razy. Boolean forceFieldEnded zapewnia, że funkcja próbuje zniszczyć pole siłowe tylko raz.
local function endForceField()
if forceFieldEnded then
return
end
forceFieldEnded = true
attributeChangedConnection:Disconnect()
characterRespawnedConnection:Disconnect()
destroyForceField(player)
endObsługa stanu klienta
Podczas gdy większość tej sekcji koncentruje się na ServerScriptService ⟩ PlayerStateHandler, istnieje inny skrypt o tej samej nazwie w ReplicatedStorage. Powód podziału wynika z architektury klient-serwer:
Klient musi rozumieć informacje o stanie gracza, aby mógł odpowiednio reagować w czasie rzeczywistym, na przykład wyświetlając odpowiednie elementy interfejsu użytkownika lub umożliwiając graczom poruszanie się i strzelanie.
Serwer potrzebuje wszystkich tych samych informacji, aby mógł zapobiegać oszustwom. Na przykład, serwer również potrzebuje stanu gracza, aby wykonywać takie działania jak spawnowanie i wyposażanie postaci, wyłączanie pól siłowych i wyświetlanie tablicy wyników. Dlatego ten skrypt znajduje się w ReplicatedStorage, a nie w czysto klienckiej lokalizacji.
Aby zobaczyć tę podstawową logikę, przejrzyj poniższy skrypt w ReplicatedStorage ⟩ PlayerStateHandler, który weryfikuje aktualny stan użytkownika, a następnie wywołuje odpowiednią funkcję, która obsługuje odpowiadające akcje dla tego stanu.
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(`Nieprawidłowy stan gracza ({newPlayerState})`)
end
endWszystkie odpowiedzi na zdarzenia są logicznie grupowane w tym skrypcie, ponieważ wymagają podobnego zachowania, polegającego na włączaniu lub wyłączaniu kontroli gracza, ruchu kamery i widoczności warstwy interfejsu użytkownika. Na przykład, podczas wyboru blastera gracze muszą być zarówno niewrażliwi, jak i niezdolni do poruszania się. Serwer już obsługuje pole siłowe, ale klient obsługuje ruch. Aby to zobrazować, jeśli sprawdzisz logikę funkcji onSelectingBlaster(), możesz zobaczyć, że klient wyłącza ruch gracza, gdy wybiera blaster.
local function onSelectingBlaster()
togglePlayerCamera(true)
togglePlayerMovement(false)
setGuiExclusivelyEnabled(playerGui.PickABlasterGui)
localPlayer:SetAttribute(PlayerAttribute.blasterStateClient, BlasterState.Disabled)
endFunkcja onPlaying() jest podobnie prosta. Włącza ruch, przechodzi do głównego wyświetlacza (HUD), włącza blaster i wywołuje tę samą funkcję pola siłowego, co serwer.
local function onPlaying()
togglePlayerMovement(true)
setGuiExclusivelyEnabled(playerGui.HUDGui)
localPlayer:SetAttribute(PlayerAttribute.blasterStateClient, BlasterState.Ready)
scheduleDestroyForceField()
endRespawn postaci
Przykładowa gra w laser tag obsługuje respawn postaci z powrotem do rundy przez stan onTaggedOut() w ReplicatedStorage ⟩ PlayerStateHandler. Podobnie jak w stanach onSelectingBlaster() i onPlaying(), onTaggedOut() wyzwala unikalne zachowanie zgodnie ze zmianami atrybutu playerState. Konkretnie, wyłącza ruch gracza, prezentuje interfejs użytkownika respawnu i wyłącza blaster.
local function onTaggedOut()
-- Wyłącz kontrole podczas wyeliminowania
togglePlayerMovement(false)
togglePlayerCamera(false)
setGuiExclusivelyEnabled(playerGui.OutStateGui)
-- Wyłącz blaster podczas wyeliminowania
localPlayer:SetAttribute(PlayerAttribute.blasterStateClient, BlasterState.Disabled)
endJeśli chcesz przetestować to zachowanie, możesz nacisnąć Esc, przejść do zakładki Ustawienia, a następnie kliknąć przycisk Resetuj postać. Zauważ, że gdy wywołasz ekran respawnu, nie możesz się poruszać, obracać kamery ani strzelać swoim blasterem.


Ważne jest, aby zauważyć, że ten skrypt nie respawnuje postaci, tylko zatrzymuje je przed działaniem i zapewnia wizualne informacje zwrotne dla graczy, że serwer respawnuje ich postacie. Aby to zobrazować, jeśli zbadujesz ServerScriptService ⟩ SetupHumanoid ⟩ setupHumanoidAsync ⟩ onHumanoidDied, skrypt ustawia PlayerState na TaggedOut (w zasadzie powiadamiając ReplicatedStorage ⟩ PlayerStateHandler), i dodaje kilka wskaźników wizualnych. Rzeczywista logika respawnowania jest wbudowanym zachowaniem Roblox.
Gdy gracze respawnuje się z powrotem do rundy, respawnuje się w lokalizacji spawnu swojej drużyny zgodnie z właściwością SpawnLocation.TeamColor. Aby dostosować czas respawnu, możesz dodać następującą linię na początku SetupHumanoid. Aby dowiedzieć się więcej o tej technice, zobacz Players.RespawnTime.
local Players = game:GetService("Players")
Players.RespawnTime = 10 -- nowa linia, w sekundachRóżne ustawienia
W ramach początkowej konfiguracji przykładowa gra w laser tag wykonuje również kilka małych, ale kluczowych kroków:
Gra zawiera pusty skrypt o nazwie StarterPlayer ⟩ StarterCharacterScripts ⟩ Health, który wyłącza domyślną regenerację zdrowia Roblox. Aby uzyskać wyjaśnienie zachowania tej właściwości, zobacz Humanoid.Health.
Gra używa kamery w trybie pierwszej osoby, ustawiając właściwość StarterPlayer.CameraMode.LockFirstPerson. Zauważ, że jeśli chcesz pozwolić użytkownikom zmieniać między kamerą w trybie pierwszej a trzeciej osoby, musisz zmieniać tę właściwość programowo, a nie tylko ustawiać ją raz w Studio, oraz modyfikować kontrolki i interfejs użytkownika, aby dostosować się do zmiany perspektywy.
Gra korzysta z wbudowanej tablicy wyników Roblox z jednostką "punkty", które gracze zdobywają za każdym razem, gdy wyeliminują innego gracza. Możesz zobaczyć konfigurację w ServerScriptService ⟩ SetupLeaderboard, ale Tablice wyników w grze oferują pełny przegląd. Zauważ, że onPlayerTagged dodaje punkty do tablicy wyników, o czym dowiesz się w Dodaj rundy i Wykryj trafienia.
Teraz, gdy gracze mogą się spawnować, wybierać blaster i celować z perspektywy pierwszej osoby, następna sekcja nauczy cię o skryptach związanych z tworzeniem rozgrywki opartej na rundach.