Generar es el proceso de crear un objeto o personaje en una experiencia, y reaparecer es el proceso de agregar un objeto o personaje de vuelta a una experiencia después de que cumplan una condición de eliminación, como que la salud de un personaje llegue a cero o caiga del mapa.Ambos procesos son importantes porque garantizan que los jugadores puedan unirse a tu experiencia y puedan seguir jugando para mejorar sus habilidades.
Usando la experiencia de etiqueta láser de muestra como referencia, esta sección del tutorial te enseña cómo usar y personalizar las funciones integradas de Roblox para manejar la generación y el reaparecimiento, incluyendo instrucciones de programación sobre:
- Configurar lugares de generación para que los jugadores solo puedan generarse 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ños mientras los jugadores aparecen y reaparecen.
- Manipular el estado del cliente para que el juego funcione correctamente en el momento adecuado.
- Reaparecer personajes después de que sean etiquetados fuera de la ronda.
- Realizar pequeñas acciones misceláneas que son cruciales para establecer los parámetros de juego y personaje.
Esta sección incluye mucho contenido de programación, pero en lugar de escribir todo desde cero al crear una experiencia, te anima a aprovechar componentes existentes, a iterar rápidamente y a averiguar qué sistemas necesitan una implementación personalizada para coincidir con tu visión.Una vez que completes esta sección, aprenderás cómo implementar el juego basado en rondas que rastrea puntos, monitorea el estado del jugador y muestra los resultados de la ronda.
Configurar lugares de aparición
Si jugaras a probar la experiencia ahora mismo, 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 juego en el que los jugadores podrían etiquetarse 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 etiqueta láser de muestra configura ambas ubicaciones de generación con una propiedad establecida en falso para restringir que los jugadores del equipo contrario se generen en la zona de generación incorrecta, y una propiedad establecida al valor correspondiente 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 Menta .
- TeamBSpawn – La ubicación de generación en la zona de generación del equipo rosa con una propiedad TeamColor establecida en Carnation Pink .


Cuando un jugador se une a la experiencia, ServerScriptService > Juego > Rondas > spawnPlayersInMap comprueba cuántos jugadores ya están en cada equipo, luego devuelve el equipo con la menor cantidad de jugadores.
Generar jugadores en el mapa
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 conoce el equipo con la menor cantidad de jugadores, lo clasifica en ese equipo, establece su propiedad Player.Neutral en falso para que el jugador solo pueda generar y reaparecer en la ubicación de generación de su equipo, luego establece su propiedad PlayerState en SelectingBlaster, lo que aprenderá más tarde en el tutorial.
Generar jugadores en el mapa
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 Espacio de trabajo > Mundo > Mapa > Genera , puedes ver que hay una ubicación de generación más en el mapa: NeutralSpawn .Esta ubicación de generación es única de las otras porque no tiene un conjunto de propiedades TeamColor configurado 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 falso para que spawnPlayersInMap pueda ordenar 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 verdadero para que los jugadores puedan aparecer allí independientemente del estado de su equipo.Este proceso es lo que hace que la ubicación de generación neutral sea un lobby funcional.

Para demostrar, si examinas Servicio de guiones de servidor > Juego > Rondas > SpawnPlayersInLobby , que se ejecuta al final de una ronda, puedes ver que para cada jugador que se pase a la tabla players: { Player }, el script:
- Establece su propiedad en verdad para restablecer automáticamente su a , permitiendo que el jugador reaparezca en el vestíbulo cuando una ronda no está activa, ya que la propiedad de la ubicación de generación también se establece en verdad .
- Cambia su PlayerState a InLobby para eliminar las visuales de la interfaz de usuario en primera persona del jugador y sus blasters.
Para obtener más información sobre la zona de generación neutral y su funcionalidad para cada ronda, consulte Añadir rondas en la siguiente sección del tutorial.
Generar jugadores en el vestíbulo
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 es impulsado por eventos, lo que significa que los scripts escuchan eventos de un servicio de Roblox, luego llaman una función en respuesta.Por ejemplo, al agregar 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 etiqueta láser de muestra, este evento correspondiente es Players.PlayerAdded:Connect .
Players.PlayerAdded:Connect es una parte de múltiples scripts en la experiencia.Si usa el atajo Ctrl/Cmd+Shift+F y busca Players.PlayerAdded:Connect, los resultados proporcionan un buen punto de partida para comprender el inicio de la configuración de la experiencia.

