Projekt referencyjny Plant

*Ta zawartość została przetłumaczona przy użyciu narzędzi AI (w wersji beta) i może zawierać błędy. Aby wyświetlić tę stronę w języku angielskim, kliknij tutaj.

Plant to gra referencyjna, w której gracze sadzą i podlewają nasiona, aby później zbierać i sprzedawać powstałe rośliny.

Baner projektu Plant

Projekt koncentruje się na typowych przypadkach użycia, które możesz napotkać podczas tworzenia gry na Roblox. Tam, gdzie to możliwe, znajdziesz uwagi na temat kompromisów, ustępstw i uzasadnienia różnych wyborów implementacyjnych, abyś mógł podjąć najlepszą decyzję dla swoich gier.

Pobierz plik

  1. Przejdź do strony gry Plant.
  2. Kliknij przycisk i Edytuj w Studio.

Przypadki użycia

Plant obejmuje następujące przypadki użycia:

  • Utrzymywanie danych sesji i danych gracza
  • Zarządzanie widokiem UI
  • Sieciowanie klient-serwer
  • Doświadczenie pierwszego użytkownika (FTUE)
  • Zakupy za pomocą waluty twardej i miękkiej

Dodatkowo, ten projekt rozwiązuje węższe zestawy problemów, które są stosowane w wielu grach, w tym:

  • Personalizacja obszaru w miejscu związanym z graczem
  • Zarządzanie prędkością ruchu postaci gracza
  • Tworzenie obiektu, który podąża za postaciami
  • Wykrywanie, w której części świata znajduje się postać

Zauważ, że w tej grze istnieje kilka przypadków użycia, które są zbyt małe, zbyt niszowe lub nie demonstrują rozwiązania interesującego wyzwania projektowego; te nie są objęte.

Struktura projektu

Pierwszą decyzją przy tworzeniu gry jest ustalenie, jak zorganizować projekt, co głównie obejmuje, gdzie umieścić konkretne instancje w modelu danych oraz jak zorganizować i strukturyzować punkty wejścia dla kodu klienta i serwera.

Model danych

Poniższa tabela opisuje, w których usługach kontenerowych w modelu danych umieszczane są instancje.

UsługaTypy instancji
Workspace

Zawiera statyczne modele reprezentujące świat 3D, a konkretnie części świata, które nie należą do żadnego gracza. Nie musisz dynamicznie tworzyć, modyfikować ani niszczyć tych instancji w czasie wykonywania, więc akceptowalne jest pozostawienie ich tutaj.

Istnieje również pusta Folder, do której modele farm graczy będą dodawane w czasie wykonywania.

Lighting

Efekty atmosferyczne i oświetleniowe.

ReplicatedFirst

Zawiera najmniejszy możliwy podzbiór instancji potrzebnych do wyświetlenia ekranu ładowania i zainicjowania gry. Im więcej instancji umieszczonych w ReplicatedFirst, tym dłużej trwa ich replikacja przed uruchomieniem kodu w ReplicatedFirst.

  • W folderze Instances znajduje się GUI ekranu ładowania.
  • W folderze Source znajduje się kod ekranu ładowania oraz kod potrzebny do oczekiwania na załadowanie reszty gry. start LocalScript jest punktem wejścia dla całego kodu po stronie klienta w projekcie.
ReplicatedStorage

Pełni rolę kontenera do przechowywania wszystkich instancji, do których dostęp jest wymagany zarówno po stronie klienta, jak i serwera.

  • W folderze Dependencies znajdują się niektóre biblioteki firm trzecich używane w projekcie.
  • W folderze Instances znajduje się szeroki zakres prefabrykowanych instancji.
  • W folderze Source znajduje się cały kod nie wymagany do procesu ładowania, który musi być dostępny zarówno dla klienta, jak i serwera.
ServerScriptService

Zawiera Script, który pełni rolę punktu wejścia dla całego kodu po stronie serwera w projekcie.

ServerStorage

