Spawning è il processo di creazione di un oggetto o personaggio in un'esperienza, e respawning è il processo di aggiunta di un oggetto o personaggio di nuovo in un'esperienza dopo aver soddisfatto una condizione di rimozione, come la salute di un personaggio che raggiunge zero o cade dalla mappa. Entrambi i processi sono importanti perché garantiscono che i giocatori possano unirsi alla tua esperienza e possano continuare a giocare per migliorare le loro abilità.
Utilizzando l'esperienza di laser tag di esempio come riferimento, questa sezione del tutorial ti insegnerà come utilizzare e personalizzare le funzionalità integrate di Roblox per gestire il spawning e il respawning, inclusa la guida alla scripting su:
- Configurare le posizioni di spawning in modo che i giocatori possano spawnare solo nella zona di spawning della loro squadra.
- Aggiungere nuovi giocatori e il loro personaggio al turno mentre si uniscono all'esperienza.
- Personalizzare i campi di forza che prevengono danni mentre i giocatori spawnano e respawnano.
- Gestire lo stato del client affinché il gameplay funzioni correttamente al momento appropriato.
- Respawnare i personaggi dopo che sono stati eliminati dal turno.
- Eseguire piccole azioni miscellanee che sono cruciali per impostare i parametri di gameplay e personaggio.
Questa sezione include molto contenuto di scripting, ma invece di scrivere tutto da zero quando crei un'esperienza, ti incoraggia a sfruttare i componenti esistenti, iterare rapidamente e capire quali sistemi necessitano di un'implementazione personalizzata per corrispondere alla tua visione. Dopo aver completato questa sezione, imparerai come implementare un gameplay a turni che tiene traccia dei punti, monitora lo stato dei giocatori e visualizza i risultati del turno.
Configura le posizioni di spawning
Se dovessi testare l'esperienza in questo momento, tutti i giocatori spawnerebbero casualmente all'interno dell'oggetto SpawnLocation nella zona di spawning della squadra verde, o all'interno dell'oggetto SpawnLocation nella zona di spawning della squadra rosa. Questo presenta un problema di gameplay in cui i giocatori potrebbero eliminarsi a vicenda all'interno di ciascuna zona di spawning non appena il campo di forza del loro avversario scompare.
Per combattere questo problema, l'esperienza di laser tag di esempio configura entrambe le posizioni di spawning con una proprietà Neutral impostata su false per limitare i giocatori della squadra avversaria dallo spawnare nella zona di spawning sbagliata, e una proprietà TeamColor impostata sul valore corrispondente di Team.Color da Assegna colori di squadra nella sezione precedente del tutorial:
- TeamASpawn – La posizione di spawning nella zona di spawning della squadra verde con una proprietà TeamColor impostata su Mint.
- TeamBSpawn – La posizione di spawning nella zona di spawning della squadra rosa con una proprietà TeamColor impostata su Carnation Pink.


Quando un giocatore si unisce all'esperienza, ServerScriptService ⟩ Gameplay ⟩ Rounds ⟩ spawnPlayersInMap controlla quanti giocatori sono già in ciascuna squadra, quindi restituisce la squadra con il minor numero di giocatori.
spawnPlayersInMap
local function getSmallestTeam(): Team
local teams = Teams:GetTeams()
-- Ordina le squadre in ordine crescente da quella più piccola a quella più grande
table.sort(teams, function(teamA: Team, teamB: Team)
return #teamA:GetPlayers() < #teamB:GetPlayers()
end)
-- Restituisce la squadra più piccola
return teams[1]
end
Una volta che sa quale squadra ha il minor numero di giocatori, la sistema in quella squadra, imposta la sua proprietà Player.Neutral su false in modo che il giocatore possa solo spawnare e respawnare nella posizione di spawning della sua squadra, quindi imposta il suo PlayerState su SelectingBlaster, di cui imparerai di più più avanti nel tutorial.
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
Se esamini Workspace ⟩ World ⟩ Map ⟩ Spawns, puoi vedere che c'è un'altra posizione di spawning nella mappa: NeutralSpawn. Questa posizione di spawning è unica rispetto alle altre perché non ha una proprietà TeamColor impostata su una delle due squadre nell'esperienza; invece, questa posizione di spawning ha una proprietà Neutral che cambia a seconda se un turno è attivo.
Ad esempio, se il turno è attivo, la proprietà Neutral è impostata su false, in modo che spawnPlayersInMap possa smistare i giocatori nelle squadre e spawnarli nell'arena. Tuttavia, se il turno non è attivo, come nel tempo trascorso tra un turno e l'altro, la proprietà Neutral è impostata su true, quindi i giocatori possono spawnare lì indipendentemente dal loro stato di squadra. Questo processo rende la posizione di spawning Neutral una lobby funzionale.

