top of page

Simple Miner

Overview

  • Role: Programmer

  • Genre: 3D Sandbox

  • Engine: Personal Engine in C++

  • Team Size: Myself

  • Development Time: 2 months

High Concept

Simple Miner is my practice project of creating a similar Minecraft game using my own personal engine. There are multiple biomes, lakes, trees in the game while the players can explore the world freely. Players can also place and destroy blocks to build their own buildings and see lighting changes as time goes by. 

Core Mechanics

World Generation

    In Minecraft, one important feature is the random and giant world, including surface biomes, creatures, and caves. Even in a small rage, there are more than one million blocks. Therefore, the biggest challenge to implementing my own version of Minecraft is how to generate the world fast and precisely. 

    The first step is to generate a chunk. Chunk is a 12 blocks wide, 12 blocks long, and 128 blocks high area. The image on the right side shows an example of a chunk where the top side has dirt blocks and grass blocks, and the middle part has rocks and mines. The whole world is built up of millions of chunks.

    However, generating one chunk is fairly easy, generating millions of chunks at the beginning of the game would take forever. To solve this problem, I used multi-threading techniques to generate multiple threads. Whenever there is a request of generating / deleting a chunk, the job will be sent to the queue and the free threads will pick up the job. This allows the game to use the full power of the CPU to generate the world N times faster than before while doesn't affect the gameplay performance. 

WeChat Screenshot_20220220195020.png

    The first image below shows how the generator thread works. It uses a mutex to lock the list queue and loop through the whole queue to see if there is any generation request. Then, it generates the blocks for the requested chunk and waits for the next request. The second image below shows the actual result in the game after applying multi-threadings. The game will load all the chunks nearby the players in a certain range and remove any loaded chunks far away from the players while the framerate is still 60.  

WeChat Screenshot_20220220200539.png
ezgif.com-gif-maker (10).gif

    Notice that the image above shows how many vertexes the game needs to render every frame. If it renders all the vertexes, the performance would be really bad. Hence, the solution is to only load surface vertexes that players can directly see. The image below illustrates the rendered vertexes in the game. 90% of the vertexes in each chunk are not rendered, which saves 90% of the performance.

WeChat Screenshot_20220220210631.png

    Another issue when generating the world is how to have nice curved hills, mountains, and plains. Since each chunk is loaded individually, there is no way to pass a public value during the generation process. The solution is to use noise just like what Minecraft does. The noise in 2D is a random and continuous line, which allows the system to generate hills, plains, and even canyons. Also, since noise is random but consistent in each seed, we can generate the same random world every time as long as the world seed is the same. 

    I was also able to implement different biomes using noises in the game. For example, whenever the noise value is greater than a certain range, then this area is now desert. If the block's height is greater than 80, then the block is covered by snow. If we set up a water level, then we can have rivers, lakes, and oceans. The image below demonstrates how the noises apply in the game to generate different biomes.

WeChat Screenshot_20220220212320.png

Lighting and Fog

    Unlike the majority of other games, Minecraft doesn't use directional lighting. Instead, the lighting expands across each block and decreases one level after each block, which is why if players stay in a cave and leave one hole to the outside, only the areas near the hole will be lit. To build the same lighting system, each block needs to have a variable that stores the highest lighting it has in all six directions. The image below shows how the lighting works in the game.

WeChat Screenshot_20220220213231.png

    As I mentioned before, the game has millions of blocks that need to be rendered in each frame. Calculating the lighting for each block seems to be expensive. Therefore, we only want to recalculate the lighting whenever there is a lighting source change or a block change nearby. If there is a change, the system will mark this block as "dirty", then it will expand at this position and mark every block and chunks nearby dirty. Then, a function would process every dirty block in the list and mark its neighborhoods as dirty if they also need to change their lighting values. This process wouldn't take very long because most lighting sources only have less than 10 lighting values, which may only affect 3-5 chunks maximum.

    The outdoor lighting is handled differently comparing other lighting sources. It is a default value passed into the shader since it is the same for every single block. Inside the shader, it will blend the indoor lighting color and outdoor lighting color based on their values to avoid a sharp boundary. The image below shows the situation of how indoor lighting and outdoor lighting are mixed together. 

WeChat Screenshot_20220220214309.png

    The first image below is the pixel shader used when rendering blocks. It calculates the final color based on passed lighting values and colors. I also pass fog information into the shader. It uses the sky color as the fog color and calculates fog fraction based on the distance. By using this simple trick, I can create new weather in the game without any complicated systems or classes. The second image shows what fog looks like in the game.

WeChat Screenshot_20220220214622.png
WeChat Screenshot_20220220214544.png

The Physics System

    Simple Miner is my first "real" 3D game in GuildHall, so one of the challenges for me was to implement 3D physics. Fortunately, all the blocks in the game are just boxes, so I didn't need to worry about polygon collisions. To handle box vs box collisions, the player class needs to do raycasting towards its current moving direction every frame. If the raycast hits any solid block, then the system needs to handle the incoming collision.

    The hardest part while implementing this physics system is to handle every edge case. Since the box is not a point, collisions can happen to every place on the box including eight corners. The code below shows how the physics system handles collisions. It will do box vs box raycasting first, and find the nearest hit place. Then, it will kill the moving direction towards that direction. This is very important because the box should keep moving even it hits something. For example, if players hit a box while they are falling, then they may stop moving forward but keep falling down. 

   

WeChat Screenshot_20220220220923.png

Post Mortem

What Went Well

  • The game is able to generate or load a world quickly.

  • The lighting works perfectly.

  • Players can move around the world without getting stuck in any weird places.

  • Many interesting biomes, trees, weather.

What Went Wrong

  • Many game settings only work on the current version. If I add new features, they need to be redesigned and implemented to fit with the new features. 

  • Performance is bad when loading more than 2000 blocks.

What Needs Work

  • More biomes and trees.

  • Lighting should apply to water.

  • The noises applied in the game can be better to create more interesting scenes.

Simple Miner
Overview
Core Mechanics
World generation
Lightig and Fog
The Physics System
Post Mortem
bottom of page