Il modello Verse - Costruzione procedurale è un progetto di esempio di Unreal Editor per Fortnite (UEFN) Re: Imagine London (Codice isola: 1442-4257-4418) che illustra il sistema di costruzione procedurale scritto in Verse.
Il sistema di edificio procedurale è interamente implementato e creato in Verse anziché in Fortnite. Con questo sistema, puoi avere un maggiore controllo su come le mesh sono assemblate nei tuoi progetti.
Attraverso Fortnite, puoi progettare il tuo gameplay con elementi di costruzione preimpostati (pavimento, soffitto e muro). Il sistema di costruzione procedurale amplia le possibilità, consentendo di creare voxel con categorie che impostano sistemi sottostanti, in cui puoi posizionare rapidamente voxel aggiuntivi mentre il sistema posiziona la mesh.
Per accedere a questo modello all'interno del Navigatore progetti di UEFN, vai su Esempi di funzionalità > Esempi di giochi > Verse - Costruzione procedurale.
Il sistema di Edificio procedurale si articola in due fasi.
Nella prima fase, puoi modificare una griglia di voxel, una griglia 3D in cui ogni cella è una casella, aggiungendo o rimuovendo voxel.
La seconda fase è eseguita ogni volta che viene modificata la griglia di voxel. La modifica della griglia di voxel prende le informazioni sui voxel ed esegue un processo che genera le mesh adatte nei punti giusti.
Puoi aggiungere e rimuovere entrambe le categorie di voxel Edificio e Parco. Ogni categoria ha regole diverse per la generazione delle mesh. Esse si riferiscono al grafico o all'albero delle operazioni che verranno eseguite per passare dai voxel posizionati alle mesh effettive
Usare il modello
Ogni livello del progetto avrà una classe di primo livello, corrispondente a root_device class. Quando un giocatore si unisce al gioco, la classe root_device crea global_player_data che imposta le informazioni sulla UI.
Ogni zona di costruzione ha un dispositivo build_zone che definisce le dimensioni del sito in unità voxel. La posizione di questo dispositivo definisce l'origine del sito. L'area di costruzione utilizza un oggetto build_system per gestire le meccaniche di costruzione e uno spawner_device per generare le mesh degli oggetti scenografici della costruzione. L'area di costruzione contiene anche un voxel_grid, un mesh_grid e un wfc_system.
Ogni area dii costruzione conterrà quanto segue:
build_zone(dispositivo) - definisce le dimensioni del sito in unità voxel.build_system- gestisce le meccaniche di costruzione.spawner_device(dispositivo) - genera le mesh degli oggetti scenografici di costruzione.spawner_asset_references(dispositivo) - fa riferimento a tutti gli oggetti scenografici generati.
Quando un giocatore entra in una build_zone, che viene attivata da un dispositivo Volume, viene creato player_data per gestire l'input del giocatore e la modifica dei voxel.
Costruzione con i voxel
Questo modello introduce un vector3i [type]() per rappresentare i voxel con le loro coordinate X, Y e Z [integer]().
Di seguito è riportato un esempio di script che utilizza vector3i.
vector3i<public> := struct<computes><concrete>:
@editable
X<public>:int = 0
@editable
Y<public>:int = 0
@editable
Z<public>:int = 0Griglie Voxel
Una griglia voxel è una griglia 3D di celle per ciascuna zona di un edificio che memorizza informazioni sul tipo di voxel dell'edificio presente. Implementato nella classe voxel_grid corrispondente a un array 1D di riferimenti opzionali a voxel_cell [objects](), come viene mostrato di seguito.
# Main array representing the 3D grid of voxels
var Cells<public> : []?voxel_cell = array{}Per impostazione predefinita, un oggetto cella voxel contiene solo un [enum]() per il tipo di voxel, come mostrato di seguito.
# 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_categoryPuoi aggiungere altre categorie di voxel di edificio estendendo l'enum come mostrato sopra.
Il codice seguente converte una coordinata Voxel 3D in un [index]() 1D.
# 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 e ClearVoxel sono funzioni chiave che modificano la griglia di voxel.
Raycasting
Dopo aver impostato la griglia dei voxel, il sistema esegue un controllo di collisione dei raggi per controllare la faccia o il lato del voxel colpito dal raggio. Ogni voxel ha sei facce, in modo simile a un dado. Durante il raycasting, è necessario identificare il voxel e la faccia colpiti per poter disegnare l'evidenziazione e posizionare un nuovo voxel rispetto quella faccia.
I controlli di raycasting sono gestiti principalmente nella classe ray_caster. Questo processo inizia determinando in quale voxel si trova il punto di partenza del raggio, trasformando la posizione della telecamera nello spazio locale della griglia. Questo viene poi diviso per le dimensioni del voxel, come mostrato di seguito.
CurrentVoxel := vector3i:
X := Floor[InitialPosition.X / GridSize.X]
Y := Floor[InitialPosition.Y / GridSize.Y]
Z := Floor[InitialPosition.Z / GridSize.Z]
La funzione [Next]() viene chiamata ripetutamente per determinare il voxel attraverso il quale passerà il raggio, controllando ogni volta, se il voxel è considerato solido.
Input del sistema di costruzione
La funzione SelectModeTick_Trace in player_data viene eseguita per ogni frame e gestisce la maggior parte della logica di modifica dei voxel e dell'aggiornamento del cursore. Due dispositivi [Input Trigger]() vengono utilizzati per sapere quando sono premuti i pulsanti Fuoco e Mira e per impostare le variabili di logica PlacePiece e DeletePiece.
Questa funzione richiede una logica aggiuntiva poiché i voxel di Parco sono considerati solo di superficie; ciò significa che non bloccano i raggi e possono esistere solo al di sopra di voxel solidi (di terreno o edifici). Puoi aggiornare la funzione CategoryIsSurfaceOnly quando aggiungi una nuova categoria che desideri sia solo di superficie.
La Costruzione turbo è supportata anche tenendo premuto il pulsante Fuoco per posizionare rapidamente più voxel su un piano. Questa funzione controlla anche se un giocatore si trova all'interno di un voxel prima di aggiungerlo alla funzione CheckPlayerOverlapsVoxel.
Generazione di oggetti scenografici
Questo progetto di esempio si basa sulla generazione degli oggetti scenografici in [runtime](). Il creative_prop_asset al momento non si riflette automaticamente nei file manifest di Verse. Pertanto, è necessario utilizzare un oggetto proxy (un'istanza di piece_type nella classe piece_type_dir) per fare riferimento a particolari oggetti scenografici in Verse.
Il dispositivo spawner_asset_references utilizza quindi un campo @editable per ogni oggetto scenografico e una tabella di mappatura dal proxy all'asset effettivo. Per aggiungere una nuova mesh, devi prima creare un BuildingProp abbinato, aggiungere un nuovo proxy, aggiungere una proprietà al dispositivo, quindi aggiornare la tabella di mappatura (come mostrato di seguito). Infine, dovrai ricompilare e aggiornare la nuova proprietà sul dispositivo in modo che si riferisca al nuovo oggetto scenografico.
Building1_corner:piece_type := piece_type{}
@editable
BP_Building1_corner : creative_prop_asset = DefaultCreativePropAsset
PT.Building1_corner => BP_Building1_corner
Generazione procedurale in Verse
Questo esempio implementa due tipi di generazione procedurale in Verse: Shape Grammar e Wave Function Collapse. Shape Grammar viene applicata agli edifici 3D, mentre il collasso della funzione d'onda viene utilizzato per le aree 2D (piatte).
In alto è riportato un esempio di oggetti scenografici di edifici generati.
In alto è riportato un esempio di oggetti scenografici di parchi generati.
Per entrambe le tecniche, devi creare una serie di oggetti scenografici modulari di tipo edificio, che Verse genera in fase di esecuzione. Questo codice è deterministico, quindi elimina e genera gli oggetti scenografici solo se necessario.
Shape Grammar
Tutti i voxel di una categoria vengono trasformati in riquadri convessi più grandi per applicare quella che viene chiamata Shape Grammar.
Shape Grammar consiste in semplici regole secondo le quali ciascuna regola prende una casella e genera una o più sottocaselle per le regole successive.
Ad esempio, una regola potrebbe ritagliare una casella elevata in un "pavimento" alto un voxel, mentre un'altra regola assegna gli angoli a una regola e i muri a un'altra. Una regola speciale può generare un oggetto scenografico nelle dimensioni e nella posizione della casella.
Ogni regola è definita come una classe Verse separata, derivata dalla classe vo_base (operatore di volume). Queste regole sono organizzate in una struttura ad albero di set di regole all'interno di una classe derivata da rs_base (set di regole).
Questo approccio semplifica la creazione di nuove regole, consentendo la sperimentazione di idee diverse e l'assegnazione di stili distinti a ogni tipo di edificio. L'applicazione di regole diverse allo stesso set di voxel produce risultati diversi.
Di seguito è riportato un semplice esempio di operatore di volume `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)Esegue l'override della funzione Operate definita nella classe di base, controlla le dimensioni della casella in entrata e decide quale delle seguenti regole (vo_pass o vo_fail) chiamare.
I set di regole contenenti molti operatori di volume sono facili da configurare in Verse. Puoi eseguire l'override della funzione setupRules e dichiarare i tuoi [operators]() e [parameters]() corrispondenti. Il punto di partenza, oppure operatore radice, è assegnato a VO_root come mostrato di seguito.
# 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
Questo metodo semplifica la creazione di nuovi operatori e impostazioni di regole per diverse categorie di edifici. I set di regole sono allocati in InitRuleSets e selezionati per una particolare categoria in SelectRuleSet, entrambi contenuti in build_system.
Wave Function Collapse
Il collasso della funzione onda (WFC) rappresenta una tecnica per generare casualmente un'area in base a regole che stabiliscono come possono combinarsi i pezzi.
In questa implementazione, puoi utilizzare un set di caselle e specificare quali di esse possono essere adiacenti. Le etichette vengono assegnate a ciascun spigolo e consentono il posizionamento solo delle caselle con etichette corrispondenti.
Di seguito è riportato un esempio di etichetta di spigolo della classe wfc_mode_factory.
WaterEL:wfc_edge_label := wfc_edge_label:
Name:="Water"
Symmetric := true
Di seguito è riportato un esempio della definizione di mesh per l'esempio precedente.
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
Puoi specificare le etichette in senso orario, partendo dalla direzione +Y.
Lo spigolo inferiore in questo esempio è "water-to-grass" dal momento che ogni spigolo è considerato in senso orario. Con questo sistema, potrai facilmente aggiungere nuove etichette e mesh o nuovi modelli WFC al tuo gameplay.
L'algoritmo WFC seleziona una posizione sulla griglia, sceglie casualmente o comprime le opzioni disponibili e poi propaga le conseguenze di quella scelta, influenzando le opzioni possibili nelle altre posizioni. Questo processo continua fino a quando non viene generata l'intera regione.
La classe wfc_system contiene lo stato corrente di tutti i tile. L'operatore speciale del volume vo_wfc legge lo stato e genera le mesh corrette.
Estensioni potenziali
Ecco alcuni modi in cui questo progetto di esempio può essere trasformato in nuove esperienze:
Aggiungi una nuova categoria di voxel/Shape Grammar
Estendi l'enum
build_categoryAggiorna tutte le istruzioni
caseche devono gestire la nuova categoriaCrea un nuovo set di regole derivante da
rs_baseAggiorna
SelectRuleSetper utilizzare il nuovo set di regole per la nuova categoria
Aggiungi nuove mesh al modello Parco WFC
Crea un oggetto scenografico di edificio per ogni nuova mesh (come descritto in Generazione di oggetti scenografici nella sezione precedente).
Aggiungi una nuova
wfc_edge_label(se lo desideri) aGetParkModelinwfc_model_factory.Aggiungi una nuova istanza
wfc_meshper ogni mesh/oggetto scenografico nuovo, definendo l'etichetta per ciascuno spigolo.Chiama
Model.AddMeshper ogni nuova mesh.
Aggiungi un nuovo modello WFC
Aggiungi di una nuova categoria di voxel per il nuovo modello.
Aggiunta di una nuova funzione a
wfc_model_factoryper creare il nuovo modello.Aggiungi un nuovo membro
wfc_modelabuild_system(comePark_WFCModel).Aggiungi una nuova funzione come
AddParkWFCTileche a sua volta aggiunge tile utilizzando il nuovo modello.Modifica
SelectModeTick_Traceper chiamare la nuova funzione per la nuova categoria.
Puoi anche generare voxel in modo procedurale. Nel progetto di esempio, sono presenti pulsanti che aggiungono edifici o regioni di parco casuali alla zona di costruzione. Utilizza le funzioni ClearRegion e AddRegion di build_system, inoltre potrebbe essere impiegata come punto di partenza per un sistema di generazione di livelli casuali.
Prestazioni in Verse
Il codice Verse in questo progetto di esempio è ottimizzato per garantire prestazioni sufficientemente rapide da elaborare gli aggiornamenti in tempo reale. Poiché la gestione di array di grandi dimensioni può causare problemi di prestazioni, tieni a mente le seguenti informazioni:
L'utilizzo di un loop
forper restituire un array è più veloce della costruzione di ciascun elemento, poiché ogni aggiunta copia l'array in modo che possa essere O(N2). Ad esempio:Verse* set OptionalArray := for(I := 0 .. ArraySize-1): falseNon passare array di grandi dimensioni per valore: inseriscili in un oggetto e richiama i metodi oppure passa direttamente l'oggetto.
Gli array multidimensionali sono lenti, perché il primo operatore
[]passa una copia all'operatore[]successivo.Chiamare
.Lengthsu un array in realtà ne crea una copia al momento, quindi conviene tenere traccia manualmente della dimensione se l'array è molto grande.
Inoltre, è molto utile utilizzare la macro profile per comprendere meglio quali parti del codice richiedono più tempo.