Le modèle Verse - Construction procédurale est un exemple de projet issu de Re: Imaginez Londres (code d'île : 1442-4257-4418)** qui présente le système de construction procédurale de Verse.
Le système de construction procédurale est entièrement implémenté et créé dans Verse au lieu de Fortnite. Ce système vous donne plus de contrôle sur le mode d'assemblage des maillages dans vos projets.
Dans Fortnite, vous pouvez concevoir votre gameplay avec des pièces de construction prédéfinies (sol, plafond et mur). Le système de construction procédurale développe vos possibilités afin que vous puissiez créer des voxels avec des catégories qui définissent des systèmes sous-jacents dans lesquels vous pouvez rapidement placer d'autres voxels lorsque le système place son maillage.
Pour accéder à ce modèle dans le navigateur de projets de l'UEFN, accédez à Exemples de fonctionnalités > Exemples de jeux > Verse - Construction procédurale.

Le système de construction procédurale fonctionne en deux phases.
-
Pour la première phase, vous modifiez une grille de voxels, une grille 3D dont chaque cellule est une case, en ajoutant ou en supprimant des voxels.
-
La deuxième phase s'exécute chaque fois que la grille de voxels est modifiée. La modification de la grille de voxels prend les informations des voxels et exécute un processus qui génère les bons maillages aux bons endroits.
Vous pouvez ajouter et supprimer les catégories de voxels Bâtiment et Parc. Chaque catégorie possède des règles différentes concernant le mode de génération des maillages. Ces règles font référence au graphique ou à l'arbre des opérations qui seront effectuées pour passer des voxels placés aux maillages réels
Utiliser le modèle
Chaque niveau de votre projet aura une classe root_device class
de niveau supérieur. Lorsqu'un joueur rejoint le jeu, la classe root_device
crée pour lui des global_player_data
qui définissent les informations de son IU.
Chaque zone de construction dispose d'un appareil build_zone
qui définit les dimensions du site en unités de voxels. La position de cet appareil définit l'origine du site. La zone de construction utilise un objet build_system
pour gérer les mécanismes de construction et un spawner_device
pour générer des maillages d'accessoires de bâtiment. La zone de construction contient également une voxel_grid
, une mesh_grid
et un wfc_system
.
Chaque zone de bâtiment abritera les éléments suivants :
-
build_zone
(appareil) - définit les dimensions du site en unités de voxels. -
build_system
- gère les mécanismes de construction. -
spawner_device
(appareil) - génère des maillages d'accessoires de bâtiment. -
spawner_asset_references
(appareil) - référence tous les accessoires générés.
Lorsqu'un joueur entre dans une build_zone
déclenchée par un volume, player_data
est créé pour gérer l'entrée de ce joueur et la modification de voxels.
Construire avec des voxels
Ce modèle introduit un vector3i [type]()
pour représenter les voxels selon leurs coordonnées [integer]()
X
, Y
et Z
.
Vous trouverez ci-dessous un exemple de script utilisant vector3i
.
# vecteur tridimensionnel avec composants `int`
vector3i<public> := struct<computes><concrete>:
@editable
X<public>:int = 0
@editable
Y<public>:int = 0
@editable
Z<public>:int = 0
Grilles de voxels
Une grille de voxels est une grille 3D de cellules pour chaque zone de bâtiment qui stocke des informations sur le type de voxel de bâtiment présent. L'implémentation a lieu dans la classe voxel_grid
sous forme de matrice 1D de références facultatives à voxel_cell [objects]()
, comme indiqué ci-dessous.
# Matrice principale représentant la grille 3D de voxels
var Cells<public> : []?voxel_cell = array{}
Par défaut, un objet de cellule de voxel contient uniquement une [enum]()
pour le type de voxel, comme indiqué ci-dessous.
# Catégorie de voxel dans notre grille de construction
build_category<public> := enum:
Building
Park
NewBuildingType
# Structure stockée pour chaque voxel occupé
voxel_cell<public> := struct<computes>:
Category<public>:build_category
Vous pouvez ajouter des catégories de voxels de bâtiment en étendant l'énumération, comme indiqué ci-dessus.
Le code ci-dessous convertit une coordonnée de voxel 3D en [index]()
1D.
# Obtient l'index de matrice 1D à partir d'un emplacement 3D dans la grille
GetVoxelIndex<public>(X:int, Y:int, Z:int)<transacts>:int=
return (X * (Size.Y*Size.Z)) + (Y * Size.Z) + Z
SetVoxel
et ClearVoxel
sont des fonctions clés qui modifient la grille de voxels.
Lancer de rayons
Une fois la grille de voxels définie, le système effectue une vérification de collision de rayons pour s'assurer de la face ou du côté du voxel touché par le rayon. Chaque voxel possède six faces, comme un dé. Lors du lancer de rayons, vous devrez savoir à la fois quel voxel et quelle face sont touchés pour tracer la surbrillance et générer un nouveau voxel contre cette face.

Les vérifications de collision de rayons sont principalement gérées dans la classe ray_caster
. Elles s'effectuent en déterminant d'abord dans quel voxel le rayon commence, en transformant l'emplacement de la caméra dans l'espace local de la grille. Une division par dimensions de voxel, comme indiqué ci-dessous, a ensuite lieu.
CurrentVoxel := vector3i:
X := Floor[InitialPosition.X / GridSize.X]
Y := Floor[InitialPosition.Y / GridSize.Y]
Z := Floor[InitialPosition.Z / GridSize.Z]
La fonction [Next]()
est appelée à répétition pour déterminer quel voxel le rayon traversera ensuite, en vérifiant à chaque fois si le voxel est considéré comme plein.
Entrées du système de bâtiment
La fonction SelectModeTick_Trace
dans player_data
s'exécute pour chaque image et gère la majorité de la logique de modification des voxels et de mise à jour du curseur. Deux appareils [Input Trigger]()
sont utilisés pour savoir quand les boutons Tir et Visée sont enfoncés, et pour définir les variables de logique PlacePiece
et DeletePiece
.
Cette fonction nécessite une logique supplémentaire puisque les voxels Parc sont considérés comme des voxels de surface uniquement, ce qui signifie qu'ils ne bloquent pas les rayons et ne peuvent exister qu'en plus de voxels pleins (le sol ou les bâtiments). Vous pouvez mettre à jour la fonction CategoryIsSurfaceOnly
lors de l'ajout d'une nouvelle catégorie que vous souhaitez affecter uniquement à la surface.
La construction turbo est également prise en charge en maintenant enfoncé le bouton Tir pour placer rapidement plusieurs voxels dans un plan. Cette fonction vérifie également si un joueur se trouve à l'intérieur d'un voxel avant son ajout à la fonction CheckPlayerOverlapsVoxel
.
Générer des accessoires
Cet exemple de projet repose sur la génération d'accessoires à [runtime]()
. Le creative_prop_asset
n'est actuellement pas automatiquement reflété dans les fichiers de manifeste Verse. Vous devez donc utiliser un objet proxy (une instance de piece_type
dans la classe piece_type_dir
) pour référencer des accessoires particuliers dans Verse.
L'appareil spawner_asset_references
utilise ensuite un champ @editable
pour chaque accessoire et une table de mappage du proxy à la ressource réelle. Pour ajouter un nouveau maillage, vous devez d'abord lui créer un BuildingProp
, ajouter un nouveau proxy, ajouter une propriété à l'appareil, puis mettre à jour la table de mappage (comme indiqué ci-dessous). Enfin, recompilez et actualisez la nouvelle propriété sur l'appareil pour pointer vers le nouvel accessoire.
Building1_corner:piece_type := piece_type{}
@editable
BP_Building1_corner : creative_prop_asset = DefaultCreativePropAsset
PT.Building1_corner => BP_Building1_corner
Génération procédurale dans Verse
Cet exemple implémente deux types de génération procédurale dans Verse : la grammaire des formes et la réduction de fonction d'onde. La grammaire des formes s'applique aux bâtiments 3D, tandis que la réduction de fonction d'onde est utilisé pour les zones 2D (plates).

