Faire apparaître est le processus de création d'un objet ou d'un personnage dans une expérience, et réapparaître est le processus de réintégration d'un objet ou d'un personnage dans une expérience après qu'il a rencontré une condition de retrait, comme la santé d'un personnage atteignant zéro ou tombant hors de la carte. Ces deux processus sont importants car ils garantissent que les joueurs peuvent rejoindre votre expérience et peuvent continuer à jouer pour améliorer leurs compétences.
En utilisant l'expérience de laser tag échantillon comme référence, cette section du tutoriel vous enseigne comment utiliser et personnaliser les fonctionnalités intégrées de Roblox pour gérer le fait de faire apparaître et de réapparaître, y compris des conseils de scripting sur :
- Configurer les emplacements de spawn afin que les joueurs ne puissent faire apparaître que dans la zone de spawn de leur équipe.
- Ajouter de nouveaux joueurs et leur personnage à la manche lorsqu'ils rejoignent l'expérience.
- Personnaliser les champs de force qui préviennent les dommages lorsque les joueurs apparaissent et réapparaissent.
- Gérer l'état client afin que le gameplay fonctionne correctement au bon moment.
- Faire réapparaître les personnages après qu'ils ont été éliminés de la manche.
- Effectuer de petites actions diverses qui sont cruciales pour définir les paramètres de gameplay et de personnage.
Cette section comprend beaucoup de contenu de scripting, mais au lieu d'écrire tout à partir de zéro lors de la création d'une expérience, elle vous encourage à tirer parti des composants existants, à itérer rapidement et à déterminer quels systèmes nécessitent une implémentation personnalisée pour correspondre à votre vision. Après avoir terminé cette section, vous apprendrez à mettre en œuvre un gameplay basé sur des manches qui suit les points, surveille l'état des joueurs et affiche les résultats des manches.
Configurer les emplacements de spawn
Si vous deviez tester l'expérience maintenant, tous les joueurs apparaîtraient aléatoirement soit à l'objet SpawnLocation dans la zone de spawn de l'équipe verte, soit à l'objet SpawnLocation dans la zone de spawn de l'équipe rose. Cela pose un problème de gameplay où les joueurs pourraient se taguer mutuellement dans chaque zone de spawn dès que le champ de force de leur adversaire disparaît.
Pour combattre ce problème, l'expérience de laser tag échantillon configure les deux emplacements de spawn avec une propriété Neutral réglée sur false pour restreindre les joueurs de l'équipe adverse à ne pas apparaître dans la mauvaise zone de spawn, et une propriété TeamColor réglée sur la valeur correspondante de Team.Color de Attribuer des couleurs d'équipe dans la section précédente du tutoriel :
- TeamASpawn – L'emplacement de spawn dans la zone de spawn de l'équipe verte avec une propriété TeamColor réglée sur Mint.
- TeamBSpawn – L'emplacement de spawn dans la zone de spawn de l'équipe rose avec une propriété TeamColor réglée sur Carnation Pink.


Lorsqu'un joueur rejoint l'expérience, ServerScriptService ⟩ Gameplay ⟩ Rounds ⟩ spawnPlayersInMap vérifie combien de joueurs sont déjà dans chaque équipe, puis renvoie l'équipe avec le moins de joueurs.
spawnPlayersInMap
local function getSmallestTeam(): Team
local teams = Teams:GetTeams()
-- Trier les équipes dans l'ordre croissant du plus petit au plus grand
table.sort(teams, function(teamA: Team, teamB: Team)
return #teamA:GetPlayers() < #teamB:GetPlayers()
end)
-- Retourner la plus petite équipe
return teams[1]
end
Une fois qu'il a identifié l'équipe avec le moins de joueurs, il classe le joueur dans cette équipe, définit sa propriété Player.Neutral sur false afin que le joueur ne puisse faire apparaître et réapparaître que dans l'emplacement de spawn de son équipe, puis définit son PlayerState sur SelectingBlaster, que vous apprendrez à connaître davantage plus tard dans le tutoriel.
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 vous examinez Workspace ⟩ World ⟩ Map ⟩ Spawns, vous pouvez voir qu'il y a un emplacement de spawn supplémentaire dans la carte : NeutralSpawn. Cet emplacement de spawn est unique par rapport aux autres car il n'a pas de propriété TeamColor réglée sur l'une des deux équipes dans l'expérience ; au lieu de cela, cet emplacement de spawn a une propriété Neutral qui change selon que la manche est active ou non.
Par exemple, si la manche est active, la propriété Neutral est réglée sur false afin que spawnPlayersInMap puisse trier les joueurs dans les équipes et les faire apparaître dans l'arène. Cependant, si la manche n'est pas active, comme le temps entre une manche et la suivante, la propriété Neutral est réglée sur true afin que les joueurs puissent apparaître là-bas indépendamment de leur statut d'équipe. Ce processus rend l'emplacement de spawn Neutral fonctionnel comme un hall.

