Criar é o processo de criar um objeto ou personagem em uma experiência, e reaparecer é o processo de adicionar um objeto ou personagem de volta a uma experiência depois que eles atendam uma condição de remoção, como a saúde de um personagem chegar a zero ou cair do mapa.Ambos os processos são importantes porque garantem que os jogadores possam se juntar à sua experiência e continuem jogando para melhorar suas habilidades.
Usando a experiência de etiqueta de laser de amostra como referência, esta seção do tutorial ensina você a usar e personalizar os recursos integrados do Roblox para lidar com o aparecimento e o respawning, incluindo orientações de script sobre:
- Configurar locais de spawn para que os jogadores possam apenas spawnar na zona de spawn de sua Equipe.
- Adicionar novos jogadores e seu personagem à rodada à medida que se juntam à experiência.
- Personalizar campos de força que impedem danos à medida que os jogadores aparecem e reaparecem.
- Manusear o estado do cliente para que o jogo funcione corretamente no momento apropriado.
- Respawnando personagens depois que eles são marcados fora da rodada.
- Realizar pequenas ações miscelâneas que são cruciais para definir parâmetros de jogabilidade e personagem.
Esta seção inclui muito conteúdo de script, mas em vez de escrever tudo do zero ao criar uma experiência, ela incentiva você a aproveitar componentes existentes, iteração rapidamente e descobrir quais sistemas precisam de uma implementação personalizada para corresponder à sua visão.Depois de concluir esta seção, você aprenderá a implementar jogabilidade baseada em rodadas que rastreia pontos, monitora o estado do jogador e exibe os resultados da rodada.
Configurar locais de spawn
Se você fosse testar a experiência agora, todos os jogadores iriam aparecer aleatoriamente no objeto SpawnLocation na zona de spawn da Equipeverde ou no objeto SpawnLocation na zona de spawn da Equiperosa.Isso apresenta um problema de jogabilidade onde os jogadores poderiam se marcar uns aos outros dentro de cada zona de spawn assim que o campo de força do oponente desaparecer.
Para combater esse problema, a experiência de tag de laser de amostra configura ambos os locais de spawn com um conjunto de propriedades falsas para restringir os jogadores do Equipeoposto de spawnar na zona de spawn errada e um conjunto de propriedades falsas definido para o valor correspondente de Assign Team Colors na seção anterior do Tutorial:
- TeamASpawn – A localização de spawn na zona de spawn do Equipeverde com um conjunto de propriedades TeamColor definido como Menta .
- TeamBSpawn – A localização de spawn na zona de spawn do Equiperosa com um conjunto de propriedades definido como Carnation Pink .


Quando um jogador se junta à experiência, Serviço de Script de Servidor > Jogabilidade > Rodadas > spawnPlayersInMap verifica para ver quantos jogadores já estão em cada Equipe, então retorna a equipe com a menor quantidade de jogadores.
gerarJogadoresNoMapa
local function getSmallestTeam(): Team
local teams = Teams:GetTeams()
-- Ordenar equipes em ordem crescente, do menor ao maior
table.sort(teams, function(teamA: Team, teamB: Team)
return #teamA:GetPlayers() < #teamB:GetPlayers()
end)
-- Retorne a menor Equipe
return teams[1]
end
Uma vez que conhece a equipe com a menor quantidade de jogadores, ela classifica o jogador naquela Equipe, define sua propriedade falsa para que o jogador só possa aparecer e reaparecer na localização de aparecimento da Equipe, e então define sua propriedade falsa para falsa, sobre a qual você aprenderá mais tarde no Tutorial.
gerarJogadoresNoMapa
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 você examinar Espaço de Trabalho > Mundo > Mapa > Spawns , você pode ver que há mais um local de spawn no mapa: NeutralSpawn .Este local de spawn é único dos outros porque não tem um conjunto de propriedades TeamColor definido para uma das duas equipes na experiência; em vez disso, este local de spawn tem uma propriedade Neutral que muda dependendo se uma rodada está ativa.
Por exemplo, se a rodada estiver ativa, o conjunto de propriedades Neutral define para falso para que spawnPlayersInMap possa classificar os jogadores em equipes e lançá-los na arena.No entanto, se a rodada não estiver ativa, como o tempo entre uma rodada e a próxima, a propriedade Neutral define para verdadeiro para que os jogadores possam aparecer lá independentemente do status de sua equipe.Esse processo é o que torna o Neutral local de spawn uma sala funcional.

