Pflanzenreferenzprojekt

*Dieser Inhalt wurde mit KI (Beta) übersetzt und kann Fehler enthalten. Um diese Seite auf Englisch zu sehen, klicke hier.

Plant ist eine Referenz-Erfahrung, bei der Spieler Samen pflanzen und gießen, um anschließend die resultierenden Pflanzen zu ernten und zu verkaufen.

Pflanzenprojektbanner

Das Projekt konzentriert sich auf häufige Anwendungsfälle, die Sie beim Entwickeln einer Erfahrung auf Roblox antreffen könnten. Wo zutreffend, finden Sie Hinweise zu Trade-offs, Kompromissen und den Gründen für verschiedene Implementierungsentscheidungen, damit Sie die besten Entscheidungen für Ihre eigenen Erfahrungen treffen können.

Datei herunterladen

  1. Navigieren Sie zur Plant-Erfahrungsseite.
  2. Klicken Sie auf die -Schaltfläche und Im Studio bearbeiten.

Anwendungsfälle

Plant behandelt die folgenden Anwendungsfälle:

  • Sitzungsdaten und Persistenz von Spieldaten
  • Verwaltung der Benutzeroberflächenansicht
  • Client-Server-Netzwerkverbindungen
  • Nutzererfahrung für Erstbenutzer (FTUE)
  • Käufe von hartem und weichem Währungsgeld

Zusätzlich löst dieses Projekt engere Problembereiche, die auf viele Erfahrungen anwendbar sind, einschließlich:

  • Anpassung eines Bereichs im Spiel, der mit einem Spieler verbunden ist
  • Verwaltung der Bewegungsgeschwindigkeit des Spielercharakters
  • Erstellen eines Objekts, das Charaktere verfolgt
  • Erkennen, in welchem Bereich der Welt ein Charakter sich befindet

Beachten Sie, dass es in dieser Erfahrung mehrere Anwendungsfälle gibt, die zu klein, zu nischenspezifisch sind oder keine Lösung für eine interessante Designherausforderung demonstrieren; diese sind nicht behandelt.

Projektstruktur

Die erste Entscheidung bei der Erstellung einer Erfahrung besteht darin, die Projektstruktur zu entscheiden, die hauptsächlich umfasst, wo spezifische Instanzen im Datenmodell platziert sind und wie die Einstiegspunkte für Client- und Servercode organisiert werden.

Datenmodell

Die folgende Tabelle beschreibt, in welchen Containerdiensten im Datenmodell Instanzen platziert sind.

DienstArten von Instanzen
Workspace

Beinhaltet statische Modelle, die die 3D-Welt repräsentieren, insbesondere Teile der Welt, die keinem Spieler gehören. Sie müssen diese Instanzen zur Laufzeit nicht dynamisch erstellen, ändern oder zerstören, daher ist es akzeptabel, sie hier zu belassen.

Es gibt auch einen leeren Folder, in den die Farmmodelle der Spieler zur Laufzeit hinzugefügt werden.

Lighting

Atmosphärische und Lichteffekte.

ReplicatedFirst

Beinhaltet die kleinste mögliche Teilmenge von Instanzen, die benötigt wird, um den Ladebildschirm anzuzeigen und die Erfahrung zu initialisieren. Je mehr Instanzen in ReplicatedFirst platziert sind, desto länger dauert das Warten, bis sie repliziert werden, bevor Code in ReplicatedFirst ausgeführt werden kann.

  • Im Instances-Ordner befindet sich die GUI des Ladebildschirms.
  • Im Source-Ordner befindet sich der Code für den Ladebildschirm und der Code, der wartet, bis der Rest der Erfahrung geladen ist. Das start LocalScript ist der Einstiegspunkt für allen clientseitigen Code im Projekt.
ReplicatedStorage