Para demostrar, abra Servicio de guión de servidor > Configurar humanoides .La distinción entre Player y Character es clave para entender este script:
- Un jugador es un cliente conectado, y un personaje es un aplicación de modelado.
- Los jugadores deben elegir un blaster y ser agregados a la tabla de clasificación. Los personajes deben generarse y recibir un blaster.
SetupHumanoid comprueba inmediatamente si el jugador tiene un personaje (acaba de unirse) o no (se está reapareciendo).Después de encontrar uno, llama onCharacterAdded() , obtiene el modelo Humanoid del personaje y lo pasa a ServerScriptService > SetupHumanoid > setupHumanoidAsync para personalización.Después de establecer estos valores, el script luego espera a que la salud del personaje llegue a cero.Aprenderá más sobre el reaparecimiento más tarde 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 con tus objetivos de juego.Por ejemplo:
- Si quieres que los nombres de personajes se muestren a distancias más cercanas, reduce el valor de Humanoid.NameDisplayDistance .
- Si solo quieres que la salud de un personaje se muestre si está por debajo del 100%, establece Humanoid.HealthDisplayType a Mostrar cuando dañado .
- Si quieres que los personajes se rompan cuando su salud llegue a 0, establece Humanoid.BreakJointsOnDeath a Verdadero .
Si cambias los valores de estas propiedades, es importante probar para que puedas ver el impacto de tus nuevas configuraciones.Puedes recrear lo que experimentan los jugadores en un entorno multijugador seleccionando al menos dos personajes en la sección Clientes y servidores de la pestaña Prueba .

Otro ejemplo del evento Players.PlayerAdded:Connect es en ServerScriptService > JugadorEstadoManipulador .Al igual que en el ejemplo anterior, PlayerStateHandler comprueba inmediatamente un carácter.Si el jugador no está en el vestíbulo, el script establece un atributo de jugador al estado SelectingBlaster , el estado inicial para una ronda en la que los jugadores pueden seleccionar uno de dos tipos diferentes de láseres después de aparecer en la arena.Este estado también incluye un campo de fuerza que impide que los jugadores reciban daños mientras están haciendo su selección.
Manojo de estado del jugador
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 guerra merece discusión: attributeChangedConnectionByPlayer .Esta tabla almacena a todos los jugadores y sus Connections a la GetAttributeChangedSignal .La razón por almacenar esta conexión en una tabla es para que PlayerStateHandler pueda desconectarse cuando el jugador abandone la experiencia.Este proceso sirve como una especie de gestión de memoria para evitar que el número de conexiones crezca cada vez más grande con el tiempo.
Manojo de estado del jugador
local attributeChangedConnectionByPlayer = {}
local function onPlayerAdded(player: Player)
-- Manija todas las actualizaciones futuras al estado del jugador
attributeChangedConnectionByPlayer[player] = player
:GetAttributeChangedSignal(PlayerAttribute.playerState)
:Connect(function()
local newPlayerState = player:GetAttribute(PlayerAttribute.playerState)
onPlayerStateChanged(player, newPlayerState)
end)
end
-- Desconectarse de la conexión cambiada por el atributo cuando el jugador se va
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 onPlayerStateChanged() .Durante la configuración inicial después de que un jugador se ordene en un equipo, onPlayerAdded() establece PlayerState a SelectingBlaster , por lo que la primera declaración if se evaluará como falsa y deshabilitará el BlasterState .En la sección de implementación de blasters más tarde del tutorial, aprenderás más detalles sobre este proceso.
Manojo de estado del jugador
local function onPlayerStateChanged(player: Player, newPlayerState: string)
-- El estado del bláster 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 del campo de fuerza de destrucción cuando el jugador comienza 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 declaración , puedes ver que se llama con frecuencia a lo largo de la experiencia: como durante la configuración inicial de una ronda, para establecerse en el camino de código principal, después de que el jugador elija un bláster, y cuando el jugador regresa al vestíbulo, o la ubicación de generación neutral.Además, después de que el jugador elija un blaster, ServerScriptService > BlasterSelectedHandler establece el PlayerState a Playing , y PlayerStateHandler finalmente puede eliminar el campo de fuerza al llamar scheduleDestroyForceField() .
Personalizar campos de fuerza
En lugar de usar una implementación personalizada, la experiencia de etiqueta láser de muestra usa la clase integrada de Studio ForceField para evitar que los jugadores se dañen mientras seleccionan su bláster.Esto garantiza que el único requisito para que los jugadores aparezcan con un campo de fuerza es incluir lugares de aparición con una propiedad SpawnLocation.Duration que sea mayor que 0.La muestra utiliza un valor arbitrario de 9,999 para habilitar campos de fuerza, luego maneja la duración real de forma programática en ReplicatedStorage > ForceFieldClientVisuals .
Similar a 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 usa el campo de fuerza brillante predeterminado en lugar del script hexagonal en StarterGui > ForceFieldGui .
Comentar las propiedades en ForceFieldClientVisuals
local function onCharacterAddedAsync(character: Model)
-- field de fuerza local = personaje:WaitForChild("ForceField", 3)
-- si no es forceField entonces
-- devolver
-- finalizar
-- forceField. visible = falso
-- localPlayer.PlayerGui:WaitForChild("ForceFieldGui").Enabled = verdad
-- forceField.Destroying: espera()
-- localPlayer.PlayerGui.ForceFieldGui.Enabled = falso
end
Debido a que el campo de fuerza personalizado es una interfaz gráfica en lugar de una nueva ParticleEmitter, el script ForceFieldClientVisuals solo afecta las visualizaciones en primera persona para cada jugador, no visualizaciones en tercera persona cuando los jugadores miran a otros jugadores.Las visuales en primera persona mantienen la aspecto, lookpredeterminada de Roblox.Para obtener más información sobre la modificación de campos de fuerza, consulte ForceField.Visible .