Pour démontrer, si vous examinez ServerScriptService ⟩ Gameplay ⟩ Rounds ⟩ SpawnPlayersInLobby, qui s'exécute à la fin d'une manche, vous pouvez voir que pour chaque joueur qui est passé dans le tableau players: { Player }, le script :
- Définit sa propriété Player.Neutral sur true pour réinitialiser automatiquement leur Player.Team à nil, permettant au joueur de réapparaître dans le hall lorsque la manche n'est pas active, car la propriété Neutral de l'emplacement de spawn est également réglée sur true.
- Change leur PlayerState en InLobby pour retirer le blaster du joueur et les visuels de l'interface utilisateur en première personne.
Pour plus d'informations sur la zone de spawn neutre et sa fonctionnalité pour chaque manche, consultez Ajouter des manches dans la prochaine section du tutoriel.
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
Connecter de nouveaux joueurs
Le code Luau dans Studio est souvent déclenché par des événements, ce qui signifie que les scripts écoutent les événements d'un service Roblox, puis appellent une fonction en réponse. Par exemple, lors de l'ajout de nouveaux joueurs à une expérience multijoueur, il doit exister un événement qui gère tout ce qui est nécessaire pour que les joueurs se connectent avec succès. Dans l'expérience de laser tag échantillon, cet événement correspondant est Players.PlayerAdded:Connect.
Players.PlayerAdded:Connect fait partie de plusieurs scripts dans l'expérience. Si vous utilisez le raccourci Ctrl/Cmd+Shift+F et recherchez Players.PlayerAdded:Connect, les résultats fournissent un bon point de départ pour comprendre la configuration initiale de l'expérience.

Pour démontrer, ouvrez ServerScriptService ⟩ SetupHumanoid. La distinction entre Player et Character est clé pour comprendre ce script :
- Les joueurs doivent choisir un blaster et être ajoutés au tableau de classement. Les personnages doivent apparaître et recevoir un blaster.
SetupHumanoid vérifie immédiatement si le joueur a un personnage (vient de rejoindre) ou n'en a pas (est en train de réapparaître). Après l'avoir trouvé, il appelle onCharacterAdded(), obtient le modèle Humanoid du personnage, et le passe à ServerScriptService ⟩ SetupHumanoid ⟩ setupHumanoidAsync pour personnalisation. Après avoir défini ces valeurs, le script attend que la santé du personnage atteigne zéro. Vous apprendrez plus sur la réapparition plus tard dans cette section du tutoriel.
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 note importante avec ce script est que les propriétés sont complètement optionnelles, ce qui signifie que si vous supprimez les six premières lignes de la fonction, l'expérience fonctionne toujours correctement. Plutôt que d'être des exigences fonctionnelles, chaque propriété vous permet de prendre des décisions de conception qui correspondent à vos objectifs de gameplay. Par exemple :
- Si vous voulez que les noms des personnages s'affichent à des distances plus rapprochées, réduisez la valeur de Humanoid.NameDisplayDistance.
- Si vous ne voulez que la santé d'un personnage s'affiche que si elle est inférieure à 100%, définissez Humanoid.HealthDisplayType sur DisplayWhenDamaged.
- Si vous souhaitez que les personnages se désassemblent lorsque leur santé atteint 0, définissez Humanoid.BreakJointsOnDeath sur True.
Si vous changez les valeurs de ces propriétés, il est important de tester le jeu afin que vous puissiez voir l'impact de vos nouveaux paramètres. Vous pouvez recréer ce que les joueurs expérimentent dans une simulation multi-client en sélectionnant au moins deux personnages dans un test de jeu Server & Clients depuis le mezzanine.