Serviert als Speichercontainer für alle Instanzen, auf die sowohl vom Client als auch vom Server zugegriffen werden muss.

  • Im Dependencies-Ordner sind einige Drittanbieter-Bibliotheken, die im Projekt verwendet werden.
  • Im Instances-Ordner gibt es eine Vielzahl von vorgefertigten Instanzen.
  • Im Source-Ordner befindet sich der gesamte Code, der nicht für den Ladeprozess erforderlich ist und von sowohl Client als auch Server zugänglich sein muss.
ServerScriptService

Beinhaltet ein Script, das als Einstiegspunkt für allen serverseitigen Code im Projekt dient.

ServerStorage

Serviert als Speichercontainer für alle Instanzen, die nicht an den Client repliziert werden müssen.

  • Im Instances-Ordner befindet sich ein Vorlagenmodell Farm. Eine Kopie davon wird in Workspace platziert, wenn der Spieler der Erfahrung beitritt, wo es an alle Spieler repliziert wird.
  • Im Source-Ordner befindet sich der gesamte Code, der exklusiv für den Server ist.
SoundService

Enthält die Sound-Objekte, die für Soundeffekte in der Erfahrung verwendet werden. Unter SoundService haben diese Sound-Objekte keine Position und werden nicht in 3D-Raum simuliert.

Einstiegspunkte

Die meisten Projekte organisieren Code innerhalb wiederverwendbarer ModuleScripts, die im gesamten Codebasis importiert werden können. ModuleScripts sind wiederverwendbar, führen aber nicht eigenständig aus; sie müssen von einem Script oder LocalScript importiert werden. Viele Roblox-Projekte haben eine große Anzahl von Script und LocalScript-Objekten, die jeweils einem Verhalten oder einem bestimmten System in der Erfahrung zugeordnet sind und mehrere Einstiegspunkte schaffen.

Für das Plant Mikrospiel wird ein anderer Ansatz durch ein einzelnes LocalScript implementiert, das der Einstiegspunkt für gesamten Clientcode ist, und ein einzelnes Script, das der Einstiegspunkt für gesamten Servercode ist. Der richtige Ansatz für Ihr Projekt hängt von Ihren Anforderungen ab, aber ein einziger Einstiegspunkt bietet eine bessere Kontrolle über die Reihenfolge, in der Systeme ausgeführt werden.

Die folgenden Listen zeigen die Trade-offs beider Ansätze:

  • Ein einzelnes Script und ein einzelnes LocalScript decken jeweils Server- und Clientcode ab.
  • Größere Kontrolle über die Reihenfolge, in der verschiedene Systeme gestartet werden, da der gesamte Code von einem einzigen Skript initialisiert wird.
  • Objekte können zwischen Systemen per Referenz übergeben werden.

Hochrangige Systemarchitektur

Die obersten Systeme im Projekt sind im Folgenden detailliert. Einige dieser Systeme sind erheblich komplexer als andere, und in vielen Fällen wird ihre Funktionalität über eine Hierarchie anderer Klassen abstrahiert.

Diagramm der Systemarchitektur des Pflanzenprojekts

Jedes dieser Systeme ist ein "Singleton", da es sich um eine nicht instanziierbare Klasse handelt, die stattdessen durch das relevante Client- oder Server-start-Skript initialisiert wird. Sie können mehr über das Singleton-Muster später in diesem Leitfaden lesen.

Server

Die folgenden Systeme sind mit dem Server verbunden.

SystemBeschreibung
Netzwerk
  • Erstellt alle RemoteEvent und RemoteFunction Instanzen.
  • Bietet Methoden zum Senden und Empfangen von Nachrichten vom Client.
  • Typvalidierung für Argumente, die zur Laufzeit vom Client empfangen werden.
PlayerDataServer
  • Speichert und lädt persistente Spieldaten mithilfe des DataStoreService.
  • Speichert Spieldaten im Speicher und repliziert Mutationen an den Client.
  • Bietet Signale und Methoden zum Abonnieren, Abfragen und Aktualisieren von Spieldaten.
