Projekt referencyjny roślin

*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.

Plantacja to doświadczenie referencyjne, w którym gracze sadzą i wodzą nasiona, aby później mogli zebrać i sprzedać wynikające z nich rośliny.

Plant project banner

Projekt koncentruje się na wspólnych przypadkach użycia, które możesz napotkać podczas rozwijania doświadczenia na Roblox. Gdzie to mające zastosowanie, znajdziesz notatki na temat tradeoffów, kompromisów i uzasadnienia różnych wyborów implementacji, abyś mógł podjąć najlepszą decyzję dla własnych doświadczeń.

Zdobądź plik

  1. Przejdź do strony doświadczenia Roślinka.
  2. Kliknij przycisk i Edytuj w Studio .

Użyj przypadków

Roślinka pokrywa następujące przypadki użycia:

  • Trwałość sesji danych i gracza
  • Zarządzanie widokiem interfejsu
  • Sieci klient-serwer
  • Pierwszy raz użytkownika doświadczenie (FTUE)
  • Kupowanie walut twardych i miękkich

Dodatkowo, ten projekt rozwiązuje węższe zestawy problemów, które mają zastosowanie do wielu doświadczeń, w tym:

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

Uwaga, że istnieje kilka przypadków użycia w tym doświadczeniu, które są zbyt małe, zbyt niszowe lub nie pokazują rozwiązania na ciekawy problem projektowy; nie są one pokryte.

結構프로젝트

Pierwszą decyzją przy tworzeniu doświadczenia jest zdecydowanie, jak strukturować projekt, który w dużej mierze obejmuje to, gdzie umieścić konkretne instancje w modelu danych i jak zorganizować i zstrukturyzować punkty dostępu dla obu kodu klienta i serwera.

Model danych

Poniższy tabela opisuje które usługi kontenerowe w instancjach modelu danych są umieszczone.

UsługaTypy instancji
Workspace

Zawiera statyczne modele przedstawiające świat 3D, w szczególności części świata, które nie należą do żadnego gracza. Nie musisz dynamycznie tworzyć, modyfikować lub niszczyć tych instancji podczas uruchomienia, więc można je tutaj zostawić.

Istnieje również pusty Class.Folder , do którego zostaną dodane modele farm

Lighting

Efekty atmosferyczne i oświetleniowe.

ReplicatedFirst

Zawiera najmniejszy podstawowy podzestaw instancji wymaganych do wyświetlenia ekranu ładowania i inicjatyzacji gra. Im więcej instancji zostanie umieszczonych w ReplicatedFirst, tym dłużej będzie czekać na ich replikację przed kodem w ReplicatedFirst.

  • W Katalogu Instytucji folderze istnieje ładowanie ekranu GUI. W rozpoczynaćfolderze istnieje kod ładowania ekranu i kod potrzebny do czekania na resztę gry do załadowania. The
ReplicatedStorage

Służy jako pojemnik do przechowywania dla wszystkich instancji, dla których wymagany jest dostęp zarówno na klienta, jak i na serwer.

  • W katalogu zależności istnieje kilka bibliotek zewnętrznych używanych przez projekt. W katalogu przypadków istnieje szeroki zbiór przypadków zaprojekt
ServerScriptService

Zawiera Script służący jako punkt wejścia dla wszystkiego kodu stron serwera w projekcie.

ServerStorage

Służy jako pojemnik do przechowywania wszystkich instancji, które nie muszą być replikowane na klienta.

W katalogu Instancje znajduje się model Gospodarstwo. Kopia tego jest umieszczona w 0> Class.Workspace0>, gdy gracz dołącza do gry, gdzie zostanie ona skopiowana do wszystkich graczy. >

SoundService

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

Punkty dostępu

Większość projektów organizuje kod w ModuleScripts , który może być importowany w całej bazie kodu. ModuleScripts

Dla mikrogry Plant zaimplementowano inny podejście poprzez pojedynczy LocalScript , który jest punktem wejścia dla wszystkiego kodu klienta, i pojedynczy Script , który jest punktem wejścia dla wszystkiego kodu serwera. Poprawny podejście dla Twojego projektu zależy od Twoich wym

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

  • Jeden Script i jeden LocalScript kryształ serwera i kod klienta.
  • Większa kontrola nad kolejnością uruchamiania różnych systemów, ponieważ wszystkie kody są inicjalizowane z jednego skryptu.
  • Można przesyłać obiekty poprzez odniesienie między systemami.

