Generar y reaparición

*Este contenido se traduce usando la IA (Beta) y puede contener errores. Para ver esta página en inglés, haz clic en aquí.

Generar es el proceso de crear un objeto o personaje en una experiencia, y reaparición es el proceso de añadir un objeto o personaje de nuevo en una experiencia después de que cumplan una condición de eliminación, como la salud de un personaje llegando a cero o cayendo del mapa. Ambos procesos son importantes porque aseguran que los jugadores puedan unirse a tu experiencia y pueden seguir jugando para mejorar sus habilidades.

Usando la experiencia de laser 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 manejar la generación y reapertura, incluyendo orientación sobre scripting sobre:

  • Configurar ubicaciones de generación para que los jugadores solo puedan aparecer en la zona de generación de su equipo.
  • Añadir nuevos jugadores y su personaje a la ronda a medida que se unen a la experiencia.
  • Personalizar campos de fuerza que previenen daño mientras los jugadores generan y reaparecen.
  • Manejar el estado del cliente para que el juego funcione correctamente en el momento apropiado.
  • Reaparecer personajes después de que sean eliminados de la ronda.
  • Realizar acciones pequeñas y diversas que son cruciales para establecer parámetros de juego y de personajes.

Esta sección incluye mucho contenido de scripting, pero en lugar de escribir todo desde cero al crear una experiencia, te anima a aprovechar los componentes existentes, iterar rápidamente y averiguar qué sistemas necesitan una implementación personalizada para ajustarse a tu visión. Después de completar esta sección, aprenderás cómo implementar un juego basado en rondas que rastrea puntos, monitorea el estado del jugador, y muestra los resultados de la ronda.

Configurar ubicaciones de generación

Si fueras a probar la experiencia en este momento, todos los jugadores aparecerían aleatoriamente en el objeto SpawnLocation en la zona de generación del equipo verde o en el objeto SpawnLocation en la zona de generación del equipo rosa. Esto presenta un problema de jugabilidad donde los jugadores podrían eliminarse mutuamente dentro de cada zona de generación tan pronto como desaparezca el campo de fuerza de su oponente.

Para combatir este problema, la experiencia de laser tag de muestra configura ambas ubicaciones de generación con una propiedad Neutral establecida en false para restringir a los jugadores del equipo contrario de aparecer en la zona de generación equivocada, y una propiedad TeamColor establecida en el correspondiente valor Team.Color de Asignar colores de equipo en la sección anterior del tutorial:

  • TeamASpawn – La ubicación de generación en la zona de generación del equipo verde con una propiedad TeamColor establecida en Mint.
  • TeamBSpawn – La ubicación de generación en la zona de generación del equipo rosa con una propiedad TeamColor establecida en Carnation Pink.
TeamASpawn
TeamBSpawn

Cuando un jugador se une a la experiencia, ServerScriptServiceGameplayRoundsspawnPlayersInMap verifica cuántos jugadores ya hay en cada equipo, luego devuelve el equipo con menos cantidad de jugadores.

spawnPlayersInMap

local function getSmallestTeam(): Team
local teams = Teams:GetTeams()
-- Ordenar equipos en orden ascendente desde el más pequeño al más grande
table.sort(teams, function(teamA: Team, teamB: Team)
return #teamA:GetPlayers() < #teamB:GetPlayers()
end)
-- Devolver el equipo más pequeño
return teams[1]
end

Una vez que sabe cuál es el equipo con menos cantidad de jugadores, lo clasifica en ese equipo, establece su propiedad Player.Neutral en false para que el jugador solo pueda aparecer y reaparecer en la ubicación de generación de su equipo, luego establece su PlayerState a SelectingBlaster, de lo cual aprenderás más adelante en el 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

Si examinas WorkspaceWorldMapSpawns, puedes ver que hay una ubicación de generación más en el mapa: NeutralSpawn. Esta ubicación de generación es única entre las demás porque no tiene una propiedad TeamColor establecida en uno de los dos equipos en la experiencia; en cambio, esta ubicación de generación tiene una propiedad Neutral que cambia dependiendo de si una ronda está activa.

