My Indie Open World Workflow (Part 1)

In this post I'm going to describe a workflow I've developed for building a large open world game using Unity and Blender. Blender is a key part of the workflow, but the rest could be adapted to pretty much any game engine. For this first part, I'll focus on the Blender side of the workflow and cover engine-integration in another post.

But first, some backstory + description of the problems and constraints I'm working with!

Intro

My new untitled game is a submarine themed open world adventure. I'm thinking of it as mechanically a space trading game like Freelancer set underwater. Maybe in another post I'll describe what my plans are for this game but for now this is all you really need to know.

a screenshot from my submarine game showing the player approaching a distance underwater station Very WIP screenshot of the game so far

Similar to my previous game Sail Forth, I intend this game to take place in a large open world with no loading screens. When I was first starting out on this project I figured I'd be able to mostly just re use and improve upon what I did before, and that has been partly true, but there are some big differences in what I'm trying to do this time that have required me to step back and figure out some new workflows.

A Hand Authored World

The first big change is that I'm planning on making a mostly hand-authored world this time around, rather than procedurally generating the world map. In Sail Forth I didn't really have to think about how to edit a large open world map because I didn't need to, I just wrote some code to scatter islands procedurally.

Continuous Terrain

This is probably one of the biggest challenges compared to Sail Forth. The nice thing about a game set on the ocean is that most of the world is water, and occasionally you have islands. Once you go under the ocean however, suddenly you realize those individual islands are actually part of a giant piece of terrain, the sea floor.

Previously I could just load in the individual islands as the player approached them, but now I'll have to figure out some way of splitting up or LODing the seafloor such that an Open World Game sized chunk of it doesn't bring your computer to its knees. And if I split the terrain up, I'll probably have to worry about seams between the chunks of seafloor!

No Heightmaps!

I figured I'm already going nuts making an open world game as a mostly solo developer so why not go all the way! I think it'd be super cool to find big caves and explore them with your sub. The problem with caves is they are more or less incompatible with a traditional heightmap approach to building terrain, as there can only be one height value for any given XY coordinate. There are some ways around this which I'll mention later but the long and short of it is I don't want to do those, so heightmaps are out!

How to make a Big Chunk of terrain

Given the above goals, it seems like the first thing we'll need is a way to make large terrains. There are, of course, a million tools out there to do this, and I looked into some of them.

Unity Terrain?

An obvious question is why not use Unity's terrain system, which has a bunch of LOD type things built into it for large terrains and is nicely integrated with the editor.

screenshot of unity terrain editor can you believe it only took me 30 seconds to make this incredible example terrain?

This is, of course, a heightmap based tool so you'd think that entirely rules it out, but it does actually have a pretty clever way of allowing for caves. It allows you to paint 'holes' in the terrain, where it literally just deletes the terrain geometry. Once you have your hole you'd then take some cave model, line it up as best you can with the hole, and generally then you'd cover up the seams with rock assets or whatever.

So, technically usable for my purposes, but the workflow sounds annoying. Also this still doesn't allow for overhangs as part of the terrain, those would have to be extra props that I place.

Finally, Unity Terrain comes with a whole bunch of other stuff like vegetation which I have my own tech for, so I think it's a no from me.

Geogen + Other Terrain Tools

This is a relatively new tool and I'm going to kind of lump it in with a bunch of 3rd party terrain tools out there.

screenshot of geogen's pretty default terrain pretty!

This one is kind of neat as it's non-destructive, but again is built upon those dang heightmaps, and this one doesn't seem to have any ability to even do the hole painting from Unity. The node based editing is neat, but I couldn't really figure out any kind of workflow where I'd be able to create a large, continuous, terrain instead of just a chunk as seen the screenshot. It's also a paid subscription model to use which gets a thumbs down from me.

Most of the other tools I could find were heightmap based, expensive, or just seemed like they'd be hard to use.

Blender <3

Finally we come to Blender, which to be honest was always going to be the solution because when it comes to gamedev tools it's sort of the only arrow in my quiver. Plus it's free, extensible, and always getting better!

screenshot of a test chunk of terrain I modeled in blender A pretty basic chunk of terrain I tried making just to see how it felt

I found it to be pretty intuitive to model terrain using a combination of proportional editing, sculpting tools, and extruding where I wanted to make holes or other types of terrain protrusions.

Blender does have some very basic terrain generation tools, but we won't be using those. I've recently been learning how to use Geometry Nodes, a newish feature in Blender that is often described as "Houdini-lite". Basically it lets you procedurally generate or modify geometry using a sequence of nodes, similar to node based shader editors or coding tools.

a cube generated via geometry nodes The incredible power of Geometry Nodes, a cube wrought from nothing

You could definitely generate entire terrains with enough node-wrangling, but remember we're making a hand authored artisan world, so instead we'll be using it to create our Big Terrain workflow + some extra procedural details.

Splitting the World Into Tiles

I know big fancy AAA games do things like Dynamic Tessellation for terrain and all kinds of other complicated stuff, and we're not going to be doing any of that. Instead, we're going to use a technique I feel is much easier to understand and implement: Tiles. If it's good enough for Minecraft, it's probably good enough for us.

The Plan

a diagram demonstrating how terrain might get split into a multiple tiles Forgive my lazy drawing, but this is the basic idea

The basic idea is to split the terrain into a bunch of square tiles which can be loaded and unloaded depending on whether the player can see them. For bonus points, we could even have lower resolution versions of each tile that get swapped out when the tile is visible but far away.