Per dimostrare, se esamini ServerScriptService ⟩ Gameplay ⟩ Rounds ⟩ SpawnPlayersInLobby, che viene eseguito alla fine di un turno, puoi vedere che per ogni giocatore passato nel tavolo players: { Player }, lo script:
- Imposta la loro proprietà Player.Neutral su true per reimpostare automaticamente la loro Player.Team su nil, consentendo al giocatore di respawnare nella lobby quando un turno non è attivo, poiché la proprietà Neutral della posizione di spawning è impostata su true.
- Cambia il loro PlayerState in InLobby per rimuovere il blaster del giocatore e le visualizzazioni dell'interfaccia utente in prima persona.
Per ulteriori informazioni sulla zona di spawning neutrale e sulla sua funzionalità per ciascun turno, vedere Aggiunta dei Turni nella prossima sezione del tutorial.
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
Collega nuovi giocatori
Il codice Luau in Studio è spesso guidato da eventi, il che significa che gli script ascoltano eventi da un servizio di Roblox, quindi chiamano una funzione in risposta. Ad esempio, quando si aggiungono nuovi giocatori a un'esperienza multiplayer, deve esserci un evento che gestisca tutto il necessario affinché i giocatori si connettano con successo. Nell'esperienza di laser tag di esempio, questo evento corrispondente è Players.PlayerAdded:Connect.
Players.PlayerAdded:Connect è parte di più script nell'esperienza. Se usi la scorciatoia Ctrl/Cmd+Shift+F e cerchi Players.PlayerAdded:Connect, i risultati forniscono un buon punto di partenza per comprendere la configurazione iniziale dell'esperienza.

Per dimostrare, apri ServerScriptService ⟩ SetupHumanoid. La distinzione tra Player e Character è fondamentale per comprendere questo script:
- I giocatori devono scegliere un blaster ed essere aggiunti alla classifica. I personaggi devono spawnare e ricevere un blaster.
SetupHumanoid controlla immediatamente se il giocatore ha un personaggio (appena unito) o non ce l'ha (sta respawnando). Dopo averne trovato uno, chiama onCharacterAdded(), ottiene il modello Humanoid dal personaggio e lo passa a ServerScriptService ⟩ SetupHumanoid ⟩ setupHumanoidAsync per la personalizzazione. Dopo aver impostato questi valori, lo script aspetta che la salute del personaggio raggiunga zero. Imparerai di più sul respawning più avanti in questa sezione del tutorial.
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
La nota importante su questo script è che le proprietà sono completamente facoltative, il che significa che se rimuovi le prime sei righe della funzione, l'esperienza funziona comunque correttamente. Piuttosto che essere requisiti funzionali, ciascuna proprietà ti consente di prendere decisioni di design che soddisfano i tuoi obiettivi di gameplay. Ad esempio:
- Se vuoi che i nomi dei personaggi siano visualizzati a distanze più ravvicinate, riduci il valore di Humanoid.NameDisplayDistance.
- Se vuoi che la salute di un personaggio venga visualizzata solo se è sotto il 100%, imposta Humanoid.HealthDisplayType su DisplayWhenDamaged.
- Se vuoi che i personaggi si disintegrino quando la loro salute raggiunge 0, imposta Humanoid.BreakJointsOnDeath su True.
Se cambi i valori di queste proprietà, è importante testare il gioco affinché tu possa vedere l'impatto delle nuove impostazioni. Puoi ricreare ciò che i giocatori vivono in una simulazione multi-client selezionando almeno due personaggi in un playtest Server & Clients dall'atrio.

