Apparition et réapparition

*Ce contenu est traduit en utilisant l'IA (Beta) et peut contenir des erreurs. Pour consulter cette page en anglais, clique ici.

L'apparition est le processus de création d'un objet ou d'un personnage dans un jeu, et la réapparition est le processus d'ajout d'un objet ou d'un personnage à nouveau dans un jeu après qu'il ait rencontré une condition de retrait, comme la santé d'un personnage atteignant zéro ou tombant hors de la carte. Les deux processus sont importants car ils garantissent que les joueurs peuvent rejoindre votre jeu et continuer à jouer pour améliorer leurs compétences.

En utilisant le jeu de laser tag exemple comme référence, cette section du tutoriel vous apprend à utiliser et à personnaliser les fonctionnalités intégrées de Roblox pour gérer l'apparition et la réapparition, y compris des conseils de script sur :

  • Configurer les emplacements d'apparition afin que les joueurs ne puissent apparaître que dans la zone d'apparition de leur équipe.
  • Ajouter de nouveaux joueurs et leur personnage au tour à mesure qu'ils rejoignent le jeu.
  • Personnaliser les champs de force qui empêchent les dégâts lorsque les joueurs apparaissent et réapparaissent.
  • Gérer l'état du client afin que le gameplay fonctionne correctement au moment approprié.
  • Réapparaître des personnages après qu'ils aient été éliminés du tour.
  • 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 script, mais au lieu d'écrire tout à partir de zéro lors de la création d'un jeu, elle vous encourage à tirer parti des composants existants, à itérer rapidement et à déterminer quels systèmes nécessitent une mise en œuvre personnalisée pour correspondre à votre vision. Après avoir terminé cette section, vous apprendrez à mettre en œuvre un gameplay basé sur des tours qui suit les points, surveille l'état des joueurs et affiche les résultats des tours.

Configurer les emplacements d'apparition

Si vous deviez tester le jeu en ce moment, tous les joueurs apparaîtraient aléatoirement soit à l'objet SpawnLocation dans la zone d'apparition de l'équipe verte, soit à l'objet SpawnLocation dans la zone d'apparition de l'équipe rose. Cela pose un problème de gameplay où les joueurs pourraient se taguer mutuellement dans chaque zone d'apparition dès que le champ de force de leur adversaire disparaît.

Pour lutter contre ce problème, le jeu de laser tag exemple configure les deux emplacements d'apparition avec une propriété Neutral définie sur false pour restreindre les joueurs de l'équipe adverse d'apparaître dans la mauvaise zone d'apparition, et une propriété TeamColor définie sur la valeur correspondante Team.Color de Attribuer des couleurs d'équipe dans la section précédente du tutoriel :

  • TeamASpawn – L'emplacement d'apparition dans la zone d'apparition de l'équipe verte avec une propriété TeamColor définie sur Mint.
  • TeamBSpawn – L'emplacement d'apparition dans la zone d'apparition de l'équipe rose avec une propriété TeamColor définie sur Carnation Pink.
TeamASpawn
TeamBSpawn

Lorsqu'un joueur rejoint le jeu, ServerScriptServiceGameplayRoundsspawnPlayersInMap vérifie combien de joueurs sont déjà dans chaque équipe, puis retourne l'équipe avec le moins de joueurs.

spawnPlayersInMap
local function getSmallestTeam(): Team
local teams = Teams:GetTeams()
-- Trier les équipes par 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 connaît 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 apparaître et réapparaître que dans l'emplacement d'apparition de son équipe, puis définit son PlayerState sur SelectingBlaster, dont vous apprendrez 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 WorkspaceWorldMapSpawns, vous pouvez voir qu'il y a un emplacement d'apparition supplémentaire dans la carte : NeutralSpawn. Cet emplacement d'apparition est unique par rapport aux autres car il n'a pas de propriété TeamColor définie sur l'une des deux équipes du jeu ; au lieu de cela, cet emplacement d'apparition a une propriété Neutral qui change en fonction de l'activation d'un tour.

Par exemple, si le tour est actif, la propriété Neutral est définie sur false afin que spawnPlayersInMap puisse classer les joueurs dans des équipes et les faire apparaître dans l'arène. Cependant, si le tour n'est pas actif, comme le temps entre un tour et le suivant, la propriété Neutral est définie sur true afin que les joueurs puissent y apparaître indépendamment de leur statut d'équipe. Ce processus est ce qui rend l'emplacement d'apparition Neutral fonctionnel.

Neutral