Markt
  • Verwaltet Transaktionen mit weicher Währung vom Client.
  • Bietet eine Methode zum Verkauf geernteter Pflanzen.
CollisionGroupManager
  • Weist Spielercharaktermodelle bestimmten Kollisionsgruppen zu.
  • Konfiguriert Kollisionsgruppen, damit Spielercharaktere nicht mit Pflanzenwagen kollidieren können.
FarmManagerServer
  • Erstellt das Farmmodell eines Spielers erneut aus dessen Spieldaten, wenn er der Erfahrung beitritt.
  • Entfernt das Farmmodell, wenn ein Spieler das Spiel verlässt.
  • Aktualisiert die Spieldaten, wenn eine Farm eines Spielers geändert wird.
  • Bietet eine Methode zum Zugriff auf die mit einem bestimmten Spieler verbundene Farm-Klasse.
PlayerObjectsContainer
  • Erstellt verschiedene Objekte, die mit der Lebensdauer eines Spielers verbunden sind und bietet eine Methode zum Abrufen dieser.
TagPlayers
FtueManagerServer
  • Führt während des FTUE jede Phase aus und wartet auf deren Abschluss.
CharacterSpawner
  • Lässt Charaktere respawnen, wenn sie sterben. Beachten Sie, dass Players.CharacterAutoLoads deaktiviert wurde, sodass das Spawnen pausiert wird, bis die Spieldaten geladen sind.

Client

Die folgenden Systeme sind mit dem Client verbunden.

SystemBeschreibung
Netzwerk
  • Wartet darauf, dass der Server alle RemoteEvent und RemoteFunction Instanzen erstellt.
  • Bietet Methoden zum Senden und Empfangen von Nachrichten an und vom Server.
  • Erzwingt die Validierung von Parametertypen zur Laufzeit.
  • Führt pcall() bei Remote-Funktionen aus.
PlayerDataClient
  • Speichert die Daten des lokalen Spielers im Speicher.
  • Bietet Methoden und Signale zum Abfragen und Abonnieren von Änderungen an den Spieldaten.
MarketClient
  • Bietet eine Methode, um den Server um den Kauf eines Gegenstands für weiche Währung zu bitten.
LocalWalkJumpManager
  • Bietet Methoden zur Änderung der WalkSpeed oder JumpHeight eines Charakters über Multiplikatoren, um Konflikte zu vermeiden, wenn diese Werte von mehreren Stellen aus geändert werden.
FarmManagerClient
  • Lauscht auf spezifische CollectionService-Tags, die auf Instanzen angewendet werden, und erstellt „Komponenten“, die Verhaltensweisen an diese Instanzen anhängen. Eine „Komponente“ bezieht sich auf eine Klasse, die erstellt wird, wenn ein CollectionService-Tag zu einer Instanz hinzugefügt und zerstört wird, wenn es entfernt wird; diese werden für die CTA-Aufforderungen in der Farm und verschiedene Klassen verwendet, die den Farmzustand an den Spieler kommunizieren.
UISetup
  • Initialisiert alle UI-Ebenen.
  • Konfiguriert bestimmte Ebenen, um nur in physischen Abschnitten der Welt sichtbar zu sein.
  • Verbindet einen speziellen Kameraeffekt für den Fall, dass Menüs aktiviert sind.
FtueManagerClient
  • Konfiguriert FTUE-Phasen auf dem Client.
CharacterSprint
  • Verwendet LocalWalkJumpManager, um die WalkSpeed zu erhöhen, wenn sich der Spielercharakter außerhalb seiner Farm befindet.

Client-Server-Kommunikation

Die meisten Roblox-Erfahrungen beinhalten ein gewisses Element der Kommunikation zwischen dem Client und dem Server. Dies kann das Anfordern eines bestimmten Handlungsablaufs durch den Client und die Replikation von Aktualisierungen an den Client durch den Server umfassen.