Of course, the last thing we want to have to do is to model a terrain and through some kind of painstaking process select individual square regions to separate out. This would both take way too long and would be a destructive action: any further edits to the tiles would risk creating seams between them.

This is where Geometry Nodes can help!

Splitting a Terrain With Geometry Nodes

I'm not going to make an in-depth tutorial for Geometry Nodes, but I'll explain the general idea here. The key comes from the Duplicate Elements node, which has an input for a selection of the geometry to duplicate.

screenshot of the duplicate elements node This bad boy is going to save us a lot of work

If we can figure out a way to select one "tile" worth of geometry, this node could let us create a copy of the terrain but scoped to just a single tile. From there, we could make multiple tiles with different coordinates to split up the big terrain.

"Selection" inputs in Geometry Nodes essentially work as truthy values. If you pass in something that evaluates to true, which in Blender is anything > 0 or a boolean "true", that element will be considered selected. So we basically need to construct some math that will result in a true value for the parts of the mesh contained in our tile, and false for everything else.

Going back to the coordinates I drew over the terrain, if a tile has the coordinates (1,0) then we want to select every face whose x position is between 1.0 and 2.0, while y is between 0.0 and 1.0.

Of course, the terrain is hundreds of units in size, so we need 1 additional input which is the size of a tile. If we say a tile is 100 units in size, then instead we'd be checking if the positions are between 100-200 and 0-100 respectively.

screenshot of the node math to calculate if a position lies inside a tile Note that you can right-click and view image in another tab to see the full resolution!

This bit of math pretty much just converts a position in "world space" into a coordinate in "tile space". If the X position is 142.5, we'd first divide by our tile size of 100, giving us 1.425, then round down to just 1, which is the X coordinate of the tile that position lies within.

Then, we can compare the resulting X,Y tile coords to the input tile coordinates, and that gives us our selection to copy from the world mesh! Now we can just plug the "Result" output into the "Selection" input of Duplicate Elements and we should have our tile mesh.

screenshot of the resulting mesh tile in blender A single tile mesh

This gives us the basic tool with which we can build a large terrain mesh, and then non destructively split it into exportable tiles while still retaining the ability to edit the terrain. So the workflow thus far goes like this:

I built a new, larger test terrain model and applied the workflow to it and ended up with something like this, where the tiles are arranged on the right side of the base terrain mesh.

a screenshot of two versions of the world terrain mesh, one split into separate tile meshes I'll explain why the right side is more detailed next

Adding procedural details

I didn't want to have to manually model every little bump onto the terrain myself, even if I'm using brush based tools. It also made sense to me that I could model the broad strokes of the world map in relatively low detail, and add finer details and subdivisions in the tiles themselves.

To do this, I made a separate Geometry Node group for the base world mesh itself, which simply applies a single subdivision and adds world space noise to the vertex positions.

geo node setup for adding noise to a mesh Using noise to offset vertex positions

This adds some basic noise offsets to the world mesh, which are configurable via some inputs to the modifier. Now we can take this same idea and apply it to the MapTile node group to add further detail.

geo node setup for MapTiles with added noise Adding noise and subdivisions to the map tiles

This is the whole MapTile node group so far, with our tile selection + noise and subdivision sections added. The super cool thing is we can still edit the base mesh normally and everything will update in place! And with the extra subdivisions and noise, even really basic little lumps we make end up looking like pretty nice bits of rocky terrain.

This is pretty great, but what about caves?

Caves!

A tricky detail about this setup is that we can't actually edit the tile meshes directly, because they are generated via Geometry Nodes. That means we can't just go in and start pushing vertices or extruding in order to create caves. We could do this over in the world map base mesh, but there are probably cases where that is too low fidelity for the kind of details we want to add.

What we can do however, is go even further into the Geometry Nodes rabbit hole and add caves using nodes! My idea for this is pretty simple: There is a boolean operation node, and using this we can probably cut out chunks of the tile in order to construct caves. To do this though, we'll first need a cave mesh to cut out of the terrain.

Creating caves using splines

One nice way to author a cave would be using a spline that defines the path the cave takes. I hope you're ready for another Geometry Node setup.

Another damn Geometry Node group Extruding a cave along a spline using the curve radius parameter

The more of these we do the less I'm going to explain them, but you can examine the nodes used here and kind of figure out what's happening. The high level of it is that we're using the Curve to Mesh node, which extrudes a given profile shape along the curve. Then we're once again adding noise to the result so our cave isn't weirdly smooth and circular. Another critical part is using the curve's "radius" parameter to scale the cave which allows us to make narrower and wider portions.

This gives us a decent mesh we can use to boolean out a cave from our tile.

Cutting caves out of the terrain

Rather than having to add a node for every single cave we add to our tile, what we can do is instead source our meshes from an entire Collection. This way we just stick any cave meshes we want under some Collection we make, assign that as the Cave collection in our MapTile modifier, and any arbitrary mesh we stick in there will act as a cave on our terrain!

the node setup for doing boolean operations using a collection as the source Our spline cave in action

Now we can start to see how powerful this non-destructive setup is. We can edit the spline of this cave and the boolean + noise nodes will automatically update the resulting terrain. We can also of course still make edits to the base terrain mesh without ruining any of this!

With this workflow so far, you could make a whole cave system and suddenly decide it should actually be on the other side of the map and just move it there no muss no fuss.

So, now we have a large piece of terrain which has been split into tiles, extra details added, and it's all non destructive! Pretty sweet, so how do we use this in-engine? That is a great question that I'll cover in Indie Open World Workflow Part 2!