Un exemple de génération d'accessoires de bâtiment est donné ci-dessus.

Un exemple de génération d'accessoires de parc est donné ci-dessus.
Pour les deux techniques, vous devez créer un ensemble d'accessoires de type bâtiment modulaire que Verse génère ensuite à l'exécution. Ce code est déterministe, puisqu'il ne supprime et ne génère des accessoires que selon les besoins.
Grammaire des formes
Tous les voxels d'une catégorie sont transformés en boîtes convexes plus grandes afin d'appliquer ce qu'on appelle la grammaire des formes.

La grammaire des formes se compose de règles simples, chacune acceptant une boîte et générant une ou plusieurs sous-boîtes pour les règles ultérieures.

Par exemple, une règle peut découper une grande boîte en un morceau de sol d'un voxel de hauteur, tandis que les coins et les murs sont attribués à des règles différentes. Une règle spéciale peut faire apparaître un accessoire de la même taille et au même emplacement que la boîte.
Chaque règle est définie comme une classe Verse distincte, dérivée de la classe vo_base
(opérateur de volume). Ces règles sont assemblées dans un arbre d'ensemble de règles à l'intérieur d'une classe dérivée de rs_base
(ensemble de règles).
Cette approche simplifie la création de nouvelles règles, permet d'expérimenter différentes idées et attribue des styles distincts à chaque type de bâtiment. L'application de règles différentes au même ensemble de voxels donne des résultats variés.
Vous trouverez ci-dessous un exemple simple d'opérateur de volume ` vo_sizecheck `.
# Vérifiez que toutes les dimensions d'une boîte sont >= à une certaine taille
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)
Il remplace la fonction Operate
définie dans la classe de basse, vérifie la taille de la boîte entrante et décide laquelle des règles suivantes (vo_pass
ou vo_fail
) appeler.
Les ensembles de règles contenant de nombreux opérateurs de volume sont faciles à configurer dans Verse. Vous pouvez remplacer la fonction setupRules
et déclarer vos [operators]()
et leurs [parameters]()
. Le point de départ, ou opérateur racine, est attribué à VO_root
, comme indiqué ci-dessous.
# Règles pour le style "Building1"
rs_building1<public> := class(rs_base):
RuleSetName<override>:string = "Building1"
SetupRules<override>(PT:piece_type_dir):void=
[...]
# Règles pour un étage du bâtiment
FloorRules := vo_cornerwallsplit:
VO_CornerLength1 := vo_corner:
VO_Corner := vo_prop:
Prop := PT.Building1_corner
VO_Face := vo_prop:
Prop := PT.Building1_face
VO_Covered := vo_prop:
Prop := PT.Building1_box
VO_Wall := vo_wall:
VO_Width1 := vo_facecoveragecheck_1voxel:
VO_Clear := vo_prop:
Prop := PT.Building1_face
VO_Covered := vo_prop:
Prop := PT.Building1_box
VO_Centre := RoofTileCheck
VO_TooSmall := vo_tilefill:
VO_Tile := FourWay
set VO_Root = vo_floorsplit:
VO_FloorHeight1 := FloorRules
Cette méthode permet de créer facilement de nouveaux opérateurs et ensembles de règles pour différentes catégories de bâtiments. Les ensembles de règles sont alloués dans InitRuleSets
et sélectionnés pour une catégorie particulière dans SelectRuleSet
, qui se trouvent tous deux dans build_system
.
Réduction de fonction d'onde
La réduction de fonction d'onde (WFC) est une technique permettant de générer aléatoirement une zone en fonction de règles qui déterminent le mode d'association de pièces.
Dans cette implémentation, vous pouvez utiliser un ensemble de tuiles, puis spécifier lesquelles d'entre elles peuvent être adjacentes. Les étiquettes s'appliquent à chaque arête, et seules les tuiles dont les étiquettes correspondent peuvent être placées.
Vous trouverez ci-dessous un exemple d'étiquette d'arête issu de la classe wfc_mode_factory
.
WaterEL:wfc_edge_label := wfc_edge_label:
Name:="Water"
Symmetric := true
Vous trouverez ci-dessous un exemple de définition de maillage pour l'exemple ci-dessus.
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