In diesem Projekt wird die Client-Server-kommunikation so generisch wie möglich gehalten, indem die Nutzung von RemoteEvent und RemoteFunction-Objekten eingeschränkt wird, um die Menge an speziellen Regeln, die zu beachten sind, zu verringern. Dieses Projekt verwendet die folgenden Methoden, in der Reihenfolge der Vorliebe:

Replikation über das Spieldaten-System

Das Spieldaten-System ermöglicht es, Daten mit dem Spieler zu verknüpfen, die zwischen Speichersitzungen bestehen bleiben. Dieses System bietet Replikation vom Client zum Server und eine Reihe von APIs, die verwendet werden können, um Daten abzufragen und Änderungen zu abonnieren, wodurch es ideal für die Replikation von Änderungen des Spielerstatus vom Server zum Client ist.

Zum Beispiel, anstatt ein maßgeschneidertes UpdateCoins RemoteEvent zu senden, um dem Client mitzuteilen, wie viele Münzen er hat, können Sie Folgendes aufrufen und den Client dies über das PlayerDataClient.updated-Ereignis abonnieren lassen.


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

Natürlich ist dies nur nützlich für die Replikation vom Server zum Client und für Werte, die zwischen Sitzungen bestehen bleiben sollen, aber dies gilt für eine überraschend große Anzahl von Fällen im Projekt, einschließlich:

  • Der aktuelle FTUE-Status
  • Das Inventar des Spielers
  • Die Anzahl an Münzen, die der Spieler hat
  • Der Zustand der Farm des Spielers

Replikation über Attribute

In Situationen, in denen der Server einen benutzerdefinierten Wert an den Client replizieren muss, der spezifisch für eine bestimmte Instance ist, können Sie Attribute verwenden. Roblox repliziert automatisch Attributwerte, sodass Sie keine Codepfade erstellen müssen, um den mit einem Objekt verbundenen Status zu replizieren. Ein weiterer Vorteil ist, dass diese Replikation zusammen mit der Instanz selbst erfolgt.

Dies ist besonders nützlich für Instanzen, die zur Laufzeit erstellt werden, da Attribute, die auf einer neuen Instanz gesetzt werden, bevor sie im Datenmodell platziert werden, atomar mit der Instanz selbst repliziert werden. Dadurch entfällt die Notwendigkeit, Code zu schreiben, um auf zusätzliche Daten zu warten, die über ein RemoteEvent oder StringValue repliziert werden.

Sie können Attribute auch direkt vom Datenmodell aus sowohl vom Client als auch vom Server mit der GetAttribute()-Methode lesen und Änderungen mit der GetAttributeChangedSignal()-Methode abonnieren. Im Plant-Projekt wird dieser Ansatz unter anderem verwendet, um den aktuellen Status von Pflanzen an die Clients zu replizieren.

Replikation über Tags

CollectionService ermöglicht es Ihnen, einen String-Tag zu einer Instance anzuwenden. Dies ist nützlich, um Instanzen zu kategorisieren und diese Kategorisierung an den Client zu replizieren.

Zum Beispiel wird das CanPlant-Tag auf dem Server angewendet, um dem Client anzuzeigen, dass ein bestimmter Topf eine Pflanze erhalten kann.

Direktes Messaging über das Netzwerkmodul

Für Situationen, in denen keine der vorherigen Optionen zutrifft, können Sie benutzerdefinierte Netzwerkaufrufe über das Netzwerk-Modul verwenden. Dies ist die einzige Option im Projekt, die die Kommunikation vom Client zum Server ermöglicht und daher am nützlichsten ist, um Client-Anfragen zu übertragen und eine Serverantwort zu erhalten.

Plant verwendet direkte Netzwerkaufrufe für eine Vielzahl von Clientanfragen, einschließlich:

  • Gießen einer Pflanze
  • Pflanzen eines Samens
  • Kauf eines Gegenstands

Der Nachteil dieses Ansatzes ist, dass jede einzelne Nachricht einige maßgeschneiderte Konfigurationen erfordert, was die Komplexität des Projekts erhöhen kann, obwohl dies, wo möglich, vermieden wurde, insbesondere für die Kommunikation vom Server an den Client.