Los campos de fuerza son útiles porque brindan a los jugadores suficiente tiempo para entretejer y reaparecer sin necesidad de preocuparse por los jugadores enemigos, pero eventualmente deben desaparecer para el juego principal de etiqueta láser.El script que maneja la eliminación del campo de fuerza está en ReplicatedStorage > scheduleDestroyForceField , y comprueba tres condiciones únicas:
- Después de que los jugadores seleccionen un bláster, los campos de fuerza deben durar lo suficiente como para permitir que los jugadores se acostumbren a su entorno.
- Durante este tiempo de aceleración, los campos de fuerza no pueden ser una ventaja, por lo que deben desaparecer el momento que un jugador lance su lanzador.
- Los campos de fuerza deben desaparecer cuando los jugadores reinicien sus personajes antes de disparar o antes de que se agote el tiempo del campo de fuerza.
Cada una de estas comprobaciones en la llamada scheduleDestroyForceField del script endForceField() para estas condiciones.
scheduleDestroyForceField
-- Campo de fuerza final si el jugador explota
local blasterStateAttribute = getBlasterStateAttribute()
attributeChangedConnection = player:GetAttributeChangedSignal(blasterStateAttribute):Connect(function()
local currentBlasterState = player:GetAttribute(blasterStateAttribute)
if currentBlasterState == BlasterState.Blasting then
endForceField()
end
end)
-- Finalizar campo de fuerza si el jugador se restablece
characterRespawnedConnection = player.CharacterRemoving:Connect(endForceField)
-- Campo de fuerza final después de 8 segundos
task.delay(MAX_FORCE_FIELD_TIME, endForceField)
endForceField() incluye una declaración aparentemente extraña if alrededor del forceFieldEnded booleano.Debido a que las comprobaciones se ejecutan secuencialmente, el script puede llamar a la función endForceField() dos o incluso tres veces.El forceFieldEnded booleano garantiza 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
Mano del estado del cliente
Mientras que la mayor parte de esta sección se centra en ServerScriptService > PlayerStateHandler , hay otro script con el mismo nombre en ReplicatedStorage .La razón de la división es la arquitectura cliente-servidor:
El cliente necesita comprender la información del estado del jugador para que pueda responder apropiadamente en tiempo real, como mostrar los elementos de interfaz de usuario correctos o permitir que los jugadores se muevan y exploten.
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 una tabla de clasificación.Es por eso que este script está en ReplicatedStorage y no es una ubicación puramente de lado del cliente.
Para ver esta lógica principal, revisa el siguiente script en ReplicatedStorage > PlayerStateHandler que verifica el estado actual del usuario, luego llama la función apropiada que maneja las acciones correspondientes para ese estado.
Manojo de estado del jugador
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 las respuestas de eventos se agrupan lógicamente juntas en este script porque requieren un comportamiento similar para habilitar o deshabilitar los controles del reproductor, el movimiento de la cámara y qué capa de interfaz de usuario es visible.Por ejemplo, durante la selección de blaster, los jugadores deben ser invulnerables y no poder herramienta de movimiento.El servidor ya maneja el campo de fuerza, pero el cliente gestiona el movimiento.Para demostrar, si verificas la lógica de la función onSelectingBlaster() , puedes ver que el cliente desactiva el movimiento del jugador mientras seleccionan un bláster.
Manojo de estado del jugador
local function onSelectingBlaster()
togglePlayerCamera(true)
togglePlayerMovement(false)
setGuiExclusivelyEnabled(playerGui.PickABlasterGui)
localPlayer:SetAttribute(PlayerAttribute.blasterStateClient, BlasterState.Disabled)
end
La función onPlaying() es similarmente sencilla.Habilita el movimiento, las transiciones al display principal de cabeceras (HUD), habilita el bláster y llama la misma función de campo de fuerza del servidor.
Manojo de estado del jugador
local function onPlaying()
togglePlayerMovement(true)
setGuiExclusivelyEnabled(playerGui.HUDGui)
localPlayer:SetAttribute(PlayerAttribute.blasterStateClient, BlasterState.Ready)
scheduleDestroyForceField()
end
Regenerar personajes
La experiencia de etiqueta láser de muestra maneja la reaparición del personaje de vuelta a una ronda a través del estado onTaggedOut() en ReplicatedStorage > JugadorEstadoManipulador .Al igual que el estado onSelectingBlaster() y onPlaying() , onTaggedOut() desencadena un comportamiento único de acuerdo con los cambios en el atributo playerState.En particular, desactiva el movimiento del jugador, presenta la interfaz de usuario de reaparición y desactiva el bláster.
Manojo de estado del jugador
local function onTaggedOut()
-- Desactivar los controles mientras esté etiquetado fuera
togglePlayerMovement(false)
togglePlayerCamera(false)
setGuiExclusivelyEnabled(playerGui.OutStateGui)
-- Desactivar el bláster mientras está etiquetado fuera
localPlayer:SetAttribute(PlayerAttribute.blasterStateClient, BlasterState.Disabled)
end
Si quieres 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 herramienta de movimiento, rotar la cámara o disparar tu bláster.


