Building a terrain engine for browser games (Games should start fast, and run fast!)

February 23, 2023Posted invideo games

I got annoyed at how complicated gaming is. You need a powerful computer, you need to find a game, decide if you want to buy it, wait for it to install and maybe register an account, and THEN you can hopefully start playing. That's a 2000s PC desktop mindset that still rules gaming, which is weird because the rest of the world are in a Tiktok mindset where we want things instantly and never wait more than a few seconds for a website to load. That's how games SHOULD be, and instead of complaining I decided to make my own game after the Tiktok mindset rather than the 2000s PC desktop mindset. It should be available in the browser as a normal website, it should START FAST and RUN FAST. On all devices. Without compromising on how big, beautiful and fun the game can be.

Now I'm gonna show how I (with help from others) created a terrain engine for my game with the goal of STARTING FAST and RUNNING FAST without compromising on the game's quality.

First, I found an orientation game from the 80s called "Forest", that simulates infinite terrain by doing simple mathematical functions on a manually created "profile" consisting of 256 heightvalues. I won't explain further how it works, but below is a sneakpeak, and here (read TerrainGeneration.pdf) you can read everything about how it works.

The creator of the game, grelf, was nice enough to let me use his code, and from his basic algorithm that creates a pseudo-random terrain that looks the same every time if you give it the same parameters (a random seed, and a value for the terrain's flatness and wideness), I created functions to modify the terrain, like raising and lowering it, creating lakes, islands and flatlands, and tilting whole areas to give more dramatic variation, etc. The terrain is a square grid of 1x1 m blocks, much like in Minecraft, with a heightvalue at each corner. These heightvalues are calculated mathematically on the fly to create the actual 3D terrain, so no 3D model files are needed. They would have been very big and take much time to load into the game, and since my game should START FAST this method was perfect for me. In this case, doing thousands of mathematical calculations to create terrain is MUCH faster than loading a 3D model file with the same terrain. The calculations are actually done on the player's computer, so we don't even have to think about the internet speed for this. Nothing is downloaded except the code that creates the terrain.

To make the game start fast I have to do as little things as possible in the start. So I only create the terrain that's close to the player, enough for the player to have something to drive around in and look at. The rest of the world/terrain has to be crested DURING the game, and it has to be created faster than the player drives, or the player will reach an empty world that hasn't been created yet. This is called "dynamic loading".

For the game to run at 60 FPS, the screen has to update every 17 milliseconds. In between this I have to create the terrain for my world. If I use more than 17 milliseconds for this, the screen won't update because it will be busy with my world creation, and this will result in LAG. So, every 17 milliseconds I have to pause the terrain creation to let the screen update. Then I have to get back to the terrain creation, so I need to know exactly where I was in the process when I last "took a break". So the terrain creation must be divided into small steps that I can pause from and return to with full consistensy, and it needs to get enough stuff done that the terrain is ready when the player reach new places. Do too much = lag, do too little = terrain won't be created in time. In programming language, I had to make a while loop that I could hop in and out of, which is the opposite of what a while loop does. Tricky.

In a once again Minecraft-similar fashion, I create the terrain in "chunks". Each chunk is 50x50 m big, they get loaded and unloaded as the player moves to new areas, and you only see the closest 3x3 chunks at a given time. If I would show more chunks, it would take longer time, and we don't have time for that. The dynamic loading makes sure that the terrain chunks outside of this 3x3 area are created in time, so they are ready to be loaded (become visible) when you reach that area. Here's how the loading and unloading of chunks looks:

Two things here can make the game run slow: the mathematical generation of heightvalues for the terrain, and the 3D rendering to the screen. Both these are reasons to not create and show more than the closest 3x3 terrain chunks at once, and one more thing I do to ease on the 3D rendering is to hide all terrain that is behind the player. You won't see that anyways. This removes maybe around 30% of the 3D rendering, without making anything look worse.

My game starts fast and runs fast much thanks to the low-resolution blocky terrain. But I still wanted a bit more high-resolution terrain, without compromising with the prestanda, so I made a function that upscales the blocky terrain by 4 times. Here I had to push what's possible to do in those 17 milliseconds between two frames. If I did this upscaling for all terrain it would take too much time, so I do it slowly in the background, only for the most relevant chunks, and when it's done the blocky low-res terrain gets seamlessly replaced by the 4x upscaled "smoother" terrain. The 4x upscaled terrain is also a lot more heavy to 3D render, so I only use it in the maybe 2-3 closest chunks. And since masthematically creating terrain with 4x resolution takes much longer time than the blocky terrain, I ran into problems in the dynamic loading. One task I did took longer time than the 17 milliseconds that are in between the 3D frames, which created LAG. It was a function in the 3D library I use, "three.js", that took too long time. The function copied all the thousands of coordinates for the terrain to a new place, and that took too long time. So I had to rewrite the function to copy the coordinates a few at a time, take a break to let the screen render, and then continue with the copying, and so on. With this, the LAG disappeared.

So, this is how you create somewhat advanced terrain for a game that starts fast and plays fast. Code from unusual places, relentlessly refusing current ideals, and compromises. And this was just the terrain. I also do similar things for trees and other objects too to make the game start and run fast.

Here you can try out the game as it is right now. If you're reading this in the future, this link will probably contain the same game but in a different version.