Adicionar rodadas permite que você estruture a jogabilidade em fases com um ponto de início e fim claro, para que os jogadores possam medir seu progresso e ter uma oportunidade periódica de um campo de jogo igualitário. Isso é particularmente importante para a jogabilidade em equipe, pois oferece aos jogadores a chance de mudar seu estilo de jogo dependendo de quem está em sua equipe durante aquela rodada.
Usando o jogo de laser tag de exemplo como referência, esta seção do tutorial ensina como usar e personalizar os recursos integrados do Roblox para estruturar cada rodada, incluindo orientações de script sobre:
- Começar uma rodada redefinindo os pontos individuais e da equipe, e então fazendo o spawn dos jogadores em suas zonas de spawn de equipe.
- Personalizar variáveis que definem o objetivo da rodada na parte superior da tela de cada jogador.
- Rastrear as contribuições de pontos dos jogadores para a pontuação de sua equipe.
- Acionar telas de UI únicas dependendo se a equipe do jogador ganhou ou perdeu a rodada.
- Terminar uma rodada desconectando os jogadores e fazendo o spawn deles no lobby neutro.
Depois de completar esta seção, você aprenderá como implementar o comportamento dos blasters que é tanto preciso quanto satisfatório para os jogadores.

Iniciar loop
ServerScriptService ⟩ Gameplay ⟩ Rounds lida com a maior parte da lógica para implementar rodadas, e começa chamando a função startRoundLoopAsync() para marcar o início de um loop de rodadas. Enquanto os jogadores entram no lobby e aguardam para serem organizados em uma equipe, startRoundLoopAsync() chama a função resetScores() em ServerScriptService ⟩ Gameplay ⟩ Scoring para redefinir tanto a tabela de líderes quanto os pontos da equipe.
function Scoring.resetScores()
for _, player in Players:GetPlayers() do
player.leaderstats.Points.Value = 0
end
for _, team in Teams:GetTeams() do
team:SetAttribute(GuiAttribute.teamPoints, 0)
end
endAgora que todos estão começando com zero pontos, startRoundLoopAsync() então define a propriedade Neutral da localização de spawn Neutral como false, para que apenas jogadores com a mesma propriedade Team.Color que a propriedade TeamColor da localização de spawn possam aparecer lá. Como a propriedade TeamColor da localização de spawn está definida como branca em vez das equipes de menta ou rosa carnation do exemplo, essa configuração impede que todos os jogadores apareçam ou reapareçam lá enquanto uma rodada está ativa.
Para jogadores que estão atualmente no lobby, startRoundLoopAsync() passa todos os jogadores atualmente no jogo para a função spawnPlayersInMap em ServerScriptService ⟩ Gameplay ⟩ Rounds ⟩ spawnPlayersInMap para organizar e equilibrar todos em uma equipe com aproximadamente o mesmo número de jogadores.
Para quaisquer novos jogadores que entrarem no jogo após o grupo do lobby ser organizado em uma equipe, startRoundLoopAsync() escuta o evento Players.PlayerAdded:Connect, e então chama a função spawnPlayersInMap novamente para adicioná-los à equipe com o menor número de jogadores. Para mais informações sobre esse processo, veja Configurar Localizações de Spawn da seção anterior Spawn e Respawn do tutorial.
-- Spawn todos os jogadores no mapa
neutralSpawn.Neutral = false
spawnPlayersInMap(Players:GetPlayers())
-- Spawn novos jogadores no mapa quando eles entram
local playerAddedConnection = Players.PlayerAdded:Connect(function(player: Player)
spawnPlayersInMap({ player })
end)Definir objetivo
Agora que cada jogador está na arena com seus companheiros de equipe, o jogo precisa fornecer instruções sobre o que fazer para ter sucesso dentro da rodada. O jogo de laser tag de exemplo aborda essa necessidade fornecendo um prompt de objetivo na parte superior da tela de cada jogador com orientações claras sobre o que a equipe precisa fazer para vencer.

Embora você possa aprender mais sobre como configurar e exibir o componente UI de Objetivo no Currículo de UI, esta seção se concentra em como implementar o objetivo assim que a rodada começa, começando com como definir a quantidade de pontos que cada equipe precisa para completar a rodada.
Embora o prompt de objetivo em tempo de execução informe os jogadores que eles precisam marcar três pontos para vencer, se você examinar o prompt em StarterGui ⟩ HUDGui, você pode ver que ele contém um "%d" configurável para o valor dos pontos.

