Projet de référence Plant

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

Plant est un jeu de référence où les joueurs plantent et arrosent des graines, afin de pouvoir récolter et vendre les plantes résultantes.

Bannière du projet Plant

Le projet se concentre sur des cas d'utilisation courants que vous pourriez rencontrer lors du développement d'un jeu sur Roblox. Lorsque cela est applicable, vous trouverez des notes sur les compromis, les choix difficiles et la logique derrière divers choix d'implémentation, afin que vous puissiez prendre la meilleure décision pour vos propres jeux.

Obtenir le fichier

  1. Naviguez vers la page du jeu Plant.
  2. Cliquez sur le bouton et Modifier dans Studio.

Cas d'utilisation

Plant couvre les cas d'utilisation suivants :

  • Persistance des données de session et des données des joueurs
  • Gestion de la vue de l'interface utilisateur
  • Réseau client-serveur
  • Expérience utilisateur pour les nouveaux utilisateurs (FTUE)
  • Achats en monnaie dure et douce

De plus, ce projet résout des ensembles de problèmes plus restreints qui sont applicables à de nombreux jeux, y compris :

  • Personnalisation d'une zone dans le lieu associée à un joueur
  • Gestion de la vitesse de mouvement du personnage du joueur
  • Création d'un objet qui suit les personnages
  • Détection de la partie du monde dans laquelle se trouve un personnage

Notez qu'il y a plusieurs cas d'utilisation dans ce jeu qui sont trop petits, trop de niche, ou ne démontrent pas une solution à un défi de conception intéressant ; ceux-ci ne sont pas couverts.

Structure du projet

La première décision lors de la création d'un jeu est de décider comment structurer le projet, qui inclut principalement où placer des instances spécifiques dans le modèle de données et comment organiser et structurer les points d'entrée pour le code client et serveur.

Modèle de données

Le tableau suivant décrit dans quels services de conteneurs dans le modèle de données les instances sont placées.

ServiceTypes d'instances
Workspace

Contient des modèles statiques représentant le monde 3D, spécifiquement des parties du monde qui n'appartiennent à aucun joueur. Vous n'avez pas besoin de créer, modifier ou détruire dynamiquement ces instances à l'exécution, donc il est acceptable de les laisser ici.

Il y a aussi un Folder vide, auquel les modèles de ferme des joueurs seront ajoutés à l'exécution.

Lighting

Effets atmosphériques et d'éclairage.

ReplicatedFirst

Contient le plus petit sous-ensemble possible d'instances nécessaires pour afficher l'écran de chargement et initialiser le jeu. Plus d'instances sont placées dans ReplicatedFirst, plus l'attente pour qu'elles se répliquent avant que le code dans ReplicatedFirst puisse s'exécuter sera longue.

  • Dans le dossier Instances se trouve l'interface utilisateur de l'écran de chargement.
  • Dans le dossier Source se trouve le code de l'écran de chargement et le code nécessaire pour attendre que le reste du jeu se charge. Le start LocalScript est le point d'entrée pour tout le code côté client dans le projet.
ReplicatedStorage

Servit de conteneur de stockage pour toutes les instances pour lesquelles un accès est requis à la fois côté client et côté serveur.

  • Dans le dossier Dependencies se trouvent certaines bibliothèques tierces utilisées par le projet.
  • Dans le dossier Instances se trouve une large gamme d'instances préfabriquées.
  • Dans le dossier Source se trouve tout le code non requis pour le processus de chargement qui doit être accessible à la fois côté client et côté serveur.
ServerScriptService

Contient un Script servant de point d'entrée pour tout le code côté serveur dans le projet.

ServerStorage

Servit de conteneur de stockage pour toutes les instances qui n'ont pas besoin d'être répliquées au client.

  • Dans le dossier Instances se trouve un modèle de Ferme. Une copie de celui-ci est placée dans Workspace lorsque le joueur rejoint le jeu, où il sera répliqué à tous les joueurs.
  • Dans le dossier Source se trouve tout le code qui est exclusif au serveur.
SoundService

Contient les objets Sound utilisés pour les effets sonores dans le jeu. Sous SoundService, ces objets Sound n'ont pas de position et ne sont pas simulés dans l'espace 3D.

Points d'entrée

La plupart des projets organisent le code à l'intérieur de ModuleScripts réutilisables qui peuvent être importés dans l'ensemble de la base de code. ModuleScripts sont réutilisables mais ne s'exécutent pas d'eux-mêmes ; ils doivent être importés par un Script ou LocalScript. De nombreux projets Roblox auront un grand nombre d'objets Script et LocalScript, chacun se rapportant à un comportement ou un système particulier dans le jeu, créant ainsi plusieurs points d'entrée.

