O modelo Verse - Construção procedural é um projeto de exemplo do Unreal Editor para Fortnite (UEFN) [Re: Imagine London (Código da ilha: 1442-4257-4418) que mostra o sistema de Construção procedural escrito em Verse.
O sistema de Construção procedural é totalmente implementado e criado no Verse e não no Fortnite. Com esse sistema, você pode ter mais controle sobre como as malhas são compostas nos seus projetos.
Por meio do Fortnite, você pode projetar o seu jogo com peças de construção predefinidas (piso, teto e parede). O sistema de Construção procedural expande suas possibilidades, para que você possa criar [voxels] com categorias que definem sistemas subjacentes nos quais é possível colocar rapidamente voxels adicionais à medida que o sistema posiciona sua malha.
Para acessar este modelo no Navegador de projetos do UEFN, navegue até Exemplos de funcionalidades > Exemplos de jogos > Verse - Construção procedural.
O sistema de Construção procedural funciona em duas fases.
Para a primeira fase, você edita uma grade de voxel, uma grade 3D em que cada célula é uma caixa, adicionando ou removendo voxels.
A segunda fase é executada cada vez que a grade de voxels é modificada. A modificação da grade de voxels usa as informações de voxels e executa um processo que gera as malhas certas nos lugares certos.
Você pode adicionar e remover as categorias de voxel Construção e Estacionamento. Cada categoria tem regras diferentes para como as malhas devem ser geradas. Essas regras referem-se ao gráfico ou árvore de operações que serão executadas para irem de voxels posicionados para malhas reais
Como usar o modelo
Cada nível do seu projeto terá uma classe de nível superior, que é a classe root_device. Quando um jogador entra no jogo, a classe root_device cria um global_player_data para ele, que define as informações da IU.
Cada zona de construção tem um dispositivo build_zone que define as dimensões do local em unidades voxel. A posição desse dispositivo define a origem do local. A área de construção usa um objeto build_system para lidar com a mecânica de construção e um spawner_device para gerar malhas de adereços de construção. A área de construção também contém voxel_grid, mesh_grid e um wfc_system.
Cada área de construção terá o seguinte:
build_zone(dispositivo): define as dimensões do local em unidades voxel.build_system: lida com a mecânica de construção.spawner_device(dispositivo): gera malhas de adereços de construção.spawner_asset_references(dispositivo): faz referência a todos os adereços gerados.
Quando um jogador entrar numa build_zone, que é acionada pelo dispositivo Volume, é criado um player_data para lidar com a entrada do jogador e edição de voxel.
Como construir com voxels
Esse modelo introduz um vector3i [type]() para representar voxels por suas coordenadas X, Y e Z [integer]().
Abaixo está um script de exemplo usando vector3i.
vector3i<public> := struct<computes><concrete>:
@editable
X<public>:int = 0
@editable
Y<public>:int = 0
@editable
Z<public>:int = 0Grades de voxels
Uma grade de voxels é uma grade 3D de células para cada zona de construção que armazena informações sobre o tipo de voxel de construção presente. Isso é implementado na classe voxel_grid como uma matriz 1D de referências opcionais a voxel_cell [objects](), conforme mostrado abaixo.
# Main array representing the 3D grid of voxels
var Cells<public> : []?voxel_cell = array{}Por padrão, um objeto de célula de voxel contém apenas um [enum]() para o tipo de voxel, conforme mostrado abaixo.
# 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_categoryVocê pode adicionar mais categorias de voxels de construção estendendo a enumeração conforme mostrado acima.
O código abaixo converte uma coordenada de voxel 3D em um [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 são funções principais que modificam a grade de voxels.
Raycasting
Após a grade de voxels ser definida, o sistema realiza uma verificação de colisão de raios para verificar a face do voxel ou o lado do voxel que o raio atinge. Cada voxel tem seis faces, semelhantes a um dado. Ao fazer uma conversão de raios, você precisará saber qual voxel e qual face atingir para desenhar o realce e gerar um novo voxel contra essa face.
As verificações de colisão de raios são tratadas principalmente na classe ray_caster. Isso é feito determinando primeiro em qual voxel o raio começa, transformando a posição da câmera no espaço local da grade. O que é então dividido pelas dimensões de voxel, conforme mostrado abaixo.
CurrentVoxel := vector3i:
X := Floor[InitialPosition.X / GridSize.X]
Y := Floor[InitialPosition.Y / GridSize.Y]
Z := Floor[InitialPosition.Z / GridSize.Z]
A função [Next]() é chamada repetidamente para determinar por qual voxel o raio passará em seguida, verificando cada vez se o voxel é considerado sólido.
Entradas do sistema de construção
A função SelectModeTick_Trace em player_data é executada para cada quadro e lida com a maior parte da edição de voxels e da lógica de atualização de cursores. Dois dispositivos [Input Trigger]() são usados para saber quando os botões Disparar e Mirar são pressionados e para definir as variáveis lógicas PlacePiece e DeletePiece.
Essa função requer lógica adicional, pois os voxels de Estacionamento são considerados apenas de superfície, o que significa que eles não bloqueiam raios e só podem existir sobre voxels sólidos (o solo ou construções). Você pode atualizar a função CategoryIsSurfaceOnly ao adicionar uma nova categoria que gostaria que fosse apenas de superfície.
A Construção turbo também é suportada. Para isso, basta manter pressionado o botão Disparar para colocar rapidamente vários voxels em um plano. Essa função também verifica se um jogador está dentro de um voxel antes de ser adicionada à função CheckPlayerOverlapsVoxel.
Como gerar adereços
Este projeto de exemplo depende do surgimento de adereços em [runtime](). Atualmente, creative_prop_asset não se reflete automaticamente nos arquivos de manifesto de Verse. Portanto, você precisa usar um objeto de proxy (uma instância de section_type na classe section_type_dir) para referenciar adereços específicos no Verse.
O dispositivo spawner_asset_references usa um campo @editable para cada adereço e uma tabela de mapeamento do proxy para o ativo real. Para adicionar uma nova malha, você deve primeiro criar um BuildingProp para ela, adicionar um novo proxy, adicionar uma propriedade ao dispositivo e, em seguida, atualizar a tabela de mapeamento (conforme mostrado abaixo). Por fim, recompile e atualize a nova propriedade no dispositivo de forma que ela aponte para o novo adereço.
Building1_corner:piece_type := piece_type{}
@editable
BP_Building1_corner : creative_prop_asset = DefaultCreativePropAsset
PT.Building1_corner => BP_Building1_corner
Geração procedural em Verse
Este exemplo implementa dois tipos de geração procedural no Verse: Gramática de formato e Recolhimento de função de onda. A Gramática de formato é aplicada a construções em 3D, enquanto o Colapso de função de onda é usado para áreas 2D (planas).
Acima está um exemplo de adereços de construção gerados.
Acima está um exemplo de adereços de estacionamento gerados.
Para ambas as técnicas, você deve criar um conjunto de adereços modulares de tipo de construção, que o Verse gera em tempo de execução. Esse código é determinístico, apenas excluindo e gerando adereços conforme necessário.
Gramática de formato
Todos os voxels de uma categoria são transformados em caixas convexas maiores para aplicar o que é chamado de Gramática de formato.
A Gramática de formato consiste em regras simples em que cada regra usa uma caixa e gera uma ou mais subcaixas para as regras subsequentes.
Por exemplo, uma regra pode dividir uma caixa alta em uma parte de piso de um voxel de altura, enquanto os cantos e as paredes são atribuídos a regras diferentes. Uma regra especial pode gerar um adereço do mesmo tamanho e posição que a caixa.
Cada regra é definida como uma classe do Verse separada, derivada da classe vo_base (operador de volume). Elas são montadas em uma árvore de conjunto de regras dentro de uma classe derivada de rs_base (conjunto de regras).
Essa abordagem simplifica a criação de novas regras, permite a experimentação com diferentes ideias, atribui estilos distintos a cada tipo de construção e permite atribuições de estilos distintos a cada tipo de construção. A aplicação de regras diferentes ao mesmo conjunto de voxels produz resultados variados.
Abaixo está um exemplo simples de um operador de 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)Ele substitui a função Operate definida na classe base, verifica o tamanho da caixa de entrada e decide qual das seguintes regras (vo_pass ou vo_fail) chamar.
Os conjuntos de regras contendo muitos operadores de volume são fáceis de configurar no Verse. Você pode substituir a função setupRules e declarar seus [operators]() e [parameters](). O ponto de partida, ou operador raiz, é atribuído a vo_root, conforme mostrado abaixo.
# 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
Esse método facilita a criação de novos operadores e conjuntos de regras para diferentes categorias de construção. Conjuntos de regras são alocados em InitRuleSets e selecionados para uma categoria específica em SelectRuleSet, que estão em build_system.
Recolhimento de função de onda
O colapso da função de onda (CFO) é uma técnica para gerar aleatoriamente uma área com base em regras que determinam como as peças podem se encaixar.
Nesta implementação, você pode usar um conjunto de ladrilhos e depois especificar quais deles podem ser adjacentes. Rótulos são aplicados a cada borda, e apenas ladrilhos com rótulos correspondentes podem ser posicionados.
Veja a seguir um exemplo de rótulo de borda da classe wfc_mode_factory.
WaterEL:wfc_edge_label := wfc_edge_label:
Name:="Water"
Symmetric := true
Veja a seguir um exemplo da definição de malha para o exemplo acima.
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
É possível especificar rótulos no sentido horário, começando na direção +Y.
A borda inferior neste exemplo é "water-to-grass", porque cada borda é considerada no sentido horário. Com esse sistema, é fácil adicionar novos rótulos e malhas ou novos modelos de CFO ao seu jogo.
O algoritmo CFO seleciona uma posição na grade, escolhe aleatoriamente ou recolhe as opções possíveis e, em seguida, propaga as efeitos dessa escolha para as opções possíveis em outras posições. Esse processo continua até que toda a região seja gerada.
A classe wfc_system contém o estado atual de todos os ladrilhos. O operador de volume especial vo_wfc faz a leitura do estado e gera as malhas corretas.
Extensões em potencial
Veja a seguir algumas maneiras de como este projeto de exemplo pode ser alterado para novas experiências.
Adicione uma nova categoria de voxel/Gramática de formato
Estenda a enumeração
build_categoryAtualize quaisquer instruções
caseque precisem lidar com a nova categoriaCrie um novo conjunto de regras derivado de
rs_baseAtualize
SelectRuleSetpara usar o novo conjunto de regras para a nova categoria
Adicione novas malhas ao modelo CFO de estacionamento
Crie um adereço de construção para cada nova malha (conforme descrito em Gerar adereços acima).
Adicione um novo
wfc_edge_label(se desejado) aGetParkModelemwfc_model_factory.Adicione uma nova instância
wfc_meshpara cada nova malha/adereço, definindo o rótulo para cada aresta.Chame
Model.AddMeshpara cada nova malha.
Adicione um novo modelo de CFO
Adicione uma nova categoria de voxel para o novo modelo.
Adicione uma nova função a
wfc_model_factorypara criar o novo modelo.Adicione um novo membro
wfc_modelabuild_system(comoPark_WFModel).Adicione uma nova função como
AddParkWFTile, que adiciona ladrilhos usando o novo modelo.Modifique
SelectModeTick_Tracepara chamar a nova função da nova categoria.
Você também pode gerar voxels de maneira procedural. No projeto de exemplo, há botões que adicionam regiões aleatórias de construção ou estacionamento à área de construção. Ele usa as funções ClearRegion e AddRegion de build_system e pode ser usado como ponto de partida para um sistema de geração de nível aleatório.
Desempenho do Verse
O código Verse neste projeto de exemplo garante que o código seja rápido o suficiente para processar atualizações em tempo real. Como lidar com matrizes grandes pode levar a problemas de desempenho, é importante lembrar das seguintes informações:
Usar um loop
forpara retornar uma matriz é mais rápido do que construí-lo elemento por elemento, já que cada adição copia a matriz, para que ela possa ser O(N2). Por exemplo:Verse* set OptionalArray := for(I := 0 .. ArraySize-1): falseNão passe matrizes grandes por valor. Em vez disso, coloque-as em um objeto e chame métodos ou passe o objeto.
Matrizes multidimensionais são lentas porque o primeiro operador
[]passa uma cópia para o próximo operador[].Chamar
.Lengthpara uma matriz faz, na verdade, uma cópia da matriz no momento, então pode ser mais rápido você mesmo acompanhar o tamanho de matrizes grandes.
Também é muito útil usar a macro profile para entender melhor e exatamente quais partes do código estão demorando mais.