Un altro esempio dell'evento Players.PlayerAdded:Connect si trova in ServerScriptService ⟩ PlayerStateHandler. Proprio come nel precedente esempio, PlayerStateHandler controlla immediatamente la presenza di un personaggio. Se il giocatore non è nella lobby, lo script imposta un attributo del giocatore sullo stato SelectingBlaster, lo stato iniziale per un turno in cui i giocatori possono selezionare uno dei due diversi tipi di blaster dopo essere stati spawnati nell'arena. Questo stato include anche un campo di forza che impedisce ai giocatori di subire danni mentre stanno effettuando la loro selezione.
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)
Una variabile particolare in PlayerStateHandler merita discussione: attributeChangedConnectionByPlayer. Questo tavolo memorizza tutti i giocatori e le loro Connections per il GetAttributeChangedSignal. Il motivo per cui memorizzare questa connessione in una tabella è in modo che PlayerStateHandler possa disconnetterla quando il giocatore lascia l'esperienza. Questo processo funge da una sorta di gestione della memoria per prevenire che il numero di connessioni cresca nel tempo.
PlayerStateHandler
local attributeChangedConnectionByPlayer = {}
local function onPlayerAdded(player: Player)
-- Gestisci tutti i futuri aggiornamenti allo stato del giocatore
attributeChangedConnectionByPlayer[player] = player
:GetAttributeChangedSignal(PlayerAttribute.playerState)
:Connect(function()
local newPlayerState = player:GetAttribute(PlayerAttribute.playerState)
onPlayerStateChanged(player, newPlayerState)
end)
end
-- Disconnetti dalla connessione dell'attributo cambiato quando il giocatore esce
local function onPlayerRemoving(player: Player)
if attributeChangedConnectionByPlayer[player] then
attributeChangedConnectionByPlayer[player]:Disconnect()
attributeChangedConnectionByPlayer[player] = nil
end
end
Puoi vedere che entrambe le funzioni connesse in onPlayerAdded() chiamano onPlayerStateChanged(). Durante la configurazione iniziale, dopo che un giocatore è stato smistato in una squadra, onPlayerAdded() imposta PlayerState su SelectingBlaster, quindi la prima istruzione if viene valutata come falsa e disabilita il BlasterState. Nella successiva sezione Implementa blaster del tutorial, imparerai maggiori dettagli su questo processo.
PlayerStateHandler
local function onPlayerStateChanged(player: Player, newPlayerState: string)
-- Lo stato del blaster è 'Pronto' solo se lo stato del giocatore è 'In Gioco'
local newBlasterState = if newPlayerState == PlayerState.Playing then BlasterState.Ready else BlasterState.Disabled
-- Pianifica la logica per distruggere il campo di forza quando il giocatore inizia a giocare
if newPlayerState == PlayerState.Playing then
scheduleDestroyForceField(player)
end
player:SetAttribute(PlayerAttribute.blasterStateServer, newBlasterState)
end
Se aggiungi punti di interruzione o anche solo una dichiarazione print(), puoi vedere che onPlayerStateChanged() viene chiamato frequentemente nell'esperienza: come durante la configurazione iniziale di un turno, per impostarsi sul percorso principale del codice, dopo che il giocatore sceglie un blaster e quando il giocatore torna alla lobby, o alla posizione di spawning Neutral. Inoltre, dopo che il giocatore sceglie un blaster, ServerScriptService ⟩ BlasterSelectedHandler imposta il PlayerState su Playing, e PlayerStateHandler può finalmente rimuovere il campo di forza chiamando scheduleDestroyForceField().
Personalizza i campi di forza
Invece di utilizzare un'implementazione personalizzata, l'esperienza di laser tag di esempio utilizza la classe integrata ForceField di Studio per prevenire che i giocatori subiscano danni mentre selezionano il loro blaster. Questo assicura che l'unico requisito per i giocatori di spawnare con un campo di forza sia includere posizioni di spawning con una proprietà SpawnLocation.Duration maggiore di 0. L'esempio utilizza un valore arbitrario di 9.999 per abilitare i campi di forza, quindi gestisce la durata effettiva programmaticamente in ReplicatedStorage ⟩ ForceFieldClientVisuals.
Simile a setupHumanoidAsync, la maggior parte delle righe in ForceFieldClientVisuals sono facoltative. Ad esempio, se commetti l'errore di commentare i contenuti della funzione come fa il seguente script, l'esperienza utilizza il campo di forza scintillante predefinito invece dello script esagonale in 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
Poiché il campo di forza personalizzato è un'interfaccia grafica anziché un nuovo ParticleEmitter, lo script ForceFieldClientVisuals influisce solo sulle visualizzazioni in prima persona per ciascun giocatore, non sulle visualizzazioni in terza persona quando i giocatori guardano altri giocatori. Le visualizzazioni in terza persona mantengono l'aspetto predefinito di Roblox. Per ulteriori informazioni su come modificare i campi di forza, vedere ForceField.Visible.