Pour le microjeu Plant, une approche différente est mise en œuvre à travers un seul LocalScript qui est le point d'entrée pour tout le code client, et un seul Script qui est le point d'entrée pour tout le code serveur. L'approche correcte pour votre projet dépend de vos exigences, mais un point d'entrée unique offre un meilleur contrôle sur l'ordre dans lequel les systèmes sont exécutés.

Les listes suivantes décrivent les compromis des deux approches :

  • Un seul Script et un seul LocalScript couvrent respectivement le code serveur et client.
  • Un meilleur contrôle sur l'ordre dans lequel différents systèmes sont démarrés car tout le code est initialisé à partir d'un seul script.
  • Peut passer des objets par référence entre les systèmes.

Architecture des systèmes de haut niveau

Les systèmes de haut niveau dans le projet sont détaillés ci-dessous. Certains de ces systèmes sont substantiellement plus complexes que d'autres, et dans de nombreux cas, leur fonctionnalité est abstraite à travers une hiérarchie d'autres classes.

Diagramme de l'architecture des systèmes du projet Plant

Chacun de ces systèmes est un "singleton", en ce sens qu'il s'agit d'une classe non instanciable qui est plutôt initialisée par le script start client ou serveur pertinent. Vous pouvez en savoir plus sur le modèle singleton plus loin dans ce guide.

Serveur

Les systèmes suivants sont associés au serveur.

SystèmeDescription
Réseau
  • Crée toutes les instances RemoteEvent et RemoteFunction.
  • Expose des méthodes pour envoyer et écouter des messages du client.
  • Validation de type pour les arguments reçus du client à l'exécution.
PlayerDataServer
  • Sauvegarde et chargement des données persistantes des joueurs en utilisant DataStoreService.
  • Stocke les données des joueurs en mémoire et réplique les mutations au client.
  • Expose des signaux et des méthodes pour s'abonner, interroger et mettre à jour les données des joueurs.
Marché
  • Gère les transactions de monnaie douce du client.
  • Expose une méthode pour vendre des plantes récoltées.
CollisionGroupManager
  • Assigne des modèles de personnages joueurs à des groupes de collision.
  • Configure les groupes de collision afin que les personnages joueurs ne puissent pas entrer en collision avec les chariots de plantes.
FarmManagerServer
  • Recrée le modèle de ferme d'un joueur à partir de ses données de joueur lorsqu'il rejoint le jeu.
  • Supprime le modèle de ferme lorsqu'un joueur quitte.
  • Met à jour les données du joueur lorsque la ferme d'un joueur est modifiée.
  • Expose une méthode pour accéder à la classe Ferme associée à un joueur donné.
PlayerObjectsContainer
  • Crée divers objets associés à la durée de vie d'un joueur et fournit une méthode pour les récupérer.
TagPlayers
FtueManagerServer
  • Pendant le FTUE, exécute chaque étape et attend qu'elle soit terminée.
CharacterSpawner
  • Respawn les personnages lorsqu'ils meurent. Notez que Players.CharacterAutoLoads a été désactivé afin que le spawn soit suspendu jusqu'à ce que les données du joueur aient été chargées.

Client

Les systèmes suivants sont associés au client.

SystèmeDescription
Réseau
  • Attend que le serveur crée toutes les instances RemoteEvent et RemoteFunction.
  • Expose des méthodes pour envoyer et écouter des messages vers et depuis le serveur.
  • Applique la validation de type des paramètres à l'exécution.
  • Exécute pcall() sur les fonctions distantes.
PlayerDataClient
  • Stocke les données du joueur local en mémoire.
  • Expose des méthodes et des signaux pour interroger et s'abonner aux changements dans les données des joueurs.
MarketClient
  • Expose une méthode pour demander au serveur d'acheter un article pour de la monnaie douce.
LocalWalkJumpManager
  • Expose des méthodes pour modifier la WalkSpeed ou JumpHeight d'un personnage via des multiplicateurs pour éviter les conflits lors de la modification de ces valeurs depuis plusieurs endroits.
FarmManagerClient
  • Écoute les tags spécifiques CollectionService appliqués aux instances et crée des "composants" ajoutant un comportement à ces instances. Un "composant" fait référence à une classe qui est créée lorsqu'un tag CollectionService est ajouté à une instance et détruit lorsqu'il est retiré ; ceux-ci sont utilisés pour les invites CTA dans la ferme et diverses classes transmettant l'état de la ferme au joueur.