Klassen und Singletons

Klassen im Plant-Projekt können, wie Instanzen in Roblox, erstellt und zerstört werden. Ihre Klassensyntax ist vom idiomatischen Lua-Ansatz des objektorientierten Programmierens inspiriert und enthält eine Reihe von Änderungen, um die Unterstützung für strikte Typprüfung zu ermöglichen.

Instanziierung

Viele Klassen im Projekt sind mit einer oder mehreren Instances verbunden. Objekte einer bestimmten Klasse werden mit einer new()-Methode erstellt, was konsistent ist mit der Art und Weise, wie Instanzen in Roblox mit Instance.new() erstellt werden.

Dieses Muster wird allgemein für Objekte verwendet, bei denen die Klasse eine physische Darstellung im Datenmodell hat und die Funktionalität der Klasse erweitert. Ein gutes Beispiel ist BeamBetween, das ein Beam-Objekt zwischen zwei gegebenen Attachment-Objekten erstellt und diese Anhänge so orientiert, dass der Strahl immer nach oben zeigt. Diese Instanzen könnten von einer vorgefertigten Version in ReplicatedStorage geklont oder als Argument in new() übergeben und im Objekt unter self gespeichert werden.

Entsprechende Instanzen

Wie oben erwähnt, haben viele Klassen in diesem Projekt eine Datenmodellrepräsentation, eine Instanz, die mit der Klasse korrespondiert und von dieser manipuliert wird.

Anstatt diese Instanzen zu erstellen, wenn ein Klassenobjekt instanziiert wird, entscheidet sich der Code in der Regel dafür, die Clone()-Methode einer vorgefertigten Version der Instance, die in ReplicatedStorage oder ServerStorage gespeichert ist, zu verwenden. Obwohl es möglich wäre, die Eigenschaften dieser Instanzen zu serialisieren und sie in den new()-Funktionen der Klasse von Grund auf zu erstellen, würde dies das Bearbeiten der Objekte sehr umständlich machen und es für einen Leser schwieriger machen, dies zu verstehen. Darüber hinaus ist das Klonen einer Instanz in der Regel eine schnellere Operation als das Erstellen einer neuen Instanz und das Anpassen ihrer Eigenschaften zur Laufzeit.

Zusammensetzung

Obwohl Vererbung in Luau mittels Metatables möglich ist, entscheidet sich das Projekt stattdessen dafür, Klassen durch Zusammensetzung zu erweitern. Wenn Klassen durch Zusammensetzung kombiniert werden, wird das "Kind"-Objekt in der new()-Methode der Klasse instanziiert und ist als Mitglied unter self enthalten.

Ein Beispiel hierfür sehen Sie in der CloseButton-Klasse, die die Button-Klasse umschließt.

Bereinigung

Ähnlich wie eine Instance mit der Methode Destroy() zerstört werden kann, können auch Klassen, die instanziiert werden können, zerstört werden. Die Zerstörungsmethode für Projektklassen ist destroy(), wobei ein kleines d für die camelCase-Konsistenz über die Methoden der Codebasis hinweg verwendet wird, sowie um zwischen den Projektklassen und Roblox-Instanzen zu unterscheiden.

Die Rolle der destroy()-Methode besteht darin, alle von dem Objekt erstellten Instanzen zu zerstören, alle Verbindungen zu trennen und destroy() für alle Kindobjekte aufzurufen. Dies ist besonders wichtig für Verbindungen, da Instanzen mit aktiven Verbindungen nicht vom Luau-Garbage-Collector bereinigt werden, selbst wenn keine Referenzen zur Instanz oder Verbindungen zur Instanz verbleiben.

Singletons