Un autre exemple de l'événement Players.PlayerAdded:Connect se trouve dans ServerScriptService ⟩ PlayerStateHandler. Tout comme dans l'exemple précédent, PlayerStateHandler vérifie immédiatement la présence d'un personnage. Si le joueur n'est pas dans le hall, le script définit un attribut de joueur sur l'état SelectingBlaster, l'état initial pour une manche où les joueurs peuvent choisir parmi deux types de blasters différents après être apparus dans l'arène. Cet état inclut également un champ de force qui empêche les joueurs de subir des dommages pendant qu'ils font leur sélection.
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)
Une variable particulière dans PlayerStateHandler mérite d'être discutée : attributeChangedConnectionByPlayer. Ce tableau stocke tous les joueurs et leurs connexions Connections à GetAttributeChangedSignal. La raison pour laquelle cette connexion est stockée dans un tableau est que PlayerStateHandler peut déconnecter celle-ci lorsque le joueur quitte l'expérience. Ce processus sert de gestion de mémoire pour éviter que le nombre de connexions n'augmente de plus en plus au fil du temps.
PlayerStateHandler
local attributeChangedConnectionByPlayer = {}
local function onPlayerAdded(player: Player)
-- Gérer toutes les mises à jour ultérieures sur l'état du joueur
attributeChangedConnectionByPlayer[player] = player
:GetAttributeChangedSignal(PlayerAttribute.playerState)
:Connect(function()
local newPlayerState = player:GetAttribute(PlayerAttribute.playerState)
onPlayerStateChanged(player, newPlayerState)
end)
end
-- Se déconnecter de la connexion d'attribut changée lorsque le joueur part
local function onPlayerRemoving(player: Player)
if attributeChangedConnectionByPlayer[player] then
attributeChangedConnectionByPlayer[player]:Disconnect()
attributeChangedConnectionByPlayer[player] = nil
end
end
Vous pouvez voir que les deux fonctions connectées dans onPlayerAdded() appellent onPlayerStateChanged(). Lors de la configuration initiale après qu'un joueur a été trié dans une équipe, onPlayerAdded() définit PlayerState sur SelectingBlaster, de sorte que la première instruction if évalue à false et désactive l'état BlasterState. Dans la section suivante Implémenter des blasters du tutoriel, vous apprendrez plus de détails sur ce processus.
PlayerStateHandler
local function onPlayerStateChanged(player: Player, newPlayerState: string)
-- L'état de blaster est 'Ready' uniquement si l'état du joueur est 'Playing'
local newBlasterState = if newPlayerState == PlayerState.Playing then BlasterState.Ready else BlasterState.Disabled
-- Planifier la logique de destruction du champ de force lorsque le joueur commence à jouer
if newPlayerState == PlayerState.Playing then
scheduleDestroyForceField(player)
end
player:SetAttribute(PlayerAttribute.blasterStateServer, newBlasterState)
end
Si vous ajoutez des points d'arrêt ou même juste une instruction print(), vous pouvez voir que onPlayerStateChanged() est appelé fréquemment tout au long de l'expérience : par exemple lors de la configuration initiale d'une manche, pour se définir sur le chemin de code principal, après que le joueur choisit un blaster, et lorsque le joueur retourne au hall, ou à l'emplacement de spawn Neutral. De plus, après que le joueur ait choisi un blaster, ServerScriptService ⟩ BlasterSelectedHandler définit PlayerState sur Playing, et PlayerStateHandler peut enfin supprimer le champ de force en appelant scheduleDestroyForceField().
Personnaliser les champs de force
Au lieu d'utiliser une implémentation personnalisée, l'expérience de laser tag échantillon utilise la classe intégrée ForceField de Studio pour empêcher les joueurs de subir des dommages pendant qu'ils sélectionnent leur blaster. Cela garantit que la seule exigence pour que les joueurs apparaissent avec un champ de force est d'inclure des emplacements de spawn avec une propriété SpawnLocation.Duration qui est supérieure à 0. L'échantillon utilise une valeur arbitraire de 9,999 pour activer les champs de force, puis gère la durée réelle par programmation dans ReplicatedStorage ⟩ ForceFieldClientVisuals.
Tout comme setupHumanoidAsync, la plupart des lignes dans ForceFieldClientVisuals sont optionnelles. Par exemple, si vous commentez le contenu de la fonction comme le fait le script suivant, l'expérience utilise le champ de force scintillant par défaut au lieu du script hexagonal dans StarterGui ⟩ ForceFieldGui.
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
Parce que le champ de force personnalisé est une interface graphique plutôt qu'un nouveau ParticleEmitter, le script ForceFieldClientVisuals n'affecte que les visuels en première personne pour chaque joueur, pas les visuels en troisième personne lorsque les joueurs regardent d'autres joueurs. Les visuels en troisième personne conservent l'apparence Roblox par défaut. Pour plus d'informations sur la modification des champs de force, consultez ForceField.Visible.