UISetup
  • Initialise toutes les couches de l'interface utilisateur.
  • Configure certaines couches pour être visibles uniquement dans des sections physiques du monde.
  • Connecte un effet de caméra spécial pour lorsque les menus sont activés.
FtueManagerClient
  • Configure les étapes FTUE sur le client.
CharacterSprint
  • Utilise LocalWalkJumpManager pour augmenter la WalkSpeed lorsqu'un personnage joueur est en dehors de sa ferme.

Communication client-serveur

La plupart des jeux Roblox impliquent un certain élément de communication entre le client et le serveur. Cela peut inclure la demande du client au serveur d'effectuer une certaine action et le serveur répliquant des mises à jour au client.

Dans ce projet, la communication client-serveur est maintenue aussi générique que possible en limitant l'utilisation des objets RemoteEvent et RemoteFunction afin de diminuer le nombre de règles spéciales à suivre. Ce projet utilise les méthodes suivantes, par ordre de préférence :

Réplication via le système de données des joueurs

Le système de données des joueurs permet d'associer des données au joueur qui persistent entre les sessions de sauvegarde. Ce système fournit une réplication du client au serveur et un ensemble d'API qui peuvent être utilisées pour interroger des données et s'abonner à des changements, ce qui le rend idéal pour répliquer les changements d'état du joueur du serveur au client.

Par exemple, plutôt que de déclencher un UpdateCoins RemoteEvent sur mesure pour dire au client combien de pièces il a, vous pouvez appeler ce qui suit et laisser le client s'y abonner via l'événement PlayerDataClient.updated.

PlayerDataServer.setValue(player, "coins", 5)

Bien sûr, cela n'est utile que pour la réplication du serveur au client et pour les valeurs que vous souhaitez conserver entre les sessions, mais cela s'applique à un nombre surprenant de cas dans le projet, y compris :

  • L'étape FTUE actuelle
  • L'inventaire du joueur
  • Le nombre de pièces que le joueur a
  • L'état de la ferme du joueur

Réplication via attributs

Dans les situations où le serveur doit répliquer une valeur personnalisée au client qui est spécifique à une Instance donnée, vous pouvez utiliser attributs. Roblox réplique automatiquement les valeurs d'attributs, donc vous n'avez pas besoin de maintenir des chemins de code pour répliquer l'état associé à un objet. Un autre avantage est que cette réplication se produit en même temps que l'instance elle-même.

Ceci est particulièrement utile pour les instances créées à l'exécution, car les attributs définis sur une nouvelle instance avant qu'elle ne soit parentée au modèle de données se répliqueront de manière atomique avec l'instance elle-même. Cela contourne tout besoin d'écrire du code pour "attendre" que des données supplémentaires soient répliquées via un RemoteEvent ou StringValue.

Vous pouvez également lire directement les attributs du modèle de données, que ce soit du côté client ou serveur, avec la méthode GetAttribute(), et vous abonner aux changements avec la méthode GetAttributeChangedSignal(). Dans le projet Plant, cette approche est utilisée pour, entre autres choses, répliquer l'état actuel des plantes aux clients.

Réplication via tags

CollectionService vous permet d'appliquer un tag de chaîne à une Instance. Cela est utile pour catégoriser les instances et répliquer cette catégorisation au client.

Par exemple, le tag CanPlant est appliqué sur le serveur pour signifier au client qu'un pot donné est capable de recevoir une plante.

Messaging directement via le module réseau

Pour les situations où aucune des options précédentes ne s'applique, vous pouvez utiliser des appels réseau personnalisés via le module Réseau. C'est la seule option dans le projet qui permet la communication client-serveur et est donc la plus utile pour transmettre des demandes du client et recevoir une réponse du serveur.

Plant utilise des appels réseau directs pour une variété de demandes du client, y compris :

  • Arroser une plante
  • Planter une graine
  • Acheter un article

L'inconvénient de cette approche est que chaque message individuel nécessite une configuration sur mesure qui peut augmenter la complexité du projet, bien que cela ait été évité autant que possible, en particulier pour la communication serveur-client.

Classes et singletons

Les classes dans le projet Plant, comme les instances sur Roblox, peuvent être créées et détruites. Sa syntaxe de classe est inspirée de l'approche idiomatique Lua pour la programmation orientée objet avec un certain nombre de changements pour permettre le support de vérification de type stricte.