Singletons sind, wie der Name schon sagt, Klassen, von denen nur ein Objekt jemals existieren kann. Sie sind das Äquivalent des Projekts zu den Diensten von Roblox. Anstatt eine Referenz zum Singleton-Objekt zu speichern und sie im Luau-Code zu übergeben, nutzt Plant die Tatsache, dass das Erfordern eines ModuleScript seinen Rückgabewert cached. Dies bedeutet, dass das Erfordern des gleichen Singleton-ModuleScript von verschiedenen Stellen aus konstant dasselbe zurückgegebene Objekt bereitstellt. Die einzige Ausnahme von dieser Regel wäre, wenn verschiedene Umgebungen (Client oder Server) auf das ModuleScript zugreifen.

Singletons unterscheiden sich von instanziierbaren Klassen dadurch, dass sie keine new()-Methode haben. Vielmehr wird das Objekt zusammen mit seinen Methoden und seinem Zustand direkt über das ModuleScript zurückgegeben. Da Singletons nicht instanziiert werden, wird die self-Syntax nicht verwendet und Methoden werden stattdessen über einen Punkt (.) und nicht über einen Doppelpunkt (:) aufgerufen.

Strikte Typinferenz

Luau unterstützt schrittweises Tippen, was bedeutet, dass Sie optionale Typdefinitionen zu einigen oder allen Ihren Code hinzufügen können. In diesem Projekt wird für jedes Skript strikte Typprüfung verwendet. Dies ist die am wenigsten permissive Option für das Script Analysis Tool von Roblox und somit am wahrscheinlichsten, Typfehler vor der Laufzeit zu erkennen.

Typisierte Klassensyntax

Der etablierte Ansatz zur Erstellung von Klassen in Lua ist gut dokumentiert, jedoch nicht gut für starkes Luau-typen. In Luau ist der einfachste Ansatz, um den Typ einer Klasse zu erhalten, die Methode typeof():


type ClassType = typeof(Class.new())

Das funktioniert, ist jedoch nicht sehr nützlich, wenn Ihre Klasse mit Werten, die nur zur Laufzeit existieren, initialisiert wird, zum Beispiel Player-Objekten. Darüber hinaus ist die Annahme, die in der idiomatischen Lua-Klassensyntax getroffen wird, dass die Deklaration einer Methode auf einer Klasse self immer eine Instanz dieser Klasse ist; dies ist keine Annahme, die der Typinferenz-Engine gemacht werden kann.

Um die strikte Typinferenz zu unterstützen, verwendet das Plant-Projekt eine Lösung, die sich in mehreren Punkten von der idiomatischen Lua-Klassensyntax unterscheidet und einige davon möglicherweise nicht intuitiv sind:

  • Die Definition von self wird dupliziert, sowohl in der Typdeklaration als auch im Konstruktor. Dies bringt eine Wartungsbelastung mit sich, aber Warnungen werden ausgegeben, wenn die beiden Definitionen nicht synchron sind.
  • Klassenmethoden werden mit einem Punkt deklariert, sodass self ausdrücklich als Typ ClassType deklariert werden kann. Methoden können weiterhin wie erwartet mit einem Doppelpunkt aufgerufen werden.

--!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

Typen nach logischen Wächtern umwandeln

Zum Zeitpunkt des Schreibens wird der Typ eines Wertes nach einer Wächterbedingung nicht eingegrenzt. Zum Beispiel ist der Typ von optionalParameter nach dem Wächter unten nicht eingegrenzt:


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

Um dies zu mildern, werden nach diesen Wächtern neue Variablen erstellt, deren Typ ausdrücklich umgewandelt wird.


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

Traversieren von DataModel-Hierarchien

In einigen Fällen muss die Codebasis die Datenmodellhierarchie eines Baumes von Objekten durchlaufen, die zur Laufzeit erstellt werden. Dies stellt eine interessante Herausforderung für die Typenprüfung dar. Zum Zeitpunkt des Schreibens ist es nicht möglich, eine generische Datenmodellhierarchie als Typ zu definieren. Daher gibt es Fälle, in denen die einzige Typinformation, die für eine Datenmodellstruktur verfügbar ist, der Typ der höchstgelegenen Instanz ist.