Por ejemplo, si la ronda está activa, la propiedad Neutral se establece en false para que spawnPlayersInMap pueda clasificar a los jugadores en equipos y generarlos en la arena. Sin embargo, si la ronda no está activa, como el tiempo entre una ronda y la siguiente, la propiedad Neutral se establece en true para que los jugadores puedan aparecer allí independientemente de su estado de equipo. Este proceso es lo que hace que la ubicación de Neutral sea un vestíbulo funcional.

Neutral

Para demostrar, si examinas ServerScriptServiceGameplayRoundsSpawnPlayersInLobby, que se ejecuta al final de una ronda, puedes ver que para cada jugador que se pasa en la tabla players: { Player }, el script:

  • Establece su propiedad Player.Neutral en true para restablecer automáticamente su Player.Team a nil, permitiendo que el jugador reaparezca en el vestíbulo cuando una ronda no está activa, ya que la propiedad Neutral de la ubicación de generación también se establece en true.
  • Cambia su PlayerState a InLobby para eliminar el blaster del jugador y los elementos visuales de la interfaz de usuario en primera persona.

Para más información sobre la zona de generación neutral y su funcionalidad para cada ronda, consulta Añadir rondas en la siguiente sección 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

Conectar nuevos jugadores

El código Luau en Studio a menudo se basa en eventos, lo que significa que los scripts escuchan eventos de un servicio de Roblox, luego llaman a una función en respuesta. Por ejemplo, al añadir nuevos jugadores a una experiencia multijugador, debe haber un evento que maneje todo lo necesario para que los jugadores se conecten con éxito. En la experiencia de laser tag de muestra, este evento correspondiente es Players.PlayerAdded:Connect.

Players.PlayerAdded:Connect es parte de varios scripts en la experiencia. Si usas el acceso directo Ctrl/Cmd+Shift+F y buscas Players.PlayerAdded:Connect, los resultados proporcionan un buen punto de partida para entender la configuración inicial de la experiencia.

Ventana de Buscar Todo de Studio con los resultados de Players.PlayerAdded resaltados.

Para demostrar, abre ServerScriptServiceSetupHumanoid. La distinción entre Player y Character es clave para entender este script:

  • Un jugador es un cliente conectado, y un personaje es un modelo de Humanoid.
  • Los jugadores necesitan elegir un blaster y ser añadidos al ranking. Los personajes necesitan generarse y recibir un blaster.

SetupHumanoid inmediatamente verifica si el jugador tiene un personaje (acaba de unirse) o no (está reapareciendo). Después de encontrar uno, llama a onCharacterAdded(), obtiene el modelo Humanoid del personaje, y lo pasa a ServerScriptServiceSetupHumanoidsetupHumanoidAsync para personalización. Después de establecer estos valores, el script espera a que la salud del personaje llegue a cero. Aprenderás más sobre la reapertura más adelante en esta sección 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 con este script es que las propiedades son completamente opcionales, lo que significa que si eliminas las primeras seis líneas de la función, la experiencia sigue funcionando correctamente. En lugar de ser requisitos funcionales, cada propiedad te permite tomar decisiones de diseño que cumplan tus objetivos de jugabilidad. Por ejemplo:

Si cambias los valores de estas propiedades, es importante probar la experiencia para que puedas ver el impacto de tus nuevos ajustes. Puedes recrear lo que los jugadores experimentan en una simulación de múltiples clientes seleccionando al menos dos personajes en una prueba de juego de Servidor & Clientes desde el entrepiso.

Opción de Servidor y Clientes en el menú desplegable de modos de prueba del entrepiso de Studio.

Otro ejemplo del evento Players.PlayerAdded:Connect se encuentra en ServerScriptServicePlayerStateHandler. Al igual que en el ejemplo anterior, PlayerStateHandler verifica inmediatamente por un personaje. Si el jugador no está en el vestíbulo, el script establece un atributo del jugador en el estado SelectingBlaster, el estado inicial para una ronda en la que los jugadores pueden seleccionar entre dos tipos diferentes de blaster después de aparecer en la arena. Este estado también incluye un campo de fuerza que evita que los jugadores tomen daño mientras están haciendo su selección.

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 variable particular en PlayerStateHandler merece discusión: attributeChangedConnectionByPlayer. Esta tabla almacena todos los jugadores y sus Connections a GetAttributeChangedSignal. La razón para almacenar esta conexión en una tabla es para que PlayerStateHandler pueda desconectarla cuando el jugador abandona la experiencia. Este proceso sirve como un tipo de gestión de memoria para prevenir que el número de conexiones crezca excesivamente con el tiempo.