Pełni rolę kontenera do przechowywania wszystkich instancji, które nie muszą być replikowane do klienta.

  • W folderze Instances znajduje się szablon modelu Farm. Kopia tego modelu jest umieszczana w Workspace, gdy gracz dołącza do gry, gdzie będzie replikowana do wszystkich graczy.
  • W folderze Source znajduje się cały kod, który jest wyłącznie dla serwera.
SoundService

Zawiera obiekty Sound używane do efektów dźwiękowych w grze. Pod SoundService te obiekty Sound nie mają pozycji i nie są symulowane w przestrzeni 3D.

Punkty wejścia

Większość projektów organizuje kod w wewnętrznych ModuleScripts, które mogą być importowane w całej bazie kodu. ModuleScripts są wielokrotnego użytku, ale nie wykonują się same; muszą być importowane przez Script lub LocalScript. Wiele projektów Roblox ma dużą liczbę obiektów Script i LocalScript, z których każdy dotyczy zachowania lub konkretnego systemu w grze, tworząc wiele punktów wejścia.

Dla mikrogry Plant zastosowano inne podejście poprzez pojedynczy LocalScript, który jest punktem wejścia dla całego kodu klienta, oraz pojedynczy Script, który jest punktem wejścia dla całego kodu serwera. Odpowiednie podejście dla twojego projektu zależy od twoich wymagań, ale pojedynczy punkt wejścia zapewnia większą kontrolę nad kolejnością, w jakiej systemy są uruchamiane.

Poniższe listy opisują kompromisy obu podejść:

  • Pojedynczy Script i pojedynczy LocalScript pokrywają odpowiednio kod serwera i klienta.
  • Większa kontrola nad kolejnością, w jakiej różne systemy są uruchamiane, ponieważ cały kod jest inicjowany z jednego skryptu.
  • Możliwość przekazywania obiektów przez referencję między systemami.

Architektura systemów na wysokim poziomie

Najwyższe systemy w projekcie są szczegółowo opisane poniżej. Niektóre z tych systemów są znacznie bardziej złożone niż inne, a w wielu przypadkach ich funkcjonalność jest abstrakcyjna w hierarchii innych klas.

Diagram architektury systemów projektu Plant

Każdy z tych systemów jest "singletonem", co oznacza, że jest to klasa, która nie może być instancjonowana, a zamiast tego jest inicjowana przez odpowiedni skrypt start klienta lub serwera. Możesz przeczytać więcej o wzorcu singletona później w tym przewodniku.

Serwer

Poniższe systemy są związane z serwerem.

SystemOpis
Sieć
  • Tworzy wszystkie instancje RemoteEvent i RemoteFunction.
  • Udostępnia metody do wysyłania i nasłuchiwania wiadomości od klienta.
  • Walidacja typów dla argumentów otrzymanych od klienta w czasie wykonywania.
PlayerDataServer
  • Zapisuje i ładuje trwałe dane gracza za pomocą DataStoreService.
  • Przechowuje dane gracza w pamięci i replikuje mutacje do klienta.
  • Udostępnia sygnały i metody do subskrybowania, zapytywania i aktualizowania danych gracza.
Rynek
  • Obsługuje transakcje waluty miękkiej z klienta.
  • Udostępnia metodę do sprzedaży zebranych roślin.
CollisionGroupManager
  • Przypisuje modele postaci gracza do grup kolizji.
  • Konfiguruje grupy kolizji, aby postacie graczy nie mogły kolidować z wozami roślinnymi.
FarmManagerServer
  • Odtwarza model farmy gracza z jego danych gracza, gdy dołącza do gry.
  • Usuwa model farmy, gdy gracz opuszcza grę.
  • Aktualizuje dane gracza, gdy farma gracza jest zmieniana.
  • Udostępnia metodę dostępu do klasy Farm związanej z danym graczem.
PlayerObjectsContainer
  • Tworzy różne obiekty związane z życiem gracza i udostępnia metodę ich pobierania.
TagPlayers
FtueManagerServer
  • W trakcie FTUE wykonuje każdy etap i czeka na jego zakończenie.
