Agregar rondas te permite estructurar la jugabilidad en fases con un claro punto de inicio y final, de modo que los jugadores puedan medir su progreso y tener una oportunidad periódica de un campo de juego igualitario. Esto es particularmente importante para la jugabilidad en equipo, ya que ofrece a los jugadores la oportunidad de cambiar su estilo de juego dependiendo de quién esté en su equipo durante esa ronda.
Usando el juego de láser tag de muestra como referencia, esta sección del tutorial te enseña cómo usar y personalizar las características integradas de Roblox para estructurar cada ronda, incluyendo orientación sobre scripting en:
- Comenzar una ronda reiniciando los puntos individuales y del equipo, y luego haciendo aparecer a los jugadores en sus zonas de aparición de equipo.
- Personalizar variables que establecen el objetivo para la ronda en la parte superior de la pantalla de cada jugador.
- Rastrear las contribuciones de puntos de los jugadores para el puntaje de su equipo.
- Activar pantallas de interfaz de usuario únicas dependiendo de si el equipo del jugador ganó o perdió la ronda.
- Terminar una ronda desconectando a los jugadores y haciéndolos aparecer en el vestíbulo neutral.
Después de completar esta sección, aprenderás cómo implementar un comportamiento de blaster que sea tanto preciso como satisfactorio para los jugadores.

Iniciar bucle
ServerScriptService ⟩ Gameplay ⟩ Rounds maneja la mayor parte de la lógica para implementar rondas, y comienza llamando a la función startRoundLoopAsync() para marcar el inicio de un bucle de ronda. Mientras los jugadores se unen al vestíbulo y esperan ser asignados a un equipo, startRoundLoopAsync() llama a la función resetScores() en ServerScriptService ⟩ Gameplay ⟩ Scoring para reiniciar tanto la tabla de clasificación como los puntos del equipo.
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
endAhora que todos comienzan con cero puntos, startRoundLoopAsync() establece la propiedad Neutral de la ubicación de aparición Neutral en false para que solo los jugadores con la misma propiedad Team.Color que la propiedad TeamColor de la ubicación de aparición puedan aparecer allí. Dado que la propiedad TeamColor de la ubicación de aparición está configurada en blanco en lugar de los equipos de menta o rosa carnal de la muestra, esta configuración evita que todos los jugadores aparezcan o reaparezcan allí mientras una ronda está activa.
Para los jugadores que están actualmente en el vestíbulo, startRoundLoopAsync() pasa todos los jugadores que están actualmente en el juego a la función spawnPlayersInMap en ServerScriptService ⟩ Gameplay ⟩ Rounds ⟩ spawnPlayersInMap para clasificar y equilibrar a todos en un equipo con aproximadamente el mismo número de jugadores.
Para cualquier nuevo jugador que se una al juego después de que el grupo del vestíbulo haya sido clasificado en un equipo, startRoundLoopAsync() escucha el evento Players.PlayerAdded:Connect, y luego llama a la función spawnPlayersInMap nuevamente para agregarlos al equipo con la menor cantidad de jugadores. Para más información sobre este proceso, consulta Configurar ubicaciones de aparición de la sección anterior Aparición y Reaparición del tutorial.
-- Hacer aparecer a todos los jugadores en el mapa
neutralSpawn.Neutral = false
spawnPlayersInMap(Players:GetPlayers())
-- Hacer aparecer a nuevos jugadores en el mapa cuando se unan
local playerAddedConnection = Players.PlayerAdded:Connect(function(player: Player)
spawnPlayersInMap({ player })
end)Establecer objetivo
Ahora que cada jugador está en la arena con sus compañeros de equipo, el juego necesita proporcionar instrucciones sobre qué hacer para tener éxito en la ronda. El juego de láser tag de muestra aborda este requisito proporcionando un aviso de objetivo en la parte superior de la pantalla de cada jugador con una guía clara sobre lo que el equipo necesita hacer para ganar.

Si bien puedes aprender más sobre cómo configurar y mostrar el componente UI de Objetivo en el Currículo de UI, esta sección se centra en cómo implementar el objetivo a medida que comienza la ronda, comenzando con cómo establecer la cantidad de puntos que cada equipo necesita para completar la ronda.
A pesar de que el aviso de objetivo en tiempo de ejecución informa a los jugadores que necesitan anotar tres puntos para ganar, si examinas el aviso en StarterGui ⟩ HUDGui, puedes ver que en su lugar contiene un "%d" configurable para el valor de puntos.