Les champs de force sont utiles car ils donnent aux joueurs suffisamment de temps entre faire apparaître et réapparaître sans avoir besoin de s'inquiéter des joueurs ennemis, mais ils doivent finalement disparaître pour le gameplay principal de laser tag. Le script qui gère la suppression des champs de force est dans ReplicatedStorage ⟩ scheduleDestroyForceField, et il vérifie trois conditions uniques :
- Après que les joueurs ont sélectionné un blaster, les champs de force doivent durer assez longtemps pour permettre aux joueurs de s'acclimater à leur environnement.
- Pendant ce temps d'acclimatation, les champs de force ne peuvent pas constituer un avantage, donc ils doivent disparaître au moment où un joueur tire avec son blaster.
- Les champs de force doivent disparaître lorsque les joueurs réinitialisent leurs personnages soit avant de tirer, soit avant que le temps du champ de force expire.
Chacune de ces vérifications dans le script scheduleDestroyForceField appelle endForceField() pour ces conditions.
scheduleDestroyForceField
-- Finir le champ de force si le joueur tire
local blasterStateAttribute = getBlasterStateAttribute()
attributeChangedConnection = player:GetAttributeChangedSignal(blasterStateAttribute):Connect(function()
local currentBlasterState = player:GetAttribute(blasterStateAttribute)
if currentBlasterState == BlasterState.Blasting then
endForceField()
end
end)
-- Finir le champ de force si le joueur se réinitialise
characterRespawnedConnection = player.CharacterRemoving:Connect(endForceField)
-- Finir le champ de force après 8 secondes
task.delay(MAX_FORCE_FIELD_TIME, endForceField)
endForceField() inclut une instruction if apparemment étrange autour du booléen forceFieldEnded. Comme les vérifications s'exécutent séquentiellement, le script peut appeler la fonction endForceField() deux ou même trois fois. Le booléen forceFieldEnded garantit que la fonction essaie de détruire un champ de force une seule fois.
scheduleDestroyForceField
local function endForceField()
if forceFieldEnded then
return
end
forceFieldEnded = true
attributeChangedConnection:Disconnect()
characterRespawnedConnection:Disconnect()
destroyForceField(player)
end
Gérer l'état client
Bien que la majeure partie de cette section se concentre sur ServerScriptService ⟩ PlayerStateHandler, il existe un autre script portant le même nom dans ReplicatedStorage. La raison de cette séparation est l'architecture client-serveur :
Le client doit comprendre les informations d'état des joueurs afin de pouvoir réagir correctement en temps réel, comme afficher les bons éléments d'interface utilisateur ou permettre aux joueurs de se déplacer et de tirer.
Le serveur a besoin de toutes ces mêmes informations afin de pouvoir empêcher les exploits. Par exemple, le serveur a également besoin de l'état des joueurs pour effectuer des actions comme faire apparaître et équiper des personnages, désactiver les champs de force et afficher un tableau de classement. C'est pourquoi ce script se trouve dans ReplicatedStorage et non dans un endroit purement côté client.
Pour voir cette logique de base, examinez le script suivant dans ReplicatedStorage ⟩ PlayerStateHandler qui vérifie l'état actuel de l'utilisateur, puis appelle la fonction appropriée qui gère les actions correspondantes à cet état.
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(`État du joueur invalide ({newPlayerState})`)
end
end
Toutes les réponses des événements sont logiquement regroupées dans ce script car elles nécessitent un comportement similaire d'activation ou de désactivation des contrôles des joueurs, de mouvement de caméra et de quel niveau d'interface utilisateur est visible. Par exemple, pendant la sélection du blaster, les joueurs doivent être à la fois invulnérables et incapables de se déplacer. Le serveur gère déjà le champ de force, mais le client gère le mouvement. Pour démontrer, si vous vérifiez la logique de la fonction onSelectingBlaster(), vous pouvez voir que le client désactive le mouvement des joueurs pendant qu'ils sélectionnent un blaster.
PlayerStateHandler
local function onSelectingBlaster()
togglePlayerCamera(true)
togglePlayerMovement(false)
setGuiExclusivelyEnabled(playerGui.PickABlasterGui)
localPlayer:SetAttribute(PlayerAttribute.blasterStateClient, BlasterState.Disabled)
end
La fonction onPlaying() est tout aussi simple. Elle active le mouvement, transitionne vers l'affichage principal (HUD), active le blaster et appelle la même fonction de champ de force que le serveur.
PlayerStateHandler
local function onPlaying()
togglePlayerMovement(true)
setGuiExclusivelyEnabled(playerGui.HUDGui)
localPlayer:SetAttribute(PlayerAttribute.blasterStateClient, BlasterState.Ready)
scheduleDestroyForceField()
end
Réapparaître des personnages
L'expérience de laser tag échantillon gère la réapparition du personnage dans une manche à travers l'état onTaggedOut() dans ReplicatedStorage ⟩ PlayerStateHandler. Tout comme l'état onSelectingBlaster() et onPlaying(), onTaggedOut() déclenche un comportement unique selon les changements de l'attribut playerState. Plus précisément, il désactive le mouvement du joueur, présente l'interface utilisateur de réapparition, et désactive le blaster.
PlayerStateHandler
local function onTaggedOut()
-- Désactiver les contrôles pendant qu'on est tagué
togglePlayerMovement(false)
togglePlayerCamera(false)
setGuiExclusivelyEnabled(playerGui.OutStateGui)
-- Désactiver le blaster pendant qu'on est tagué
localPlayer:SetAttribute(PlayerAttribute.blasterStateClient, BlasterState.Disabled)
end
Si vous souhaitez tester ce comportement, vous pouvez appuyer sur Esc, naviguer jusqu'à l'onglet Paramètres, puis cliquer sur le bouton Réinitialiser le personnage. Remarquez que lorsque vous déclenchez l'écran de réapparition, vous ne pouvez pas bouger, faire pivoter la caméra ou tirer avec votre blaster.