I campi di forza sono utili perché forniscono ai giocatori abbastanza tempo tra lo spawning e il respawning senza doversi preoccupare dei giocatori nemici, ma alla fine devono scomparire per il gameplay principale del laser tag. Lo script che gestisce la rimozione del campo di forza è in ReplicatedStorage ⟩ scheduleDestroyForceField, e controlla tre condizioni uniche:
- Dopo che i giocatori selezionano un blaster, i campi di forza devono durare a lungo abbastanza per consentire ai giocatori di adattarsi all'ambiente circostante.
- Durante questo tempo di acclimatazione, i campi di forza non possono essere un vantaggio, quindi devono scomparire nel momento in cui un giocatore spara il proprio blaster.
- I campi di forza devono scomparire quando i giocatori ripristinano i loro personaggi sia prima di sparare che prima che il campo di forza scada.
Ognuno di questi controlli nello script scheduleDestroyForceField chiama endForceField() per queste condizioni.
scheduleDestroyForceField
-- Termina il campo di forza se il giocatore spara
local blasterStateAttribute = getBlasterStateAttribute()
attributeChangedConnection = player:GetAttributeChangedSignal(blasterStateAttribute):Connect(function()
local currentBlasterState = player:GetAttribute(blasterStateAttribute)
if currentBlasterState == BlasterState.Blasting then
endForceField()
end
end)
-- Termina il campo di forza se il giocatore si resetta
characterRespawnedConnection = player.CharacterRemoving:Connect(endForceField)
-- Termina il campo di forza dopo 8 secondi
task.delay(MAX_FORCE_FIELD_TIME, endForceField)
endForceField() include una dichiarazione if apparentemente strana attorno al booleano forceFieldEnded. Poiché i controlli vengono eseguiti in sequenza, lo script può chiamare la funzione endForceField() due o persino tre volte. Il booleano forceFieldEnded garantisce che la funzione provi a distruggere un campo di forza solo una volta.
scheduleDestroyForceField
local function endForceField()
if forceFieldEnded then
return
end
forceFieldEnded = true
attributeChangedConnection:Disconnect()
characterRespawnedConnection:Disconnect()
destroyForceField(player)
end
Gestisci lo stato del client
Mentre la maggior parte di questa sezione si concentra su ServerScriptService ⟩ PlayerStateHandler, c'è un altro script dello stesso nome in ReplicatedStorage. Il motivo per cui la divisione è dovuta all'architettura client-server:
Il client deve comprendere le informazioni sullo stato del giocatore affinché possa rispondere in modo appropriato in tempo reale, come visualizzare gli elementi dell'interfaccia utente corretti o consentire ai giocatori di muoversi e sparare.
Il server ha bisogno di tutte queste stesse informazioni affinché possa prevenire exploit. Ad esempio, il server ha anche bisogno dello stato del giocatore per eseguire azioni come spawnare ed equipaggiare personaggi, disabilitare campi di forza e visualizzare una classifica. Questo è il motivo per cui questo script è in ReplicatedStorage e non in una posizione puramente client-side.
Per vedere questa logica di base, rivedi il seguente script in ReplicatedStorage ⟩ PlayerStateHandler che verifica lo stato attuale dell'utente, quindi chiama la funzione appropriata che gestisce le azioni corrispondenti per quello stato.
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
Tutte le risposte agli eventi sono logicamente raggruppate insieme in questo script perché richiedono un comportamento simile di abilitazione o disabilitazione dei controlli del giocatore, movimento della telecamera e quale livello dell'interfaccia utente è visibile. Ad esempio, durante la selezione del blaster, i giocatori devono essere sia invulnerabili che incapaci di muoversi. Il server gestisce già il campo di forza, ma il client gestisce il movimento. Per dimostrare, se controlli la logica per la funzione onSelectingBlaster(), puoi vedere che il client disabilita il movimento del giocatore mentre selezionano un blaster.
PlayerStateHandler
local function onSelectingBlaster()
togglePlayerCamera(true)
togglePlayerMovement(false)
setGuiExclusivelyEnabled(playerGui.PickABlasterGui)
localPlayer:SetAttribute(PlayerAttribute.blasterStateClient, BlasterState.Disabled)
end
La funzione onPlaying() è altrettanto semplice. Abilita il movimento, passa all'interfaccia principale (HUD), abilita il blaster e chiama la stessa funzione del campo di forza del server.
PlayerStateHandler
local function onPlaying()
togglePlayerMovement(true)
setGuiExclusivelyEnabled(playerGui.HUDGui)
localPlayer:SetAttribute(PlayerAttribute.blasterStateClient, BlasterState.Ready)
scheduleDestroyForceField()
end
Respawnare i personaggi
L'esperienza di laser tag di esempio gestisce il respawning dei personaggi in un turno tramite lo stato onTaggedOut() in ReplicatedStorage ⟩ PlayerStateHandler. Proprio come negli stati onSelectingBlaster() e onPlaying(), onTaggedOut() attiva un comportamento unico in base ai cambiamenti dell'attributo playerState. In particolare, disabilita il movimento del giocatore, presenta l'interfaccia di respawn e disabilita il blaster.
PlayerStateHandler
local function onTaggedOut()
-- Disabilita i controlli mentre è taggato
togglePlayerMovement(false)
togglePlayerCamera(false)
setGuiExclusivelyEnabled(playerGui.OutStateGui)
-- Disabilita il blaster mentre è taggato
localPlayer:SetAttribute(PlayerAttribute.blasterStateClient, BlasterState.Disabled)
end
Se vuoi testare questo comportamento, puoi premere Esc, navigare nella scheda Impostazioni, quindi fare clic sul pulsante Reset Character. Nota che quando attivi la schermata di respawn, non puoi muoverti, ruotare la telecamera o sparare con il tuo blaster.