PlayerStateHandler

local attributeChangedConnectionByPlayer = {}
local function onPlayerAdded(player: Player)
-- Manejar todas las futuras actualizaciones al estado del jugador
attributeChangedConnectionByPlayer[player] = player
:GetAttributeChangedSignal(PlayerAttribute.playerState)
:Connect(function()
local newPlayerState = player:GetAttribute(PlayerAttribute.playerState)
onPlayerStateChanged(player, newPlayerState)
end)
end
-- Desconectar de la conexión del atributo cambiado cuando el jugador abandona
local function onPlayerRemoving(player: Player)
if attributeChangedConnectionByPlayer[player] then
attributeChangedConnectionByPlayer[player]:Disconnect()
attributeChangedConnectionByPlayer[player] = nil
end
end

Puedes ver que ambas funciones conectadas en onPlayerAdded() llaman a onPlayerStateChanged(). Durante la configuración inicial, después de que un jugador se clasifica en un equipo, onPlayerAdded() establece PlayerState en SelectingBlaster, por lo que la primera instrucción if evalúa a falsa y desactiva el BlasterState. En la sección posterior Implementar blasters del tutorial, aprenderás más detalles sobre este proceso.

PlayerStateHandler

local function onPlayerStateChanged(player: Player, newPlayerState: string)
-- El estado del blaster es 'Listo' solo si el estado del jugador es 'Jugando'
local newBlasterState = if newPlayerState == PlayerState.Playing then BlasterState.Ready else BlasterState.Disabled
-- Programar la lógica para destruir el campo de fuerza cuando el jugador comience a jugar
if newPlayerState == PlayerState.Playing then
scheduleDestroyForceField(player)
end
player:SetAttribute(PlayerAttribute.blasterStateServer, newBlasterState)
end

Si agregas puntos de interrupción o incluso solo una instrucción print(), puedes ver que onPlayerStateChanged() se llama con frecuencia a lo largo de la experiencia: como durante la configuración inicial de una ronda, para establecerse en la ruta principal del código, después de que el jugador elige un blaster, y cuando el jugador regresa al vestíbulo, o a la ubicación de generación Neutral. Además, después de que el jugador elige un blaster, ServerScriptServiceBlasterSelectedHandler establece el PlayerState en Playing, y PlayerStateHandler finalmente puede eliminar el campo de fuerza al llamar a scheduleDestroyForceField().

Personalizar campos de fuerza

En lugar de utilizar una implementación personalizada, la experiencia de laser tag de muestra utiliza la clase ForceField integrada de Studio para evitar que los jugadores tomen daño mientras están seleccionando su blaster. Esto asegura que el único requisito para que los jugadores aparezcan con un campo de fuerza sea incluir ubicaciones de generación con una propiedad SpawnLocation.Duration que sea mayor que 0. La muestra utiliza un valor arbitrario de 9,999 para habilitar los campos de fuerza, luego maneja la duración real programáticamente en ReplicatedStorageForceFieldClientVisuals.

Al igual que setupHumanoidAsync, la mayoría de las líneas en ForceFieldClientVisuals son opcionales. Por ejemplo, si comentas el contenido de la función como lo hace el siguiente script, la experiencia utiliza el campo de fuerza brillante predeterminado en lugar del script hexagonal en StarterGuiForceFieldGui.

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

Debido a que el campo de fuerza personalizado es una interfaz gráfica de usuario en lugar de un nuevo ParticleEmitter, el script ForceFieldClientVisuals solo afecta los elementos visuales en primera persona para cada jugador, no los elementos visuales en tercera persona cuando los jugadores miran a otros jugadores. Los elementos visuales en tercera persona conservan la apariencia predeterminada de Roblox. Para más información sobre la modificación de campos de fuerza, consulta ForceField.Visible.

Los elementos visuales del campo de fuerza en primera persona incluyen una cuadrícula hexagonal futurista en el perímetro de la pantalla.
Elementos visuales del campo de fuerza en primera persona
Los elementos visuales del campo de fuerza en tercera persona incluyen un orbe azul brillante alrededor del jugador que aparece en la experiencia.
Elementos visuales del campo de fuerza en tercera persona