Il est important de noter que ce script ne fait pas réellement réapparaître les personnages, il arrête juste leurs actions et fournit un retour visuel aux joueurs que le serveur réapparaît leurs personnages. Pour démontrer, si vous examinez ServerScriptService ⟩ SetupHumanoid ⟩ setupHumanoidAsync ⟩ onHumanoidDied, le script définit PlayerState sur TaggedOut (notifiant essentiellement ReplicatedStorage ⟩ PlayerStateHandler), et ajoute quelques indicateurs visuels. La logique réelle de réapparition est un comportement intégré de Roblox.
Lorsque les joueurs réapparaissent dans la manche, ils réapparaissent à l'emplacement de spawn de leur équipe selon la propriété SpawnLocation.TeamColor. Pour personnaliser le temps de réapparition, vous pouvez ajouter la ligne suivante en haut de SetupHumanoid. Pour en savoir plus sur cette technique, consultez Players.RespawnTime.
SetupHumanoidlocal Players = game:GetService("Players")Players.RespawnTime = 10 -- nouvelle ligne, en secondes
Configuration divers
Dans le cadre de la configuration initiale, l'expérience de laser tag échantillon effectue également quelques étapes petites mais cruciales :
L'expérience inclut un script vide nommé StarterPlayer ⟩ StarterCharacterScripts ⟩ Health qui désactive la régénération de santé par défaut de Roblox. Pour une explication du comportement de cette propriété, consultez Humanoid.Health.
L'expérience utilise une caméra en première personne en réglant la propriété StarterPlayer.CameraMode.LockFirstPerson. Notez que si vous souhaitez permettre aux utilisateurs de changer entre les caméras en première et en troisième personne, vous devez modifier la propriété par programmation plutôt que de simplement la définir une fois dans Studio, et modifier les contrôles et l'interface utilisateur pour compenser le changement de perspective.
L'expérience utilise le tableau de classement intégré de Roblox avec l'unité "points", que les joueurs gagnent chaque fois qu'ils taguent un autre joueur. Vous pouvez voir la configuration dans ServerScriptService ⟩ SetupLeaderboard, mais Tableaux de classement en expérience offre un aperçu complet. Notez que onPlayerTagged ajoute des points au tableau de classement, ce que vous apprendrez dans Ajouter des manches et Détecter les coups.
Maintenant que les joueurs peuvent faire apparaître, choisir un blaster et le viser d'un point de vue en première personne, la prochaine section vous apprend les scripts derrière la création d'un gameplay basé sur des manches.