Pflanzenreferenzprojekt

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

Pflanze ist ein Referenzspiel, in dem Spieler Samen pflanzen und gießen, damit sie später die resultierenden Pflanzen ernten und verkaufen können.

Banner des Pflanzenprojekts

Das Projekt konzentriert sich auf häufige Anwendungsfälle, die Sie beim Entwickeln eines Spiels auf Roblox antreffen könnten. Wo anwendbar, finden Sie Hinweise zu Kompromissen, Abwägungen und der Begründung verschiedener Implementierungsentscheidungen, damit Sie die beste Entscheidung für Ihre eigenen Spiele treffen können.

Datei herunterladen

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

Anwendungsfälle

Pflanze deckt die folgenden Anwendungsfälle ab:

  • Sitzungsdaten und Persistenz von Spieldaten
  • Verwaltung der UI-Ansicht
  • Client-Server-Netzwerk
  • Erstmalige Benutzererfahrung (FTUE)
  • Käufe mit harter und weicher Währung

Darüber hinaus löst dieses Projekt engere Problemstellungen, die auf viele Spiele anwendbar sind, einschließlich:

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

Bitte beachten Sie, dass es in diesem Spiel mehrere Anwendungsfälle gibt, die zu klein, zu nischenspezifisch oder nicht interessant genug sind, um eine Lösung für eine interessante Designherausforderung zu demonstrieren; diese werden nicht behandelt.

Projektstruktur

Die erste Entscheidung beim Erstellen eines Spiels besteht darin, wie man das Projekt strukturiert, was hauptsächlich umfasst, wo spezifische Instanzen im Datenmodell platziert werden und wie man Einstiegspunkte für sowohl Client- als auch Servercode organisiert und strukturiert.

Datenmodell

Die folgende Tabelle beschreibt, in welchen Container-Diensten die Instanzen im Datenmodell platziert sind.

DienstArten von Instanzen
Workspace

Enthält statische Modelle, die die 3D-Welt darstellen, 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

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

  • Im Instances-Ordner befindet sich die GUI für den Ladebildschirm.
  • Im Source-Ordner befindet sich der Code für den Ladebildschirm und der Code, der benötigt wird, um auf das Laden des restlichen Spiels zu warten. Das start LocalScript ist der Einstiegspunkt für allen clientseitigen Code im Projekt.
ReplicatedStorage

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

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

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

ServerStorage

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

  • Im Instances-Ordner befindet sich ein Vorlage Farm-Modell. Eine Kopie davon wird in Workspace platziert, wenn der Spieler dem Spiel beitritt, wo es an alle Spieler repliziert wird.
  • Im Source-Ordner befindet sich der gesamte Code, der ausschließlich für den Server ist.
SoundService

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

Einstiegspunkte

Die meisten Projekte organisieren Code in wiederverwendbaren ModuleScripts, die im gesamten Codebasis importiert werden können. ModuleScripts sind wiederverwendbar, führen sich jedoch nicht selbst 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 im Spiel zugeordnet sind, wodurch mehrere Einstiegspunkte entstehen.

Für das Pflanze Mikrogame wird ein anderer Ansatz durch ein einzelnes LocalScript implementiert, das der Einstiegspunkt für allen Client-Code ist, und ein einzelnes Script, das der Einstiegspunkt für allen Server-Code ist. Der richtige Ansatz für Ihr Projekt hängt von Ihren Anforderungen ab, aber ein einzelner Einstiegspunkt bietet eine größere Kontrolle über die Reihenfolge, in der Systeme ausgeführt werden.

Die folgenden Listen beschreiben die Abwägungen beider Ansätze:

  • Ein einzelnes Script und ein einzelnes LocalScript decken server- und clientseitigen Code 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 durch Referenz zwischen Systemen übergeben werden.

Hochrangige Systemarchitektur

Die obersten Systeme im Projekt sind unten 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 später in diesem Leitfaden mehr über das Singleton-Muster lesen.

Server

Die folgenden Systeme sind mit dem Server verbunden.

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

Client

Die folgenden Systeme sind mit dem Client verbunden.

