La plantilla de Verse: construcción procedimental es un proyecto de ejemplo de Unreal Editor para Fortnite (UEFN) Re: Imagine London (Código de isla: 1442-4257-4418) que muestra el sistema de construcción procedimental escrito en Verse.
El sistema de construcción procedimental está completamente implementado y creado en Verse en lugar de en Fortnite. Con este sistema, puedes tener más control sobre cómo se ensamblan las mallas en tus proyectos.
A través de Fortnite, puedes diseñar tu juego con piezas de construcción preestablecidas (suelo, techo y pared). El sistema de construcción procedimental amplía tus posibilidades para que puedas crear vóxeles con categorías que establecen sistemas subyacentes en los que puedes colocar rápidamente vóxeles adicionales a medida que el sistema coloca su malla.
Para acceder a esta plantilla en el explorador de proyectos de UEFN, ve a Ejemplos destacados > Ejemplos de juego > Verse: construcción procedimental.

El sistema de Construcción procedimental funciona en dos fases.
-
En la primera fase, se edita una cuadrícula vóxeles, una cuadrícula 3D en la que cada celda es una caja, mediante la adición o eliminación de vóxeles.
-
La segunda fase se ejecuta cada vez que se modifica la cuadrícula de vóxeles. La modificación de la cuadrícula de vóxeles toma la información de vóxeles y ejecuta un proceso que genera las mallas correctas en los lugares correctos.
Puedes añadir y eliminar las categorías de vóxeles Construcción y Parque. Cada categoría tiene diferentes reglas sobre cómo deben generarse las mallas. Estas reglas se refieren al gráfico o árbol de operaciones que se realizarán para pasar de los vóxeles colocados a las mallas reales.
Cómo usar la plantilla
Cada nivel dentro de tu proyecto tendrá una clase de nivel superior, que es la root_device class
. Cuando un jugador se une al juego, la clase root_device
crea un global_player_data
para él que establece su información de IU.
Cada zona de construcción tiene un dispositivo build_zone
que define las dimensiones del sitio en unidades vóxel. La posición de este dispositivo define el origen del sitio. El área de construcción utiliza un objeto build_system
para manejar las mecánicas de construcción y un spawner_device
para generar mallas de utilería de construcción. El área de construcción también contiene un voxel_grid
, un mesh_grid
y un wfc_system
.
Cada área de construcción contendrá lo siguiente:
-
build_zone
(dispositivo): define las dimensiones del sitio en unidades de vóxeles. -
build_system
: controla las mecánicas de construcción. -
spawner_device
(dispositivo): genera mallas de utilería de construcción. -
spawner_asset_references
(dispositivo): hace referencia a todas las utilerías generadas.
Cuando un jugador entra en una build_zone
, que se activa con un dispositivo de volumen, se crea player_data
para controlar la entrada y la edición de vóxeles de ese jugador.
Construye con vóxeles
Esta plantilla introduce un vector3i [type]()
para representar vóxeles por sus coordenadas X
, Y
y Z
[integer]()
.
A continuación se muestra una secuencia de comandos de ejemplo que utiliza vector3i
.
# Vector tridimensional con componentes `int`
vector3i<public> := struct<computes><concrete>:
@editable
X<public>:int = 0
@editable
Y<public>:int = 0
@editable
Z<public>:int = 0
Cuadrículas de vóxeles
Una cuadrícula de vóxeles es una cuadrícula 3D de celdas para cada zona de construcción que almacena información sobre el tipo de vóxel de construcción presente. Esto se implementa en la clase voxel_grid
como una matriz unidimensional de referencias opcionales a voxel_cell [objects]()
, como se muestra a continuación.
# Matriz principal que representa la cuadrícula 3D de vóxeles
var Cells<public> : []?voxel_cell = array{}
De forma predeterminada, un objeto de celda de vóxel contiene solo un [enum]()
para el tipo de vóxel, como se muestra a continuación.
# Categoría de vóxel en nuestra cuadrícula de construcción
build_category<public> := enum:
Building
Park
NewBuildingType
# Estructura almacenada para cada vóxel ocupado
voxel_cell<public> := struct<computes>:
Category<public>:build_category
Puedes agregar más categorías de vóxeles de construcción al extender la enumeración como se muestra arriba.
El siguiente código convierte una coordenada de vóxel 3D en un [index]()
1D.
# Obtiene el índice de matriz 1D de una ubicación 3D en la cuadrícula
GetVoxelIndex<public>(X:int, Y:int, Z:int)<transacts>:int=
return (X * (Size.Y*Size.Z)) + (Y * Size.Z) + Z
SetVoxel
y ClearVoxel
son funciones clave que modifican la cuadrícula de vóxeles.
Proyección de rayos
Una vez establecida la cuadrícula de vóxeles, el sistema realiza una comprobación de colisión de rayos para comprobar la cara del vóxel o el lado del vóxel donde golpea el rayo. Cada vóxel tiene seis caras, similar a un dado. Al proyectar el rayo, necesitarás saber qué vóxel y qué cara golpeas para dibujar el resaltado y generar un nuevo vóxel contra esa cara.

