Spawnowanie to proces tworzenia obiektu lub postaci w doświadczeniu, a respawnowanie to proces ponownego dodawania obiektu lub postaci do doświadczenia po spełnieniu warunku usunięcia, takim jak zdrowie postaci osiągające zero lub spadnięcie z mapy. Oba procesy są ważne, ponieważ zapewniają graczom możliwość dołączenia do doświadczenia i kontynuowania gry w celu poprawy swoich umiejętności.
Używając przykładowego doświadczenia tagu laserowego jako odniesienia, ta część samouczka uczy, jak korzystać z wbudowanych funkcji Roblox do obsługi spawnowania i respawnowania, w tym wskazówki dotyczące skryptowania:
- Konfigurowanie miejsc spawnowania, aby gracze mogli spawnować się tylko w strefie spawnowania swojej drużyny.
- Dodawanie nowych graczy i ich postaci do rundy w miarę dołączania do doświadczenia.
- Dostosowywanie pól siłowych, które zapobiegają obrażeniom podczas spawnowania i respawnowania graczy.
- Obsługa stanu klienta, aby rozgrywka działała poprawnie w odpowiednim czasie.
- Respawnowanie postaci po tym, jak zostały wykluczone z rundy.
- Wykonywanie małych, różnorodnych czynności, które są kluczowe dla ustawienia parametrów rozgrywki i postaci.
Ta sekcja zawiera wiele treści skryptowych, ale zamiast pisać wszystko od podstaw przy tworzeniu doświadczenia, zachęca do wykorzystania istniejących komponentów, szybkiego iterowania i określania, które systemy wymagają niestandardowej implementacji, aby dopasować się do twojej wizji. Po ukończeniu tej sekcji nauczysz się, jak zaimplementować rozgrywkę opartą na rundach, która śledzi punkty, monitoruje stan gracza i wyświetla wyniki rundy.
Konfiguracja miejsc spawnowania
Jeśli teraz przetestujesz doświadczenie, wszyscy gracze będą się losowo spawnować w obiekcie SpawnLocation w strefie spawnowania drużyny zielonej lub w obiekcie SpawnLocation w strefie spawnowania drużyny różowej. Tworzy to problem z rozgrywką, gdzie gracze mogą oznaczać się nawzajem w każdej strefie spawnowania, gdy tylko pole siłowe ich przeciwnika zniknie.
Aby przeciwdziałać temu problemowi, przykładowe doświadczenie tagu laserowego konfiguruje oba miejsca spawnowania z właściwością Neutral ustawioną na false w celu ograniczenia graczy przeciwnej drużyny przed spawnowaniem się w niewłaściwej strefie spawnowania oraz właściwością TeamColor ustawioną na odpowiadającą wartość Team.Color z Przypisz kolory drużynom w poprzedniej części samouczka:
- TeamASpawn – Miejsce spawnowania w strefie spawnowania drużyny zielonej z właściwością TeamColor ustawioną na Mint.
- TeamBSpawn – Miejsce spawnowania w strefie spawnowania drużyny różowej z właściwością TeamColor ustawioną na Carnation Pink.


Gdy gracz dołącza do doświadczenia, ServerScriptService ⟩ Gameplay ⟩ Rounds ⟩ spawnPlayersInMap sprawdza, ilu graczy jest już w każdej drużynie, a następnie zwraca drużynę z najmniejszą liczbą graczy.
spawnPlayersInMap
local function getSmallestTeam(): Team
local teams = Teams:GetTeams()
-- Sort teams in ascending order from smallest to largest
table.sort(teams, function(teamA: Team, teamB: Team)
return #teamA:GetPlayers() < #teamB:GetPlayers()
end)
-- Return the smallest team
return teams[1]
end
Gdy już wie, która drużyna ma najmniej graczy, przypisuje gracza do tej drużyny, ustawia ich właściwość Player.Neutral na false, aby gracz mógł spawnować się i respawnować tylko w strefie spawnowania swojej drużyny, a następnie ustawia ich PlayerState na SelectingBlaster, o czym dowiesz się więcej później w samouczku.
spawnPlayersInMap
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
end
Jeśli zbadacie Workspace ⟩ World ⟩ Map ⟩ Spawns, możecie zobaczyć, że w mapie jest jeszcze jedno miejsce spawnowania: NeutralSpawn. To miejsce spawnowania różni się od innych, ponieważ nie ma właściwości TeamColor ustawionej na jedną z dwóch drużyn w doświadczeniu; zamiast tego, to miejsce spawnowania 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 ustawiana na false, aby spawnPlayersInMap mogło przypisać 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 ustawiana jest na true, aby gracze mogli się tam spawnować niezależnie od statusu drużyny. Ten proces sprawia, że miejsce spawnowania Neutral jest funkcjonalnym lobby.