Architektura systemów wysokiego poziomu

Najwyższy poziomowe 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.

Plant project systems architecture diagram

Każdy z tych systemów jest "singletonem", ponieważ jest to klasa nieinstytantialna, która zostaje zainicjatyzowana przez odpowiedni skrypt start. Możesz przeczytać więcej o wzorcu singtona później w tej przewodniku.

Serwer

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

SystemOpis
Sieć

Wszystkie instancje Class.RemoteEvent i Class.RemoteFunction. 0> 1> Wyświetla metody wysyłania i słuchania wiadomości z klienta. 1>

  • 0> 1> Typy walidacji dla argumentów otrzymywanych z klienta podczas uruchomienia. 1> 4>
PlayerDataServer

Zapisywanie i ładowanie trwałych danych gracza przy użyciu Class.DataStoreService . >

  • Przechowuje dane gracza w pamięci i replikuje mutacje na klienta. > 0> 1> Wyświetla sygnały i metody subskrypcji, zapytania i aktual
Rynek
  • Przyjmij miękkie transakcje waluty z klienta.
  • Wyświetla metodę sprzedaży zebranych roślin.
KolisionGroupManager
  • Przydziela modele postaci gracza do grup kolizji. . Konfiguruje grupy kolizji, aby postacie graczy nie mogły kolizować z wagonami roślinnymi. .
FarmManagerServer
  • Otworzy model farmy gracza z jego danych gracza, gdy dołączy do gry.

  • Usunie model farmy gracza, gdy gracz opuści.

  • Aktualizuje dane gracza, gdy zmienia się model farmy.

ContainerPlayerObjektów gracza
  • tworzy różne obiekty związane z życiem gracza i dostarcza metodę do odzyskiwania tych.
TagPlayers
FtueManagerServer ]
  • Podczas FTUE wykonuje każdy etap i czeka na jego zakończenie.
Generator postaci
  • Respawns characters when they die. Note that Players.CharacterAutoLoads has been disabled so that spawning is paused until the gracz's data has loaded.

Klient

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

SystemOpis
Sieć
ClientPlayerData
  • Przechowuje lokalne dane gracza w pamięci.
  • Wyświetla metody i sygnały do zapytania i subskrypcji zmian w danymach gracza.
Klient Marketowy
  • Wyeksponuje metodę, aby poprosić serwer o zakup przedmiotu za miękką waluta.
Lokalny Menedżer Skoku
  • Wyposaż metody, aby zmodyfikować WalkSpeed lub JumpHeight jednego z postaci za pośrednictwem mnożników, aby uniknąć konfliktów podczas modyfikowania tych wartości z wielu lokalizacji.
Kliент FarmManager

Słucha szczególnych tagów Class.CollectionService , które są stosowane do instancji, i tworzy "komponenty" zachowań przy zastosowaniu tych instancji. Komponent odnosi się do klasy, która jest tworzona, gdy tag Class.CollectionService jest dodany do instancji i zostanie usunięty; te są uży

Ustawienia interfejsu
  • Inicjalizuje wszystkie warstwy interfejsu użytkownika. Konfiguruje niektóre warstwy, aby były widoczne tylko w fizycznych sekcjach świata gry. Zakręca specjalny efekt kamerowy dla gdy menu są włączone. Zakręca specjalny efekt kamerowy dla gdy menu są włączone.
    1>Zakr
FtueManagerClient
  • Konfiguruje etapy FTUE na klientach.
Bieg znaków
  • Używa Lokalnego kodu źródłowego Walkera , aby zwiększyć Class.Humanoid.WalkSpeed|WalkSpeed kiedy postać gracza jest poza farmą.

Komunikacja klient-serwer

Większość doświadczeń Roblox wiąże się z pewnym elementem komunikacji między klientem a serwerem. To może obejmować prośbę klienta o wykonanie pewnej akcji i serwer replikując aktualizacje do klienta.