Para demonstrar, se você examinar Serviço de Script de Servidor > Jogabilidade > Rodadas > SpawnPlayersInLobby , que é executado no final de uma rodada, você pode ver que para cada jogador que é passado para a tabela players: { Player }, o script:
- Define sua propriedade Player.Neutral de verdade para redefinir automaticamente seu Player.Team para nil , permitindo que o jogador reapareça no lobby quando uma rodada não estiver ativa, pois a propriedade Neutral da localização de spawn também é definida como verdadeira .
- Muda seu PlayerState para InLobby para remover os visuais de blaster e da interface do usuário em primeira pessoa do jogador.
Para mais informações sobre a zona de spawn neutra e sua funcionalidade para cada rodada, veja Adicionando Rondas na próxima seção do Tutorial.
gerarJogadoresNoLobby
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
Conectar novos jogadores
O código Luau no Studio é frequentemente impulsionado por eventos, o que significa que os scripts ouvem eventos de um serviço do Roblox e então chamam uma função em resposta.Por exemplo, ao adicionar novos jogadores a uma experiência multijogador, deve haver um evento que lidará com tudo o que for necessário para que os jogadores se conectem com sucesso.Na experiência de tag de laser de amostra, este evento correspondente é Players.PlayerAdded:Connect .
Players.PlayerAdded:Connect é uma parte de vários scripts na experiência.Se você usar o atalho Ctrl/Cmd+Shift+F e pesquisar por Players.PlayerAdded:Connect, os resultados fornecem um bom ponto de partida para entender a configuração inicial da experiência.

Para demonstrar, abra Serviço de Script de Servidor > Configurar Humanóide .A distinção entre Player e Character é chave para entender este script:
- Jogadores precisam escolher um blaster e serem adicionados ao classificação. Personagens precisam aparecer e receber um blaster.
SetupHumanoid imediatamente verifica se o jogador tem um personagem (acabou de entrar) ou não (está sendo respawnado).Depois de encontrar um, chama onCharacterAdded() , obtém o modelo Humanoid do personagem e o passa para Serviço de Script de Servidor > SetupHumanoid > setupHumanoidAsync para personalização.Depois de definir esses valores, o script então aguarda que a saúde do personagem chegue a zero.Você aprenderá mais sobre respawning mais tarde nesta seção do 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
A nota importante com este script é que as propriedades são completamente opcionais, o que significa que, se você remover as primeiras seis linhas da função, a experiência ainda funciona corretamente.Em vez de serem requisitos funcionais, cada propriedade permite que você tome decisões de design que atendam aos seus objetivos de jogabilidade.Por exemplo:
- Se você quiser que os nomes de personagens sejam exibidos a uma distância menor, reduza o valor de Humanoid.NameDisplayDistance .
- Se você quiser apenas a saúde de um personagem para exibir se estiver abaixo de 100%, defina Humanoid.HealthDisplayType para Exibir quando danificado .
- Se você quiser que os personagens se separem quando sua saúde chegar a 0, defina Humanoid.BreakJointsOnDeath para Verdade .
Se você alterar os valores dessas propriedades, é importante testar para ver o impacto de suas novas configurações.Você pode recriar o que os jogadores experimentam em um ambiente multijogador selecionando pelo menos dois personagens na seção Clientes e Servidores da aba Teste .