Los campos de fuerza son útiles porque brindan a los jugadores suficiente tiempo entre la aparición y la reaparición sin necesidad de preocuparse por los jugadores enemigos, pero eventualmente necesitan desaparecer para la jugabilidad principal de laser tag. El script que maneja la eliminación de campos de fuerza se encuentra en ReplicatedStoragescheduleDestroyForceField, y verifica tres condiciones únicas:

  • Después de que los jugadores eligen un blaster, los campos de fuerza deben durar lo suficiente para permitir que los jugadores se acclimaten a su alrededor.
  • Durante este tiempo de aclimatación, los campos de fuerza no pueden ser una ventaja, por lo que necesitan desaparecer en el momento en que un jugador dispara su blaster.
  • Los campos de fuerza deben desaparecer cuando los jugadores restablecen sus personajes ya sea antes de disparar o antes de que el campo de fuerza expire.

Cada una de estas verificaciones en el script scheduleDestroyForceField llama a endForceField() para estas condiciones.

scheduleDestroyForceField

-- Terminar campo de fuerza si el jugador dispara
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 fuerza si el jugador se restablece
characterRespawnedConnection = player.CharacterRemoving:Connect(endForceField)
-- Terminar campo de fuerza después de 8 segundos
task.delay(MAX_FORCE_FIELD_TIME, endForceField)

endForceField() incluye una declaración if que parece extraña alrededor del booleano forceFieldEnded. Debido a que las verificaciones se ejecutan secuencialmente, el script puede llamar a la función endForceField() dos o incluso tres veces. El booleano forceFieldEnded asegura que la función solo intente destruir un campo de fuerza una vez.

scheduleDestroyForceField

local function endForceField()
if forceFieldEnded then
return
end
forceFieldEnded = true
attributeChangedConnection:Disconnect()
characterRespawnedConnection:Disconnect()
destroyForceField(player)
end

Manejar el estado del cliente

Mientras que la mayor parte de esta sección se centra en ServerScriptServicePlayerStateHandler, hay otro script del mismo nombre en ReplicatedStorage. La razón de la división es la arquitectura cliente-servidor:

  • El cliente necesita entender la información del estado del jugador para que pueda responder apropiadamente en tiempo real, como mostrar los elementos correctos de interfaz de usuario, o permitir que los jugadores se muevan y disparen.

  • El servidor necesita toda esta misma información para que pueda prevenir exploits. Por ejemplo, el servidor también necesita el estado del jugador para realizar acciones como generar y equipar personajes, deshabilitar campos de fuerza y mostrar un ranking. Esta es la razón por la que este script está en ReplicatedStorage y no en una ubicación puramente del lado del cliente.

Para ver esta lógica central, revisa el siguiente script en ReplicatedStoragePlayerStateHandler que verifica el estado actual del usuario, luego llama a la función apropiada que maneja las acciones correspondientes para ese estado.

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(`Estado de jugador inválido ({newPlayerState})`)
end
end

Todas las respuestas a eventos están lógicamente agrupadas en este script porque requieren un comportamiento similar de habilitar o deshabilitar los controles del jugador, el movimiento de la cámara y qué capa de interfaz de usuario está visible. Por ejemplo, durante la selección de blasters, los jugadores necesitan ser tanto invulnerables como incapaces de moverse. El servidor ya maneja el campo de fuerza, pero el cliente maneja el movimiento. Para demostrar, si revisas la lógica de la función onSelectingBlaster(), puedes ver que el cliente desactiva el movimiento del jugador mientras seleccionan un blaster.

PlayerStateHandler

local function onSelectingBlaster()
togglePlayerCamera(true)
togglePlayerMovement(false)
setGuiExclusivelyEnabled(playerGui.PickABlasterGui)
localPlayer:SetAttribute(PlayerAttribute.blasterStateClient, BlasterState.Disabled)
end

La función onPlaying() es igualmente sencilla. Habilita el movimiento, transita a la pantalla principal de visualización (HUD), habilita el blaster y llama a la misma función del campo de fuerza que el servidor.

PlayerStateHandler