W tym projekcie komunikacja klient-serwer jest możliwa w takim stopniu, jak to możliwe, ograniczając użycie RemoteEvent i RemoteFunction obiektów, aby zmniejszyć ilość specjalnych zasad do śledzenia. Ten projekt używa następujący metody, w kolejności preferencji:

Replikacja za pośrednictwem systemu danych gracza

system danych gracza umożliwia łączenie danych z graczem, który trwa między sesjami zapisu. Ten system zapewnia replikację od klienta do serwera i zestaw API, które można użyć do zapisu danych i subskrypcji zmian, co czyni go idealnym do replikacji zmian w stanie gracza z serwera na klienta.

Na przykład, zamiast wystrzelić niestandardowy UpdateCoins``Class.RemoteEvent aby powiedzieć klientowi, ile monet ma, możesz zadzwonić następujące i pozwolić klientowi subskrybować go poprzez wydarzenie PlayerDataClient.updated .


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

Oczywiście, jest to tylko użyteczne dla replikacji serwer-do-klienta i wartości, które chcesz utrzymać między sesjami, ale dotyczy to zadziwiającej liczby przypadków w projekcie, w tym:

  • Obecny etap FTUE
  • wyposażeniegracza
  • Ilość monet, które gracz ma
  • Stan farmy gracza

Replikacja za pośrednictwem cech

W sytuacjach, w których serwer musi replikować niestandardową wartość dla 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 związanych ze stanem związanym z danym obiektu. Innym plusem

Jest to szczególnie przydatne dla instancji utworzonych podczas uruchomienia, ponieważ zmienne ustawione na nowej instancji przed jej zaimportowaniem do modelu danych będą replikowane atomowo z instancji samą w sobie. To unika wszelkiej potrzeby pisania kodu "czekaj" na dodatkowe dane do zaimportowania poprzez RemoteEvent lub StringValue .

Możesz również bezpośrednio czytać właściwości z modelu danych, zarówno z klienta, jak i serwera, za pomocą metody GetAttribute() i subskrybować zmiany za pomocą metody GetAttributeChangedSignal(). W projekcie Plant metoda ta jest używ

Replikacja za pomocą Tagów

CollectionService pozwala na zastosowanie etykiety stron do Instance . To jest użyteczne do kategorystyki instancji i replikacji tej kategorii na klienta.

Na przykład tag CanPlant jest zastosowany na serwerze, aby określić dla klienta, że dany gąbka może otrzymać roślinę.

Wysyłanie bezpośrednio za pośrednictwem modułu sieci

Dla sytuacji, w których żadne z wcześniejszych opcji nie ma zastosowania, możesz używać niestandardowych wezwów sieciowych poprzez moduł Sieć . Jest to jedyna opcja w projekcie, która umożliwia komunikację klient-serwer i jest więc najbardziej przydatna do odbierania żądań klientów i otrzymywania odpowiedzi serwera.

Roślinka używa bezpośrednich wezwów sieciowych dla różnych wniosków klienta, w tym:

  • Podlewanie rośliny
  • Zasadzanie nasiona
  • Kupowanie przedmiotu

Wadą tego podejścia jest to, że każda pojedyncza wiadomość wymaga nieco niestandardowej konfiguracji, co może zwiększyć złożoność projektu, choćby to zostało uniknięte, szczególnie dla komunikacji serwer-klient.

Klasy i Singletons

Klasy w projekcie Plant, podobnie jak instancje na Roblox, można tworzyć i usuwać. Jego słownik klas jest zainspirowany przez idiomatyczny podejście Lua do Programowanie obiektowe z kilkoma zmianami włączającymi wsparcie dla ściślej sprawdzenia.

Instalacja

Wiele klas w projekcie jest powiązanych z jedną lub większą liczbą Instances. Objetoce jednej danej klasy są tworzone przy użyciu metody new(), zgodnie z tym, jak instancje są tworzone w Roblox przy użyciu Instance.new().

Ten wzór jest ogólnie używany dla obiektów, w których klasa ma fizyczną reprezentację w modelu danych i klasa rozszerza swoją funkcjonalność. Dobrym przykładem jest Beam

Zgadzające się instancje

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

Zamiast tworzyć te instancje, gdy obiekt klasy zostanie zainstalowany, kod generalnie wybiera Clone() w wersji zapakiet

Skład