Las comprobaciones de colisión de rayos se manejan principalmente en la clase ray_caster
. Para hacer esto, primero se determina en qué vóxel comienza el rayo, mediante la transformación de la ubicación de la cámara en el espacio local de la cuadrícula. Luego, esto se divide por las dimensiones de vóxeles, como se muestra a continuación.
CurrentVoxel := vector3i:
X := Floor[InitialPosition.X / GridSize.X]
Y := Floor[InitialPosition.Y / GridSize.Y]
Z := Floor[InitialPosition.Z / GridSize.Z]
La función [Next]()
se llama repetidamente para determinar por qué vóxel pasará el rayo, cada vez que se comprueba si el vóxel se considera sólido.
Entradas del sistema de construcción
La función SelectModeTick_Trace
en player_data
se ejecuta para cada fotograma y controla la mayor parte de la lógica de edición de vóxeles y actualización del cursor. Se utilizan dos dispositivos [Input Trigger]()
para saber cuándo se presionan los botones Disparar y Apuntar, y para establecer las variables lógicas PlacePiece
y DeletePiece
.
Esta función requiere lógica adicional, ya que los vóxeles Parque se consideran solo en la superficie, lo que significa que no bloquean los rayos y solo pueden existir sobre vóxeles sólidos (el suelo o los edificios). Puedes actualizar la función CategoryIsSurfaceOnly
al añadir una nueva categoría que quieras que sea solo de superficie.
La construcción turbo también es posible si se mantiene presionado el botón Disparo para colocar rápidamente varios vóxeles en un plano. Esta función también comprueba si un jugador está dentro de un vóxel antes de añadirlo a la función CheckPlayerOverlapsVoxel
.
Generación de utilerías
Este proyecto de ejemplo se basa en la aparición de utilerías en [runtime]()
. Actualmente, creative_prop_asset
no se refleja automáticamente en los archivos de manifiesto de Verse. Por lo tanto, necesitas usar un objeto proxy (una instancia de piece_type
en la clase piece_type_dir
) para hacer referencia a utilerías particulares en Verse.
El dispositivo spawner_asset_references
usa un campo @editable
para cada utilería y una tabla de mapeo desde el proxy hasta el recurso real. Para añadir una nueva malla, primero debes crear una BuildingProp
para ella, añadir un nuevo proxy, añadir una propiedad al dispositivo y, luego, actualizar la tabla de mapeo (como se muestra a continuación). Por último, vuelve a compilar y actualiza la nueva propiedad en el dispositivo para que apunte a la nueva utilería.
Building1_corner:piece_type := piece_type{}
@editable
BP_Building1_corner : creative_prop_asset = DefaultCreativePropAsset
PT.Building1_corner => BP_Building1_corner
Generación procedimental en Verse
Este ejemplo implementa dos tipos de generación procedimental en Verse: Gramática de formas y Contracción de la función de onda. La Gramática de formas se aplica a los edificios 3D, mientras que la Colapso de función de onda se utiliza para las áreas 2D (planas).

Arriba se muestra un ejemplo de utilería de construcción generada.

Arriba se muestra un ejemplo de utilería de parque generada.
Para ambas técnicas, debes crear un conjunto de utilerías modulares de tipo construcción, que Verse genera durante el tiempo de ejecución. Este código es determinista, solo elimina y genera utilerías según sea necesario.
Gramática de formas
Todos los vóxeles de una categoría se transforman en cajas convexas más grandes para aplicar lo que se denomina Gramática de formas.

La gramática de formas consiste en reglas simples donde cada regla toma una caja y genera una o más subcajas para las reglas subsiguientes.