È importante notare che questo script non respawna effettivamente i personaggi, ma ferma solo il loro comportamento e fornisce un feedback visivo ai giocatori che il server sta respawnando i loro personaggi. Per dimostrare, se esamini ServerScriptService ⟩ SetupHumanoid ⟩ setupHumanoidAsync ⟩ onHumanoidDied, lo script imposta PlayerState su TaggedOut (essenzialmente notificando ReplicatedStorage ⟩ PlayerStateHandler), e aggiunge alcuni indicatori visivi. La logica effettiva di respawning è un comportamento integrato di Roblox.
Quando i giocatori si respawnano nel turno, respawnano nella posizione di spawning della loro squadra in base alla proprietà SpawnLocation.TeamColor. Per personalizzare il tempo di respawn, puoi aggiungere la seguente riga all'inizio di SetupHumanoid. Per maggiori informazioni su questa tecnica, vedere Players.RespawnTime.
SetupHumanoidlocal Players = game:GetService("Players")Players.RespawnTime = 10 -- nuova riga, in secondi
Impostazione miscellanea
Come parte della configurazione iniziale, l'esperienza di laser tag di esempio esegue anche alcuni piccoli ma critici passaggi:
L'esperienza include uno script vuoto denominato StarterPlayer ⟩ StarterCharacterScripts ⟩ Health che disabilita la rigenerazione della salute predefinita di Roblox. Per una spiegazione del comportamento di questa proprietà, vedere Humanoid.Health.
L'esperienza usa una telecamera in prima persona impostando la proprietà StarterPlayer.CameraMode.LockFirstPerson. Nota che se desideri consentire agli utenti di cambiare tra telecamere in prima e terza persona, devi cambiare la proprietà programmaticamente anziché impostarla una sola volta in Studio, e modificare i controlli e l'interfaccia utente per compensare il cambiamento di prospettiva.
L'esperienza utilizza la classifica integrata di Roblox con l'unità di "punti", che i giocatori guadagnano ogni volta che eliminano un altro giocatore. Puoi vedere la configurazione in ServerScriptService ⟩ SetupLeaderboard, ma Classifiche in Esperienza offre una panoramica completa. Nota che onPlayerTagged aggiunge punti alla classifica, di cui imparerai nel Aggiungi turni e Rileva colpi.
Ora che i giocatori possono spawnare, scegliere un blaster e mirare da un punto di vista in prima persona, la prossima sezione ti insegnerà sugli script dietro la creazione di un gameplay basato su turni.