Chociaż dziedzictwo jest możliwe w Lua używając metatabeli, projekt zdecydował się zamiast tego pozwolić klasom rozciągnąć się nawzajem poprzez kompozycję . Gdy łączymy klasy poprzez kompozycję, obiekt "dziecka" jest instancyjowany w metodzie new() klasy i jest włączony jako członek pod

Aby uzyskać przykład tego w akcji, zobacz klasę CloseButton, która otacza klasę Button.

Czyszczenie

Podobnie jak można zniszczyć Class.Instance za pomocą metody Class.Instance:Destroy() , klasy, które można zainstalować, również mogą być zniszczone. Metoda dezstruktora dla klas projektowych jest destruct() z niższą dużą Destroy() dla camelCase

Rolem metody destroy() jest zniszczenie dowolnych instancji stworzonych przez obiekt, odłączenie dowolnych połączeń i wezwanie destroy() na dowolnym dziecięcym obiektach. Jest to szczególnie ważne dla połączeń, ponieważ instancje z aktywnymi połączeniami nie są czyszczone przez zbieracz kosza Lua, nawet jeśli nie pozostają żadne odniesienia do

Jedynaki

Singletons, jak nazwa sugeruje, są klasami dla których tylko jeden obiekt może kiedykolwiek istnieć. Są one równivalentem Roblox's Usługi . Zami

Singletons są odróżnione od natychmiastowych klas przez to, że nie mają new() metody. Zamiast tego obiekt wraz ze swoimi metodami i stanem jest bezpośrednio zwracany poprzez ModuleScript . Nie jest używana więc Syntax self , a metody są zamiast

Ścisłe wiązanie typu

Luau wspiera stopniowe wtyczanie, co oznacza, że możesz dodać opcjonalne definicje typu do niektórych lub wszystkich swoich kodów. W tym projekcie używany jest strict typcheckingu dla każdego skryptu. Jest to najmniej uprawnione opcja dla narzędzia do analizy kodu Roblox i więc najbardziej prawdopodobne, aby złapać błędy typ

Wykazany Syntetyczny Klasy

Najlepiej ustanowiony podejście do tworzenia klas w Lua jest dobrze opisowany, jednak nie nadaje się do silnego wpisywania Luau. W Lua najprostszym podejściem do uzyskania typu klasy jest metoda typeof():


type ClassType = typeof(Class.new())

To działa, ale nie jest to bardzo użyteczne, gdy twój klas jest inicjowany z wartościami, które istnieją tylko podczas uruchomienia, na przykład Player obiektów. Ponadto założenie stworzone w idiomaticznej klasy Lua jest, że deklarując metodę na klasie self zawsze będzie to instancja tego klasu; to nie jest założenie,

Aby popierać rygorystyczną infrastrukturę typu, projekt Plant używa rozwiązania, które różnią się od idiomaticznej klasy Lua w kilku sposób, niektóre z których mogą wydawać się nieintuicyjne:

  • Definicja self jest duplikowana zarówno w deklaracji typu, jak i w konstruktorze. To wprowadza obciążenie utrzymywalności, ale ostrzeżenia będą wyświetlać się, jeśli dwa definicje spadną z synchro z jedną z nich.
  • Modyfikatory klasy są deklarowane za pomocą kropki, więc self można wyraźnie deklarować, że jest typu ClassType . Modyfikatory nadal można wzywać za pomocą kolonki, jak oczekiwano.

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

Wykorzystywanie typów po logicznych filtrach

Podczas pisania typu wartości nie jest ograniczany po stwierdzeniu warunku. Na przykład, po następującym użyciu optionalParameter typu number nie jest ograniczony do number.


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

Aby to wyeliminować, nowe zmienne są tworzone po tych ochronach z ich wyraźnie określonym typem.


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

Przeglądanie Hierarchii Modelu Danych

W niektórych przypadkach baza kodowa musi przejść przez hierarchię modelu danych drzewa obiektów, które są tworzone podczas uruchomienia. To przedstawia zainteresowujące wyzwanie dla typcheckingu. W momencie pisania nie jest możliwe określenie genérica modelu danych jako wpisywać. W wynikistnieją przypadki, w których jedyną dostępną informacją dla modelu danych jest typ instancjana poziomie góry