Por ejemplo, una regla podría dividir una caja alta en una pieza de suelo de un vóxel de altura, mientras que las esquinas y las paredes se asignan a reglas diferentes. Una regla especial puede generar una utilería del mismo tamaño y ubicación que la caja.
Cada regla se define como una clase de Verse independiente, derivada de la clase vo_base
(operador de volumen). Estas se ensamblan en un árbol de conjunto de reglas dentro de una clase rs_base
(derivada de un conjunto de reglas).
Este enfoque simplifica la creación de nuevas reglas, permite experimentar con diferentes ideas, asigna estilos distintos a cada tipo de edificio y permite la asignación de estilos distintos a cada tipo de edificio. Aplicar diferentes reglas al mismo conjunto de vóxeles produce resultados variados.
A continuación se muestra un ejemplo sencillo de un operador de volumen ` vo_sizecheck`.
# Comprobar que todas las dimensiones de una caja sean >= de un tamaño determinado
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)
Anula la función Operate
definida en la clase de bajo, comprueba el tamaño de la caja entrante y decide cuál de las siguientes reglas (vo_pass
o vo_fail
) llamar.
Los conjuntos de reglas que contienen muchos operadores de volumen son fáciles de configurar en Verse. Puedes anular la función setupRules
y declarar tus [operadores]()
y sus [parámetros]()
. El punto inicial, u operador raíz, se asigna a VO_root
como se muestra a continuación.
# Reglas para el estilo 'Building1'
rs_building1<public> := class(rs_base):
RuleSetName<override>:string = "Building1"
SetupRules<override>(PT:piece_type_dir):void=
[...]
# Reglas para un piso del edificio
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
Este método facilita la creación de nuevos operadores y conjuntos de reglas para diferentes categorías de edificios. Los conjuntos de reglas se asignan en InitRuleSets
y se seleccionan para una categoría particular en SelectRuleSet
, ambos en build_system
.
Colapso de la función de onda
El colapso de la función de onda (WFC) es una técnica para generar aleatoriamente un área basada en reglas que determinan cómo pueden encajar las piezas.
En esta implementación, puedes usar un conjunto de cuadros y luego especificar cuáles de ellos pueden ser adyacentes. Las etiquetas se aplican a cada borde, y solo se pueden colocar cuadros con etiquetas coincidentes.
A continuación, se muestra un ejemplo de una etiqueta de borde de la clase wfc_mode_factory
.
WaterEL:wfc_edge_label := wfc_edge_label:
Name:="Water"
Symmetric := true
A continuación se muestra un ejemplo de la definición de malla para el ejemplo anterior.
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

Puedes especificar las etiquetas en el sentido de las agujas del reloj, empezando por la dirección +Y.

El borde inferior en este ejemplo es "agua a césped" porque cada borde se considera en el sentido de las agujas del reloj. Con este sistema, es fácil añadir nuevas etiquetas y mallas, o nuevos modelos WFC, a tu juego.
El algoritmo de WFC selecciona una ubicación en la cuadrícula, elige aleatoriamente o colapsa entre las opciones posibles y, luego, propaga las consecuencias de esa elección a las opciones posibles en otras ubicaciones. Este proceso continúa hasta que se genera toda la región.
La clase wfc_system
contiene el estado actual de todas las casillas. El operador de volumen especial vo_wfc
lee el estado y genera las mallas correctas.
Extensiones potenciales
A continuación, se muestran algunas formas en que este proyecto de ejemplo podría modificarse para crear nuevas experiencias.
Añadir una nueva categoría de vóxeles/Gramática de formas
-
Extender la enumeración
build_category
-
Actualizar todas las declaraciones
case
que deban controlar la nueva categoría. -
Crear un nuevo conjunto de reglas derivado de
rs_base
. -
Actualizar
SelectRuleSet
para usar el nuevo conjunto de reglas para la nueva categoría
Añadir nuevas mallas al modelo de la WFC de Parque
-
Crear una utilería de construcción para cada malla nueva (como se describe en Utilerías de generación más arriba).
-
Añadir una nueva
wfc_edge_label
(si lo deseas) aGetParkModel
enwfc_model_factory
. -
Añadir una nueva instancia de
wfc_mesh
para cada nueva malla/utilería, y definir la etiqueta para cada borde. -
Llamar a
Model.AddMesh
para cada malla nueva.
Añadir un nuevo modelo de WFC
-
Añadir una nueva categoría de vóxeles para el nuevo modelo.
-
Añadir una nueva función a
wfc_model_factory
para crear el nuevo modelo. -
Añadir un nuevo miembro
wfc_model
abuild_system
(comoPark_WFCModel
). -
Añadir una nueva función como
AddParkWFCTile
que agregue cuadros con el nuevo modelo. -
Modificar
SelectModeTick_Trace
para llamar a la nueva función para la nueva categoría.

También puedes generar vóxeles procedimentalmente. En el proyecto de ejemplo, hay botones que añaden edificios aleatorios o regiones de parque a la zona de construcción. Esto usa las funciones ClearRegion
y AddRegion
de build_system
y podría usarse como punto de partida para un sistema de generación aleatoria de niveles.
Rendimiento de Verse
El código de Verse de este proyecto de ejemplo garantiza que sea lo suficientemente rápido como para procesar las actualizaciones en tiempo real. Dado que trabajar con matrices grandes puede generar problemas de rendimiento, es importante tener en cuenta la siguiente información:
- Usar un bucle
for
para devolver una matriz es más rápido que construirlo elemento por elemento, ya que cada adición copia la matriz para que sea O(N2). Por ejemplo:
* set OptionalArray := for(I := 0 .. ArraySize-1):
false
-
No pases matrices grandes por valor; en su lugar, colócalas en un objeto y llama a métodos o pasa el objeto.
-
Las matrices multidimensionales son lentas porque el primer operador
[]
pasa una copia al siguiente operador[]
. -
Llamar a
.Length
en una matriz en realidad hace una copia de la matriz en este momento, por lo que puede ser más rápido realizar un seguimiento del tamaño de las matrices grandes.
También es muy útil utilizar la macro profile
para comprender mejor qué partes del código están tomando más tiempo.