Vous pouvez spécifier des étiquettes dans le sens des aiguilles d'une montre, en commençant par la direction +Y.

L'arête inférieure de cet exemple est "eau vers herbe", car chaque arête est considérée dans le sens des aiguilles d'une montre. Avec ce système, il est facile d'ajouter de nouvelles étiquettes et de nouveaux maillages, ou de nouveaux modèles WFC à votre gameplay.
L'algorithme WFC sélectionne un emplacement sur la grille, choisit ou réduit au hasard les options possibles, puis propage les conséquences de ce choix aux options possibles à d'autres emplacements. Ce processus se poursuit jusqu'à ce que toute la région soit générée.
La classe wfc_system
contient l'état actuel de toutes les tuiles. L'opérateur de volume spécial vo_wfc
lit l'état et génère les bons maillages.
Extensions potentielles
Vous trouverez ci-dessous quelques façons de transformer cet exemple en de nouvelles expériences.
Ajouter une nouvelle catégorie de voxels/grammaire de formes
-
Étendre l'enum
build_category
-
Actualiser toutes les instructions
case
qui doivent gérer la nouvelle catégorie -
Créer un nouvel ensemble de règles dérivant de
rs_base
-
Actualiser
SelectRuleSet
pour utiliser le nouvel ensemble de règles dans la nouvelle catégorie
Ajouter de nouveaux maillages au modèle WFC Parc
-
Créer un accessoire de bâtiment pour chaque nouveau maillage (comme décrit dans Générer des accessoires, ci-dessus).
-
Ajouter un nouveau
wfc_edge_label
(si vous le souhaitez) auGetParkModel
danswfc_model_factory
. -
Ajouter une nouvelle instance
wfc_mesh
pour chaque nouveau maillage/accessoire, définissant l'étiquette de chaque arête. -
Appeler
Model.AddMesh
pour chaque nouveau maillage.
Ajouter un nouveau modèle WFC
-
Ajouter une nouvelle catégorie de voxels pour le nouveau modèle.
-
Ajouter une nouvelle fonction à
wfc_model_factory
pour créer le nouveau modèle. -
Ajouter un nouveau membre
wfc_model
àbuild_system
(commePark_WFCModel
). -
Ajouter une nouvelle fonction comme
AddParkWFCTile
qui ajoute des tuiles en utilisant le nouveau modèle. -
Modifier
SelectModeTick_Trace
de manière à appeler la nouvelle fonction pour la nouvelle catégorie.