Jednym podejściem do tego wyzwania jest rzucanie do any i następnie refinowanie. Na przykład:


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

Problem z tym podejściem jest to, że wpływa na czytelność. Zamiast tego projekt używa generycznego modułu nazywanego getInstance do przechwytywania hierarchii modelu danych, które zwracają się do any wewnętrznie.


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

Podczas gdy rozwija się zrozumienie silnika typu danych, może się okazać, że wzory takie nie będą już konieczne.

Interfejs użytkownika

Roślinka Zawiera różnorodne interfejsy użytkownika 2D. W tym znajdują się nieinteraktywne głowy uproszczonej animacji (HUD), takie jak koszyk koinowy i złożone interaktywne menu, takie jak robić zakupy.

Podejście UI

Możesz swobodnie porównać Roblox interfejs użytkownika do DOMu HTML, ponieważ jest to hierarchia obiektów, która opisuje to, co użytkownik powinien widzieć. Zakresy do tworzenia i aktualizacji interfejsu użytkownika są podzielone szeroko na praktyki zakazowe i odeklaracyjne .

PodejścieZa i przeciw
Imperatyw

W podejściu imperatywnym UI jest traktowana jak każda inna hier

Oświadczenie

W podejściu deklaratywnym wymierzone stany instancji UI są deklarowane wyraźnie, a efektywne implementowanie tego stanu jest abstrakcją przez biblioteki, takie jak

Roślinka używa podejścia imperatywnego pod koncepcją, że pokazanie transformacji bezpośrednio daje bardziej efektywny wgląd na to, jak UI jest tworzona i manipulowana na Roblox. Nie byłoby to możliwe przy podejściu deklaratywnym. Niektóre powtarzające się struktury i zasady logiczne są również abstrakcyjne w komponentach

Wysokozwровadzona Architektura

Plant project UI architecture diagram

Warstwa i komponenty

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

  • Layer jest zdefiniowany jako poziomowy grupowy singiel, który zapakowuje w ReplicatedStorage prefabrykatyzowane struktury UI. Plikier może zawierać wiele komponentów lub może całkowicie zapsłonować własną logikę. Przykładami plików są menu ekwipunku lub licznik monet w głównym okienku wyświetlania.
  • Component jest elementem interfejsu użytkownika ponownie używalnym. Gdy nowy element komponentu zostanie zainstalowany, kopiuje gotowy szablon z ReplicatedStorage. Komponenty mogą samodzielnie zawierać inne komponenty. Przykładami komponentów są genérica klasy przycisku lub koncepcja listy przedmiotów.

Zobacz sterowanie

Zwykłym problemem zarządzania interfejsem jest ustawienie widoku. Ten projekt ma zestaw menu i pozycji HUD, niektóre z których słuchają wejścia użytkownika, a właściwe zarządzanie tymi widokami lub włączaniem ich wymaga.

Roślinka podejmuje ten problem z jego systemem UIHandler , który zarządza, kiedy warstwa UI powinna lub nie powinna być widoczna. Wszystkie warstwy UI w grze są kategoryzowane jako HUD lub 0>Menu0> i ich widoczność jest zarządzana przez następujące zasady:

  • Włączone stanowiska Menu i HUD można przełączać.
  • Włączone HUD warstwy są pokazane tylko wtedy, gdy nie włączone są Menu warstwy.
  • Włączone Menu warstwy są zapisane w pila, a tylko jedna Menu warstwa jest widoczna na raz. Gdy włączone jest Menu warstwa, jest wstawiona na przód pila i wyświetlana. Gdy włączone jest 1> Menu1> warstwa, jest usunię

Ten podejście jest intuicyjny, ponieważ umożliwia menu przeglądane z historią. Jeśli jedno menu zostanie otworzone z innego menu, zamknięcie nowego menu pokaże stary menu ponownie.

Layery interfejsu rejestrują się z UIHandler i otrzymują sygnał, który uruchamia się, gdy jego widoczność powinna się zmienić.

Dalsze czytanie

Z tego kompleksowego przegladu projektu Planta, mozna chciec zbada? nast?puj?ce przewodniki, kt?re id? dalej w g?b?d?ce zwi?zane z koncepcjami i tematami.