Es importante tener en cuenta que este script en realidad no respawna personajes, solo los detiene de actuar y proporciona retroalimentación visual a los jugadores que el servidor está respawnando sus personajes.Para demostrar, si examinas ServerScriptService > SetupHumanoid > setupHumanoidAsync > onHumanoidDied , el script establece PlayerState para TaggedOut (esencialmente notificando ReplicatedStorage > PlayerStateHandler ), y agrega algunos indicadores visuales.La lógica real de reaparecer 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 con la propiedad SpawnLocation.TeamColor.Para personalizar el tiempo de reaparición, puedes agregar la siguiente línea a la parte superior de SetupHumanoid .Para aprender más sobre esta técnica, vea Players.RespawnTime .
Configurar humanoides
local Players = game:GetService("Players")Players.RespawnTime = 10 -- new line, in seconds
Configuración miscelánea
Como parte de la configuración inicial, la experiencia de etiqueta láser de muestra también realiza algunos pasos pequeños, pero críticos:
La experiencia incluye un script vacío llamado StarterPlayer > StarterCharacterScripts > Salud que desactiva la regeneración de salud predeterminada de Roblox.Para una explicación del comportamiento de esta propiedad, vea Humanoid.Health .
La experiencia usa una cámara en primera persona al establecer la propiedad StarterPlayer.CameraMode.LockFirstPerson.Tenga en cuenta que si desea permitir que los usuarios cambien entre cámaras de primera y tercera persona, debe cambiar la propiedad de forma programática en lugar de simplemente establecerla una vez en Studio y modificar los controles y la interfaz de usuario para compensar el cambio de perspectiva.
La experiencia utiliza la tabla de clasificación integrada de Roblox con la unidad de "puntos", que los jugadores ganan cada vez que etiquetan a otro jugador.Puedes ver la configuración en Servicio de guía de configuración > Tabla de clasificación en experiencia , pero In-Experience Leaderboards ofrece una vista general completa.Tenga en cuenta que onPlayerTagged agrega puntos a la tabla de clasificación, que aprenderá sobre en Añadir rondas y Detectar golpes .
Ahora que los jugadores pueden generar, elige un bláster y apunta desde un punto de vista en primera persona, la siguiente sección te enseña sobre los scripts detrás de la creación de un juego basado en rondas.