SystemBeschreibung
Netzwerk
  • Wartet darauf, dass der Server alle RemoteEvent- und RemoteFunction-Instanzen erstellt.
  • Stellt Methoden zum Senden und Empfangen von Nachrichten an und vom Server zur Verfügung.
  • Erzwingt die Validierung der Parameterarten zur Laufzeit.
  • Führt pcall() bei Remote-Funktionen aus.
PlayerDataClient
  • Speichert die lokalen Spieldaten des Spielers im Speicher.
  • Stellt Methoden und Signale zum Abfragen und Abonnieren von Änderungen an Spieldaten zur Verfügung.
MarketClient
  • Stellt eine Methode zur Verfügung, um den Server zu bitten, einen Artikel für weiche Währung zu kaufen.
LocalWalkJumpManager
  • Stellt Methoden zur Verfügung, um die WalkSpeed oder JumpHeight eines Charakters über Multiplikatoren zu ändern, 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 Verhalten zu diesen Instanzen hinzufügen. Eine "Komponente" bezieht sich auf eine Klasse, die erstellt wird, wenn ein CollectionService-Tag zu einer Instanz hinzugefügt wird und zerstört wird, wenn es entfernt wird; diese werden für die CTA-Aufforderungen in der Farm und verschiedene Klassen verwendet, die den Farmstatus an den Spieler übermitteln.
UISetup
  • Initialisiert alle UI-Schichten.
  • Konfiguriert bestimmte Schichten, die nur in physischen Abschnitten der Welt sichtbar sind.
  • Verknüpft einen speziellen Kameraeffekt, wenn Menüs aktiviert sind.
FtueManagerClient
  • Konfiguriert FTUE-Phasen auf dem Client.
CharacterSprint
  • Verwendet LocalWalkJumpManager, um die WalkSpeed zu erhöhen, wenn sich ein Spielercharakter außerhalb seiner Farm befindet.

Client-Server-Kommunikation

Die meisten Roblox-Spiele beinhalten ein gewisses Element der Kommunikation zwischen Client und Server. Dies kann das Anfordern des Servers umfassen, eine bestimmte Aktion auszuführen, und der Server repliziert Updates an den Client.

In diesem Projekt wird die Client-Server-Kommunikation so allgemein wie möglich gehalten, indem die Verwendung von RemoteEvent- und RemoteFunction-Objekten eingeschränkt wird, um die Anzahl der speziellen Regeln, die zu beachten sind, zu verringern. Dieses Projekt verwendet die folgenden Methoden, in der Reihenfolge der Präferenz:

Replikation über das Spielerdaten-System

Das Spielerdaten-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, was es ideal macht, um Änderungen am Spielerstatus vom Server an den Client zu replizieren.

Zum Beispiel, anstatt ein maßgeschneidertes UpdateCoins RemoteEvent auszulösen, um dem Client mitzuteilen, wie viele Münzen er hat, können Sie Folgendes aufrufen und den Client ü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:

  • Die aktuelle FTUE-Phase
  • Das Inventar des Spielers
  • Die Anzahl der Münzen, die der Spieler hat
  • Der Status 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 Attributwerte automatisch, sodass Sie keine Codepfade aufrechterhalten müssen, um den Status, der mit einem Objekt verbunden ist, zu replizieren. Ein weiterer Vorteil ist, dass diese Replikation zusammen mit der Instanz selbst erfolgt.

Dies ist besonders nützlich für zur Laufzeit erstellte Instanzen, da Attribute, die auf einer neuen Instanz gesetzt werden, bevor sie dem Datenmodell zugeordnet wird, atomar mit der Instanz selbst repliziert werden. Dies umgeht 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 aus dem Datenmodell, sowohl vom Client als auch vom Server, mit der Methode GetAttribute() lesen und Änderungen mit der Methode GetAttributeChangedSignal() abonnieren. Im Pflanze Projekt wird dieser Ansatz unter anderem verwendet, um den aktuellen Status der Pflanzen an die Clients zu replizieren.

Replikation über Tags

CollectionService ermöglicht es Ihnen, ein String-Tag auf eine 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 in der Lage ist, eine Pflanze zu empfangen.

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 Clientanfragen zu übertragen und eine Serverantwort zu erhalten.

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

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

