Szablon Verse – budowanie proceduralne jest przykładowym projektem z Unreal Editor dla Fortnite (UEFN) Re: Imagine London (Kod wyspy: 1442-4257-4418), który prezentuje system budowania proceduralnego napisany w języku Verse.
System budowania proceduralnego jest w całości zaimplementowany i stworzony w Verse, a nie w Fortnite. Dzięki temu systemowi masz większą kontrolę nad sposobem tworzenia siatek w swoich projektach.
W Fortnite możesz projektować rozgrywkę, korzystając z gotowych elementów budowli (podłogi, sufitu i ściany). System budowania proceduralnego zwiększa dostępne możliwości, dzięki czemu możesz tworzyć woksele z kategoriami, które ustawiają podstawowe systemy, w których możesz szybko umieszczać dodatkowe woksele, w miarę jak system umieszcza swoją siatkę.
Aby uzyskać dostęp do tego szablonu w przeglądarce projektów UEFN, przejdź do Przykłady funkcji > Przykłady gier > Verse – budowanie proceduralne.
System budowania proceduralnego działa w dwóch fazach.
W pierwszej fazie edytujesz siatkę wokseli – siatkę 3D, w której każda komórka jest sześcianem – poprzez dodawanie lub usuwanie wokseli.
Druga faza jest uruchamiana za każdym razem, gdy jest modyfikowana siatka wokseli. Modyfikacja siatki wokseli powoduje pobranie informacji o wokselu i uruchomienie procesu, który generuje właściwe siatki we właściwych miejscach.
Możesz dodawać i usuwać woksele kategorii Budowla i Park. Każda kategoria ma inne reguły określające, jak siatki powinny być spawnowane. Te reguły odnoszą się do grafu lub drzewa operacji, które zostaną wykonane, aby przejść od umieszczonych wokseli do rzeczywistych siatek
Korzystanie z szablonu
Każdy poziom w projekcie będzie miał jedną klasę najwyższego poziomu, czyli root_device class. Gdy gracz dołącza do gry, klasa root_device tworzy dla niego global_player_data, co ustawia informacje w jego UI.
Każda strefa budowania ma urządzenie build_zone, które definiuje wymiary terenu w jednostkach wokseli. Pozycja tego urządzenia określa źródło terenu. Obszar kompilacji wykorzystuje obiekt build_system do obsługiwania mechaniki budowania i spawner_device do spawnowania siatek rekwizytów budowli. Obszar budynku zawiera również voxel_grid, mesh_grid i wfc_system.
Każdy obszar budowli będzie zawierał następujące elementy:
build_zone(urządzenie) – określa wymiary terenu w jednostkach wokseli.build_system– obsługuje mechanikę budowania.spawner_device(urządzenie) – spawnuje siatki rekwizytów budowli.spawner_asset_references(urządzenie) – zawiera odwołania do wszystkich spawnowanych rekwizytów.
Gdy gracz wejdzie do strefy build_zone, która jest aktywowana przez urządzenie wolumenu, tworzony jest obiekt player_data na potrzeby obsługiwania danych wejściowych gracza i edytowania wokseli.
Budowanie za pomocą wokseli
Ten szablon wprowadza typ vector3i [type](), który będzie reprezentował woksele za pomocą ich współrzędnych X, Y i Z [integer]().
Poniżej znajduje się przykładowy skrypt wykorzystujący wektor vector3i.
vector3i<public> := struct<computes><concrete>:
@editable
X<public>:int = 0
@editable
Y<public>:int = 0
@editable
Z<public>:int = 0Siatki wokseli
Siatka wokseli to trójwymiarowa siatka komórek dla każdej strefy budynku, w której są przechowywane informacje o typie woksela budowli. Jest to zaimplementowane w klasie voxel_grid jako tablica 1D opcjonalnych odwołań do obiektów voxel_cell [objects](), jak pokazano poniżej.
# Main array representing the 3D grid of voxels
var Cells<public> : []?voxel_cell = array{}Domyślnie obiekt komórki wokselizacji zawiera tylko typ wyliczeniowy [enum]() dla typu woksela, jak pokazano poniżej.
# Category of voxel in our build grid
build_category<public> := enum:
Building
Park
NewBuildingType
# Structure stored for each occupied voxel
voxel_cell<public> := struct<computes>:
Category<public>:build_categoryMożesz dodać więcej kategorii wokseli budowli, rozszerzając wyliczenie, jak pokazano powyżej.
Poniższy kod służy do konwertowania współrzędnej woksela 3D na indeks 1D [index]().
# Gets the 1D array index from a 3D location in grid
GetVoxelIndex<public>(X:int, Y:int, Z:int)<transacts>:int=
return (X * (Size.Y*Size.Z)) + (Y * Size.Z) + ZSetVoxel i ClearVoxel to kluczowe funkcje, które modyfikują siatkę wokseli.
Ray casting
Po ustawieniu siatki wokseli system przeprowadza kontrolę kolizji promienia, aby sprawdzić powierzchnię czołową woksela lub bok woksela, w który uderza promień. Każdy woksel ma sześć powierzchni, podobnie do kości do gry. Podczas wykonywania rzucania promieni (raycastingu) musisz wiedzieć, w który woksel i w którą powierzchnię czołową trafisz, aby narysować podświetlenie i wygenerować nowy woksel przy tej powierzchni.
Kontrole kolizji promienia są zwykle obsługiwane w klasie ray_caster. Odbywa się to poprzez ustalenie najpierw woksela, w którym zaczyna się promień, poprzez przekształcenie lokalizacji kamery na lokalną przestrzeń siatki. Następnie jest to dzielone przez wymiary woksela, jak pokazano poniżej.
CurrentVoxel := vector3i:
X := Floor[InitialPosition.X / GridSize.X]
Y := Floor[InitialPosition.Y / GridSize.Y]
Z := Floor[InitialPosition.Z / GridSize.Z]
Funkcja [Next]() jest wywoływana wielokrotnie w celu ustalenia, przez który woksel będzie przechodził promień w następnej kolejności. Za każdym razem sprawdzane jest, czy dany woksel jest uznawany za lity.
Wejścia systemu budowania
Funkcja SelectModeTick_Trace w obiekcie player_data działa dla każdej klatki i obsługuje większość logiki edycji wokseli i aktualizacji kursora. Dwa urządzenia aktywatora sterowania [Input Trigger]() służą do wykrywania naciśnięcia przycisków strzału i celowania oraz do ustawiania zmiennych logicznych PlacePiece i DeletePiece.
Ta funkcja wymaga dodatkowej logiki, ponieważ woksele typu Park są traktowane wyłącznie jako powierzchnia, co oznacza, że nie blokują promieni i mogą istnieć tylko na wokselach litych (ziemi lub budowli). Funkcję CategoryIsSurfaceOnly można zaktualizować podczas dodawania nowej kategorii, która ma być dostępna wyłącznie jako powierzchnia.
Turbo-budowanie jest również obsługiwane po przytrzymaniu przycisku strzału, aby szybko umieścić wiele wokseli na płaszczyźnie. Funkcja ta sprawdza również, czy gracz znajduje się wewnątrz woksela, zanim zostanie dodany do funkcji CheckPlayerOverlapsVoxel.
Spawnowanie rekwizytów
Ten przykładowy projekt polega na spawnowaniu rekwizytów w czasie wykonywania [runtime](). Zasób creative_prop_asset nie jest obecnie automatycznie uwzględniany w plikach manifestu Verse. Dlatego musisz używać obiektu proxy (instancji piece_type w klasie piece_type_dir), aby odwoływać się do konkretnych rekwizytów w Verse.
Następnie urządzenie spawner_asset_references używa pola @editable dla każdego rekwizytu i tabeli mapowania z proxy na rzeczywisty zasób. Aby dodać nową siatkę, musisz najpierw utworzyć dla niej obiekt BuildingProp, dodać nowe proxy, dodać właściwość do urządzenia, a następnie zaktualizować tabelę mapowania (jak pokazano poniżej). Na koniec ponownie skompiluj i zaktualizuj nową właściwość na urządzeniu tak, aby wskazywała nowy rekwizyt.
Building1_corner:piece_type := piece_type{}
@editable
BP_Building1_corner : creative_prop_asset = DefaultCreativePropAsset
PT.Building1_corner => BP_Building1_corner
Generowanie proceduralne w Verse
Ten przykład implementuje w Verse dwa typy generowania proceduralnego: Shape Grammar oraz Wave Function Collapse. Do budowli 3D zastosowano Shape Grammar, a do obszarów 2D (płaskich) zastosowano Wave Function Collapse.
Powyżej znajduje się przykład wygenerowanych rekwizytów budowli.
Powyżej znajduje się przykład wygenerowanych rekwizytów parku.
W przypadku obu technik musisz utworzyć zestaw rekwizytów modułowych, typu budowla, które następnie Verse wygeneruje w czasie wykonywania programu. Ten kod jest deterministyczny. Usuwa i spawnuje rekwizyty w miarę potrzeby.
Shape Grammar
Wszystkie woksele należące do kategorii są przekształcane na większe sześciany wypukłe w celu zastosowania tak zwanego Shape Grammar.
Shape Grammar składa się z prostych reguł, w których każda reguła zajmuje pole i generuje co najmniej jedno pole podrzędne dla kolejnych reguł.
Na przykład jedna reguła może przeciąć wysoki sześcian na element podłogi o wysokości jednego woksela, podczas gdy rogi i ściany będą przypisane do różnych reguł. Specjalna reguła może wygenerować rekwizyt o tym samym wymiarze i lokalizacji, co sześcian.
Każda reguła jest zdefiniowana jako oddzielna klasa Verse, wywodząca się z klasy vo_base (operator wolumenu). Są one składane w drzewo zestawu reguł w klasie pochodnej rs_base (zestaw reguł).
Takie rozwiązanie upraszcza tworzenie nowych reguł, umożliwia eksperymentowanie z różnymi pomysłami, przypisuje różne style do każdego typu budowli i umożliwia przypisywanie różnych stylów do każdego typu budowli. Zastosowanie różnych reguł do tego samego zestawu wokseli przynosi różne rezultaty.
Poniżej przedstawiono prosty przykład operatora woluminu ` vo_sizecheck`.
# Check all dimensions of a box are >= a certain size
vo_sizecheck := class(vo_base):
Size:vector3i = vector3i{X:=0, Y:=0, Z:=0}
VO_Pass:vo_base = vo_base{}
VO_Fail:vo_base = vo_base{}
Operate<override>(Box:voxel_box, Rot:prop_yaw, BuildSystem:build_system):void=
if(Box.Size.X >= Size.X and Box.Size.Y >= Size.Y and Box.Size.Z >= Size.Z):
VO_Pass.Operate(Box, Rot, BuildSystem)
else:
VO_Fail.Operate(Box, Rot, BuildSystem)Zastępuje funkcję Operate zdefiniowaną w klasie bazowej, sprawdza rozmiar pola przychodzącego i decyduje, którą z poniższych reguł (vo_pass lub vo_fail) należy wywołać.
Zestawy reguł zawierające wiele operatorów woluminu można łatwo konfigurować w Verse. Możesz zastąpić funkcję setupRules i zadeklarować operatory [operators]() i ich parametry [parameters](). Punkt początkowy, czyli operator główny, jest przypisany do VO_root, jak pokazano poniżej.
# Rules for 'Building1' style
rs_building1<public> := class(rs_base):
RuleSetName<override>:string = "Building1"
SetupRules<override>(PT:piece_type_dir):void=
[...]
# Rules for one floor of the building
FloorRules := vo_cornerwallsplit:
VO_CornerLength1 := vo_corner:
VO_Corner := vo_prop:
Prop := PT.Building1_corner
Metoda ta ułatwia tworzenie nowych operatorów i zestawów reguł dla różnych kategorii budowli. Zestawy reguł są przydzielane w zasobie InitRuleSets i wybierane dla konkretnej kategorii w zasobie SelectRuleSet – oba te zasoby znajdują się w obiekcie build_system.
Wave Function Collapse
Wave Function Collapse (WFC) to technika losowego generowania obszaru w oparciu o reguły określające, w jaki sposób elementy pasują do siebie.
W tej implementacji można użyć zestawu kafelków, a następnie określić, które z nich mogą ze sobą sąsiadować. Do każdej krawędzi są stosowane etykiety. Można umieszczać tylko kafelki z pasującymi etykietami.
Poniżej przedstawiono przykład etykiety krawędzi z klasy wfc_mode_factory.
WaterEL:wfc_edge_label := wfc_edge_label:
Name:="Water"
Symmetric := true
Poniżej znajduje się przykładowa definicja siatki dla powyższego przykładu.
Park_Grass_Water_InCorn:wfc_mesh := wfc_mesh:
Props := array:
PT.Park_Grass_Water_Incorn
Name := "Park_Grass_Water_Incorn"
Edges := array:
WaterEL
WaterEL
WaterToGrassEL
GrassToWaterEL
Etykiety można określać w kierunku zgodnym z ruchem wskazówek zegara, zaczynając od kierunku +Y.
Dolna krawędź w tym przykładzie to "woda do trawy", ponieważ każda krawędź jest prawoskrętna. System ten ułatwia dodawanie nowych etykiet i siatek, a także nowych modeli WFC do rozgrywki.
Algorytm WFC wybiera lokalizację na siatce, losowo wybiera lub zwija opcje spośród możliwych, a następnie propaguje konsekwencje tego wyboru do możliwych opcji w innych lokalizacjach. Proces ten trwa, dopóki nie zostanie wygenerowany cały region.
Klasa wfc_system zawiera bieżący stan wszystkich kafelków. Specjalny operator wolumenu vo_wfc odczytuje stan i spawnuje odpowiednie siatki.
Potencjalne rozszerzenia
Poniżej podajemy kilka sposobów na zmodyfikowanie tego przykładowego projektu w celu uzyskania nowych doświadczeń.
Dodaj nową kategorię wokseli / technikę Shape Grammar
Rozszerz typ wyliczeniowy (enum)
build_categoryZaktualizuj wszystkie instrukcje
case, które muszą obsługiwać nową kategorięUtwórz nowy zestaw reguł pochodzący z
rs_baseZaktualizuj
SelectRuleSet, aby używać nowego zestawu reguł dla nowej kategorii
Dodaj nowe siatki do modelu WFC parku
Utwórz rekwizyt budowli dla każdej nowej siatki (jak opisano powyżej w sekcji Spawnowanie rekwizytów).
Dodaj nową etykietę
wfc_edge_label(w razie potrzeby) doGetParkModelwwfc_model_factory.Dodaj nową instancję
wfc_meshdla każdej nowej siatki/rekwizytu, definiując etykietę dla każdej krawędzi.Wywołaj
Model.AddMeshdla każdej nowej siatki.
Dodaj nowy model WFC
Dodaj nową kategorię wokseli do nowego modelu.
Dodaj nową funkcję do
wfc_model_factory, aby utworzyć nowy model.Dodaj nową składową
wfc_modeldobuild_system(np.Park_WFCModel).Dodaj nową funkcję, taką jak
AddParkWFCTile, która dodaje kafelki wykorzystujące nowy model.Zmodyfikuj
SelectModeTick_Trace, aby wywoływać nową funkcję dla nowej kategorii.
Woksele mogą być również generowane proceduralnie. W przykładowym projekcie istnieją przyciski, które dodają losowe regiony budowli lub parku do strefy budowania. Wykorzystuje to funkcje ClearRegion i AddRegion z build_system i może być używane jako punkt wyjścia do systemu losowego generowania poziomów.
Wydajność Verse
Kod Verse w tym przykładowym projekcie zapewnia, że kod jest wystarczająco szybki, aby przetwarzać aktualizacje w czasie rzeczywistym. Przetwarzanie dużych tablic może prowadzić do problemów z wydajnością, dlatego ważne jest, aby pamiętać o następujących informacjach:
Użycie pętli
fordo zwrócenia tablicy jest szybsze niż budowanie jej element po elemencie, ponieważ każde dodanie kopiuje tablicę, więc może ona być O(N2). Na przykład:Verse* set OptionalArray := for(I := 0 .. ArraySize-1): falseNie przekazuj dużych tablic według wartości. Zamiast tego umieść je w obiekcie i wywołaj metody lub przekaż obiekt.
Tablice wielowymiarowe działają wolno, ponieważ pierwszy operator
[]przekazuje kopię do kolejnego operatora[].Wywołanie
.Lengthdla tablicy w rzeczywistości powoduje utworzenie kopii tablicy, więc samodzielne śledzenie rozmiaru dużych tablic może być szybsze.
Bardzo pomocne jest również użycie makra profile. Dzięki niemu można lepiej zrozumieć, które części kodu zajmują najwięcej czasu.