CharacterSpawner
  • Odtwarza postacie, gdy umierają. Zauważ, że Players.CharacterAutoLoads zostało wyłączone, aby spawnowanie było wstrzymane, dopóki dane gracza nie zostaną załadowane.

Klient

Poniższe systemy są związane z klientem.

SystemOpis
Sieć
  • Czeka na serwer, aby utworzyć wszystkie instancje RemoteEvent i RemoteFunction.
  • Udostępnia metody do wysyłania i nasłuchiwania wiadomości do i z serwera.
  • Wymusza walidację typów parametrów w czasie wykonywania.
  • Uruchamia pcall() na funkcjach zdalnych.
PlayerDataClient
  • Przechowuje dane lokalnego gracza w pamięci.
  • Udostępnia metody i sygnały do zapytywania i subskrybowania zmian w danych gracza.
MarketClient
  • Udostępnia metodę do żądania od serwera zakupu przedmiotu za walutę miękką.
LocalWalkJumpManager
  • Udostępnia metody do modyfikacji WalkSpeed lub JumpHeight postaci za pomocą mnożników, aby uniknąć konfliktów przy modyfikacji tych wartości z wielu miejsc.
FarmManagerClient
  • Nasłuchuje na konkretne tagi CollectionService stosowane do instancji i tworzy "komponenty", które dodają zachowanie do tych instancji. "Komponent" odnosi się do klasy, która jest tworzona, gdy tag CollectionService jest dodawany do instancji i niszczona, gdy jest usuwana; są one używane do komunikatów CTA w farmie oraz różnych klas przekazujących stan farmy do gracza.
UISetup
  • Inicjalizuje wszystkie warstwy UI.
  • Konfiguruje niektóre warstwy, aby były widoczne tylko w fizycznych sekcjach świata.
  • Podłącza specjalny efekt kamery, gdy menu są włączone.
FtueManagerClient
  • Konfiguruje etapy FTUE po stronie klienta.
CharacterSprint
  • Używa LocalWalkJumpManager, aby zwiększyć WalkSpeed, gdy postać gracza znajduje się poza swoją farmą.

Komunikacja klient-serwer

Większość gier Roblox obejmuje jakiś element komunikacji między klientem a serwerem. Może to obejmować żądanie klienta, aby serwer wykonał określoną akcję oraz replikację aktualizacji do klienta.

W tym projekcie komunikacja klient-serwer jest utrzymywana tak ogólnie, jak to możliwe, poprzez ograniczenie użycia obiektów RemoteEvent i RemoteFunction, aby zmniejszyć liczbę specjalnych reguł do śledzenia. Ten projekt wykorzystuje następujące metody, w kolejności preferencji:

Replikacja za pomocą systemu danych gracza

System danych gracza pozwala na powiązanie danych z graczem, które utrzymują się między sesjami zapisu. Ten system zapewnia replikację z klienta do serwera oraz zestaw interfejsów API, które można wykorzystać do zapytywania o dane i subskrybowania zmian, co czyni go idealnym do replikacji zmian stanu gracza z serwera do klienta.

Na przykład, zamiast uruchamiać specjalny UpdateCoins RemoteEvent, aby powiedzieć klientowi, ile ma monet, możesz wywołać następujące i pozwolić klientowi subskrybować to za pomocą zdarzenia PlayerDataClient.updated.

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

Oczywiście, to jest użyteczne tylko dla replikacji z serwera do klienta i dla wartości, które chcesz, aby utrzymywały się między sesjami, ale dotyczy to zaskakującej liczby przypadków w projekcie, w tym:

  • Bieżący etap FTUE
  • Inwentarz gracza
  • Ilość monet, które ma gracz
  • Stan farmy gracza

Replikacja za pomocą atrybutów

W sytuacjach, gdy serwer musi zreplikować niestandardową wartość do klienta, która jest specyficzna dla danej Instance, możesz użyć atrybutów. Roblox automatycznie replikuje wartości atrybutów, więc nie musisz utrzymywać żadnych ścieżek kodu do replikacji stanu powiązanego z obiektem. Inną zaletą jest to, że ta replikacja odbywa się równolegle z instancją.