Pour démontrer, si vous examinez ServerScriptServiceGameplayRoundsSpawnPlayersInLobby, qui s'exécute à la fin d'un tour, vous pouvez voir que pour chaque joueur passé dans le tableau players: { Player }, le script :

  • Définit leur 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 le tour n'est pas actif, car la propriété Neutral de l'emplacement d'apparition est également définie 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 d'apparition neutre et sa fonctionnalité pour chaque tour, voir Ajouter des tours dans la section suivante 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 basé sur 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 à un jeu multijoueur, il doit y avoir un événement qui gère tout ce qui est nécessaire pour que les joueurs se connectent avec succès. Dans le jeu de laser tag exemple, cet événement correspondant est Players.PlayerAdded:Connect.

Players.PlayerAdded:Connect fait partie de plusieurs scripts dans le jeu. 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 du jeu.

La fenêtre Find All de Studio avec les résultats de Players.PlayerAdded mis en surbrillance.

Pour démontrer, ouvrez ServerScriptServiceSetupHumanoid. La distinction entre Player et Character est clé pour comprendre ce script :

  • Un joueur est un client connecté, et un personnage est un modèle Humanoid.
  • 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 en avoir trouvé un, il appelle onCharacterAdded(), obtient le modèle Humanoid du personnage et le passe à ServerScriptServiceSetupHumanoidsetupHumanoidAsync pour personnalisation. Après avoir défini ces valeurs, le script attend que la santé du personnage atteigne zéro. Vous en apprendrez davantage 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, le jeu fonctionne toujours correctement. Plutôt que d'être des exigences fonctionnelles, chaque propriété vous permet de prendre des décisions de conception qui répondent à vos objectifs de gameplay. Par exemple :

  • Si vous souhaitez que les noms des personnages s'affichent à des distances plus proches, réduisez la valeur de Humanoid.NameDisplayDistance.
  • Si vous souhaitez que la santé d'un personnage ne 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 de voir l'impact de vos nouveaux paramètres. Vous pouvez recréer ce que les joueurs vivent dans une simulation multi-client en sélectionnant au moins deux personnages dans un test de jeu Server & Clients depuis le mezzanine.

Option Server & Clients dans le menu déroulant des modes de test du mezzanine de Studio.

Un autre exemple de l'événement Players.PlayerAdded:Connect se trouve dans ServerScriptServicePlayerStateHandler. 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 du joueur sur l'état SelectingBlaster, l'état initial pour un tour dans lequel les joueurs peuvent choisir parmi deux types de blasters différents après être apparus dans l'arène. Cet état comprend également un champ de force qui empêche les joueurs de subir des dégâts 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 Connections au GetAttributeChangedSignal. La raison de stocker cette connexion dans un tableau est que PlayerStateHandler peut déconnecter celle-ci lorsque le joueur quitte le jeu. Ce processus sert de gestion de la mémoire pour éviter que le nombre de connexions ne croisse indéfiniment au fil du temps.

PlayerStateHandler
local attributeChangedConnectionByPlayer = {}
local function onPlayerAdded(player: Player)
-- Gérer toutes les futures mises à jour de 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 quitte
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 soit classé dans une équipe, onPlayerAdded() définit PlayerState sur SelectingBlaster, de sorte que la première instruction if évalue à false et désactive le BlasterState. Dans la section 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 du blaster est 'Prêt' uniquement si l'état du joueur est 'Jouant'
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 du jeu : par exemple, lors de la configuration initiale d'un tour, pour se définir sur le chemin principal du code, après que le joueur ait choisi un blaster, et lorsque le joueur retourne au hall, ou à l'emplacement d'apparition Neutral. De plus, après que le joueur ait choisi un blaster, ServerScriptServiceBlasterSelectedHandler définit le 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 mise en œuvre personnalisée, le jeu de laser tag exemple utilise la classe intégrée ForceField de Studio pour empêcher les joueurs de subir des dégâts 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 d'apparition avec une propriété SpawnLocation.Duration supérieure à 0. L'exemple utilise une valeur arbitraire de 9 999 pour activer les champs de force, puis gère la durée réelle de manière programmatique dans ReplicatedStorageForceFieldClientVisuals.

Semblable à 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, le jeu utilise le champ de force scintillant par défaut au lieu du script hexagonal dans 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

Comme le champ de force personnalisé est une interface utilisateur 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 par défaut de Roblox. Pour plus d'informations sur la modification des champs de force, voir ForceField.Visible.

Les visuels du champ de force en première personne incluent une grille hexagonale futuriste sur le périmètre de l'écran.
Visuels du champ de force en première personne
Les visuels du champ de force en troisième personne incluent une orbe scintillante bleue autour du joueur apparaissant dans le jeu.
Visuels du champ de force en troisième personne

Les champs de force sont utiles car ils donnent aux joueurs suffisamment de temps entre l'apparition et la réapparition sans avoir à se soucier des joueurs ennemis, mais finalement, ils doivent disparaître pour le gameplay principal du laser tag. Le script qui gère la suppression du champ de force se trouve dans ReplicatedStoragescheduleDestroyForceField, et il vérifie trois conditions uniques :

  • Après que les joueurs aient sélectionné un blaster, les champs de force doivent durer suffisamment longtemps pour permettre aux joueurs de s'acclimater à leur environnement.
  • Pendant ce temps d'acclimatation, les champs de force ne peuvent pas être 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 champ de force ne se termine.