Ein Ansatz zu dieser Herausforderung besteht darin, zu any zu casten und dann zu verfeinern. Zum Beispiel:


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

Das Problem mit diesem Ansatz besteht darin, dass die Lesbarkeit beeinträchtigt wird. Stattdessen verwendet das Projekt ein generisches Modul namens getInstance, um die Hierarchien des Datenmodells zu durchlaufen, das intern zu any castet.


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

Da das Verständnis der Typ-Engine für das Datenmodell weiterentwickelt wird, könnten Muster wie dieses möglicherweise nicht mehr erforderlich sein.

Benutzeroberfläche

Plant umfasst eine Vielzahl von komplexen und einfachen 2D-Benutzeroberflächen. Dazu gehören nicht interaktive HUD-Elemente wie der Münzzähler und komplexe interaktive Menüs wie das Geschäft.

UI-Ansatz

Sie können Roblox UI lose mit dem HTML DOM vergleichen, da es eine Hierarchie von Objekten ist, die beschreiben, was der Benutzer sehen sollte. Ansätze zur Erstellung und Aktualisierung einer Roblox-UI werden grob in imperative und declarative Praktiken unterteilt.

AnsatzVorteile und Nachteile
Imperativ

Im imperativen Ansatz wird die UI wie jede andere Instanzhierarchie auf Roblox behandelt. Die UI-Struktur wird vor der Laufzeit im Studio erstellt und dem Datenmodell hinzugefügt, typischerweise direkt in StarterGui. Dann manipuliert der Code zur Laufzeit bestimmte Teile der UI, um den Zustand widerzuspiegeln, den der Ersteller benötigt.

Dieser Ansatz hat einige Vorteile. Sie können die UI von Grund auf im Studio erstellen und im Datenmodell speichern. Dies ist eine einfache und visuelle Bearbeitungserfahrung, die die UI-Erstellung beschleunigen kann. Da sich imperative UI-Codes nur mit dem befassen, was sich ändern muss, sind einfache UI-Änderungen auch leicht umzusetzen.

Ein bemerkenswerter Nachteil ist, dass, da imperative UI-Ansätze erfordern, den Zustand manuell in Form von Transformationen einzuführen, komplexe Darstellungen des Zustands sehr schwer zu finden und zu debuggen sein können. Fehler treten häufig auf, wenn imperative UI-Codes entwickelt werden, insbesondere wenn der Zustand und die UI aufgrund mehrerer Updates, die in unerwarteter Reihenfolge interagieren, desynchronisiert werden.

Eine weitere Herausforderung bei imperativen Ansätzen ist, dass es schwieriger ist, die UI in bedeutungsvolle Komponenten zu zerlegen, die einmal deklariert und wiederverwendet werden können. Da der gesamte UI-Baum zur Bearbeitungszeit deklariert wird, können häufige Muster an mehreren Stellen im Datenmodell wiederholt werden.

Deklarativ

Im deklarativen Ansatz werden die gewünschten Zustände der UI-Instanzen explizit deklariert, und die effiziente Implementierung dieses Zustands wird von Bibliotheken wie Roact oder Fusion abstrahiert.

Der Vorteil dieses Ansatzes ist, dass die Implementierung des Zustands trivial wird und Sie nur beschreiben müssen, wie Ihre UI aussehen soll. Dies erleichtert das Auffinden und Beheben von Fehlern erheblich.

Der wesentliche Nachteil besteht darin, dass die gesamte UI-Hierarchie im Code deklariert werden muss. Bibliotheken wie Roact und Fusion haben Syntax, um dies zu erleichtern, aber es ist immer noch ein zeitaufwändiger Prozess und eine weniger intuitive Bearbeitungserfahrung beim Erstellen der UI.

