Spawnowanie i respawnowanie

*Ta zawartość została przetłumaczona przy użyciu narzędzi AI (w wersji beta) i może zawierać błędy. Aby wyświetlić tę stronę w języku angielskim, kliknij tutaj.

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.
TeamASpawn
TeamBSpawn

Gdy gracz dołącza do doświadczenia, ServerScriptServiceGameplayRoundsspawnPlayersInMap 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 WorkspaceWorldMapSpawns, 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.

Neutral

Aby to zobrazować, jeśli zbadacie ServerScriptServiceGameplayRoundsSpawnPlayersInLobby, 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.

Okno Find All w Studio z podświetlonymi wynikami Players.PlayerAdded.

Aby to zobrazować, otwórz ServerScriptServiceSetupHumanoid. Różnica między Player a Character jest kluczowa dla zrozumienia tego skryptu:

  • gracz to podłączony klient, a postać to model Humanoid.
  • 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 ServerScriptServiceSetupHumanoidsetupHumanoidAsync 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 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.

Opcja Serwer i Klienci w rozwijanej liście trybów testowania w mezzaninie Studio.

Kolejnym przykładem zdarzenia Players.PlayerAdded:Connect jest w ServerScriptServicePlayerStateHandler. 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, ServerScriptServiceBlasterSelectedHandler 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 ReplicatedStorageForceFieldClientVisuals.

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 StarterGuiForceFieldGui.

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.

Wizualizacje pól siłowych w widoku pierwszej osoby obejmują futurystyczną heksagonalną siatkę na obrzeżu ekranu.
Wizualizacje pól siłowych w widoku pierwszej osoby
Wizualizacje pól siłowych w widoku trzeciej osoby obejmują niebieską iskrzącą się kulę wokół gracza wchodzącego do doświadczenia.
Wizualizacje pól siłowych w widoku trzeciej osoby

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 ReplicatedStoragescheduleDestroyForceField, 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 ServerScriptServicePlayerStateHandler, 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 ReplicatedStoragePlayerStateHandler, 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 ReplicatedStoragePlayerStateHandler. 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.

Menu ustawień Roblox z wyróżnionym przyciskiem Resetuj postać.
Przycisk Resetuj postać
Ekran respawn wyświetla się, gdy gracz respawnuje z powrotem do rundy.
Ekran respawn

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 ServerScriptServiceSetupHumanoidsetupHumanoidAsynconHumanoidDied, skrypt ustawia PlayerState na TaggedOut (w zasadzie informując ReplicatedStoragePlayerStateHandler), 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.

SetupHumanoid

local 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 StarterPlayerStarterCharacterScriptsHealth, 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 ServerScriptServiceSetupLeaderboard, 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.

©2026 Roblox Corporation. Nazwa Roblox, logo Roblox oraz hasło „Powering Imagination” należą do naszych zarejestrowanych i niezarejestrowanych znaków towarowych na terenie Stanów Zjednoczonych oraz w innych krajach.