Chacune de ces vérifications dans le script scheduleDestroyForceField appelle endForceField() pour ces conditions.

scheduleDestroyForceField
-- Terminer 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)
-- Terminer le champ de force si le joueur se réinitialise
characterRespawnedConnection = player.CharacterRemoving:Connect(endForceField)
-- Terminer 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 fois, voire 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 du client

Bien que la plupart de cette section se concentre sur ServerScriptServicePlayerStateHandler, il existe un autre script du même nom dans ReplicatedStorage. La raison de cette séparation est l'architecture client-serveur :

  • Le client doit comprendre les informations d'état du joueur afin de pouvoir répondre de manière appropriée 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 prévenir les exploits. Par exemple, le serveur a également besoin de l'état du joueur pour effectuer des actions telles que faire apparaître et équiper des personnages, désactiver des champs de force et afficher un tableau de classement. C'est pourquoi ce script se trouve dans ReplicatedStorage et non dans un emplacement purement côté client.

Pour voir cette logique de base, examinez le script suivant dans ReplicatedStoragePlayerStateHandler qui vérifie l'état actuel de l'utilisateur, puis appelle la fonction appropriée qui gère les actions correspondantes pour 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 de joueur invalide ({newPlayerState})`)
end
end

Toutes les réponses aux é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, du mouvement de la caméra et de la couche d'interface utilisateur 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 du joueur pendant qu'il sélectionne un blaster.

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

La fonction onPlaying() est également assez simple. Elle active le mouvement, passe à 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

Le jeu de laser tag exemple gère la réapparition des personnages dans un tour à travers l'état onTaggedOut() dans ReplicatedStoragePlayerStateHandler. Comme les états onSelectingBlaster() et onPlaying(), onTaggedOut() déclenche un comportement unique en fonction des 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'il est tagué
togglePlayerMovement(false)
togglePlayerCamera(false)
setGuiExclusivelyEnabled(playerGui.OutStateGui)
-- Désactiver le blaster pendant qu'il est tagué
localPlayer:SetAttribute(PlayerAttribute.blasterStateClient, BlasterState.Disabled)
end

Si vous souhaitez tester ce comportement, vous pouvez appuyer sur Esc, naviguer vers 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 vous déplacer, faire pivoter la caméra ou tirer avec votre blaster.

Le menu des paramètres de Roblox avec le bouton Réinitialiser le personnage mis en surbrillance.
Bouton Réinitialiser le personnage
L'écran de réapparition s'affiche alors qu'un joueur réapparaît dans le tour.
Écran de réapparition

Il est important de noter que ce script ne réapparaît pas réellement les personnages, il les empêche simplement d'agir et fournit un retour visuel aux joueurs que le serveur réapparaît leurs personnages. Pour démontrer, si vous examinez ServerScriptServiceSetupHumanoidsetupHumanoidAsynconHumanoidDied, le script définit PlayerState sur TaggedOut (notifiant essentiellement ReplicatedStoragePlayerStateHandler), 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 le tour, ils réapparaissent à l'emplacement d'apparition 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, voir Players.RespawnTime.

SetupHumanoid
local Players = game:GetService("Players")
Players.RespawnTime = 10 -- nouvelle ligne, en secondes

Configuration divers

Dans le cadre de la configuration initiale, le jeu de laser tag exemple effectue également quelques petites étapes, mais critiques :

  • Le jeu comprend un script vide nommé StarterPlayerStarterCharacterScriptsHealth qui désactive la régénération de santé par défaut de Roblox. Pour une explication du comportement de cette propriété, voir Humanoid.Health.

  • Le jeu utilise une caméra en première personne en définissant 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 changer la propriété de manière programmatique 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.

  • Le jeu utilise le tableau de classement intégré de Roblox avec l'unité de "points", que les joueurs gagnent chaque fois qu'ils éliminent un autre joueur. Vous pouvez voir la configuration dans ServerScriptServiceSetupLeaderboard, mais Tableaux de classement en jeu offre un aperçu complet. Notez que onPlayerTagged ajoute des points au tableau de classement, ce que vous apprendrez dans Ajouter des tours et Détecter les coups.

Maintenant que les joueurs peuvent apparaître, choisir un blaster et le viser d'un point de vue en première personne, la prochaine section vous enseignera les scripts derrière la création d'un gameplay basé sur des tours.

©2026 Société Roblox. Roblox, le logo Roblox et Powering Imagination font partie de nos marques déposées aux États-Unis et dans d'autres pays.