Jest to szczególnie przydatne dla instancji tworzonych w czasie wykonywania, ponieważ atrybuty ustawione na nowej instancji przed jej umiejscowieniem w modelu danych będą replikowane atomowo z samą instancją. To omija potrzebę pisania kodu, aby "czekać" na dodatkowe dane do zreplikowania za pomocą RemoteEvent lub StringValue.

Możesz również bezpośrednio odczytać atrybuty z modelu danych, zarówno z klienta, jak i serwera, za pomocą metody GetAttribute(), oraz subskrybować zmiany za pomocą metody GetAttributeChangedSignal(). W projekcie Plant to podejście jest używane między innymi do replikacji bieżącego statusu roślin do klientów.

Replikacja za pomocą tagów

CollectionService pozwala na zastosowanie tagu tekstowego do Instance. Jest to przydatne do kategoryzowania instancji i replikacji tej kategoryzacji do klienta.

Na przykład, tag CanPlant jest stosowany na serwerze, aby oznaczyć klientowi, że dany doniczkowy jest w stanie przyjąć roślinę.

Wysyłanie wiadomości bezpośrednio za pomocą modułu sieciowego

W sytuacjach, gdy żadne z poprzednich opcji nie mają zastosowania, możesz użyć niestandardowych wywołań sieciowych za pośrednictwem modułu Sieć. To jest jedyna opcja w projekcie, która pozwala na komunikację z klienta do serwera i jest zatem najbardziej przydatna do przesyłania żądań klienta i otrzymywania odpowiedzi od serwera.

Plant wykorzystuje bezpośrednie wywołania sieciowe do różnych żądań klienta, w tym:

  • Podlewanie rośliny
  • Sadzenie nasiona
  • Zakup przedmiotu

Wadą tego podejścia jest to, że każda pojedyncza wiadomość wymaga pewnej specjalnej konfiguracji, co może zwiększyć złożoność projektu, chociaż tego unikano, gdzie to możliwe, szczególnie w przypadku komunikacji z serwera do klienta.

Klasy i singletony

Klasy w projekcie Plant, podobnie jak instancje w Roblox, mogą być tworzone i niszczone. Ich składnia klas jest inspirowana idiomatycznym podejściem Lua do programowania obiektowego z wieloma zmianami, aby umożliwić wsparcie dla ścisłej kontroli typów.

Instancjonowanie

Wiele klas w projekcie jest związanych z jedną lub więcej Instances. Obiekty danej klasy są tworzone za pomocą metody new(), zgodnie z tym, jak instancje są tworzone w Roblox za pomocą Instance.new().

Ten wzorzec jest zazwyczaj używany dla obiektów, gdzie klasa ma fizyczną reprezentację w modelu danych, a klasa rozszerza swoją funkcjonalność. Dobrym przykładem jest BeamBetween, który tworzy obiekt Beam między dwoma danymi obiektami Attachment i utrzymuje te załączniki w orientacji, aby wiązka zawsze była skierowana w górę. Te instancje mogą być klonowane z prefabrykowanej wersji w ReplicatedStorage lub przekazywane do new() jako argument i przechowywane wewnątrz obiektu pod self.

Odpowiednie instancje

Jak wspomniano powyżej, wiele klas w tym projekcie ma reprezentację w modelu danych, instancję, która odpowiada klasie i jest przez nią manipulowana.

Zamiast tworzyć te instancje, gdy obiekt klasy jest instancjonowany, kod zazwyczaj decyduje się na Clone() prefabrykowanej wersji Instance przechowywanej w ReplicatedStorage lub ServerStorage. Chociaż możliwe byłoby serializowanie właściwości tych instancji i tworzenie ich od podstaw w funkcjach new() klasy, uczyniłoby to edytowanie obiektów bardzo uciążliwym i trudnym do zrozumienia dla czytelnika. Dodatkowo, klonowanie instancji jest zazwyczaj szybszą operacją niż tworzenie nowej instancji i dostosowywanie jej właściwości w czasie wykonywania.

