Building a World of Yesterday – Rock Raiders Remake DevBlog #1

BergerBytes Procedural, Rock Raiders Remake, Unity 0 Comments

Consider this a bit of a “working document”. Things will be updated and changed out of time constraint.

One of the main goals of remaking Rock Raiders for me was learning more about procedural generation and runtime creation. It can really open up a lot of possibilities and, if done well, offer endless variety and content. I’ve seen procedural content, as a consumer, done really well and really poorly. I believe a lot of the success comes from solid game design and code. Luckily for me, I am focusing on recreating a pre-existing system, so a lot of the choices have already been made. That being said the solution I am going to talk about here is the third version and there is still a room for a ton of improvement.

Architecture

To give some insight into how things work before jumping into code, I want to give a very simple outline of the classes responsible for creating the map.

I did say simple! I am using a modified MVC model throughout the project, It’s more of an experiment than an informed decision, and the success or failure of which is sure to become a blog post of it’s own. The main part I am going to focus on in this post is the MapRenderer and it’s job of creating everything the player will see. It is the only class responsible for producing visuals and does not alter or change the data. This makes it very easy to swap it out with something else, for example, let’s say I wanted to create a 2D version of the game. All I would have to do it change MapRenderer out with a class that creates 2D visuals. That’s a little more of a “out there” example, the main benefit comes in the form of easier debugging and less spaghetti code.

  • The MapData object is pretty straight forward. It’s a model object with some helper functions for interacting with the data. A simple sudo-codeish would look something like the code to the right. TileData is an object holding data about a specific “Tile”, for example, The MapCoord it is located and the Type of tile it is. (“Floor”, “Dirt”, “Loose Rock”, etc) All very simple and boring but important to know going forward.
MapRenderer

In the first two iterations of the MapRenderer I was creating a single mesh on a single game object. It worked just fine but there were some drawbacks.

  1. There was only one material for the entire map, potentially nice for performance but it prevents have different shaders for things like Lava.
  2. With one material there haves to be one texture. Something completely possible with a texture atlas, in fact, I even created one and had it working just fine. However, since the textures are meant to tile, on some edges of textures there is something I can only call “Bilinear Filtering Bleed”. The end result was a line of incorrect colors between the textures ruining any hope of perfectly tiled textures. (Don’t even get me started on mipmaps!)
  3. The third issue was lighting. Now, this wouldn’t be an issue with the original game because as far as I can tell it only ever uses a single vertex light that follows the mouse cursor. But because I was playing with some ideas that I will withhold, for now, that wouldn’t work for me. With one mesh you are limited to the number of lights that can be lighting it. I would want to use pixel lights because ( it’s no longer the 90s ) it allows for much better-looking lighting and shadows. With Forward Rendering it can get very expensive to have a lot of pixel lights on a single object.

So I decided to change my approach and split the map into discrete game objects. The end result would look the same (in this case) and would eliminate the aforementioned drawbacks.  It turned out to also be an easier way to create the geometry, allowed for dynamic batching and material instance sharing. So a win all around in my book. (Please tell me the error of my ways if you are shaking your head right now!)

“Let’s get to the code already!” you say eagerly! Ok! Let’s jump in.

Creating the Vertices

First, let’s cover the creating of the actual Mesh pieces.
We create a 2D array of gameObjects the size of our map that will hold our meshes.

Working with 2D arrays requires a lot of nested for loops. It can be easy to make a type-o and cause bugs that can be hard to find. So I have created a simple reusable static function to help with this.

Within that 2D loop, we create a gameObject and give it the required components we will need. If you are unfamiliar with lambdas a simple nested for loop works just fine.

But I will still use the Loop2D for readability

Now we need to create the vertices of the mesh. You might have noticed that the array for the vertices is a single dimension array. All vertices in meshes are stored like this. So to loop through them in a 2D fashion, which makes it easier in our case, we just need to keep an external index count.

The only thing we are trying to determine when setting the vertex position is the height. One advantage to using separate game objects is the vertices are in local space to that object. For example, if x = 20, z = 18, vx = 0 and vz =1, the vertex position would be (0, y, 1).

  • Those are the easy checks. However, if a Tile is a Loose Rock wall, then we need to look to its neighbors to figure out which of its vertices should be raised. For example let’s say we are looking at the vertex (vx,vz) where vx = 0 and vz = 0. We would need to check the three tiles that would “share” the vertex in question. If any of the neighboring tiles are flat then the vertex could not be raised. And if any of the tiles are not valid tiles (don’t exist because they are out of bounds) then the vertex can safely be raised.

At this point in the vertex loop we know is the vertex should be raised or not. This means we have all the info needed to create the vertex.

Four vertices don’t do much for us at the moment; we need to calculate the triangles for anything to be shown. I achieve this using another static helper function which I will cover below. Once we have the triangles we can assign the vertices, triangles, normals and colors to the mesh, and give the mesh to the meshFilter of the gameObject.

At the end of the Generate function, you are left with a map rendered in the game.

Creating the Triangles

Coming Soon! (Need more hours)

Leave a Reply

Your email address will not be published. Required fields are marked *