Vous pouvez également générer des voxels de manière procédurale. Dans l'exemple de projet, il existe des boutons qui ajoutent des régions de construction ou de parc aléatoires à la zone de construction. Les fonctions ClearRegion
et AddRegion
de build_system
sont utilisées et pourraient servir de point de départ pour un système de génération de niveaux aléatoires.
Performances Verse
Le code Verse de cet exemple de projet garantit que le code est suffisamment rapide pour traiter les modifications en temps réel. Les grandes matrices étant susceptibles d'entraîner des problèmes de performances, il est important de garder à l'esprit les informations suivantes :
- Utiliser une boucle
for
pour renvoyer une matrice est plus rapide que de la créer élément par élément, car chaque ajout copie la matrice afin qu'elle puisse être O(N2). Par exemple :
* set OptionalArray := for(I := 0 .. ArraySize-1):
false
-
Ne transmettez pas de grandes matrices par valeur, placez-les plutôt dans un objet et appelez des méthodes ou transmettez l'objet.
-
Les matrices multidimensionnelles sont lentes, car le premier opérateur
[]
transmet une copie à l'opérateur[]
suivant. -
L'appel de
.Length
sur une matrice crée en fait une copie de la matrice sur le moment, il peut donc être plus rapide de suivre vous-même la taille des grandes matrices.
Il est également très utile d'utiliser la macro profile
pour mieux comprendre exactement quelles parties du code prennent le plus de temps.