Kompozycja

Chociaż dziedziczenie jest możliwe w Luau za pomocą metatables, projekt decyduje się na umożliwienie klasom rozszerzania się nawzajem poprzez kompozycję. Podczas łączenia klas poprzez kompozycję, obiekt "dziecka" jest instancjonowany w metodzie new() klasy i jest uwzględniany jako członek pod self.

Aby zobaczyć przykład tego w działaniu, zobacz klasę CloseButton, która opakowuje klasę Button.

Czyszczenie

Podobnie jak instancja Instance może być zniszczona za pomocą metody Destroy(), klasy, które mogą być instancjonowane, mogą również być niszczone. Metoda destruktora dla klas projektu to destroy() z małą literą d dla spójności camelCase w metodach bazy kodu, a także aby odróżnić klasy projektu od instancji Roblox.

Rola metody destroy() polega na zniszczeniu wszelkich instancji utworzonych przez obiekt, odłączeniu wszelkich połączeń i wywołaniu destroy() na wszelkich obiektach podrzędnych. Jest to szczególnie ważne dla połączeń, ponieważ instancje z aktywnymi połączeniami nie są czyszczone przez zbieracz śmieci Luau, nawet jeśli nie pozostają żadne odniesienia do instancji ani połączenia z instancją.

Singletony

Singletony, jak sama nazwa wskazuje, to klasy, dla których może istnieć tylko jeden obiekt. Są one odpowiednikiem usług Services w Roblox. Zamiast przechowywać odniesienie do obiektu singletona i przekazywać je w kodzie Luau, Plant korzysta z faktu, że wymaganie ModuleScript buforuje jego zwróconą wartość. Oznacza to, że wymaganie tego samego singletona ModuleScript z różnych miejsc konsekwentnie zapewnia ten sam zwrócony obiekt. Jedynym wyjątkiem od tej zasady byłoby, gdyby różne środowiska (klient lub serwer) uzyskiwały dostęp do ModuleScript.

Singletony różnią się od klas instancjonowanych tym, że nie mają metody new(). Zamiast tego obiekt wraz z jego metodami i stanem jest zwracany bezpośrednio przez ModuleScript. Ponieważ singletony nie są instancjonowane, składnia self nie jest używana, a metody są wywoływane za pomocą kropki (.) zamiast dwukropka (:).

Ścisła inferencja typów

Luau wspiera stopniowe typowanie, co oznacza, że możesz dodać opcjonalne definicje typów do niektórych lub wszystkich swoich kodów. W tym projekcie używana jest ścisła kontrola typów dla każdego skryptu. Jest to najmniej pobłażliwa opcja dla narzędzia Analiza skryptów w Roblox i zatem najbardziej prawdopodobna do wychwycenia błędów typów przed czasem wykonywania.

Składnia klas typowanych

Ustanowione podejście do tworzenia klas w Lua jest dobrze udokumentowane, jednak nie jest dobrze dostosowane do silnego typowania Luau. W Luau najprostszym podejściem do uzyskania typu klasy jest metoda typeof():

type ClassType = typeof(Class.new())

To działa, ale nie jest zbyt użyteczne, gdy twoja klasa jest inicjowana wartościami, które istnieją tylko w czasie wykonywania, na przykład obiektami Player. Dodatkowo, założenie przyjęte w idiomatycznej składni klas Lua jest takie, że deklarowanie metody na klasie self zawsze będzie instancją tej klasy; to nie jest założenie, które silnik inferencji typów może przyjąć.

Aby wspierać ścisłą inferencję typów, projekt Plant używa rozwiązania, które różni się od idiomatycznej składni klas Lua na wiele sposobów, z których niektóre mogą wydawać się nieintuicyjne:

  • Definicja self jest powielana, zarówno w deklaracji typu, jak i w konstruktorze. To wprowadza obciążenie związane z utrzymywaniem, ale ostrzeżenia będą zgłaszane, jeśli dwie definicje przestaną być zgodne.
  • Metody klas są deklarowane za pomocą kropki, więc self może być jawnie zadeklarowane jako typu ClassType. Metody nadal mogą być wywoływane za pomocą dwukropka, jak oczekiwano.
