Gerar é o processo de criação de um objeto ou personagem em uma experiência, e respawnar é o processo de adicionar um objeto ou personagem de volta à uma experiência após atender uma condição de remoção, como a saúde de um personagem atingindo zero ou caindo do mapa. Ambos os processos são importantes porque garantem que os jogadores possam entrar na sua experiência e continuar jogando para melhorar suas habilidades.
Usando a experiência de laser de exemplo como referência, esta seção do tutorial ensina você a usar e personalizar os recursos incorporados do Roblox para lidar com o spawning e respawning, incluindo diretrizes de script em:
- Configurando locais de spawn para que os jogadores só possam spawnar na zona de spawn de sua Equipe.
- Adicionar novos jogadores e seus personagens à partida à medida que eles se juntam à experiência.
- Personalizar campos de força que impedem danos à medida que os jogadores aparecem e respawnam.
- Gerenciando o estado do cliente para que o jogo funcione corretamente no momento apropriado.
- Respawning 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 jogo e personagens.
Esta seção inclui muito conteúdo de script, mas em vez de escrever tudo do zero ao criar uma experiência, ela encoraja você a usar componentes existentes, itera rapidamente e descobre quais sistemas precisam de uma implementação personalizada para corresponder à sua visão. Depois de completar esta seção, você aprenderá a implementar jogos baseados em rodadas que rastreiam pontos, monitoram o estado do jogador e exibem resultados de rodada.
Configurar Locais de Spawn
Se você testasse a experiência agora, todos os jogadores seriam aleatórios para aparecer na SpawnLocation objeto no zona de spawn do Equipeverde ou na zona de spawn do Equiperosa. Isso apresenta um problema de gameplay onde os jogadores podem rastrear uns aos outros dentro de cada zona de spawn tão logo o campo de força de seu oponente desaparece.
Para combater este problema, a experiência de laser de exemplo configura ambos os locais de spawn com um conjunto de propriedade Neutral para restrear os jogadores do Equipeoponente de spawn na zona de spawn errada e um conjunto de propriedade 1> Class.S
- TeamASpawn – O local de spawn na zona de spawn verde com um conjunto de propriedades TeamColor definido para Mint .
- TeamBSpawn – O local de spawn na zona de spawn da Equiperosa com um conjunto de propriedade TeamColor definido para Carnation Pink .
Quando um jogador se juntar à experiência, ServerScriptService > Gameplay > Rounds > 1> Rounds1> > 4> spawntPlayersInMap4> verifica para ver quantos jogadores 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 times em ordem crescente, do menor ao maior
table.sort(teams, function(teamA: Team, teamB: Team)
return #teamA:GetPlayers() < #teamB:GetPlayers()
end)
-- Retorne a Equipemais pequena
return teams[1]
end
Uma vez que ele saiba a equipe com a menor quantidade de jogadores, ele sorteia o jogador naquela Equipe, define sua propriedade Player.Neutral para falso para que o jogador só possa gerar e respawnar na localização de spawn da Equipe, então configura seu PlayerState para 1> SelectingBlaster1>, o que você aprender
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 Workspace > World > Map > 1> Spawns1> , você pode ver que há mais um local de spawn na experiência: 4>
Por exemplo, se a rodada estiver ativa, as propriedades Neutral são definidas para falso para que o spawnPlayersInMap</
Para demonstrar, se você examinar ServerScriptService > Gameplay > Rounds > 1> SpawnPlayersInLobby1>, que é executado no final de uma rodada, você pode ver que para cada jogador que é passado na tabela 4>players: player4>, o script:
- Define sua propriedade Player.Neutral para verdadeiro para redefinir automaticamente seu Player.Team para nil, permitindo que o jogador respawne no lobby quando uma rodada não está ativa, como a propriedade Neutral da localização de spawn também é definida como 1>verdadeiro1>.
- Altera seu PlayerState para InLobby para remover os visuais de UI do jogador e os visuais de primeira pessoa.
Para mais informações sobre a zona de spawn neutral e sua funcionalidade para cada rodada, see Adicionando Rodadas 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 geralmente é baseado em eventos, o que significa que scripts ouvem eventos de um serviço Roblox e, em seguida, 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 é necessário para os jogadores se conectarem com sucesso. Na experiência de laser de exemplo, este evento correspondente é Players.PlayerAdded:Connect.
Players.PlayerAdded:Connect é uma parte de vários scripts na experiência. Se você usar o atalho de teclado 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 ServerScriptService > SetupHumanoid . A distinção entre Player e 1> Class.Player.Character|Character1> é a chave para entender este script:
- Um player é um cliente conectado, e um carakter é um modelo Humanoid.
- Os jogadores precisam escolher um blaster e serem adicionados à tabela de classificação. Os personagens precisam ser gerados e receber um blaster.
SetupHumanoid imediatamente verifica se o jogador tem um personagem (acabou de entrar) ou não (está respawnando). Depois de encontrar um, chama onCharacterAdded(), obtém
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 seis primeiras linhas da função, a experiência ainda funciona normalmente. Em vez de ser requisitos funcionais, cada propriedade permite que você tome decisões de design que atendem aos seus objetivos de jogo. Por exemplo:
- Se você quiser que os nomes dos personagens sejam exibidos em distâncias mais próximas, reduza o valor de Humanoid.NameDisplayDistance.
- Se você só quiser a saúde de um personagem para exibir se estiver abaixo de 100%, set Humanoid.HealthDisplayType para Exibir quando danificado .
- Se você quiser que os personagens se desfaçam quando a saúde deles atinge 0, configure Humanoid.BreakJointsOnDeath para Verdadeiro .
Se você alterar os valores dessas propriedades, é importante testar o jogo para que você possa 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 da aba Testes.
Outro exemplo do evento Players.PlayerAdded:Connect em ServerScriptService > PlayerStateHandler é imediatamente verificado por um característica. Se o jogador não estiver no lobby, o script configura um atributo
Gerenciador de estado do 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 particular em PlayerStateHandler requer discussão: attributeChangedConnectionByPlayer. Esta tabela armazena todos os jogadores e suas Connections ao 1>
Gerenciador de estado do jogador
local attributeChangedConnectionByPlayer = {}
local function onPlayerAdded(player: Player)
-- Gerencie todas as atualizações futuras para o estado do jogador
attributeChangedConnectionByPlayer[player] = player
:GetAttributeChangedSignal(PlayerAttribute.playerState)
:Connect(function()
local newPlayerState = player:GetAttribute(PlayerAttribute.playerState)
onPlayerStateChanged(player, newPlayerState)
end)
end
-- Desconecte da conexão atribuída quando o jogador sair
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 em onPlayerAdded() chamam onPlayerStateChanged() . Durante a configuração inicial após um jogador classificar em uma Equipe, on
Gerenciador de estado do jogador
local function onPlayerStateChanged(player: Player, newPlayerState: string)
-- O estado do Blaster é 'Ready' apenas se o estado do jogador é 'Playing'
local newBlasterState = if newPlayerState == PlayerState.Playing then BlasterState.Ready else BlasterState.Disabled
-- Agende a lógica de campo de força de destruição quando o jogador começar a jogar
if newPlayerState == PlayerState.Playing then
scheduleDestroyForceField(player)
end
player:SetAttribute(PlayerAttribute.blasterStateServer, newBlasterState)
end
Se você adicionar pontos de interrupção ou até mesmo uma declaração <
Personalizar Campos de Força
Em vez de usar uma implementação personalizada, a experiência de laser de exemplo usa a classe integrada ForceField do Studio para impedir que os jogadores levem dano enquanto eles estão selecionando seu blaster. Isso garante que a única exigência para os jogadores
Semelhante a setupHumanoidAsync , a maioria das linhas em ForceFieldClientVisuals são opcionais. Por exemplo, se você comentar os conteúdos da função, como o seguinte script, a experiência usa o campo de força padrão em vez do campo de força hexagonal em StarterGui > 2>ForceFieldGui2>.
Comentando Propriedades em ForceFieldClientVisuals
local function onCharacterAddedAsync(character: Model)
-- campo de força local = personagem: WaitForChild("ForceField", 3)
-- a menos que forceField então
-- retornar
-- terminar/parar/sair
-- forceField.Visible = falso
-- localPlayer.PlayerGui:WaitForChild("ForceFieldGui").Enabled = verdadeiro
-- forceField.Destroying:Wait()
-- localPlayer.PlayerGui.ForceFieldGui.Enabled = falso
end
Como o campo de força personalizado é um GUI, em vez de um novo ParticleEmitter, o script ForceFieldClientVisuals só afeta os visuais de primeira pessoa para cada jogador, não os visuais de terceira pessoa quando os jogadores olham para outros jogadores. Os visuais de terceira pessoa mantêm a ap
Campos de força são úteis porque fornecem tempo suficiente para os jogadores entre o spawn e o respawn sem precisar se preocupar com jogadores inimigos, mas eventualmente eles precisam desaparecer para o principal jogabilidadede laser tag. O script que lida com a remoção de campo de força é 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 aclimatem ao seu ambiente.
- Durante este tempo de aceleração, campos de força não podem ser uma vantagem, então eles precisam desaparecer no momento em que um jogador explodir seu blaster.
- Campos de força precisam desaparecer quando os jogadores redefinem seus personagens, antes de explodir ou antes que o campo de força expire.
Cada um desses cheques no scheduleDestroyForceField script call endForceField() para essas condições.
campo de força de destruição
-- Terminar 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)
-- Terminar campo de força se o jogador redefinir
characterRespawnedConnection = player.CharacterRemoving:Connect(endForceField)
-- Campo de força finalizado após 8 segundos
task.delay(MAX_FORCE_FIELD_TIME, endForceField)
endForceField() inclui uma declaração de if que parece estranha ao redor do forceFieldEnded botão. Como os cheques são executados em série, o script pode chamar a função 0> endForceField0> duas ou até mesmo três vezes. A função endForceField()3> garante que a função apenas destrua um campo
campo de força de destruição
local function endForceField()
if forceFieldEnded then
return
end
forceFieldEnded = true
attributeChangedConnection:Disconnect()
characterRespawnedConnection:Disconnect()
destroyForceField(player)
end
Manipular o estado do cliente
Enquanto a maioria desta seção se concentra em ServerScriptService > PlayerStateHandler, há outro script do mesmo nome em ReplicatedStorage. A razão para a divisão é a arquitetura cliente-servidor:
O cliente precisa entender informações sobre o estado do jogador para que ele possa responder adequadamente em tempo real, como exibir os elementos da interface do usuário certos ou habilitar os jogadores a se mover e explodir.
O servidor precisa de toda essa mesma informação para que ele possa prevenir exploits. Por exemplo, o servidor também precisa de estado de jogador para executar ações como o spawning e equipando personagens, desabilitando campos de força e exibindo uma classificação. É por isso que este script está em Armazenamento Replicado e não é um local puramente do lado do cliente.
Para ver essa lógica do núcleo, 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.
Gerenciador de estado do 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 lógica grupos juntos neste script porque eles requerem comportamento semelhante de habilitar ou desativar controles do jogador, movimento da câmera e qual camada de UI é visível. Por exemplo, durante a seleção de um blaster, os jogadores precisam ser ambos invencíveis e incapazes de se mover. O servidor já lida com o campo de
Gerenciador de estado do jogador
local function onSelectingBlaster()
togglePlayerCamera(true)
togglePlayerMovement(false)
setGuiExclusivelyEnabled(playerGui.PickABlasterGui)
localPlayer:SetAttribute(PlayerAttribute.blasterStateClient, BlasterState.Disabled)
end
A função onPlaying() é semelhante. Ela habilita movimento, transições para a tela principal (HUD), habilita o blaster e chama a mesma função de campo de força que o servidor.
Gerenciador de estado do jogador
local function onPlaying()
togglePlayerMovement(true)
setGuiExclusivelyEnabled(playerGui.HUDGui)
localPlayer:SetAttribute(PlayerAttribute.blasterStateClient, BlasterState.Ready)
scheduleDestroyForceField()
end
Gerar Personagens
A experiência de laser de ressurgimento de personagens é tratada com o retorno de um personagem de volta em uma rodada através do estado onTaggedOut() em Armazenamento Replicado > Jogador状態Handler
Gerenciador de estado do jogador
local function onTaggedOut()
-- Desabilitar controles enquanto estiver fora
togglePlayerMovement(false)
togglePlayerCamera(false)
setGuiExclusivelyEnabled(playerGui.OutStateGui)
-- Desabilitar o blaster enquanto estiver fora
localPlayer:SetAttribute(PlayerAttribute.blasterStateClient, BlasterState.Disabled)
end
Se você quiser testar esse comportamento, você pode pressionar Esc, navegar até a Configurações aba, então clique no botão Reset Character . Observe que quando você ativar a tela de ressurgimento, você não pode se movimento, girar a Câmeraou explodir seu blaster.
É importante notar que este script não é realmente respawnar personagens, ele apenas os impede de agir e fornece
Quando os jogadores reaparecem na ronda, 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 na parte superior de SetupHumanoid. Para aprender mais sobre esta técnica, veja Players.RespawnTime.
ConfigurarHumanoid
local Players = game:GetService("Players")Players.RespawnTime = 10 -- new line, in seconds
Configurações Diversas
Como parte do setup inicial, a experiência de laser de exemplo também executa alguns pequenos, mas críticos passos:
A experiência inclui um script vazio chamado StarterPlayer > StarterCharacterScripts > Health que desativa a regeneração de saúde padrão do Roblox. Para uma explicação sobre o comportamento desta propriedade, veja 1> Class.Humanoid.Health1> .
A experiência usa uma câmera de primeira pessoa ao configurar a propriedade StarterPlayer.CameraMode.LockFirstPerson. Observe que se você quiser permitir que os usuários alterne entre câmeras de primeira e terceira pessoa, você deve alterar o programa de propriedade de forma programática, em vez de apenas definir uma vez no Studio, e modificar os controles e UI para compensar a mudança de perspectiva.
A experiência usa o placar de classificação integrado do Roblox com a unidade de "pontos", que os jogadores ganham cada vez que marcam outro jogador fora. Você pode ver a configuração em ServerScriptService > SetupLeaderboard , mas In-Experience Leaderboards
Agora que os jogadores podem Gerar, escolha um blaster e aponta-o a partir de uma primeira pessoa, a próxima seção ensina você sobre os scripts por trás da criação de uma jogabilidade baseada em rodadas.