Aby to zobrazować, jeśli zbadacie ServerScriptService ⟩ Gameplay ⟩ Rounds ⟩ SpawnPlayersInLobby, które działa na końcu rundy, możecie 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 miejsca spawnowania jest również ustawiona na true.
- Zmienia ich PlayerState na InLobby, aby usunąć blaster gracza i wizualne elementy UI z widoku pierwszej osoby.
Aby uzyskać więcej informacji na temat neutralnej strefy spawnowania i jej funkcjonalności w każdej rundzie, zobacz Dodawanie rund w następnej części samouczka.
spawnPlayersInLobby
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
Połączenie nowych graczy
Kod Luau w Studio często oparty jest na zdarzeniach, co oznacza, że skrypty nasłuchują zdarzeń z usługi Roblox, a następnie wywołują funkcję w odpowiedzi. Na przykład, przy dodawaniu nowych graczy do wieloosobowego doświadczenia, musi być zdarzenie, które obsługuje wszystko, co jest potrzebne, aby gracze mogli się pomyślnie połączyć. W przykładowym doświadczeniu tagu laserowego tym odpowiadającym zdarzeniem jest Players.PlayerAdded:Connect.
Players.PlayerAdded:Connect jest częścią wielu skryptów w doświadczeniu. 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 doświadczenia.

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 (respawnowanie). Po znalezieniu jednej, 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 na osiągnięcie zdrowia postaci zera. Dowiesz się więcej o respawnowaniu później w tej części samouczka.
setupHumanoidAsync
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)
end
Ważna uwaga dotycząca tego skryptu polega na tym, że właściwości są całkowicie opcjonalne, co oznacza, że jeśli usuniesz pierwsze sześć linii funkcji, doświadczenie wciąż będzie działać poprawnie. Zamiast być wymaganiami funkcjonalnymi, każda właściwość pozwala podejmować decyzje projektowe, które spełnią 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 przeprowadzić testy, aby zobaczyć wpływ nowych ustawień. Możesz powtórzyć to, co gracze doświadczają w symulacji wielu klientów poprzez zaznaczenie co najmniej dwóch postaci w teście Server & Clients z mezzaniny.

