Pflanze ist eine Referenz-Erfahrung, in der Spieler Pflanzen pflanzen und gießen, damit sie später ernten und verkaufen können.
Das Projekt konzentriert sich auf häufige Anwendungsfälle, die Sie beim Entwickeln eines Erlebnisses auf Roblox möglicherweise treffen. Wo es geltendist, finden Sie Notizen über Tradeouts, Kompromisse und die Rationale verschiedener Umsetzungswählungen, damit Sie die beste Entscheidung für Ihre eigenen Erlebnisse treffen können.
Hol dir das Datei
- Navigiere zur Planten-Erlebnisseite.
- Klicken Sie auf die Schaltfläche ⋯ und Bearbeiten in Studio .
Verwenden Sie Fälle
Pflanze abdeckt die folgenden Anwendungsfälle:
- Session-Daten und Spieler-Daten-Persistenz
- UI-Ansichtsverwaltung
- Client-Server-Netzwerk
- Benutzererlebnis beim ersten Mal (FTUE)
- Kaufe harte und weiche Währungen
Darüber hinaus löst dieses Projekt schmalere Sätze von Problemen, die für viele Erlebnisse anwendbar sind, einschließlich:
- Anpassung eines Bereichs im Zusammenhang mit einem Spieler:in
- Verwalten der Bewegungsgeschwindigkeit des Spielers
- Erstellen eines Objekts, das Charaktere umhüllt
- Erkennen, in welchem Teil der Welt sich ein Charakter befindet
Beachten Sie, dass es in dieser Erfahrung mehrere Anwendungsfälle gibt, die zu klein, zu Nischen oder nicht zu einer interessanten Design-Herausforderung sind; diese werden nicht abgedeckt.
Projektstruktur
Die erste Entscheidung beim Erstellen eines Erlebnisses ist die Entscheidung, wie die Projekt in das Datenmodell platziert werden soll und wie die Einträge für Client- und Codesorganisiert und strukturiert werden sollen.
Datenmodell
Die folgende Tabelle beschreibt, in welche Container-Services im Datenmodell-Instanzen platziert werden.
Dienst | Typen von Instanzen |
---|---|
Workspace | Enthält statische Modelle, die die 3D-Welt darstellen, und speziell Teile der Welt, die keinem Spieler:ingehören. Du musst diese Instanzen nicht dynamisch erstellen, modifizieren oder zerstören, so dass es in Ordnung ist, sie hier zu lassen. Es gibt auch eine leere Folder , zu der die Farm-Model von Spielern zur Laufzeit hinzugefügt werden. |
Lighting | Atmosphärische und Beleuchtungseffekte. |
ReplicatedFirst | Enthält die kleinste mögliche Unter集, die benötigt wird, um den Ladebildschirm anzuzeigen und das Spiel zu initialisieren. Je mehr Instanzen in ReplicatedFirst platziert werden, desto länger die Wartezeit, bevor der Code in ReplicatedFirst ausführenwerden kann.
|
ReplicatedStorage | Dient als Speicher容器 für alle Instanzen für die Zugriff erforderlich auf sowohl auf dem Client als auch auf dem Server ist.
|
ServerScriptService | Enthält einen Script , der als Eintrittspunkt für alle serverseitigen Codes im Projekt dient. |
ServerStorage | Dient als Speicher容器 für alle Instanzen, die nicht auf den Client repliziert werden müssen. In der Instanzen-Ordner gibt es eine Vorlage Farm Modell. Eine Kopie davon wird in 0> Class.Arbeitsbereich0> platziert, wenn der Spieler dem Spiel beitritt, wo sie allen Spielern repliziert wird. 3> 4> In der 6> |
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 in Platzsimuliert. |
Eintragspunkte
Die meisten Projekte organisieren Code in ModuleScripts , der importiert werden kann, indem er über die gesamte Codebasis importiert wird. ModuleScripts
Für das Plant-Microgame werden durch ein einzelnes LocalScript , das der Eintrittspunkt für alle Client-Codes ist, und ein einzelnes Script , das der Eintrittspunkt für alle Server-Codes ist, der richtige Ansatz für Ihr Projekt verwendet. Die richtige Ansicht für Ihr Projekt hängt von Ihren Anforderungen ab, aber ein einzelner Zugangspunkt bietet eine
Die folgenden Tabellen beschreiben die Tradeoffs beider Ansätze:
- Ein einzelner Script und ein einzelner LocalScript-Server- und Client-Code.
- Größere Kontrolle darüber, in welcher Reihenfolge verschiedene Systeme ausgeführt werden, da der gesamte Code aus einem einzigen Skript, das. PL: die Skriptsinitialisiert wird.
- Kann Objekte über Verweisung zwischen Systemen durchgeben.
Architektur der hohen Ebene-Systeme
Die obersten Systeme im Projekt sind unten aufgeführt. Einige dieser Systeme sind im Grunde komplexer als andere und in vielen Fällen ist die Funktionalität über eine Hierarchie von anderen Klassen abstrakt.
Jedes dieser Systeme ist ein "Singleton", in dem es eine nicht-instantielle Klasse ist, die stattdessen vom relevanten Client oder Server start Skript, das. PL: die Skriptsinitialisiert wird. Sie können mehr über das Singleton-Muster später in dieser Anleitung lesen.
Server
Die folgenden Systeme sind mit dem Server verbunden.
System | Beschreibung |
---|---|
Netzwerk | Erstellt alle Class.RemoteEvent und Class.RemoteFunction Instanzen. 0> Exposiert Methoden zum Senden und Empfangen von Nachrichten vom Client. 0>
|
PlayerDataServer | Speichert und lädt dauerhafte Spielerdaten mit Class.DataStoreService . >
|
Markt | Handhabt weiche Währungstransaktionen vom Client. Offenbart eine Methode, um geerntete Pflanzen zu verkaufen. Offenbart eine Methode, um geerntete Pflanzen zu verkaufen. |
KollisionGroupManager |
|
FarmManagerServer | Wiedererstellt das Farmmodell eines Spieler:inaus seinen Spieldaten, wenn er dem Spiel beitritt. Entfernt das Farmmodell, wenn ein Spieler das Spieler:inverlässt. Aktualisiert die Spieldaten, wenn die Farmmodell eines Spieler:ingeändert wird. |
PlayerObjectsContainer |
|
Spieler-Tags |
|
FtueManagerServer |
|
CharakterSpawner |
|
Client
Die folgenden Systeme sind mit dem Client verbunden.
System | Beschreibung |
---|---|
Netzwerk |
|
PlayerDataClient |
|
MarktplatzClient |
|
lokaler WalkJumpManager |
|
FarmManagerClient | Hört auf bestimmte Class.CollectionService-Tags, die auf Instanzen angewendet werden, und erstellt "Komponenten"-Apps, die auf diesen Instanzen angewendet werden. Ein "Komponente" bezieht sich auf eine Klasse, die erstellt wird, wenn ein Class.CollectionService-Tag zu einer Instanz hinzugefügt wird und zerstört wird |
UI-Einstellungen |
|
FtueManagerClient |
|
CharakterSprint |
|
Client-Server-Kommunikation
Die meisten Roblox-Erlebnisse beinhalten eine gewisse Kommunikation zwischen dem Client und dem Server. Dies kann die Client-Anfrage umfassen, den Server eine bestimmte Aktion auszuführen, und den Server Updates auf dem Client replizieren.
In diesem Projekt wird die Client-Server-Kommunikation so allgemein wie möglich eingeschränkt, indem die Verwendung von RemoteEvent und RemoteFunction -Objekten begrenzt wird, um die Anzahl der Spezialregeln zu verringern, die verfolgt werden müssen. Dieses Projekt verwendet die folgenden Methoden in der Reihenfolge der Präferenz:
- Replikation über das Spielerdatensystem.
- Replikation über Attribute .
- Replikation via Tags.
- Messaging direkt via das Netzwerk -Modul.
Replikation über das Spielerdatensystem
Das Spielerdatensystem ermöglicht es Daten mit dem Spieler zu verknüpfen, der zwischen Sitzungen gespeichert bleibt. Dieses System bietet Replikation von Client zu Server und eine Reihe von APIs, die verwendet werden können, um Daten abzurufen und sich abzumelden, so dass es ideal ist, die Änderungen des Spielers vom Server zum Client zu replizieren.
Zum Beispiel, anstatt ein maßgefertigtes UpdateCoins``Class.RemoteEvent zu feuern, um dem Client zu sagen, wie viele Münzen er hat, können Sie die folgenden aufrufen und den Client via das PlayerDataClient.updated -Ereignis abonnieren lassen.
PlayerDataServer:setValue(player, "coins", 5)
Natürlich ist dies nur für die Server-zu-Client-Replikation und Werte, die Sie zwischen Sitzungen bestehen möchten, nützlich, aber dies gilt für eine überraschende Anzahl von Fällen im Projekt, einschließlich:
- Die aktuelle FTUE-Stufe
- Das Inventar des Spieler:in
- Die Menge an Münzen, die der Spieler hat
- Der Zustand der Farm des Spieler:in
Replikation über Attribute
In Situationen, in denen der Server eine benutzerdefinierte Werte auf den Client replizieren muss, die spezifisch für ein bestimmtes Instance ist, können Sie Attribute verwenden. Roblox repliziert automatisch Attribute-werte, sodass Sie keine Code-Wege zum Replizieren des Zustands eines Objekts müssen. Ein weiterer Vorteil ist, dass diese Replikation neben der Instanz selbst stattfindet.
Dies ist besonders nützlich für Instanzen, die zur Laufzeit erstellt wurden, da Attributs auf einer neuen Instanz vor dem Elternteilen mit dem Datenmodell wiederum atomar mit dem Instanz selbst repliziert werden. Dies umgeht die Notwendigkeit, Code zu "warten", dass zusätzliche Daten über einen RemoteEvent oder StringValue repliziert werden.
Sie können auch direkt Attribute aus dem Modelllesen, sowohl vom Client als auch vom Server, mit der Methode GetAttribute(), und sich abonnieren Sie Änderungen mit der Methode GetAttributeChangedSignal(). Im Plant-Projekt wird diese Ansicht für, unter anderen Dingen, verwendet, um den aktuellen Zustand der Pflan
Replikation über Tags
CollectionService lässt Sie eine Strings-Tag an eine Instance anwenden. Dies ist nützlich, um Instanzen zu kategorisieren und diese Kategorisierung auf den Client zu replizieren.
Zum Beispiel wird das CanPlant-Tag auf dem Server angewendet, um dem Client zu signalisieren, dass ein bestimmter Topf eine Pflanze empfangen kann.
Direkt über das Netzwerk-Modul senden
Für Situationen, in denen keine der vorherigen Optionen anzuwenden sind, können Sie benutzerdefinierte Netzwerk-Anrufe über das Netzwerk-Modul verwenden. Dies ist die einzige Option im Projekt, die die Client-zu-Server-Kommunikation erlaubt und daher für die Übertragung von Client-Anfragen und das Erhalten eines Servers-Antwortes am wichtigsten ist.
Pflanze verwendet direkte Netzwerk-Anrufe für eine Vielzahl von Client-Anfragen, einschließlich:
- Gießen einer Pflanze
- Pflanzen eines Samens
- Ein Artikelkaufen
Der Nachteil bei dieser Herangehensweise ist, dass jede einzelne Nachricht einige benutzerdefinierte Konfigurationen erfordert, die die Komplexität des Projekts erhöhen können, obwohl dies bei Möglichkeit vermieden wurde, insbesondere für die Server-zu-Client-Kommunikation.
Klassen und Singletons
Klassen im Plant-Projekt, wie Instanzen auf Roblox, können erstellt und zerstört werden. seine KlassenSyntax ist von der idiomatischen Lua-Ansatz inspiriert, um Objekt-orientierte Programmierung mit einer Reihe von Änderungen zu aktivieren Strict Typechecking Support.
Einrichtung
Viele Klassen im Projekt sind mit einem 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 in der Regel für Objekte verwendet, bei denen die Klasse eine physische Repräsentation im Modellhat, und die Klasse ihre Funktionalität erweitert. Ein gutes Beispiel ist BeamBetween
Correspondierende Instanzen
Wie oben erwähnt haben viele Klassen in diesem Projekt ein Datenmodell-Repräsentation, eine Instanz, die mit der Klasse übereinstimmt und von ihr manipuliert wird.
Statt diese Instanzen zu erstellen, wenn ein Klassen-Objekt instanziiert wird, wählt der Code allgemein Clone() eine vordefinierte Version des
Zusammensetzung
Obwohl das Erbeben in Lua mit Metatables möglich ist, wählt das Projekt stattdessen, Klassen miteinander über Zusammensetzung zu erweitern. Wenn Sie Klassen durch Zusammensetzung kombinieren, wird das "Kind" -Objekt im new() -Methode der Klasse instantiiert und als Mitglied unter 1> self1> enthalten.
Für ein Beispiel dafür in der Action, siehe die CloseButton Klasse, die die Button Klasse umgibt.
Sauber machen
Ganz ähnlich, wie ein Instance mit der Methode Destroy() zerstört werden kann, können auch Klassen, die instanziiert werden können, zerstört werden. Die Zerstörermethode für Projektklassen ist destroy() mit einem kleineren 2>d2> für 5>
Die Rolle der Methode destroy() ist es, alle Instanzen, die vom Objekt erstellt wurden, zu zerstören, alle Verbindungen zu trennen und destroy() auf jedem Kind-Objekt aufzurufen. Dies ist besonders wichtig für Verbindungen, da Instanzen mit aktiven Verbindungen nicht von dem Lua-Müll-Sammler gereinigt werden, auch wenn keine Verweise auf die Instanz oder Verbindungen zu der Instanz übrig bleiben.
Singletons
Singletons sind, wie der Name schon sagt, Klassen für die nur ein Objekt existieren kann. Sie sind die Projekt-Äquivalente von Roblox's Diensten. Statt einen Verweis auf das Singleton-Objekt und
Singletons sind von sofortige Klassen unterscheidbar, indem sie kein new() -Methode haben. Stattdessen wird das Objekt mit seinen Methoden und Zustand direkt über den ModuleScript zurückgegeben. Als Singletons nicht instantiiert, wird die Syntax self nicht verwendet und Methoden werden stattdessen mit einem Punkt ( <
Strenger Typ-Verweigerung
Luau unterstützt diegraduelle Schreibweise, was bedeutet, dass Sie optionale Typ- Definitionen zu einigen oder allen Ihres Codes hinzufügen können. In diesem Projekt wird strict für jedes Skript, das. PL: die Skriptsverwendet. Dies ist die wenigsten Berechtigung für Roblox-Skript-Analyse-Werkzeug und damit die wahrscheinlichste Option, um Fehler vor der Laufzeit zu erkennen.
Getypte KlassenSyntax
Der etablierte Ansatz zum Erstellen von Klassen in Lua ist gut dokumentiert, aber er ist nicht gut für starke Luau-Schreibweise geeignet. In Luau ist der einfachste Ansatz für das Erhalten der Klassenart die Methode typeof():
type ClassType = typeof(Class.new())
Dies funktioniert, aber es ist nicht sehr nützlich, wenn deine Klasse mit Wertobjekten, die nur zur Laufzeit existieren, z. B. Player -Objekten, initiiert wird. Darüber hinaus ist die Annahme, die in der idiomischen Lua-Klassensyntax gemacht wird, dass die Erklärung einer Methode auf einer Klasse self immer eine Instanz dieser Klasse sein wird; dies ist keine Annahme, die der Typ-Inferenz-Engine machen
Um eine strenge Typoschreibung unterstützen zu können, verwendet das Plant-Projekt eine Lösung, die in mehreren Hinsichts von der idiomatischen Lua-KlassenSyntax abweicht, einige davon können unintuativ sein:
- Die Definition von self wird in der Typerklärung und im Konstruktor dupliziert. Dies führt zu einer Wartbarkeitsbelastung, aber Warnungen werden ausgetauscht, wenn die beiden Definitionen nicht in Einklang stehen.
- Class- Methods werden mit einem Punkt deklariert, so dass self explizit deklariert werden kann, dass sie von Type ClassType sind. Methoden können immer noch mit einem Komma wie erwartet aufgerufen werden.
--! streng
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
Wurden Casting-Typen nach logischen Wachen hinzugefügt
Bei der Erstellung eines Typs wird nach einer Wert-Wächter-Anweisung nicht der Werttyp nach optionalParameter eingeschränkt. Zum Beispiel, nach dem Wert number ist der Werttyp nicht auf number eingeschränkt.
--! streng
local function foo(optionalParameter: number?)
if not optionalParameter then
return
end
print(optionalParameter + 1)
end
Um dies zu beheben, werden nach diesen Wachen neue Variablen mit ihrem explizit geschalteten Typ erstellt.
--! streng
local function foo(optionalParameter: number?)
if not optionalParameter then
return
end
local parameter = optionalParameter :: number
print(parameter + 1)
end
Überquerung von Datenmodell-Hierarchien
In einigen Fällen muss die Codebase die Datenmodell-Hierarchie eines Baumes von Objekten durchlaufen, die beim Laufzeit-Scannen erstellt werden. Dies stellt eine interessante Herausforderung für die Typerüberprüfung dar. Zum Zeitpunkt des Schreibens ist es nicht möglich, eine generische Datenmodell-Hierarchie als eingebenzu definieren. Als Ergebnis gibt es Fälle, in denen die einzige verfügbare Typer-Information für eine Datenmodell-Struktur die Instanz
Eine Herangehensweise an diese Herausforderung ist es, auf any zu werfen und dann zu verbessern. Zum Beispiel:
local function enableVendor(vendor: Model)
local zonePart: BasePart = (vendor :: any).ZonePart
end
Das Problem mit dieser Herangehensweise ist, dass sie die Lesbarkeit beeinträchtigt. Stattdessen verwendet das Projekt ein generisches Modul namens getInstance , um durch die Datenmodell-Hierarchien zu durchschalten, die intern auf any zurückgreifen.
local function enableVendor(vendor: Model)
local zonePart: BasePart = getInstance(vendor, "ZonePart")
end
Wenn die Verständnis des Typle Engine-Datenmodells entwickelt, ist es möglich, dass Muster wie diese nicht mehr notwendig sind.
Bedieneroberfläche
Pflanze enthält eine Vielzahl von komplexen und einfachen 2D-Benutzeroberflächen. Dies beinhaltet nicht-interaktive Head-up-Anzeigen (HUD), wie die Münzen-Zähler und komplexe interaktive Menüs wie den kaufen.
UI-Ansatz
Du kannst Roblox UI ähnlich vergleichen mit dem HTML DOM, da es eine Hierarchie von Objekten ist, die beschreibt, was der Benutzer sehen soll. Ansätze zum Erstellen und Aktualisieren einer Roblox-UI sind breitely in immanente und deklarative Praktiken unterteilt.
Ansatz | Vorteile und Nachteile |
---|---|
Imperativ | Im imperativen Ansatz wird die UI wie jede andere Instanz-Hierarchie auf Roblox behandelt. Die UI-Struktur wird vor der Laufzeit in Studio erstellt und dem Modellhinzugefügt, typischerweise direkt in Class.St |
Declarativ | Im deklarativen Ansatz werden die gewünschten Zustände von UI-Instanzen deklariert, und die effiziente Umsetzung dieses Zustands wird von Bibliotheken wie Roact oder Fusion</ |
Plant verwendet einen imperativen Ansatz unter der Annahme, dass das Anzeigen der Transformationen direkt ein effektiveres Gesamtbild von der Verarbeitung und Manipulation von UI auf Roblox bietet. Dies wäre mit einem deklarativen Ansatz nicht möglich. Einige wiederholte UI-Strukturen und Logik werden auch in wiederverwendbaren Komponenten abstraktiert, um eine gemeinsame Falle in der imperativen UI-Design zu
Hochlevel-Architektur
Ebenen und Komponenten
In Plant sind alle UI-Strukturen entweder ein Layer oder ein Component.
- Layer ist als oberste Gruppierungssingleton definiert, das vordefinierte UI-Strukturen in ReplicatedStorage einwickelt. Ein Layer kann eine Reihe von Komponenten enthalten oder seine eigene Logik vollständig kapsulieren. Beispiele für Layer sind das Inventar-Menü oder der Münzen-Indikator im Kopf-Anzeigebereich.
- Component ist ein wiederverwendbares UI-Element. Wenn ein neues Komponentenobjekt instanziiert wird, kloniert es eine vordefinierte Vorlage aus ReplicatedStorage. Komponenten können in sich selbst andere Komponenten enthalten. Beispiele für Komponenten sind eine generische Schaltflächenelement oder das Konzept einer Liste von Elementen.
Steuerung anzeigen
Ein häufiges Problem bei der Verwaltung von Benutzeroberflächen ist die Ansichtsverarbeitung. Dieses Projekt hat eine Reihe von Menüs und HUD-Elementen, von denen einige Benutzereingaben hören und sorgfältige Verwaltung erforderlich ist, wenn sie sichtbar oder aktiviert sind.
Plant Ansätze dieses Problems mit seinem UIHandler -System, das verwaltet, wenn ein UI-Lay einfügbar sein sollte oder sollte nicht. Alle UI-Lay in dem Spiel sind als HUD oder 0> Menu0> 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 zu einer Zeit sichtbar. Wenn eine Menu Schicht aktiviert ist, wird sie in die Front des Stapels eingefügt und angezeigt. Wenn eine 1> Menu1> Schicht deaktiviert ist, wird sie aus dem St
Dieser Ansatz ist intuitiv, da er es ermöglicht, Menüs mit der Geschichte zu navigieren. Wenn ein Menü aus einem anderen Menü geöffnet wird, wird das Schließen des neuen Menüs das alte Menü erneut anzeigen.
Singleton-UI-Schichten registerieren sich mit dem UIHandler und werden mit einem Signal bereitgestellt, das ausgelöst wird, wenn seine Sichtbarkeit sich ändert.
Weitere Lektüre
Von dieser umfassenden Übersicht des Plant-Projekts möchten wir Ihnen die folgenden Guides anbieten, die in Bezug auf verwandte Konzepte und Themen noch weiter vertiefen.
- Client-Server-Modell — eine Übersicht des Client-Server-Modells in Roblox.
- Remote-Ereignisse und -Rückrufe — Alles über Remote-Netzwerk-Ereignisse und -Rückrufe für die Kommunikation über die Client-Server-Grenze.
- UI — Details zu Benutzeroberflächenelementen und Design auf Roblox.