Outro exemplo do evento Players.PlayerAdded:Connect está em ServerScriptService > PlayerStateHandler .Assim como no exemplo anterior, PlayerStateHandler imediatamente verifica um personagem.Se o jogador não estiver no lobby, o script define um atributo de jogador para o estado SelectingBlaster , o estado inicial para uma rodada em que os jogadores podem selecionar um dos dois tipos de arma diferentes após entrar na arena.Este estado também inclui um campo de força que impede os jogadores de receberem dano enquanto estão fazendo sua seleção.
Manuseador de Estado de Jogador
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)
Uma variável específica em PlayerStateHandler guerra merece discussão: attributeChangedConnectionByPlayer .Esta tabela armazena todos os jogadores e seus Connections para o GetAttributeChangedSignal .A razão para armazenar essa conexão em uma tabela é para que PlayerStateHandler possa desconectá-la quando o jogador deixa a experiência.Esse processo serve como uma espécie de gerenciamento de memória para evitar que o número de conexões cresça cada vez mais ao longo do tempo.
Manuseador de Estado de Jogador
local attributeChangedConnectionByPlayer = {}
local function onPlayerAdded(player: Player)
-- Gerencie todas as atualizações futuras ao estado do jogador
attributeChangedConnectionByPlayer[player] = player
:GetAttributeChangedSignal(PlayerAttribute.playerState)
:Connect(function()
local newPlayerState = player:GetAttribute(PlayerAttribute.playerState)
onPlayerStateChanged(player, newPlayerState)
end)
end
-- Desconectar-se da conexão modificada pelo atributo quando o jogador sai
local function onPlayerRemoving(player: Player)
if attributeChangedConnectionByPlayer[player] then
attributeChangedConnectionByPlayer[player]:Disconnect()
attributeChangedConnectionByPlayer[player] = nil
end
end
Você pode ver que ambas as funções conectadas na chamada onPlayerAdded() chamam onPlayerStateChanged().Durante a configuração inicial após um jogador se organizar em uma Equipe, onPlayerAdded() define PlayerState para SelectingBlaster , então a primeira declaração if avalia como falsa e desabilita o BlasterState .Na seção mais tarde Implementar explosores do Tutorial, você aprenderá mais detalhes sobre esse processo.
Manuseador de Estado de Jogador
local function onPlayerStateChanged(player: Player, newPlayerState: string)
-- O estado do blaster é 'Pronto' apenas se o estado do jogador for 'Jogando'
local newBlasterState = if newPlayerState == PlayerState.Playing then BlasterState.Ready else BlasterState.Disabled
-- Agende a lógica do campo de força de destruição quando o jogador começa a jogar
if newPlayerState == PlayerState.Playing then
scheduleDestroyForceField(player)
end
player:SetAttribute(PlayerAttribute.blasterStateServer, newBlasterState)
end
Se você adicionar pontos de interrupção ou mesmo apenas uma declaração print(), você pode ver que onPlayerStateChanged() é chamada com frequência durante a experiência: como durante a configuração inicial de uma rodada, para se colocar no caminho principal do código, após o jogador escolher um blaster e quando o jogador retorna ao lobby, ou na localização de spawn Neutral .Além disso, depois que o jogador escolhe um blaster, Serviço de Script de Servidor > Gerenciador Selecionado de Blaster define o PlayerState para Playing e PlayerStateHandler pode finalmente remover o campo de força ao chamar scheduleDestroyForceField() .
Personalizar campos de força
Em vez de usar uma implementação personalizada, a experiência de tag de laser de amostra usa a classe integrada do Studio ForceField para impedir que os jogadores recebam danos enquanto selecionam seu blaster.Isso garante que o único requisito para que os jogadores apareçam com um campo de força é incluir locais de spawn com uma propriedade SpawnLocation.Duration que seja maior que 0.A amostra usa um valor arbitrário de 9,999 para habilitar campos de força, então lida com a duração real programaticamente em ReplicatedStorage > ForceFieldClientVisuals .
Semelhante a setupHumanoidAsync, a maioria das linhas em ForceFieldClientVisuals são opcionais.Por exemplo, se você comentar o conteúdo da função como o seguinte script faz, a experiência usa o campo de força padrão em vez do script hexagonal em StarterGui > ForceFieldGui .
Comentando Propriedades em ForceFieldClientVisuals
local function onCharacterAddedAsync(character: Model)
-- forceField local = personagem:WaitForChild("ForceField", 3)
-- se não for campo de força então
-- retornar
-- terminar/parar/sair
-- forceField.Visibility = falso
-- localPlayer.PlayerGui:WaitForChild("ForceFieldGui").Enabled = verdadeiro
-- forceField.Destruindo: Espere()
-- localPlayer.PlayerGui.ForceFieldGui.Enabled = falso
end
Como o campo de força personalizado é uma GUI em vez de um novo ParticleEmitter, o script ForceFieldClientVisuals afeta apenas os visuais em primeira pessoa para cada jogador, não visuais em terceira pessoa quando os jogadores olham para outros jogadores.Visuais de terceira pessoa mantêm a aparência padrão do Roblox.Para mais informações sobre a modificação de campos de força, veja ForceField.Visible .