"%d" es una cadena de marcador de posición que puedes aumentar o disminuir en cualquier momento para cumplir con tus propios requisitos de jugabilidad actualizando la variable TEAM_SCORE_LIMIT en ReplicatedStorage ⟩ TEAM_SCORE_LIMIT. Por ejemplo, si estableces este número en un excesivamente alto 200, el aviso y el contador de puntos del equipo se actualizarían en consecuencia.
local TEAM_SCORE_LIMIT = 200 -- línea actualizada, asegúrate de cambiarla de nuevo
return TEAM_SCORE_LIMIT
Esta simple actualización de variable funciona en tiempo de ejecución porque, a medida que comienza la ronda, ReplicatedStorage ⟩ HUDGuiSetup ⟩ SetObjective requiere el script de módulo TEAM_SCORE_LIMIT para que pueda intercambiar la cadena de marcador de posición en el objeto TextLabel del 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 puntos
Ahora que los jugadores tienen un objetivo para la ronda, el juego necesita llevar un registro de los puntos de cada equipo hasta que cumplan su objetivo. Mientras que el comportamiento predeterminado del servicio Teams agrupa automáticamente a cada jugador debajo de su equipo y suma las contribuciones de cada jugador a su puntaje de equipo, es importante almacenar y monitorear los puntos en un lugar separado para la jugabilidad basada en rondas, porque si un jugador anota y luego se va antes de que la ronda termine, su contribución se deduce de la tabla de clasificación tan pronto como se desconecta del juego.
Para asegurarse de que esto no suceda y que cada contribución hacia el objetivo del equipo se preserve, ReplicatedStorage ⟩ HUDGuiSetup ⟩ StartSyncingTeamPoints almacena todos los puntos por separado bajo el atributo teamPoints en el servicio Teams. A medida que teamPoints se incrementa, este script de módulo llama a la función startSyncingTeamPoints para encontrar el contador de equipo Class.GuiObjects dentro del componente UI de Objetivo.
Cuando localiza TeamACounter y TeamBCounter, obtiene su atributo teamColor, que se correlaciona con las zonas de aparición del equipo: TeamACounter muestra los puntos del equipo verde, y TeamBCounter rastrea los puntos del equipo 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)El script de módulo luego llama a su función getTeamFromTeamColor para validar que el atributo teamColor de mint de TeamACounter y el atributo teamColor de carnation pink de TeamBCounter coincidan con las propiedades Team.Color correspondientes debajo del servicio Teams. Si es así, devuelve ambos equipos.
local function getTeamFromTeamColor(teamColor: Color3): Team?
for _, team in Teams:GetTeams() do
if team.TeamColor == teamColor then
return team
end
end
return nil
endCuando esto ocurre, startSyncingTeamPoints establece los objetos TextLabel de ambos contadores de equipo a sus valores correspondientes de teamPoints, y continúa actualizándolos cada vez que un jugador anota un punto al eliminar a otro jugador del equipo contrario.
teamPointCounter.TextLabel.Text = team:GetAttribute(GuiAttribute.teamPoints)
team:GetAttributeChangedSignal(GuiAttribute.teamPoints):Connect(function()
teamPointCounter.TextLabel.Text = team:GetAttribute(GuiAttribute.teamPoints)Todo en esta sección hasta ahora se ha centrado en cómo rastrear puntos en la pantalla del jugador, pero es importante revisar la lógica que maneja el rastreo de puntos en el servidor para que sepa cuándo un equipo cumple con el objetivo y gana la ronda. Si vuelves a ServerScriptService ⟩ Gameplay ⟩ Scoring, puedes ver que el script de módulo comienza creando un evento vinculable, que se activará cada vez que un jugador anote un punto.
local teamScoreChangedBindable = Instance.new("BindableEvent")
local Scoring = {
teamScoreChanged = teamScoreChangedBindable.Event,
}Luego llama a la función incrementScore, que realiza las siguientes acciones:
- Obtiene el puntaje individual del jugador en la tabla de clasificación y suma uno.
- Activa el evento vinculable mencionado anteriormente con tanto el equipo del jugador como su puntaje.
Este proceso mantiene efectivamente alineados tanto al cliente como al servidor en cuanto a los puntajes individuales de los jugadores y sus puntajes de equipo.
function Scoring.incrementScore(player: Player, amount: number)
local team = player.Team
assert(team, `El jugador {player.Name} debe estar en un equipo para anotar un punto, pero no tiene equipo`)
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)
endMostrar resultados
A medida que los jugadores se eliminan entre sí y anotan puntos para su equipo, ServerScriptService ⟩ Gameplay ⟩ Rounds verifica si el equipo que anotó cumplió con el objetivo de la ronda. Si su puntaje de equipo es inferior a la variable TEAM_SCORE_LIMIT en ReplicatedStorage ⟩ TEAM_SCORE_LIMIT, el servidor continúa esperando hasta que uno de los equipos anote nuevamente.
Sin embargo, una vez que el puntaje de un equipo alcanza la variable TEAM_SCORE_LIMIT, el script activa una instancia de evento roundWinnerRemote con el nombre del jugador y su equipo.
-- Verificar si la ronda ha terminado después de cada puntaje
local team: Team
local score: number = 0
while score < TEAM_SCORE_LIMIT do
team, score = Scoring.teamScoreChanged:Wait()
end
-- Mostrar equipo ganador
for _, player in Players:GetPlayers() do
-- Enviando a qué equipo pertenece el jugador al final de la ronda
-- porque el equipo del jugador está a punto de ser eliminado, así que el cliente
-- no podrá verificar su propio equipo
roundWinnerRemote:FireClient(player, team, player.Team)
endEl script ReplicatedStorage ⟩ RoundResultsGuiSetup en cada cliente escucha esta instancia de evento roundWinnerRemote para que pueda:
- Mostrar una pantalla de UI única StarterGui ⟩ RoundResultsGui que anuncia los resultados de la ronda y si el jugador estaba en el equipo ganador.
- Reproducir un clip de audio de victoria o derrota.
Por ejemplo, si un jugador está en el equipo que anotó el punto ganador, recibe múltiples formas de retroalimentación sobre los resultados de la ronda en forma de una pantalla de UI que muestra texto de victoria, y un clip de audio que reproduce un sonido alegre. Por el contrario, si un jugador no está en el equipo que anotó el punto ganador, recibe una pantalla de UI que muestra texto de derrota, y un clip de audio que reproduce un sonido ominoso.
local function onRoundWinner(winner: Team, localTeam: Team?)
local victoryDefeatText = "¡La ronda ha terminado!"
if localTeam then
-- Si nuestro equipo ganó, mostraremos ¡Victoria! De lo contrario, mostraremos Derrota...
local isVictory = winner == localTeam
if isVictory then
victorySound:Play()
victoryDefeatText = VICTORY_TEXT
else
defeatSound:Play()
victoryDefeatText = DEFEAT_TEXT
end
endReiniciar equipos
Al mismo tiempo que ServerScriptService ⟩ Gameplay ⟩ Rounds verifica que un equipo cumplió con el objetivo de la ronda y activa la pantalla de UI apropiada para cada jugador, también transporta a todos los jugadores de la arena al vestíbulo desconectándolos de la ronda. Esto inicia el proceso de finalizar formalmente la ronda y reiniciar ambos equipos.
Usando la misma lógica en Configurar ubicaciones de aparición, Rounds establece la propiedad Neutral de la ubicación de aparición Neutral en true para que los jugadores puedan aparecer allí independientemente de su estado de equipo. Esto significa que el vestíbulo se convierte en la única ubicación donde los jugadores pueden aparecer después de ser desconectados de la ronda.
-- Enviar a todos al vestíbulo
playerAddedConnection:Disconnect()
neutralSpawn.Neutral = true
spawnPlayersInLobby(Players:GetPlayers())Después de esperar diez segundos para un intermedio, el script del servidor Rounds comienza el bucle nuevamente reiniciando los puntajes de todos y clasificándolos en nuevos equipos. La muestra repite este proceso cíclico de rondas nuevamente hasta que no haya jugadores en el servidor.
Ahora que los jugadores pueden aparecer en el mapa con su propio equipo y jugar una ronda completa, la siguiente sección te enseña sobre los scripts detrás del comportamiento de cada blaster.