Instanciation

De nombreuses classes dans le projet sont associées à une ou plusieurs Instances. Les objets d'une classe donnée sont créés en utilisant une méthode new(), conforme à la façon dont les instances sont créées dans Roblox en utilisant Instance.new().

Ce modèle est généralement utilisé pour les objets où la classe a une représentation physique dans le modèle de données, et la classe étend sa fonctionnalité. Un bon exemple est BeamBetween qui crée un objet Beam entre deux objets Attachment donnés et maintient ces attachements orientés de sorte que le faisceau soit toujours dirigé vers le haut. Ces instances pourraient être clonées à partir d'une version préfabriquée dans ReplicatedStorage ou passées dans new() comme argument et stockées à l'intérieur de l'objet sous self.

Instances correspondantes

Comme noté ci-dessus, de nombreuses classes dans ce projet ont une représentation dans le modèle de données, une instance qui correspond à la classe et est manipulée par elle.

Plutôt que de créer ces instances lorsqu'un objet de classe est instancié, le code opte généralement pour Clone() une version préfabriquée de la Instance stockée sous ReplicatedStorage ou ServerStorage. Bien qu'il soit possible de sérialiser les propriétés de ces instances et de les créer à partir de zéro dans les fonctions new() de la classe, cela rendrait l'édition des objets très encombrante et plus difficile à comprendre pour un lecteur. De plus, cloner une instance est généralement une opération plus rapide que de créer une nouvelle instance et de personnaliser ses propriétés à l'exécution.

Composition

Bien que l'héritage soit possible en Luau en utilisant des métatables, le projet opte plutôt pour permettre aux classes de s'étendre les unes aux autres par composition. Lors de la combinaison de classes par composition, l'objet "enfant" est instancié dans la méthode new() de la classe et est inclus comme membre sous self.

Pour un exemple de cela en action, voir la classe CloseButton qui enveloppe la classe Button.

Nettoyage

De la même manière qu'une Instance peut être détruite avec la méthode Destroy(), les classes qui peuvent être instanciées peuvent également être détruites. La méthode destructrice pour les classes du projet est destroy() avec un d minuscule pour la cohérence camelCase à travers les méthodes de la base de code, ainsi que pour distinguer entre les classes du projet et les instances Roblox.

Le rôle de la méthode destroy() est de détruire toutes les instances créées par l'objet, de déconnecter toutes les connexions, et d'appeler destroy() sur tous les objets enfants. Cela est particulièrement important pour les connexions car les instances avec des connexions actives ne sont pas nettoyées par le ramasse-miettes Luau, même s'il n'y a plus de références à l'instance ou de connexions à l'instance.

Singletons

Les singletons, comme leur nom l'indique, sont des classes pour lesquelles un seul objet peut jamais exister. Ils sont l'équivalent du projet des Services de Roblox. Plutôt que de stocker une référence à l'objet singleton et de le passer dans le code Luau, Plant tire parti du fait que l'exigence d'un ModuleScript met en cache sa valeur retournée. Cela signifie que l'exigence du même ModuleScript singleton à partir de différents endroits fournit de manière cohérente le même objet retourné. La seule exception à cette règle serait si différents environnements (client ou serveur) accédaient au ModuleScript.

Les singletons se distinguent des classes instanciables par le fait qu'ils n'ont pas de méthode new(). Au lieu de cela, l'objet ainsi que ses méthodes et son état sont retournés directement via le ModuleScript. Comme les singletons ne sont pas instanciés, la syntaxe self n'est pas utilisée et les méthodes sont plutôt appelées avec un point (.) plutôt qu'avec un deux-points (:).

Vérification de type stricte

Luau prend en charge le typage graduel, ce qui signifie que vous êtes libre d'ajouter des définitions de type optionnelles à une partie ou à l'ensemble de votre code. Dans ce projet, une vérification de type strict est utilisée pour chaque script. C'est l'option la moins permissive pour l'outil Analyse de script de Roblox et donc la plus susceptible de détecter des erreurs de type avant l'exécution.

Syntaxe de classe typée

L'approche établie pour créer des classes en Lua est bien documentée, cependant elle n'est pas bien adaptée au typage fort de Luau. En Luau, l'approche la plus simple pour obtenir le type d'une classe est la méthode typeof() :

type ClassType = typeof(Class.new())