Campos de força são úteis porque fornecem aos jogadores tempo suficiente para entre o aparecimento e o respawn sem precisar se preocupar com jogadores inimigos, mas eventualmente precisam desaparecer para o principal jogabilidadede tag de laser.O script que lida com a remoção de campo de força está em ReplicatedStorage > scheduleDestroyForceField e verifica três condições únicas:
- Depois que os jogadores selecionam um blaster, os campos de força precisam durar o suficiente para permitir que os jogadores se acostumem com seus arredores.
- Durante esse tempo de acclimação, os campos de força não podem ser uma vantagem, então eles precisam desaparecer no momento em que um jogador lança seu blaster.
- Campos de força precisam desaparecer quando os jogadores redefinirem seus personagens antes de explodir ou antes que o tempo do campo de força acabe.
Cada uma dessas verificações na chamada de script para essas condições.
chedulerDestruirForceField
-- Acabe o campo de força se o jogador explodir
local blasterStateAttribute = getBlasterStateAttribute()
attributeChangedConnection = player:GetAttributeChangedSignal(blasterStateAttribute):Connect(function()
local currentBlasterState = player:GetAttribute(blasterStateAttribute)
if currentBlasterState == BlasterState.Blasting then
endForceField()
end
end)
-- Acabe o campo de força se o jogador reiniciar
characterRespawnedConnection = player.CharacterRemoving:Connect(endForceField)
-- Campo de força final após 8 segundos
task.delay(MAX_FORCE_FIELD_TIME, endForceField)
endForceField() inclui uma declaração aparentemente estranha if em torno do forceFieldEnded booleano.Como os testes são executados sequencialmente, o script pode chamar a função endForceField() duas ou até três vezes.O forceFieldEnded booleano garante que a função só tente destruir um campo de força uma vez.
chedulerDestruirForceField
local function endForceField()
if forceFieldEnded then
return
end
forceFieldEnded = true
attributeChangedConnection:Disconnect()
characterRespawnedConnection:Disconnect()
destroyForceField(player)
end
Manusear estado do cliente
Embora a maior parte desta seção se concentre em Serviço de Script de Servidor > Gerenciador de Estado do Jogador , há outro script com o mesmo nome em Armazenamento Replicado .A razão para a divisão é a arquitetura cliente-servidor:
O cliente precisa entender a informação de estado do jogador para que possa responder adequadamente em tempo real, como exibir os elementos de interface de usuário corretos ou permitir que os jogadores se movam e explodam.
O servidor precisa de toda essa mesma informação para que possa evitar exploits.Por exemplo, o servidor também precisa do estado do jogador para executar ações como gerar e equipar personagens, desabilitar campos de força e exibir uma tabela de classificação.É por isso que este script está em ReplicatedStorage e não em uma localização puramente do lado do cliente.
Para ver essa lógica principal, revise o seguinte script em ReplicatedStorage > PlayerStateHandler que verifica o estado atual do usuário, então chama a função apropriada que lida com as ações correspondentes para esse estado.
Manuseador de Estado de Jogador
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
Todas as respostas de eventos são agrupadas logicamente neste script porque requerem comportamento semelhante de habilitar ou desabilitar controles do jogador, movimento da câmera e qual camada de UI está visível.Por exemplo, durante a seleção de blaster, os jogadores precisam ser invulneráveis e incapazes de se mover.O servidor já lida com o campo de força, mas o cliente lida com o movimento.Para demonstrar, se você verificar a lógica para a função onSelectingBlaster() , você pode ver que o cliente desabilita o movimento do jogador enquanto eles estão selecionando um blaster.
Manuseador de Estado de Jogador
local function onSelectingBlaster()
togglePlayerCamera(true)
togglePlayerMovement(false)
setGuiExclusivelyEnabled(playerGui.PickABlasterGui)
localPlayer:SetAttribute(PlayerAttribute.blasterStateClient, BlasterState.Disabled)
end
A função onPlaying() é semelhantemente direta.Ele habilita o movimento, transições para a exibição principal de cabeça para baixo (HUD), habilita o blaster e chama a mesma função de campo de força do servidor.
Manuseador de Estado de Jogador
local function onPlaying()
togglePlayerMovement(true)
setGuiExclusivelyEnabled(playerGui.HUDGui)
localPlayer:SetAttribute(PlayerAttribute.blasterStateClient, BlasterState.Ready)
scheduleDestroyForceField()
end
Respawnar personagens
A experiência de tag de laser de amostra que lida com a ressurgência do personagem de volta a uma rodada através do estado onTaggedOut() na ReplicatedStorage > Gerenciador de Estado do Jogador .Como o estado onSelectingBlaster() e onPlaying(), os gatilhos onTaggedOut() ativam um comportamento único de acordo com as alterações no atributo playerState.Especificamente, desabilita o movimento do jogador, apresenta a interface de reaparecimento e desabilita o blaster.
Manuseador de Estado de Jogador
local function onTaggedOut()
-- Desabilitar controles enquanto estiver marcado fora
togglePlayerMovement(false)
togglePlayerCamera(false)
setGuiExclusivelyEnabled(playerGui.OutStateGui)
-- Desabilite o blaster enquanto estiver marcado fora
localPlayer:SetAttribute(PlayerAttribute.blasterStateClient, BlasterState.Disabled)
end
Se você quiser testar esse comportamento, você pode pressionar Esc , navegue até a aba Configurações e, em seguida, clique no botão Redefinir Personagem .Observe que quando você ativa a tela de respawn, você não pode se movimento, girar a Câmeraou explodir seu blaster.