--!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

Rzutowanie typów po warunkach logicznych

W momencie pisania, typ wartości nie jest zawężany po instrukcji warunkowej. Na przykład, po warunku poniżej, typ optionalParameter nie jest zawężany do number.

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

Aby to złagodzić, nowe zmienne są tworzone po tych warunkach z ich typem jawnie rzuconym.

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

Przechodzenie przez hierarchie DataModel

W niektórych przypadkach, baza kodu musi przechodzić przez hierarchię modelu danych drzewa obiektów, które są tworzone w czasie wykonywania. To stawia interesujące wyzwanie dla kontroli typów. W momencie pisania, nie jest możliwe zdefiniowanie ogólnej hierarchii modelu danych jako typu. W rezultacie, istnieją przypadki, w których jedyną informacją o typie dostępną dla struktury modelu danych jest typ instancji najwyższego poziomu.

Jednym z podejść do tego wyzwania jest rzutowanie na any, a następnie udoskonalanie. Na przykład:

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

Problem z tym podejściem polega na tym, że wpływa na czytelność. Zamiast tego, projekt używa ogólnego modułu o nazwie getInstance do przechodzenia przez hierarchie modelu danych, który wewnętrznie rzutuje na any.

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

W miarę jak zrozumienie silnika typów modelu danych ewoluuje, możliwe, że wzorce takie jak ten nie będą już konieczne.

Interfejs użytkownika

Plant zawiera różnorodne złożone i proste interfejsy użytkownika 2D. Obejmują one nieinteraktywne elementy wyświetlacza (HUD), takie jak licznik monet oraz złożone interaktywne menu, takie jak sklep.

Podejście do UI

Możesz luźno porównać interfejs UI Roblox do HTML DOM, ponieważ jest to hierarchia obiektów, które opisują, co użytkownik powinien widzieć. Podejścia do tworzenia i aktualizowania interfejsu Roblox są szeroko podzielone na praktyki imperatywne i deklaratywne.

PodejścieZalety i wady
Imperatywne

W podejściu imperatywnym, UI traktowane jest jak każda inna hierarchia instancji w Roblox. Struktura UI jest tworzona przed czasem wykonywania w Studio i dodawana do modelu danych, zazwyczaj bezpośrednio w StarterGui. Następnie, w czasie wykonywania, kod manipuluje konkretnymi elementami UI, aby odzwierciedlić stan, który wymaga twórca.

To podejście ma pewne zalety. Możesz stworzyć UI od podstaw w Studio i przechować je w modelu danych. To jest proste i wizualne doświadczenie edycyjne, które może przyspieszyć tworzenie UI. Ponieważ kod UI imperatywnego dotyczy tylko tego, co wymaga zmiany, również ułatwia to wprowadzanie prostych zmian w UI.

Znaczną wadą jest to, że ponieważ podejścia imperatywne wymagają, aby stan był ręcznie wdrażany w formie transformacji, złożone reprezentacje stanu mogą stać się bardzo trudne do znalezienia i debugowania. Często pojawiają się błędy podczas rozwijania kodu UI imperatywnego, zwłaszcza gdy stan i UI stają się niesynchronizowane z powodu interakcji wielu aktualizacji w nieoczekiwanej kolejności.

Kolejnym wyzwaniem w podejściu imperatywnym jest to, że trudniej jest podzielić UI na znaczące komponenty, które można zadeklarować raz i ponownie wykorzystać. Ponieważ całe drzewo UI jest deklarowane w czasie edycji, wspólne wzorce mogą być powtarzane w wielu częściach modelu danych.

Deklaratywne

W podejściu deklaratywnym, pożądany stan instancji UI jest jawnie deklarowany, a efektywna implementacja tego stanu jest abstrahowana przez biblioteki takie jak Roact lub Fusion.