Cela fonctionne mais ce n'est pas très utile lorsque votre classe est initiée avec des valeurs qui n'existent qu'à l'exécution, par exemple des objets Player. De plus, l'hypothèse faite dans la syntaxe de classe idiomatique Lua est que déclarer une méthode sur une classe self sera toujours une instance de cette classe ; ce n'est pas une hypothèse que le moteur d'inférence de type peut faire.

Pour prendre en charge l'inférence de type stricte, le projet Plant utilise une solution qui diffère de la syntaxe de classe idiomatique Lua de plusieurs manières, dont certaines peuvent sembler non intuitives :

  • La définition de self est dupliquée, à la fois dans la déclaration de type et dans le constructeur. Cela introduit un fardeau de maintenabilité, mais des avertissements seront signalés si les deux définitions ne sont plus synchronisées.
  • Les méthodes de classe sont déclarées avec un point, de sorte que self peut être explicitement déclaré comme étant de type ClassType. Les méthodes peuvent toujours être appelées avec un deux-points comme prévu.
--!strict
local MyClass = {}
MyClass.__index = MyClass
export type ClassType = typeof(setmetatable(
{} :: {
property: number,
},
MyClass
))
function MyClass.new(property: number): ClassType
local self = {
property = property,
}
setmetatable(self, MyClass)
return self
end
function MyClass.addOne(self: ClassType)
self.property += 1
end
return MyClass

Types de cast après des gardes logiques

Au moment de la rédaction, le type d'une valeur n'est pas réduit après une instruction conditionnelle de garde. Par exemple, après la garde ci-dessous, le type de optionalParameter n'est pas réduit à number.

--!strict
local function foo(optionalParameter: number?)
if not optionalParameter then
return
end
print(optionalParameter + 1)
end

Pour atténuer cela, de nouvelles variables sont créées après ces gardes avec leur type explicitement casté.

--!strict
local function foo(optionalParameter: number?)
if not optionalParameter then
return
end
local parameter = optionalParameter :: number
print(parameter + 1)
end

Traverser les hiérarchies du DataModel

Dans certains cas, la base de code doit traverser la hiérarchie du modèle de données d'un arbre d'objets qui sont créés à l'exécution. Cela présente un défi intéressant pour la vérification de type. Au moment de la rédaction, il n'est pas possible de définir une hiérarchie de modèle de données générique en tant que type. En conséquence, il y a des cas où la seule information de type disponible pour une structure de modèle de données est le type de l'instance de niveau supérieur.

Une approche à ce défi consiste à caster en any puis à affiner. Par exemple :

local function enableVendor(vendor: Model)
local zonePart: BasePart = (vendor :: any).ZonePart
end

Le problème avec cette approche est qu'elle impacte la lisibilité. Au lieu de cela, le projet utilise un module générique appelé getInstance pour traverser les hiérarchies du modèle de données qui cast en any en interne.

local function enableVendor(vendor: Model)
local zonePart: BasePart = getInstance(vendor, "ZonePart")
end

À mesure que la compréhension du moteur de type du modèle de données évolue, il est possible que des modèles comme celui-ci ne soient plus nécessaires.

Interface utilisateur

Plant comprend une variété d'interfaces utilisateur 2D complexes et simples. Celles-ci incluent des éléments d'affichage tête haute (HUD) non interactifs comme le compteur de pièces et des menus interactifs complexes comme le magasin.

Approche de l'UI

Vous pouvez comparer de manière lâche l'UI de Roblox à l'HTML DOM, car c'est une hiérarchie d'objets qui décrit ce que l'utilisateur devrait voir. Les approches pour créer et mettre à jour une UI Roblox sont largement divisées en pratiques impératives et déclaratives.

ApprocheAvantages et inconvénients
Impératif

Dans l'approche impérative, l'UI est traitée comme n'importe quelle autre hiérarchie d'instances sur Roblox. La structure de l'UI est créée avant l'exécution dans Studio et ajoutée au modèle de données, généralement directement dans StarterGui. Ensuite, à l'exécution, le code manipule des morceaux spécifiques de l'UI pour refléter l'état requis par le créateur.

Cette approche présente certains avantages. Vous pouvez créer l'UI à partir de zéro dans Studio et la stocker dans le modèle de données. C'est une expérience d'édition simple et visuelle qui peut accélérer la création de l'UI. Comme le code de l'UI impératif ne se préoccupe que de ce qui doit être changé, cela rend également les changements simples de l'UI faciles à mettre en œuvre.

