Final Project Post 1 - The idea, problems and solution

Elemental Minecraft

The idea

 The idea was generated in order to fulfil the requirements of the project, which were to create an educational, science-based game aimed at children that are 8 to 12 years old. After a while of brainstorming and discussion, the group agreed upon a Minecraft style game that includes the combination of Elements, in order to increase the educational aspect. The idea came out of the inspiration of two pre-existing games, Minecraft and Little Alchemy.

The Problems

Up until this point, all games that the group created simply involved adding and manipulating GameObjects through the Unity editor or instantiating entire GameObjects through code from a prefab. For an idea like Minecraft, which was the basis of our world design, it doesn't take long to see how this form of world generation becomes an issue. A Minecraft style world is also known as a Voxel and as such will be called so from this point forward. Voxel world generation needs to be far more efficient than our previous game creating knowledge allows. If we were to generate a world where all cubes are rendered, performance issues become apparent in a very short space of time, as can be seen in this example:

Imagine a world of only 1000*1000 cubes, with a height of just 10 cubes. If each of these cubes represented a prefab GameObject, there would be 10,000,000 instantiated cubes in the scene, each with 6 rendered sides and a texture, which would be incredibly inneficient and cause issues for a lot of PCs.

When breaking down this problem we can start to look at a few different ways of increasing performance. For the sake of this project we focussed on Efficient Rendering, Chunking and Render Distance, which will each be discussed next.

The Solutions

Efficient Rendering

When thinking about the above issue of rendering 10,000,000 cubes, it's quite easy to see why this would be very inneficient. A large majority of the cubes are not visible to the player, so to increase efficiency these blocks should only be rendered when they become visible. So, what blocks are visible? If we take a surface layer of cubes within our world, and empty space ("air" blocks) above it, we can assume that any cube face that is in contact with an air block could potentially be visible to the player and should be rendered. So, how do we create cubes from scratch then only render sides of that cube that are in contact with air? To do that we needed to learn how to generate and render squares, then combine squares to render cubes while still giving us control to render only the faces of the cube we wish to, this also required us to learn about mesh triangles and can be seen in the following code snippet:
The "CubeTop" method refers to a single side of the cube. Each of the other sides (Bottom, North, East, South and West) are done in the same way with different x,y,z offsets. Triangles are then added to each face followed by a texture, which is added based on the argument "byte block" using a switch statement. This could have been done more efficiently using a BlockTypes enum class, but fits purpose for now. Now that we can reference different block types by passing a byte as argument, we can reserve the 0 byte for "air" blocks and do a check to see which faces are visible (in contact with air), then generate the meshes of the visible faces, like this:
This concludes the section on Efficient Rendering.

Chunking

Now that we can efficiently render blocks, we should group them into chunks so that the entire world doesn't need to be rerendered if there are any changes to blocks in future. This further increases performance. The way in which we chunk the world is quite simple, a Chunk consists of x*y*z blocks, then a world consists of x*y*z chunks. The smaller the chunk size, the longer it takes to initially load the game, but the more efficient it will be to render each chunk if changes occur, and the opposite can be said for larger chunks. As a side note, the chunks within this project have blocks placed in a semi-random way, so that the entire world is not completely flat. This is achieved using "Perlin Noise" and is not of importance to this blog as it was not something that was created by the team, credit for the Perlin Noise ("Noise") script in this project goes to Stefan Gustavson (stegu@itn.liu.se). All we need to know for this project is that we can increase the height and power of the Perlin Noise to generate different blocks at different heights.
When the world is generated, we can generate chunks, which in turn generate cubes as shown above, the logic for this can be seen in the World script attached to the repository linked at the bottom of this project. Now if we place or destroy a block, we only have to render a chunk again rather than everything, this does however come with one bug that needed addressing, which involved destroying a block on the edge of a chunk causing neighboring chunks to not render some sides, which was also fixed and can be seen in the "UpdateChunkAt" method of the "Modify Terrain" class.

Render Distance

Now that we have the world split in to chunks, we can choose which chunks to render. This is first done by assigning a distance variable, which can be used to load columns of chunks (so that tall chunks on the y axis are loaded together) and unload columns at a set distance, allowing for less chunks to be rendered. This creates a look that breaks immersion of the player can see parts of the world being loaded in, which can be counteracted by adding a layer of fog to the game.

The Conclusion

Now that we have implemented the above, we can see major performance increases, and one look into how much is being rendered shows just how major this is. The previous example that used 60,000,000 rendered faces is now reduced down to around 1,000,000 using the same 1000*1000*10 cube setup with an infinite render distance. Limitting the render distance improves this moreso, as an example a render distance of 100 blocks lowers the rendered faces to 10,000 without limitting the player at all.

Next Steps

The next steps for the project will be to figure out how to add and remove blocks to this generated terrain, so that we can begin with the Little Alchemy portion of combining elements to make new things.

References

This project so far has taken a very large amount of influence from: [Tutorial] Procedural meshes and voxel terrain C#
As well as the previously mentioned Perlin Noise reference. The textures and ideas behind the project were heavily influenced by Minecraft and Little Alchemy, which also have links above in this post.
A link to the entire project:  https://github.com/KAdams01/GMDFinalProject

Comments