Zaletą tego podejścia jest to, że implementacja stanu staje się trywialna i musisz tylko opisać, jak chcesz, aby twój interfejs wyglądał. To znacznie ułatwia identyfikację i rozwiązywanie błędów.

Główną wadą jest konieczność zadeklarowania całego drzewa UI w kodzie. Biblioteki takie jak Roact i Fusion mają składnię, aby to ułatwić, ale wciąż jest to czasochłonny proces i mniej intuicyjne doświadczenie edycyjne podczas komponowania UI.

Plant używa podejścia imperatywnego w przekonaniu, że bezpośrednie pokazywanie transformacji daje bardziej efektywny przegląd tego, jak UI jest tworzone i manipulowane w Roblox. To nie byłoby możliwe w podejściu deklaratywnym. Niektóre powtarzające się struktury UI i logika są również abstrahowane w wielokrotnego użytku komponenty, aby uniknąć powszechnej pułapki w projektowaniu UI imperatywnym.

Architektura na wysokim poziomie

Diagram architektury UI projektu Plant

Warstwa i komponenty

W Plant wszystkie struktury UI są albo Layer, albo Component.

  • Layer jest definiowany jako singleton grupujący na najwyższym poziomie, który opakowuje prefabrykowane struktury UI w ReplicatedStorage. Warstwa może zawierać wiele komponentów lub może całkowicie encapsulować swoją logikę. Przykłady warstw to menu inwentarza lub wskaźnik liczby monet w wyświetlaczu.
  • Component to wielokrotnego użytku element UI. Gdy nowy obiekt komponentu jest instancjonowany, klonuje prefabrykowany szablon z ReplicatedStorage. Komponenty mogą same w sobie zawierać inne komponenty. Przykłady komponentów to ogólna klasa przycisku lub koncepcja listy przedmiotów.

Zarządzanie widokiem

Typowym problemem zarządzania UI jest zarządzanie widokiem. Ten projekt ma szereg menu i elementów HUD, z których niektóre nasłuchują na wejście użytkownika, a staranne zarządzanie tym, kiedy są widoczne lub włączone, jest wymagane.

Plant podchodzi do tego problemu za pomocą systemu UIHandler, który zarządza tym, kiedy warstwa UI powinna być widoczna lub nie. Wszystkie warstwy UI w grze są klasyfikowane jako HUD lub Menu, a ich widoczność jest zarządzana przez następujące zasady:

  • Stan włączony warstw Menu i HUD może być przełączany.
  • Włączone warstwy HUD są pokazywane tylko wtedy, gdy żadne warstwy Menu nie są włączone.
  • Włączone warstwy Menu są przechowywane w stosie, a tylko jedna warstwa Menu jest widoczna w danym czasie. Gdy warstwa Menu jest włączona, jest wstawiana na przód stosu i pokazywana. Gdy warstwa Menu jest wyłączona, jest usuwana ze stosu, a następna włączona warstwa Menu w kolejce jest pokazywana.

To podejście jest intuicyjne, ponieważ pozwala na nawigację po menu z historią. Jeśli jedno menu jest otwierane z innego menu, zamknięcie nowego menu ponownie pokaże stare menu.

Dalsze czytanie

Z tego szczegółowego przeglądu projektu Plant możesz chcieć zbadać następujące przewodniki, które idą dalej w głąb pokrewnych koncepcji i tematów.

  • Model klient-serwer — Przegląd modelu klient-serwer w Roblox.
  • Luau — Szczegóły dotyczące Luau, języka skryptowego stworzonego przez Roblox, pochodzącego z Lua 5.1.
  • Zdarzenia zdalne i wywołania zwrotne — Wszystko o zdalnych zdarzeniach sieciowych i wywołaniach zwrotnych do komunikacji przez granicę klient-serwer.
  • UI — Szczegóły dotyczące obiektów interfejsu użytkownika i projektowania w Roblox.
©2026 Roblox Corporation. Nazwa Roblox, logo Roblox oraz hasło „Powering Imagination” należą do naszych zarejestrowanych i niezarejestrowanych znaków towarowych na terenie Stanów Zjednoczonych oraz w innych krajach.