Plant verwendet einen imperativen Ansatz in der Annahme, dass das direkte Anzeigen der Transformationen einen effektiveren Überblick darüber bietet, wie UI in Roblox erstellt und manipuliert wird. Dies wäre mit einem deklarativen Ansatz nicht möglich. Einige wiederholte UI-Strukturen und Logiken werden auch in wiederverwendbare Komponenten abstrahiert, um eine häufige Falle im imperativen UI-Design zu vermeiden.

Hochrangige Architektur

Diagramm der UI-Architektur des Pflanzenprojekts

Ebene und Komponenten

In Plant besteht jede UI-Struktur entweder aus einer Layer oder einem Component.

  • Layer wird als übergeordnete Gruppierung-Singleton definiert, das vorgefertigte UI-Strukturen in ReplicatedStorage umschließt. Eine Ebene kann eine Anzahl von Komponenten enthalten oder ihre eigene Logik vollständig kapseln. Beispiele für Ebenen sind das Inventarmenü oder der Münzzähler im HUD.
  • Component ist ein wiederverwendbares UI-Element. Wenn ein neues Komponentenobjekt instanziiert wird, klont es eine vorgefertigte Vorlage aus ReplicatedStorage. Komponenten können wiederum andere Komponenten enthalten. Beispiele für Komponenten sind eine allgemeine Schaltflächenklasse oder das Konzept einer Liste von Elementen.

Sichtverwaltung

Ein häufiges Problem beim Umgang mit der UI ist die Sichtverwaltung. Dieses Projekt hat eine Reihe von Menüs und HUD-Elementen, von denen einige auf Benutzereingaben hören, und eine sorgfältige Verwaltung, wann sie sichtbar oder aktiviert sind, ist erforderlich.

Plant geht dieses Problem mit seinem UIHandler-System an, das verwaltet, wann eine UI-Ebene sichtbar sein sollte oder nicht. Alle UI-Ebenen in der Erfahrung werden als HUD oder Menu kategorisiert und ihre Sichtbarkeit wird durch die folgenden Regeln verwaltet:

  • Der aktivierte Zustand von Menu und HUD-Ebenen kann umgeschaltet werden.
  • Aktivierte HUD-Ebenen werden nur angezeigt, wenn keine Menu-Ebenen aktiviert sind.
  • Aktivierte Menu-Ebenen werden in einem Stapel gespeichert und nur eine Menu-Ebene ist gleichzeitig sichtbar. Wenn eine Menu-Ebene aktiviert wird, wird sie an die Spitze des Stapels eingefügt und angezeigt. Wenn eine Menu-Ebene deaktiviert wird, wird sie aus dem Stapel entfernt und die nächste aktivierte Menu-Ebene in der Warteschlange wird angezeigt.

Dieser Ansatz ist intuitiv, da er es ermöglicht, Menüs mit Historie zu navigieren. Wenn ein Menü aus einem anderen Menü geöffnet wird, wird das Schließen des neuen Menüs das alte Menü wieder anzeigen.

UI-Ebenen-Singletons registrieren sich beim UIHandler und erhalten ein Signal, das ausgelöst wird, wenn sich ihre Sichtbarkeit ändern sollte.

Weiterführende Literatur

Aus diesem umfassenden Überblick über das Plant-Projekt möchten Sie möglicherweise die folgenden Leitfäden erkunden, die tiefer in verwandte Konzepte und Themen eintauchen:

  • Client-Server-Modell — Ein Überblick über das Client-Server-Modell in Roblox.
  • Luau — Einzelheiten über Luau, die von Roblox entwickelte Skriptsprache, die von Lua 5.1 abstammt.
  • Remote Events und Rückrufe — Alles über Remote-Netzwerkevents und Rückrufe für die Kommunikation über die Client-Server-Grenze.
  • UI — Einzelheiten zu Benutzeroberflächenobjekten und -design in Roblox.
©2026 Roblox Corporation. Roblox, das Roblox-Logo und "Powering Imagination" gehören zu unseren eingetragenen und nicht eingetragenen Markenzeichen in den USA und anderen Ländern.