Here I come to confuse the day!
So… I can’t give you screenshots… I haven’t done it yet, though I started to play around with procedural meshes in blueprints, so it at least is not totally impossible…
Caveat: It would be more straightforward to do this in C++. That said, it’s not -impossible-, though it might be impractical; it just probably won’t be without its problems (especially since the feature is “beta”)…
Overview
I’m going to describe a method for making a 2D procedural mesh, then describe a method of, using noise, cutting away sections to provide a cavern like system. This will be simple, and by no means a complete project. Would be interesting if it does actually work though…
Part 1: Make a square.
Part 2: Lay out a collection of squares in a 2 dimensional grid (a chunk) to produce one big square.
Part 3: Use a look-up table to determine where a square should be placed in the aforementioned grid to create a differentiation between land and sky.
Part 4: Set a selection of tiles in the land portion to be “sky”, thus creating caverns.
1.) Let’s Make A Square
I do this in an actor blueprint called “ProcSquare”. I replace the default scene component with a ProceduralMesh component, and leave the name as “ProceduralMesh”
So our first goal should be to make one square. For ease, I’d work in 1m sizes (100 U) – this will be easily visible.
Let’s define some variables:
Create a vector variable called “Origin”, compile, and set it to 0, 0, 0. (You can change this later to define the initial point elsewhere if you so wish)
Create a vector variable called “Dimensions”, compile, and set to 100, 0, 100 (1m to the left, and 1m to the up directions)
Add a Material variable, and set it to some material.
Create a function called “CreateSquare” and give it two vector inputs – “SquareOrigin” and “SquareDimensions”. Give it two integer inputs as well – “xOffset” and “zOffset” (these will be used later to create the chunk).
In your construction script, wire the ConstructionScript exec output to “CreateSquare” exec input. Feed “Origin” and “Dimensions” into “CreateSquare”. Leave “xOffset” and “zOffset” at “0”.
Open the “CreateSquare” function.
You’re going to need to create 4 local variables, coinciding to the four vertices of the square. I name them “v0” for the 0,0 Cartesian point, “v1” for the 1,0 point, “v2” for the 1,1 point, and “v3” for the 1,0 point.
On the input, split the “SquareOrigin” and “SquareDimensions” structs so you can see the individual XYZ points.
Add a Set node for each of the 4 local variables, and split their input structs. Here’s the “math” as it were:
v0 x = “SquareOrigin:x * xOffset”, v0 z = “SquareOrigin:z * zOffset”
v1 x = “SquareOrigin:x * xOffset”, v1 z = "SquareOrigin:z * zOffset + “SquareDimensions:z”
v2 x = “SquareOrigin:x * xOffset + SquareDimensions:x”, v2 z = “SquareOrigin:z * zOffset + SquareDimensions:z”
v3 x = “SquareOrigin:x * xOffset”, v3 z = “SquareOrigin:z * zOffset + SquareDimensions:z”
You’ll need to wire from the function input exec through each of the Set nodes.
Add a “MakeArray(Vector)” node with 4 inputs. v0 => [0], v1 => [1], v2 => [2], and v3 => [3]. Add a comment marking this as your “Vertices Array”
From the v3 exec pin, pull into a “Clear Mesh Section” node. The target will be your procedural mesh component. Leave “Section Index” at 0.
From the “Clear Mesh Section” node, pull a “Calculate Tangents for Mesh” node. The “vertices” input pin should be fed the output pin from your Vertices Array. From the Triangles pin, pull out a “Make Array(int)” node, and give it five pins with the following values (in order): 0,1,2,0,2,3 (Add a comment that this is your Triangles Array). Pull out from the UVs pin and add a “MakeArray(Vector2d)” node with 4 pins. I take the “SquareDimensions:x / 100.0” and “SquareDimensions:z / 100.0” as my U and V maxes respectively. So my “MakeArray(Vector2d)” has the following values:
[0] = <0.0, 0.0>
[1] = <0.0, “SquareDimensions:z / 100.0”>
[2] = <“SquareDimensions:x / 100.0”, “SquareDimensions:z / 100.0”>
[3] = <“SquareDimensions:x / 100.0”, 0.0>
Add a comment that that MakeArray is your UVs Array.
From “Calculate Tangents for Mesh”'s exec pin, pull off for a “Create Mesh Section” node. Target is your procedural mesh component. Section index is 0. Vertices can be fed from your Vertices Array. Triangles can be fed off your Triangles Array. Normals will come from the Normals pin of the “Calculate Tangents for Mesh” node. UV0 can be fed your UVs array. You do not need to hook up the Vertex Colors pin. Tangents can be fed off the tangents pin from “Calculate Tangents for Mesh” node.
From the “Create Mesh Section” node’s exec pin, drag off to add a “Set Material” node. The target is the procedural mesh component. Material is your material variable. Wire the exec pin out to the return node.
Drop your blueprint in your scene and you have a square. Not super useful, but there you go.
2.) Let’s Make A (Bigger) Square
Sooooo, first off, we’re not really making one bigger square. We’re creating a 2 dimensional array of smaller squares.
Rather than hand hold you through this one, I’m going to give you more of an overview. Mostly because this is a point I haven’t done, but this would be my approach.
Create two variables:
An integer called “ChunkWidth”
An integer called “ChunkHeight”
Compile and set both to 100.
These are going to be the dimensions of your chunk, or a collection of individual squares.
Now in your construction script, before you call “CreateSquare”, you’re going to add two loops “For 0 to ChunkHeight-1” and “For 0 to ChunkWidth-1”, with the latter nested inside the former. The indices are going to be your offset (which you’ll have to multiply times the SquareDimensions:x or z to get the appropriate offset). So these will be fed into the “CreateSquare” function.
3.) Earth and Sky
You can approach this part relatively simply, because it’s not going to be that different either way… Before calling “CreateSquare” add a branch. Your boolean expression should evaluate if the index of “For 0 to ChunkHeight-1” is less than some value, create a square. Otherwise, continue. So you can check it against a value of 50, for instance. Easy peasy.
4.) Caverns!
Just for ease, we’ll do this with non-uniform noise.
After the last branch statement, add another branch. Your comparison this time will be of a random float between 0 and 1, against a threshold value (.65 for instance). So add a “float <= float” comparison, and off the first float choose “Get random float in range from stream” Min is “0.0”, Max is “1.0”. Pull off stream and select “Make Random Stream”. Now you can play with seed values.
Ideally, you’d want a uniform noise distribution, like Perlin or Simplex noise would offer. There’s no out of the box way to do this that I know of in blueprints, but this can be done in blueprints as well, though it’s not exactly clean. Straight PRN is going to give you missing squares in weird spots, and some holes could be bigger than others, though it won’t be organic.
Anyhow, hope that gives you a bit of a foot in the right direction.