case study · 2022Minecrafui - A Case Study
@jorenrui / September 09, 2022
7 min read
Minecrafui is a simple Minecraft clone built to practice ThreeJS. It includes the basics features of the game such as first-person camera view, movement controls, collision detection, inventory / block selector, and sound effects.
For this project, I've used
Instanced Mesh for generating the blocks,
Perspective Camera and
Pointer Lock Controls for making the first person view work, then
Raycasting for collision detection.
- Individual Contributor
- Tech Stack
Goals and Motivation
I built Minecrafui as a way to practice ThreeJS.
It started when my employer asked whether I'm interested to work on a browser-native sandbox game built with ThreeJS. Seeing the opportunity to learn ThreeJS and try my hand at game development, I agreed. With this, I started learning by going through the ThreeJS Journey course by Bruno Simon. Although, I am able to contribute to the project by doing ThreeJS and Rails work, I feel my knowledge and experience is lacking and because of that I'm moving much slower.
So with this, I decided to build my first ThreeJS side project called Minecrafui.
Tech Stack Decisions
Moreover, ReactJS is the library / framework I am most experienced with and most comfortable in using. Meaning I can finish the app more quickly and that I can focus more in practicing ThreeJS.
As for not using React Three Fiber, I decided to focus on practicing and being comfortable with ThreeJS first before diving into it.
Not wanting to use complex bundler like Webpack since I'm not really doing a production kind of code and there's not much customization with the bundler that I needed, I decided to use a quick and simple bundler which is Vite.
Implementation, Challenges and Solutions
I only know and built tutorial-based ThreeJS apps so I'm not that confident yet in making my own. Nevertheless, I tried my best to find solutions to problems that I encountered while making it.
I started building the game by focusing on making a box move using keyboard controls, specifically
D. It was I had an idea how to move the positioning of objects from the course that I took. I just needed to set the block to move in a specific direction only when a certain key has been pressed.
The first challenge that I encountered was making the camera follow the cube and let the users control the rotation based mouse movements. Then moving the cube on that updated rotation.
After much research, I've encountered the Pointer Lock Example and studied how they implemented it. With this, I finally was able to make it work.
Then rather than using the resource loader from the ThreeJS course code boilerplate, I decided to create my own logic for the assets loader which loads a specified count of assets in parallel to make loading faster. I also used event emitters in communicating from ThreeJS to ReactJS.
Then I started building my own Block class which accepts a block type and a biome as parameters. It was surprising to learn through experimentation that the leaves image, which is black and white, can be used as an alpha map to add transparency and that you can set a color to a specific face of the geometry. With this, the leaves are now transparent and their color depends on the biome.
The second challenge that I encountered is the block selector. Although I could get the selected block through ray casting, I needed the block selector to spawn a 1 unit beside the intersected face of the block. After much experimentations, I discovered that the intersected object's normal was the property I was looking for. Adding the position and the normal of the intersection object gave me the right position for the block selector.
The third challenge was the collision detection. I decided to use raycasters for this one. I was mostly having trouble with which is the front, back, left, and right collide because it changes when the player has changed its rotation. After much tweaking, I made it work. Tho I did try CannonJS then implemented the physics engine with web workers, but it was getting complicated so I changed my strategy.
The final challenge was optimizing the game. Increasing the number of blocks makes the FPS drops since a block is equivalent to one mesh. To fixed this I decided to try my hand at Instanced Mesh which reduces the number of draw calls in generating meshes with same geometry and material but different world transformations. At first I was so confused as to how to get an instance position from the instanced mesh. Tho in the end I learned that I need to copy the instance's matrix over to a dummy 3D object which I can modify and get properties like position, rotation, etc. Each block type would be its own instanced mesh. For placing and removing blocks, I ran the
updateMatrix afterwards so that the updates would take effect instantly. Then I created a new matrix, populating it with placed blocks from all the block types / instanced mesh for collision detection.
Lastly, I've added in inventory and sound effects for the game using ReactJS. With that my first iteration of the project is finished.
Lessons Learned and Moving Forward
I learned a ton regarding ThreeJS. Since I'm more of a practical learner, I gain more understanding on how the camera, raycasting and object placement works. Moreover, I learned new stuffs like Instanced Mesh, setting colors to faces, object's normals, etc. It also increased my confidence in building my own ThreeJS projects.
Although I would like to add more, I think I had enough learnings for now and will return to finish it in the future. I would also like to focus first on finishing the beta version of Sutle. Terrain generation and infinite world building is something I need to study and research more before diving to code.
Other features worth adding are the welcome screen, adding a first person view model, touch controls for mobile, and mobile responsiveness.