É importante notar que este script não reseta realmente os personagens, apenas os impede de agir e fornece feedback visual aos jogadores que o servidor está respawnando seus personagens.Para demonstrar, se você examinar Serviço de Script de Servidor > SetupHumanoid > setupHumanoidAsync > onHumanoidDied , o script define PlayerState para TaggedOut (essencialmente notificando ReplicatedStorage > PlayerStateHandler ), e adiciona alguns indicadores visuais.A lógica real de reaparecer é um comportamento integrado do Roblox.
Quando os jogadores reaparecem na rodada, eles reaparecem na localização de spawn de sua Equipede acordo com a propriedade SpawnLocation.TeamColor.Para personalizar o tempo de reaparecimento, você pode adicionar a seguinte linha ao topo de SetupHumanoid .Para aprender mais sobre essa técnica, veja Players.RespawnTime .
SetupHumanoid
local Players = game:GetService("Players")Players.RespawnTime = 10 -- new line, in seconds
Configuração diversa
Como parte da configuração inicial, a experiência de tag de laser de amostra também realiza alguns passos pequenos, mas críticos:
A experiência inclui um script vazio chamado StarterPlayer > StarterCharacterScripts > Saúde que desabilita a regeneração de saúde padrão do Roblox.Para uma explicação do comportamento dessa propriedade, veja Humanoid.Health .
A experiência usa uma câmera em primeira pessoa ao definir a propriedade StarterPlayer.CameraMode.LockFirstPerson.Observe que, se você quiser deixar os usuários mudarem entre câmeras de primeira e terceira pessoa, você deve alterar a propriedade programaticamente, ao invés de apenas definir uma vez no Studio e modificar os controles e a interface para compensar a mudança de perspectiva.
A experiência usa a tabela de classificação integrada do Roblox com a unidade de "pontos", que os jogadores ganham cada vez que marcam outro jogador.Você pode ver a configuração em Serviço de Script de Servidor > Placar de Líderes na Experiência , mas In-Experience Leaderboards oferece uma visão geral completa.Observe que onPlayerTagged adiciona pontos à tabela de classificação, que você aprenderá sobre em Adicionar rodadas e Detectar acertos.
Agora que os jogadores podem aparecer, escolha um blaster e mire-o a partir de um ponto de verem primeira pessoa, a próxima seção ensina você sobre os scripts por trás da criação de jogabilidade baseada em rodadas.