Hello, and welcome! In this article, I’ll be showing you how to use Unreal’s new Geometry Script system to create a custom landscape tool for your project. Once we’re done, you’ll be able to quickly and easily create curved level geometry with adjustable shapes and heights.
First off, some prerequisites:
- You should be at least somewhat familiar with Unreal Engine and Blueprints. No C++ knowledge is necessary.
- You do not need to be familiar with the Geometry Script system, as I will cover the basics in this tutorial. If you want to learn more about it, you could try reading the official documentation, but I personally found it to be pretty unfriendly to newcomers. Instead, I recommend this playlist by Ryan Schmidt.
- Since we’re using Geometry Script, you need to be on Unreal 5.0 or later.
- If you’re trying to make large-scale, realistic landscapes, that’s what the Landscape system is for, this tutorial is not for you.
Good? Good. Let’s get started.
Step 1: Set up the actor
Before anything else, make sure that you have the Geometry Script plugin enabled for your project. Depending on what version of Unreal 5 you have, you might get a big scary popup about how the plugin is still experimental. How seriously you take this message is up to you, but I’m just going to ignore it.
Now, create a new blueprint actor that will serve as our tool. Base the actor on the GeneratedDynamicMeshActor class, which comes prebuilt with a DynamicMeshComponent that serves as the basis for the Geometry Script system. Technically, you could just use a normal actor and just add a DynamicMeshComponent to it, but then you’d miss out on the handy “On Rebuild Generated Mesh” event, which lets the us see how the mesh responds to our changes in real time.
Next up is to add some spline components. Splines are generally the go-to if you want to define a curved shape in 2D or 3D space, and we’ll need two of them, one to define the borders of the landscape, and one to define the height. Go ahead and make an arbitrary shape for the border spline, and mark it as “closed loop”.
Step 2: Define the border
The nice thing about splines is that you can define fairly complicated shapes using relatively few control points. However, if we try using just those points to make a mesh, we’re going to lose a lot of detail and end up with something that looks very jagged. To fix this, we can sample a series of points at equal distances along the spline so that we can approximate a smooth curve with a large number of straight segments.
This is done by getting the length of the border spline, dividing it by the desired number of edges, and then looping down the length of the spline. At each distance, store the position of the spline to an array for later use.
We’ll go ahead and make the number of edges an instance editable variable, so that we can adjust it in the level editor. Just keep in mind that more edges means a smoother boundary, but also a more complicated, and therefore slower, mesh. I also recommend setting the minimum for this variable to be three, so we always at least have a triangle.
Step 3: Create a grid
Unreal’s built-in landscape system uses a grid, where each point along that grid can be adjusted vertically. We want to do something similar, and so we’ll start by making a grid big enough to cover the entire space laid out by the border spline. We can figure out the necessary dimensions of the grid by finding the minimum and maximum X and Y values in our array of points. Here’s a quick function for that. We can also make this function Pure, since it doesn’t rely on anything aside from its input parameter and doesn’t modify anything either.
Now that we have this information, it’s finally time to start talking about the basic structure of the Geometry Script system. Each DynamicMeshComponent has a corresponding UDynamicMesh object, which is what you will actually need to modify. This is most easily accessed from the “On Rebuild Generated Mesh” event, though you can also get it directly from the component if needed.
Once we have a reference to the UDynamicMesh object, we can apply a Geometry Script function to it. There are a lot of different functions available, but for now we’ll focus on “Append Rectangle XY”. As the name suggests, this allows us to add a rectangle to our mesh. We can specify the transform of the center of the rectangle, it’s length and width, and also how many pieces to break it up into. Go ahead and make the number of X and Y divisions into instance editable variables as well. Note that the transform is in local space, relative to the DynamicMeshComponent.
Let’s take a moment to look at this “Primitive Options” input. Pretty much every Geometry Script function has something like this, and if you break it apart you’ll find a number of less-common features you can play with. For example, if we wanted we could flip the orientation of the rectangle, so that the normal vector would be facing down rather than up. For this operation though, we don’t need any of that, so we can move on.
Now, we can head over to the viewport, and see what we have so far. Mess around with the border spline, and you should see that the generated rectangle constantly updates to fit it. If you switch to wireframe view, you can see the X and Y divisions are also responding appropriately.
Step 4: Set the height
Ok, now it’s time to set the height of the landscape. There are a number of ways to accomplish this, but for this tool we will iterate through each vertex in the grid, find the closest point on the height spline, and match that height.
To start, we can use the function “Get All Vertex IDs” to get our vertices. For some reason I don’t understand, rather than giving us an array, we instead get a struct. Fortunately, we can easily convert this to an array using a built-in function.
Now, loop through each vertex ID, and retrieve the position. Note that this position is in local space, and we’ll need to transform it to world space. We can then use a super-useful function, “Find Location Closest to World Location”, which will tell us the position on the height spline that is closest to this vertex. Extract the Z coordinate from this, and then set the vertex’s new position.
Once again, head over the viewport. This time, modify the control points of the height spline, and see how the grid reacts.
Step 5: Trim the grid
This is nice, but now we need to trim the grid so that it actually matches the shape of our boundary spline. To do this, we will perform a boolean operation, and to do that, we will need to make a temporary UDynamicMesh object.
This is accomplished using the “Allocate Compute Mesh” function. Just make sure that whenever you create a compute mesh, you also release it once you are finished with it. I’m honestly not sure what happens if you don’t, but I assume it’s bad.
We’ll fill this temporary mesh with a simple extruded polygon. Set the height to be something huge, and change the origin to be at the center of the extrusion rather than the bottom. This should ensure that the tool always intersects with the grid.
The “Polygon Vertices” input requires an array of two-dimensional vectors, so unfortunately this means we’ll have to convert our edge points from 3D to 2D. Here’s a quick little function for that. Again, you can mark it as Pure since it is self-contained.
That done, we can plug the resulting array into the append function. Our temporary mesh is now filled with a giant column with a cross-section that matches our boundary spline. However, it’s only a temporary mesh, so we can’t actually see it right now.
Next, go back and grab our original mesh object, and bring up the “Apply Mesh Boolean” function. Plug the temporary mesh in as the tool, and go into the options and make sure “Fill Holes” is off. If you leave the operation as “Union”, you can go over to the viewport to preview what our temporary mesh currently looks like. Change it to “Intersection”, and the grid will now be the desired shape!
Step 6: Add the sides
The final step is to add the sides of the landscape. To do this, we’ll use the “Apply Mesh Extrude” function, which strangely hides most of the useful bits in the options struct. Set the extrude thickness to whatever you want, but be sure that it’s a negative value. You could theoretically make the extrude direction negative instead, but for whatever reason that flips all the mesh normals the wrong way, so don’t do that. I would also recommend setting the UV scaling to something tiny, so that the UVs on the sides better match the UVs on the top.
Make sure to release the extra compute meshes, and with that you are done! Congratulations. You now have a custom landscape that you can change and manipulate as you desire.
So, there’s a lot of tweaks and extra features that you can add, if desired.
For example, you’ll probably want the landscape to have collision. To do this, go over to your DynamicMeshComponent, open up the collision settings, enable complex collisions, and change the collision type to “Use Complex Collision As Simple”.
If you want to add additional material slots to the mesh, you can do so. If, say, we want the top of the landscape to use a different material, you can insert a “Remap Material IDs” node at any point between making the grid and extruding the sides. Then, at the end of the function, get a reference to the DynamicMeshComponent, call “Configure Material Set”, and give it an array of the desired materials.
If you want the edges of the landscape to be smoother, you can use the “Apply Iterative Smoothing” function. This works particularly well if you have a landscape material that will change based on the mesh normal.
There’s lots more, but I’ll leave it here. Again, I recommend checking out the official list and seeing where that takes you.
That’s all from me for now. I hope you found this tutorial useful, and I hope to make more in the future. If you’re willing, please check out my upcoming game, Grippy Golf, on Steam!