"%d" é uma string de espaço reservado que você pode aumentar ou diminuir a qualquer momento para atender aos seus próprios requisitos de jogabilidade, atualizando a variável TEAM_SCORE_LIMIT em ReplicatedStorage ⟩ TEAM_SCORE_LIMIT. Por exemplo, se você definir esse número para um excessivamente alto 200, o prompt e o contador de pontos da equipe seriam atualizados de acordo.
local TEAM_SCORE_LIMIT = 200 -- linha atualizada, certifique-se de mudar de volta
return TEAM_SCORE_LIMIT
Essa simples atualização de variável funciona em tempo de execução porque, assim que a rodada começa, ReplicatedStorage ⟩ HUDGuiSetup ⟩ SetObjective requer o script de módulo TEAM_SCORE_LIMIT para que ele possa trocar a string de espaço reservado no objeto TextLabel do UI de Objetivo.
local TEAM_SCORE_LIMIT = require(ReplicatedStorage.TEAM_SCORE_LIMIT)
local function setObjective(gui: ScreenGui)
local bodyTextLabel = gui.Objective.ObjectiveDisplay.Body.BodyTextLabel
bodyTextLabel.Text = bodyTextLabel.Text:format(TEAM_SCORE_LIMIT)
endRastrear pontos
Agora que os jogadores têm um objetivo para a rodada, o jogo precisa acompanhar os pontos de cada equipe até que eles atinjam seu objetivo. Enquanto o comportamento padrão do serviço Teams agrupa automaticamente cada jogador sob sua equipe e soma as contribuições de cada jogador para a pontuação de sua equipe, é importante armazenar e monitorar os pontos em um local separado para a jogabilidade baseada em rodadas, porque se um jogador marcar e sair antes que a rodada termine, sua contribuição é deduzida da tabela de líderes assim que ele se desconecta do jogo.
Para garantir que isso não aconteça e que cada contribuição em direção ao objetivo da equipe seja preservada, ReplicatedStorage ⟩ HUDGuiSetup ⟩ StartSyncingTeamPoints armazena todos os pontos separadamente sob o atributo teamPoints no serviço Teams. À medida que teamPoints é incrementado, esse script de módulo chama a função startSyncingTeamPoints para encontrar o contador da equipe Class.GuiObjects dentro do componente UI de Objetivo.
Quando localiza TeamACounter e TeamBCounter, ele obtém seu atributo teamColor, que se correlaciona com as zonas de spawn da equipe: TeamACounter exibe os pontos da equipe verde, e TeamBCounter rastreia os pontos da equipe rosa.
local function startSyncingTeamPoints(gui: ScreenGui)
for _, teamPointCounter in gui.Objective.TeamPointCounter:GetChildren() do
if not teamPointCounter:IsA("GuiObject") then
continue
end
local iconTeamColor = teamPointCounter:GetAttribute(GuiAttribute.teamColor)O script de módulo então chama sua função getTeamFromTeamColor para validar que o atributo teamColor mint do TeamACounter e o atributo teamColor carnation pink do TeamBCounter correspondem às propriedades Team.Color correspondentes sob o serviço Teams. Se sim, ele retorna ambas as equipes.
local function getTeamFromTeamColor(teamColor: Color3): Team?
for _, team in Teams:GetTeams() do
if team.TeamColor == teamColor then
return team
end
end
return nil
endQuando isso ocorre, startSyncingTeamPoints define os objetos TextLabel de ambos os contadores de equipe para seus valores correspondentes de teamPoints, e continua a atualizá-los sempre que um jogador marca um ponto ao eliminar outro jogador da equipe oposta.
teamPointCounter.TextLabel.Text = team:GetAttribute(GuiAttribute.teamPoints)
team:GetAttributeChangedSignal(GuiAttribute.teamPoints):Connect(function()
teamPointCounter.TextLabel.Text = team:GetAttribute(GuiAttribute.teamPoints)Tudo nesta seção até agora se concentrou em como rastrear pontos na tela do jogador, mas é importante revisar a lógica que lida com o rastreamento de pontos no servidor para que ele saiba quando uma equipe atinge o objetivo e vence a rodada. Se você revisitar ServerScriptService ⟩ Gameplay ⟩ Scoring, você pode ver que o script de módulo começa criando um evento vinculável, que será acionado cada vez que um jogador marca um ponto.
local teamScoreChangedBindable = Instance.new("BindableEvent")
local Scoring = {
teamScoreChanged = teamScoreChangedBindable.Event,
}Em seguida, ele chama a função incrementScore, que realiza as seguintes ações:
- Obtém a pontuação individual do jogador na tabela de líderes e adiciona um.
- Aciona o evento vinculável mencionado anteriormente com a equipe do jogador e sua pontuação.
Esse processo mantém efetivamente tanto o cliente quanto o servidor alinhados em relação às pontuações individuais dos jogadores e suas pontuações de equipe.
function Scoring.incrementScore(player: Player, amount: number)
local team = player.Team
assert(team, `Player {player.Name} must be on a team to score a point, but has no team`)
local teamPoints = team:GetAttribute(GuiAttribute.teamPoints)
teamPoints += amount
team:SetAttribute(GuiAttribute.teamPoints, teamPoints)
local leaderstat = player.leaderstats.Points
leaderstat.Value += amount
teamScoreChangedBindable:Fire(team, teamPoints)
endExibir resultados
À medida que os jogadores se eliminam e marcam pontos para sua equipe, ServerScriptService ⟩ Gameplay ⟩ Rounds verifica se a equipe que marcou atendeu ao objetivo da rodada. Se a pontuação da equipe for inferior à variável TEAM_SCORE_LIMIT em ReplicatedStorage ⟩ TEAM_SCORE_LIMIT, o servidor continua a esperar até que uma das equipes marque novamente.
No entanto, uma vez que a pontuação de uma equipe atinge a variável TEAM_SCORE_LIMIT, o script aciona uma instância de evento roundWinnerRemote com o nome do jogador e sua equipe.
-- Verifica se a rodada terminou após cada pontuação
local team: Team
local score: number = 0
while score < TEAM_SCORE_LIMIT do
team, score = Scoring.teamScoreChanged:Wait()
end
-- Exibir equipe vencedora
for _, player in Players:GetPlayers() do
-- Enviando qual equipe o jogador está no final da rodada
-- porque a equipe do jogador está prestes a ser removida, então o cliente
-- não poderá verificar sua própria equipe
roundWinnerRemote:FireClient(player, team, player.Team)
endO script ReplicatedStorage ⟩ RoundResultsGuiSetup em cada cliente escuta por essa instância de evento roundWinnerRemote para que possa:
- Exibir uma tela de UI única StarterGui ⟩ RoundResultsGui que anuncia os resultados da rodada e se o jogador estava na equipe vencedora.
- Tocar um clipe de áudio de vitória ou derrota.
Por exemplo, se um jogador estiver na equipe que marcou o ponto vencedor, ele recebe várias formas de feedback sobre os resultados da rodada na forma de uma tela de UI que exibe texto de vitória e um clipe de áudio que toca um som alegre. Por outro lado, se um jogador não estiver na equipe que marcou o ponto vencedor, ele recebe uma tela de UI que exibe texto de derrota e um clipe de áudio que toca um som ominoso.
local function onRoundWinner(winner: Team, localTeam: Team?)
local victoryDefeatText = "Rodada encerrada!"
if localTeam then
-- Se nossa equipe venceu, exibiremos Vitória! Caso contrário, exibiremos Derrota...
local isVictory = winner == localTeam
if isVictory then
victorySound:Play()
victoryDefeatText = VICTORY_TEXT
else
defeatSound:Play()
victoryDefeatText = DEFEAT_TEXT
end
endRedefinir equipes
Ao mesmo tempo que ServerScriptService ⟩ Gameplay ⟩ Rounds verifica se uma equipe atendeu ao objetivo da rodada e aciona a exibição de UI apropriada para cada jogador, ele também transporta todos os jogadores da arena para o lobby, desconectando-os da rodada. Isso inicia o processo de encerrar formalmente a rodada e redefinir ambas as equipes.
Usando a mesma lógica em Configurar Localizações de Spawn, Rounds então define a propriedade Neutral da localização de spawn Neutral como true, para que os jogadores possam aparecer lá independentemente de seu status de equipe. Isso significa que o lobby se torna o único local onde os jogadores podem aparecer após serem desconectados da rodada.
-- Enviar todos para o lobby
playerAddedConnection:Disconnect()
neutralSpawn.Neutral = true
spawnPlayersInLobby(Players:GetPlayers())Após esperar dez segundos para uma intermissão, o script do servidor Rounds então inicia o loop novamente redefinindo as pontuações de todos e organizando-os em novas equipes. O exemplo repete esse processo cíclico de rodadas novamente até que não haja mais jogadores no servidor.
Agora que os jogadores podem aparecer no mapa com sua própria equipe e jogar uma rodada completa, a próxima seção ensina sobre os scripts por trás do comportamento de cada blaster.