A Very Important Message
Overview
This was a Ludum Dare 53 game jam project which I worked on for 3 days in April 2023 in a team of 5, where 2 of us did programming and 3D asset creation, 1 of us did just programming, 1 of us did 2D art and music and one of us did voice acting.
The theme of this game jam was "Delivery" so we decided to create a launcher game where the player has to guide a ballista bolt, carrying a message, from one kingdom to another over an infinite strip of terrain to prevent or incite a war.
This Jam is our highest-placed Ludum Dare entry to date, placing at 136th overall out of 1721 participants.
Terrain Mesh Generation
The inspiration of our game came from 2D launcher games which often the player is forced to travel a great distance in a straight line where the path is width is confined by the width of the window (or screen in the case of a mobile game) the game is played in. However, the game we wanted to make we decided would be 3D and played on a Computer so we needed an artificial way of encouraging the player to remain within a relatively narrow area as they travelled towards the target location. The solution to this was to create a narrow strip of land which infinitely generates off into the distance.
I started the terrain generation by creating a series of chunks, each with the width of the world (150 units) and a depth which was impossible for the bolt to travel in one frame even with maximum upgrades (50 units). I then set to creating a queue system which would delete chunks behind the player which were more than a certain distance away and add as many chunks as were deleted ahead of the player. This meant there was always a constant 25 chunks of the world in the scene at any given time even though it gives the impression to the player that the world goes on infinitely.
When generating the terrain, Perlin noise is used as a base to create height variations to simulate hills and mountains. A heightmap mask is then applied to the Perlin noise. The equation for the line to generate the height mask can be seen below. The idea is that by multiplying the Perlin noise result by the height mask values or 0 (whichever is higher), the terrain should be lowest at the very edges (so it can go beneath the water), highest towards the edges and then at an intermediate value down the middle. The idea of this is to encourage the player to remain as close to the centre of the path as possible.
Terrain Shader
In order to emphasise the infinite terrain, I decided to use a world-bending effect similar to what is seen in games such as Animal Crossing or Eco. This effect is achieved by displacing vertices on the y-axis by an amount which is relative to how far the vertex is from the camera in the XZ plane in the vertex shader. This means that things which are further away from the camera appear lower down than their true position, while things which are closer to the camera appear at their true position. This gives the effect the world is curved. This effect is also applied to all other objects in the scene, further helping the world to appear curved.
In addition to the world-bending effect, I also worked on the texturing of the terrain. The terrain uses several techniques to achieve the effect seen above. Firstly, the fragments are textured differently based on height with low-lying fragments being sandy, medium-height ones being grassy and the highest ones being rocky. Some noise is applied to the terrain height value before that is passed into a smooth step function in order to improve the blend between the different bands of terrain textures.
As the terrain is curvy, with normals facing in many directions, triplanar mapping has been used to remove stretching artefacts from the texture of the terrain. In addition to this, each of the three samples used in the triplanar mapping is actually a blend of three different samples of the texture, all taken at different rotations. This removes the gridded effect that the textures had on the terrain when standard triplanar mapping was used. A consequence of this is that each fragment samples 9 different textures for its diffuse colour, however, as the 9 samples come from the same textures the performance hit is not very large.