local function onPlaying()
togglePlayerMovement(true)
setGuiExclusivelyEnabled(playerGui.HUDGui)
localPlayer:SetAttribute(PlayerAttribute.blasterStateClient, BlasterState.Ready)
scheduleDestroyForceField()
end

Reaparecer personajes

La experiencia de laser tag de muestra maneja la reaparición del personaje de nuevo en una ronda a través del estado onTaggedOut() en ReplicatedStoragePlayerStateHandler. Al igual que los estados onSelectingBlaster() y onPlaying(), onTaggedOut() desencadena un comportamiento único de acuerdo a los cambios en el atributo playerState. Específicamente, desactiva el movimiento del jugador, presenta la interfaz de usuario de reaparición y desactiva el blaster.

PlayerStateHandler

local function onTaggedOut()
-- Desactivar controles mientras se está eliminado
togglePlayerMovement(false)
togglePlayerCamera(false)
setGuiExclusivelyEnabled(playerGui.OutStateGui)
-- Desactivar el blaster mientras se está eliminado
localPlayer:SetAttribute(PlayerAttribute.blasterStateClient, BlasterState.Disabled)
end

Si deseas probar este comportamiento, puedes presionar Esc, navegar a la pestaña Configuración, y luego hacer clic en el botón Restablecer personaje. Nota que cuando activas la pantalla de reaparición, no puedes moverte, rotar la cámara, ni disparar tu blaster.

Menú de configuración de Roblox con el botón Restablecer personaje resaltado.
Botón Restablecer Personaje
La pantalla de reaparición se muestra mientras un jugador reaparece en la ronda.
Pantalla de Reaparición

Es importante notar que este script no respawnea realmente a los personajes, solo detiene su acción y proporciona retroalimentación visual a los jugadores de que el servidor está reapareciendo a sus personajes. Para demostrar, si examinas ServerScriptServiceSetupHumanoidsetupHumanoidAsynconHumanoidDied, el script establece PlayerState en TaggedOut (esencialmente notificando a ReplicatedStoragePlayerStateHandler), y agrega algunos indicadores visuales. La lógica real de reaparición es un comportamiento integrado de Roblox.

Cuando los jugadores reaparecen en la ronda, reaparecen en la ubicación de generación de su equipo de acuerdo a la propiedad SpawnLocation.TeamColor. Para personalizar el tiempo de reaparición, puedes añadir la siguiente línea en la parte superior de SetupHumanoid. Para aprender más sobre esta técnica, consulta Players.RespawnTime.

SetupHumanoid

local Players = game:GetService("Players")
Players.RespawnTime = 10 -- nueva línea, en segundos

Configuración miscelánea

Como parte de la configuración inicial, la experiencia de laser tag de muestra también realiza algunos pequeños pasos, pero críticos:

  • La experiencia incluye un script vacío llamado StarterPlayerStarterCharacterScriptsHealth que desactiva la regeneración de salud predeterminada de Roblox. Para una explicación del comportamiento de esta propiedad, consulta Humanoid.Health.

  • La experiencia usa una cámara en primera persona configurando la propiedad StarterPlayer.CameraMode.LockFirstPerson. Ten en cuenta que si deseas permitir que los usuarios cambien entre las cámaras en primera y tercera persona, debes cambiar la propiedad programáticamente en lugar de simplemente establecerla una vez en Studio, y modificar los controles y la interfaz de usuario para compensar el cambio en la perspectiva.

  • La experiencia utiliza el ranking integrado de Roblox con la unidad de "puntos", que los jugadores ganan cada vez que eliminan a otro jugador. Puedes ver la configuración en ServerScriptServiceSetupLeaderboard, pero Ranking en la experiencia ofrece una visión completa. Ten en cuenta que onPlayerTagged añade puntos al ranking, del cual aprenderás en Añadir rondas y Detectar golpes.

Ahora que los jugadores pueden aparecer, elegir un blaster y apuntar desde un punto de vista en primera persona, la siguiente sección te enseñará sobre los scripts detrás de la creación de jugabilidad basada en rondas.

©2026 Roblox Corporation. Roblox, el logotipo de Roblox y "Powering Imagination" son algunas de nuestras marcas registradas y no registradas en los Estados Unidos y otros países.