Der Nachteil dieses Ansatzes besteht darin, dass jede einzelne Nachricht eine maßgeschneiderte Konfiguration erfordert, die die Komplexität des Projekts erhöhen kann, obwohl dies wo immer möglich vermieden wurde, insbesondere für die Kommunikation vom Server zum Client.

Klassen und Singletons

Klassen im Pflanze Projekt, wie Instanzen auf Roblox, können erstellt und zerstört werden. Ihre Klassensyntax ist inspiriert von dem idiomatischen Lua-Ansatz für objektorientierte Programmierung mit einer 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, die mit der Art und Weise übereinstimmt, wie Instanzen in Roblox mit Instance.new() erstellt werden.

Dieses Muster wird im Allgemeinen für Objekte verwendet, bei denen die Klasse eine physische Darstellung im Datenmodell hat, und die Klasse erweitert ihre Funktionalität. 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 übereinstimmt und von ihr manipuliert wird.

Anstatt diese Instanzen zu erstellen, wenn ein Klassenobjekt instanziiert wird, entscheidet sich der Code im Allgemeinen dafür, eine vorgefertigte Version der Instance unter ReplicatedStorage oder ServerStorage zu Clone(). 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, sie zu verstehen. Darüber hinaus ist das Klonen einer Instanz im Allgemeinen eine schnellere Operation als das Erstellen einer neuen Instanz und das Anpassen ihrer Eigenschaften zur Laufzeit.

Komposition

Obwohl Vererbung in Luau mithilfe von Metatabellen möglich ist, entscheidet sich das Projekt stattdessen dafür, Klassen durch Komposition zu erweitern. Bei der Kombination von Klassen durch Komposition wird das "Kind"-Objekt in der new()-Methode der Klasse instanziiert und als Mitglied unter self aufgenommen.

Ein Beispiel dafür in Aktion ist die 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 Destruktormethode für Projektklassen ist destroy() mit einem kleinen d für die Konsistenz von camelCase über die Methoden des Codes, sowie um zwischen den Klassen des Projekts und den Roblox-Instanzen zu unterscheiden.

Die Rolle der destroy()-Methode besteht darin, alle Instanzen, die vom Objekt erstellt wurden, 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 auf die Instanz oder Verbindungen zur Instanz mehr bestehen.

Singletons

Singletons, wie der Name schon sagt, sind Klassen, für die nur ein Objekt existieren kann. Sie sind das Äquivalent des Projekts zu den Diensten von Roblox. Anstatt eine Referenz auf das Singleton-Objekt zu speichern und es im Luau-Code weiterzugeben, nutzt Pflanze die Tatsache, dass das Erfordern eines ModuleScript seinen zurückgegebenen Wert zwischenspeichert. Das bedeutet, dass das Erfordern des gleichen Singleton ModuleScript aus verschiedenen Orten konsistent dasselbe zurückgegebene Objekt bereitstellt. Die einzige Ausnahme von dieser Regel wäre, wenn unterschiedliche Umgebungen (Client oder Server) auf das ModuleScript zugreifen.

Singletons unterscheiden sich von instanziierbaren Klassen dadurch, dass sie keine new()-Methode haben. Stattdessen 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 mit einem Punkt (.) anstelle eines Doppelpunktes (:) aufgerufen.

Strikte Typinferenz

Luau unterstützt schrittweises Typing, was bedeutet, dass Sie optional Typdefinitionen zu einem Teil oder zu allem Ihrem Code hinzufügen können. In diesem Projekt wird für jedes Skript eine strikte Typprüfung verwendet. Dies ist die am wenigsten permissive Option für das Script Analysis-Tool von Roblox und daher 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 geeignet für starkes Luau-Typing. In Luau ist der einfachste Ansatz, um den Typ einer Klasse zu erhalten, die Methode typeof():

type ClassType = typeof(Class.new())

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

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

  • Die Definition von self wird sowohl in der Typdeklaration als auch im Konstruktor dupliziert. Dies führt zu einer Wartungsbelastung, aber Warnungen werden angezeigt, wenn die beiden Definitionen nicht mehr synchron sind.
  • Klassenmethoden werden mit einem Punkt deklariert, sodass self explizit als 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 wird 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 mit ihrem Typ explizit umgewandelt.

