Today I’ll be talking about the procedural environment prop decorator I built for Reforge. The need for this came about from trying to solve the problem of decorating the external environment in interesting ways that also didn’t involve incredible amounts of manual positioning.
I knew I needed to define an area where the props would be placed. Using a quad seemed viable since it would give me Vector3 boundaries to use in the placement loop.
I was able to use the quad for dual purposes. It defines the boundaries for the props and acts as the floor plane for the game. Using Matrix4x4, I was able to visualize the four coordinates I’d be using.
The code is something like this:
Matrix4x4 localToWorld = region.transform.localToWorldMatrix;
for (int i = 0; i < region.mesh.vertices.Length; i++)
Vector3 vertex = localToWorld.MultiplyPoint3x4(region.mesh.vertices[i]);
GameObject sphere = GameObject.CreatePrimitive(PrimitiveType.Sphere);
sphere.name = "Boundary Marker " + i.ToString();
sphere.transform.position = new Vector3(vertex.x, vertex.y, vertex.z);
sphere.transform.localScale = new Vector3(10f, 10f, 10f);
Here’s what that looks like:
After I had that, I knew I wanted to randomly pull from an array of prefab props to use in decorating the area. Setting that up was simple enough:
public Transform objectsToPlace;
Then it was populated in the editor with the prefabs:
After getting the min and max X and Z coordinates, I ran a loop on both axes, incrementing by a small amount, 5 units currently. I also used a modulus of a random int on the iteration to make sure it doesn’t always place an item.
This was an early result:
Unfortunately, with realtime shadows enabled, this proved to perform badly on mobile hardware, so I opted to create a blob shadow using a circular plane and gradient texture. The current texture atlas is RGB, so I needed to create a new RGBA atlas for this purpose. It looked much nicer than without shadows and still had high performance on mobile.
Here’s what that looked like:
Most importantly for me was to give this effect of fullness in the environment. I bumped up the density of the decorations to see how it looked.
I didn’t like how it all looked aligned to a grid, even though it was. I opted to add a slight offset in X and Z using Random.Range(-1,1), just to tweak the position and break up the lines a bit. The results were starting to look pleasing:
Someone on social media noticed there would probably be a lot of overdraw with this many models on screen. He was very correct. This came about when I posted these two screenshots to Twitter:
After some thought, I added a scale offset using another random range and lowered the density with much better results.
The final piece was to exclude those areas I didn’t want decorated. I created a string array of tags I wanted to exclude and used the Physics.OverlapSphere method to determine which colliders were being hit. If any collider matched any of the excluded tag names, no object is placed.
The final results looked good. The forest looks full and it performs well even on several year old mobile hardware.
Finally, here’s how this all looks in game:
Total time spent designing and implementing this feature was about 4 hours.
In the next steps, I’ll be saving the data out, including the asset name, position, rotation and scale, to the server, which will be saved to the user account. Subsequent loads will be populated by this static data.
Thanks for joining along on the development of Reforge! More to come.