Un inconvénient notable est que, puisque les approches UI impératives nécessitent que l'état soit implémenté manuellement sous forme de transformations, des représentations complexes de l'état peuvent devenir très difficiles à trouver et à déboguer. Il est courant que des erreurs émergent lors du développement de code UI impératif, surtout lorsque l'état et l'UI deviennent désynchronisés en raison de mises à jour multiples interagissant dans un ordre inattendu.

Un autre défi avec les approches impératives est qu'il est plus difficile de décomposer l'UI en composants significatifs qui peuvent être déclarés une fois et réutilisés. Comme l'ensemble de l'arbre UI est déclaré au moment de l'édition, des modèles communs peuvent être répétés dans plusieurs parties du modèle de données.

Déclaratif

Dans l'approche déclarative, l'état souhaité des instances UI est déclaré explicitement, et l'implémentation efficace de cet état est abstraite par des bibliothèques telles que Roact ou Fusion.

L'avantage de cette approche est que l'implémentation de l'état devient triviale et vous n'avez qu'à décrire à quoi vous voulez que votre UI ressemble. Cela rend l'identification et la résolution des bogues significativement plus faciles.

L'inconvénient clé est de devoir déclarer l'ensemble de l'arbre UI dans le code. Des bibliothèques comme Roact et Fusion ont une syntaxe pour faciliter cela, mais c'est tout de même un processus long et une expérience d'édition moins intuitive lors de la composition de l'UI.

Plant utilise une approche impérative sous l'idée que montrer les transformations directement donne une vue d'ensemble plus efficace de la façon dont l'UI est créée et manipulée sur Roblox. Cela ne serait pas possible avec une approche déclarative. Certaines structures et logiques UI répétées sont également abstraites en composants réutilisables pour éviter un piège commun dans la conception UI impérative.

Architecture de haut niveau

Diagramme de l'architecture UI du projet Plant

Couches et composants

Dans Plant, toutes les structures UI sont soit une Layer, soit un Component.

  • Layer est défini comme un singleton de regroupement de niveau supérieur qui enveloppe des structures UI préfabriquées dans ReplicatedStorage. Une couche peut contenir un certain nombre de composants, ou elle peut encapsuler entièrement sa propre logique. Des exemples de couches sont le menu d'inventaire ou l'indicateur du nombre de pièces dans l'affichage tête haute.
  • Component est un élément UI réutilisable. Lorsqu'un nouvel objet composant est instancié, il clone un modèle préfabriqué à partir de ReplicatedStorage. Les composants peuvent eux-mêmes contenir d'autres composants. Des exemples de composants sont une classe de bouton générique ou le concept d'une liste d'articles.

Gestion des vues

Un problème courant de gestion de l'UI est la gestion des vues. Ce projet a une gamme de menus et d'éléments HUD, dont certains écoutent les entrées utilisateur, et une gestion soigneuse de quand ils sont visibles ou activés est requise.

Plant aborde ce problème avec son système UIHandler qui gère quand une couche UI doit ou ne doit pas être visible. Toutes les couches UI dans le jeu sont catégorisées comme HUD ou Menu et leur visibilité est gérée par les règles suivantes :

  • L'état activé des couches Menu et HUD peut être basculé.
  • Les couches HUD activées ne sont affichées que si aucune couche Menu n'est activée.
  • Les couches Menu activées sont stockées dans une pile, et une seule couche Menu est visible à la fois. Lorsqu'une couche Menu est activée, elle est insérée au début de la pile et affichée. Lorsqu'une couche Menu est désactivée, elle est retirée de la pile et la prochaine couche Menu activée dans la file d'attente est affichée.

Cette approche est intuitive car elle permet de naviguer dans les menus avec un historique. Si un menu est ouvert à partir d'un autre menu, fermer le nouveau menu affichera à nouveau l'ancien menu.

Les singletons de couche UI s'enregistrent auprès du UIHandler et reçoivent un signal qui se déclenche lorsque sa visibilité doit changer.

Lectures complémentaires

À partir de cet aperçu approfondi du projet Plant, vous voudrez peut-être explorer les guides suivants qui approfondissent des concepts et des sujets connexes.

  • Modèle Client-Serveur — Un aperçu du modèle client-serveur dans Roblox.
  • Luau — Détails sur Luau, le langage de script créé par Roblox descendant de Lua 5.1.
  • Événements distants et rappels — Tout sur les événements réseau distants et les rappels pour la communication à travers la frontière client-serveur.
  • UI — Détails sur les objets et la conception de l'interface utilisateur sur Roblox.
©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.