--!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 Baums von Objekten durchlaufen, die zur Laufzeit erstellt werden. Dies stellt eine interessante Herausforderung für die Typprü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 einzigen Typinformationen, die für eine Datenmodellstruktur verfügbar sind, der Typ der obersten Instanz sind.

Ein Ansatz für diese Herausforderung besteht darin, auf 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 ist, dass er die Lesbarkeit beeinträchtigt. Stattdessen verwendet das Projekt ein generisches Modul namens getInstance, um Datenmodellhierarchien zu durchlaufen, das intern auf 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 sich weiterentwickelt, ist es möglich, dass Muster wie dieses nicht mehr notwendig sein werden.

Benutzeroberfläche

Pflanze umfasst eine Vielzahl von komplexen und einfachen 2D-Benutzeroberflächen. Dazu gehören nicht-interaktive Heads-up-Display (HUD)-Elemente wie der Münzzähler und komplexe interaktive Menüs wie der Shop.

UI-Ansatz

Sie können Roblox UI grob mit dem HTML DOM vergleichen, da es sich um eine Hierarchie von Objekten handelt, 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 spezifische 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 Erstellung von UI beschleunigen kann. Da imperativer UI-Code sich nur um das kümmert, was geändert werden muss, macht es auch einfache UI-Änderungen leicht umsetzbar.

Ein bemerkenswerter Nachteil ist, dass, da imperativen UI-Ansätze erfordern, dass der Zustand manuell in Form von Transformationen implementiert wird, komplexe Darstellungen des Zustands sehr schwer zu finden und zu debuggen sein können. Es ist üblich, dass Fehler beim Entwickeln von imperativem UI-Code auftreten, insbesondere wenn der Zustand und die UI aufgrund mehrerer Updates, die in unerwarteter Reihenfolge interagieren, desynchronisiert werden.

Eine weitere Herausforderung bei imperativen Ansätzen besteht darin, dass es schwieriger ist, die UI in sinnvolle 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 in mehreren Teilen des Datenmodells wiederholt werden.

Deklarativ

Im deklarativen Ansatz werden die gewünschten Zustände von UI-Instanzen explizit deklariert, und die effiziente Implementierung dieses Zustands wird durch 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 macht das Identifizieren und Beheben von Fehlern erheblich einfacher.

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

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

Hochrangige Architektur

Diagramm der UI-Architektur des Pflanzenprojekts

Schicht und Komponenten

In Pflanze sind alle UI-Strukturen entweder eine Layer oder eine Component.

  • Layer wird als eine oberste Gruppierungssingleton definiert, die vorgefertigte UI-Strukturen in ReplicatedStorage umschließt. Eine Schicht kann eine Anzahl von Komponenten enthalten oder ihre eigene Logik vollständig kapseln. Beispiele für Schichten sind das Inventarmenü oder der Münzindikator im Heads-up-Display.
  • Component ist ein wiederverwendbares UI-Element. Wenn ein neues Komponentenobjekt instanziiert wird, klont es eine vorgefertigte Vorlage aus ReplicatedStorage. Komponenten können selbst andere Komponenten enthalten. Beispiele für Komponenten sind eine generische Schaltflächenklasse oder das Konzept einer Liste von Elementen.

Ansichtshandhabung

Ein häufiges Problem bei der UI-Verwaltung ist die Ansichtshandhabung. 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.

Pflanze geht dieses Problem mit seinem UIHandler-System an, das verwaltet, wann eine UI-Schicht sichtbar sein sollte oder nicht. Alle UI-Schichten im Spiel werden als HUD oder Menu kategorisiert, und ihre Sichtbarkeit wird durch die folgenden Regeln verwaltet:

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

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

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

Weiterführende Literatur

Von diesem umfassenden Überblick über das Pflanze 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 — Details zu Luau, der von Roblox erstellten Skriptsprache, die von Lua 5.1 abstammt.
  • Remote-Events und Rückrufe — Alles über Remote-Netzwerkereignisse und Rückrufe zur Kommunikation über die Client-Server-Grenze.
  • UI — Details zu Benutzeroberflächenobjekten und -design auf 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.