Kolejnym przykładem zdarzenia Players.PlayerAdded:Connect jest w ServerScriptService ⟩ PlayerStateHandler. Tak jak w poprzednim przykładzie, PlayerStateHandler natychmiast sprawdza, czy jest postać. Jeśli gracz nie jest w lobby, skrypt ustawia atrybut gracza na stan SelectingBlaster, początkowy stan rundy, w której gracze mogą wybierać jeden z dwóch typów blasterów po spawnowaniu się na arenie. Ten stan obejmuje również pole siłowe, które zapobiega obrażeniom, podczas gdy gracze dokonują swojego wyboru.
PlayerStateHandler
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 doświadczenie. Ten proces służy jako rodzaj zarządzania pamięcią, aby zapobiec zbyt dużemu wzrostowi liczby połączeń z czasem.
PlayerStateHandler
local attributeChangedConnectionByPlayer = {}
local function onPlayerAdded(player: Player)
-- Handle all future updates to player state
attributeChangedConnectionByPlayer[player] = player
:GetAttributeChangedSignal(PlayerAttribute.playerState)
:Connect(function()
local newPlayerState = player:GetAttribute(PlayerAttribute.playerState)
onPlayerStateChanged(player, newPlayerState)
end)
end
-- Disconnect from the attribute changed connection when the player leaves
local function onPlayerRemoving(player: Player)
if attributeChangedConnectionByPlayer[player] then
attributeChangedConnectionByPlayer[player]:Disconnect()
attributeChangedConnectionByPlayer[player] = nil
end
end
Możesz zauważyć, że obie połączone funkcje w onPlayerAdded() wywołują onPlayerStateChanged(). Podczas początkowej konfiguracji, gdy gracz jest przypisany do drużyny, onPlayerAdded() ustawia PlayerState na SelectingBlaster, więc pierwsze oświadczenie if ocenia się jako fałsz i wyłącza BlasterState. W późniejszej sekcji Implementacja blasterów dowiesz się więcej szczegółów na temat tego procesu.
PlayerStateHandler
local function onPlayerStateChanged(player: Player, newPlayerState: string)
-- Blaster state is 'Ready' only if player state is 'Playing'
local newBlasterState = if newPlayerState == PlayerState.Playing then BlasterState.Ready else BlasterState.Disabled
-- Schedule the destroy force field logic when the player begins playing
if newPlayerState == PlayerState.Playing then
scheduleDestroyForceField(player)
end
player:SetAttribute(PlayerAttribute.blasterStateServer, newBlasterState)
end
Jeśli dodasz punkty przerwania lub nawet po prostu print(), możesz zobaczyć, że onPlayerStateChanged() jest wywoływane często w całym doświadczeniu: na przykład podczas początkowej konfiguracji rundy, aby ustawić się na głównym ścieżce kodu, po tym, jak gracz wybiera blaster, oraz gdy gracz wraca do lobby lub strefy spawnowania 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().
Dostosowanie pól siłowych
Zamiast używać niestandardowej implementacji, przykładowe doświadczenie tagu laserowego wykorzystuje wbudowaną klasę ForceField w Studio do zapobiegania obrażeniom dla graczy, podczas gdy wybierają swój blaster. To zapewnia, że jedynym wymaganiem dla graczy, aby spawnowali się z polem siłowym, jest uwzględnienie miejsc spawnowania z właściwością SpawnLocation.Duration większą niż 0. Przykład używa dowolnej wartości 9,999, aby włączyć pola siłowe, a następnie obsługuje rzeczywistą długość trwania programowo w ReplicatedStorage ⟩ ForceFieldClientVisuals.
Podobnie jak w przypadku setupHumanoidAsync, większość linii w ForceFieldClientVisuals jest opcjonalna. Na przykład, jeśli skomentujesz zawartość funkcji jak poniższy skrypt, doświadczenie użyje domyślnego iskrzącego się pola siłowego zamiast heksagonalnego skryptu w StarterGui ⟩ ForceFieldGui.
Commenting Out Properties in ForceFieldClientVisuals
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
end
Ponieważ niestandardowe pole siłowe to GUI zamiast nowego ParticleEmitter, skrypt ForceFieldClientVisuals wpływa tylko na wizualizacje w widoku pierwszej osoby dla każdego gracza, nie na wizualizacje w widoku trzeciej osoby, gdy gracze patrzą na innych graczy. Wizualizacje w widoku 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ż zapewniają graczom wystarczająco dużo czasu między spawnowaniem a respawnowaniem bez konieczności martwienia się o graczy wrogów, ale w końcu muszą zniknąć, aby główna rozgrywka tagu laserowego mogła się toczyć. Skrypt obsługujący 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ą stanowić przewagi, więc muszą zniknąć w momencie, gdy gracz wystrzeli swój blaster.
- Pola siłowe muszą zniknąć, gdy gracze resetują swoje postacie, albo przed wystrzałem, albo przed upływem czasu pola siłowego.
Każde z tych sprawdzeń w skrypcie scheduleDestroyForceField wywołuje endForceField() dla tych warunków.
scheduleDestroyForceField
-- End force field if player blasts
local blasterStateAttribute = getBlasterStateAttribute()
attributeChangedConnection = player:GetAttributeChangedSignal(blasterStateAttribute):Connect(function()
local currentBlasterState = player:GetAttribute(blasterStateAttribute)
if currentBlasterState == BlasterState.Blasting then
endForceField()
end
end)
-- End force field if player resets
characterRespawnedConnection = player.CharacterRemoving:Connect(endForceField)
-- End force field after 8 seconds
task.delay(MAX_FORCE_FIELD_TIME, endForceField)
endForceField() zawiera zdaje się dziwne oświadczenie if otaczające 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.
scheduleDestroyForceField
local function endForceField()
if forceFieldEnded then
return
end
forceFieldEnded = true
attributeChangedConnection:Disconnect()
characterRespawnedConnection:Disconnect()
destroyForceField(player)
end
Obsługa stanu klienta
Chociaż większość tej sekcji koncentruje się na ServerScriptService ⟩ PlayerStateHandler, istnieje również inny skrypt o tej samej nazwie w ReplicatedStorage. Powód dla podziału to architektura klient-serwer:
Klient musi rozumieć informacje o stanie gracza, aby móc odpowiednio reagować w czasie rzeczywistym, takie jak wyświetlanie odpowiednich elementów interfejsu użytkownika lub umożliwienie graczom poruszania się i wystrzeliwania.
Serwer potrzebuje wszystkich tych samych informacji, aby mógł zapobiegać oszustwom. Na przykład, serwer potrzebuje także stanu gracza, aby wykonywać działania takie jak spawnowanie i wyposażanie postaci, wyłączanie pól siłowych oraz wyświetlanie tablicy wyników. To dlatego ten skrypt znajduje się w ReplicatedStorage, a nie w czysto klienckiej lokalizacji.
Aby zobaczyć tę główną logikę, przeglądaj następujący 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.
PlayerStateHandler
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
end
Wszystkie odpowiedzi zdarzeń są logicznie grupowane razem w tym skrypcie, ponieważ wymagają podobnego zachowania umożliwiającego lub wyłączającego kontrole gracza, ruch kamery i warstwę interfejsu użytkownika, która jest widoczna. Na przykład, podczas wyboru blastera, gracze muszą być zarówno niezwyciężeni, jak i niezdolni do poruszania się. Serwer już obsługuje pole siłowe, ale klient obsługuje ruch. Aby zobrazować to, jeśli sprawdzisz logikę funkcji onSelectingBlaster(), możesz zobaczyć, że klient wyłącza ruch gracza podczas wyboru blastera.
PlayerStateHandler
local function onSelectingBlaster()
togglePlayerCamera(true)
togglePlayerMovement(false)
setGuiExclusivelyEnabled(playerGui.PickABlasterGui)
localPlayer:SetAttribute(PlayerAttribute.blasterStateClient, BlasterState.Disabled)
end
Funkcja onPlaying() jest równie 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.
PlayerStateHandler
local function onPlaying()
togglePlayerMovement(true)
setGuiExclusivelyEnabled(playerGui.HUDGui)
localPlayer:SetAttribute(PlayerAttribute.blasterStateClient, BlasterState.Ready)
scheduleDestroyForceField()
end
Respawnowanie postaci
Przykładowe doświadczenie tagu laserowego obsługuje respawnowanie postaci z powrotem do rundy przez stan onTaggedOut() w ReplicatedStorage ⟩ PlayerStateHandler. Podobnie jak stany onSelectingBlaster() i onPlaying(), onTaggedOut() uruchamia unikalne zachowanie zgodnie z zmianami atrybutu playerState. Konkretnie, wyłącza ruch gracza, wyświetla interfejs użytkownika respawnowania i wyłącza blaster.
PlayerStateHandler
local function onTaggedOut()
-- Disable controls while tagged out
togglePlayerMovement(false)
togglePlayerCamera(false)
setGuiExclusivelyEnabled(playerGui.OutStateGui)
-- Disable blaster while tagged out
localPlayer:SetAttribute(PlayerAttribute.blasterStateClient, BlasterState.Disabled)
end
Jeśli chcesz przetestować to zachowanie, możesz nacisnąć Esc, przejść do zakładki Ustawienia, a następnie kliknąć przycisk Resetuj postać. Zauważ, że po wywołaniu ekranu respawn nie możesz się poruszać, obracać kamery ani strzelać z blastera.


Ważne jest, aby zauważyć, że ten skrypt nie rzeczywiście respawnuje postaci, tylko zatrzymuje ich działanie i dostarcza wizualne informacje dla graczy, że serwer respawnuje ich postacie. Aby to zobrazować, jeśli zbadacie ServerScriptService ⟩ SetupHumanoid ⟩ setupHumanoidAsync ⟩ onHumanoidDied, skrypt ustawia PlayerState na TaggedOut (w zasadzie informując ReplicatedStorage ⟩ PlayerStateHandler), i dodaje pewne wskaźniki wizualne. Rzeczywista logika respawnowania to wbudowane zachowanie Roblox.
Gdy gracze respawnują się z powrotem do rundy, respawnują się w strefie spawnowania swojej drużyny zgodnie z właściwością SpawnLocation.TeamColor. Aby dostosować czas respawnowania, możesz dodać następującą linię na górze SetupHumanoid. Aby dowiedzieć się więcej o tej technice, zobacz Players.RespawnTime.
SetupHumanoidlocal Players = game:GetService("Players")Players.RespawnTime = 10 -- nowa linia, w sekundach
Różne konfiguracje
W ramach początkowej konfiguracji, przykładowe doświadczenie tagu laserowego również wykonuje kilka małych, ale kluczowych kroków:
Doświadczenie zawiera pusty skrypt o nazwie StarterPlayer ⟩ StarterCharacterScripts ⟩ Health, który wyłącza domyślną regenerację zdrowia Roblox. Aby uzyskać wyjaśnienie działania tej właściwości, zobacz Humanoid.Health.
Doświadczenie wykorzystuje kamerę w widoku pierwszej osoby, ustawiając właściwość StarterPlayer.CameraMode.LockFirstPerson. Należy pamiętać, że jeśli chcesz pozwolić użytkownikom na zmianę między kamerą w widoku pierwszej i trzeciej osoby, musisz zmieniać właściwość programowo, a nie tylko ustawiać ją raz w Studio oraz zmodyfikować kontrolki i interfejs użytkownika, aby uwzględnić zmianę perspektywy.
Doświadczenie korzysta z wbudowanej tablicy wyników Roblox z jednostką "punkty", które gracze zdobywają za każdym razem, gdy wypiszą innego gracza. Możesz zobaczyć konfigurację w ServerScriptService ⟩ SetupLeaderboard, ale Tablice wyników w doświadczeniu oferują pełny przegląd. Należy pamiętać, że onPlayerTagged dodaje punkty do tablicy wyników, o czym dowiesz się w Dodaj rundy i Wykryj trafienia.
Teraz, gdy gracze mogą spawnować się, wybierać blaster i celować z pierwszoosobowego punktu widzenia, następna sekcja nauczy cię o skryptach stojących za tworzeniem rozgrywki opartej na rundach.