Sound Horizons Devlog #5 - The Shapes of Mountains


After a series of entries dedicated to sounds, I finally have visuals for Sound Horizons! Huge progress has been made during these last two weeks. In this post, I'll talk about procedural generation, and how the landscape of Sound Horizons was built.

Travel plan

When starting Sound Horizons, I had to ask myself the question: how should the game look? Remember, One Colorful Grid was a 2D rhythm game with very minimalist visuals. To be fair, one could say that it only had a UI. What should exist outside of that UI? Should the game still be in 2D, or 3D? Could the UI be better integrated to the environment? How could we give Sound Horizons a pleasant visual identity?

This what we're starting from

After sketching some ideas, I found one for a 3D translation of the UI. Instead of display 4 bars pointing toward the center, these bars could be placed around the screen, and be directed toward the player. The notes to hit would come from the distant front, and advance toward the camera. It would be as if the player was in a tunnel! This is a common presentation in rhythm games (Guitar Hero, Beat Sabers…), that most players can understand immediately. And it would allow me to create a 3D environment around the game.

I took inspiration from Panoramicala unique musical game where the player interacts directly with the music and the environment at the same time. I'd have a lot to say about Panoramical, it's one of my major inspiration for game design. The interesting aspect for Sound Horizon is that each level in Panoramical has a strong visual identity (combined with a musical one), and uses procedural generation to create infinite landscapes that morph with the music. Since Sound Horizons must also makes its visuals react to the music, this seemed like a perfect fit! The bars in the UI moving toward the player would make sense with the movement of the camera scrolling forward. Plus, on the long run, I intend to make several levels. If each one of those take place in a different landscape, with their own animations and mood, it would really make them stand out and offer to the player the joy of discovering them.


Another relevant aspect of Panoramical is that it uses mostly low-poly, with a lot of abstract shapes.  Since I'm not particularly talented in 3D modeling, this seems a good direction to take in order to produce nice visuals. Thus, for the current level of Sound Horizons, I decided to make a procedural mountains landscape, that will evolve with each layers of the music.

The pitfall of simplicity

Generating procedural mountains in Unity is certainly something that has been made plenty of times! Surely there should be tons of resources and tutorial for that. As it turned out, like many other things in Unity, there are too many. A lot of different methods, some deprecated, some not scalable, some poorly documented…

With so many options, my first decision was to look for an existing terrain generation plugin. There are certainly a lot of them, why reinventing the wheel? They will do a better job than what I'm capable of, and come with advanced features that would allow me to experiment quickly. It's not like a have specific needs anyway (well, actually, we'll see later that it is not true). So after browsing the free plugins for terrain generation, I decided to use Map Magic 2. It has interesting noise features, is popular enough, well documented, and still maintained. The only downside: it doesn't have option for low-poly. Welp, so much for that style direction then. Maybe we can try to find settings that generate a close enough result? One neat feature of Map Magic is its node system that allows to create different kind of noises and mix them. By playing with it, I found out that a Cellular "Voronoi" noise provided very great landscape results! After mixing it with a bit of Perlin Noise, I was able to generate infinite chunks of mountains that could scroll infinitely.

This is what you've seen on last entry

There was an issue however. A deal-breaking one: Map Magic can not change its terrain on runtime. Terrain could only be built once, changing any parameters would destroy the mesh and recreate it (Map Magic's API doesn't provide methods for this anyway). This is problematic, because I intended to animate that terrain with the music! It's a core part of the game identity, I cannot skip that. Thus, I have to ditch Map Magic and find another solution.

How to build your mountain

There's no way to avoid it: I have to build my own terrain generator from scratch! Because yes, modifying the terrain properties during runtime is actually a very specific use of terrain generation, that most existing tools don't implement! I should have foreseen this issue before looking for plugins. Making my own terrain generation will be harder, but will give me more freedom afterward. And hey, it means we can actually make it low-poly after all!

When looking specifically for low-poly terrain generation tutorials, I quickly found several great resources that interconnect between each other. I mostly followed this video by Kristin Lague, and this article by Evgeniya Glazycheva. Both use more or less the same solution, that not only allows to generate a low-poly procedural terrain from perlin noise, but also offers in editor tools to edit it! Without going too much into details, here are the steps of the algorithm:

  1. Place random points on a plane. This can be done simply by picking random coordinates for as many vertices we want, or by using a Poisson distribution to keep a regular distance between each points. I found the first method to create more surprising results, so I eventually kept it.
  2. Triangulate the vertices of the mesh. This means rearranging the points for them to form separate triangles. This is not the kind of algorithm you want to implement yourself, so I used the Delaunay triangulation found in the Triangle.Net library (which exist in way too many repos, finding one that is still maintained is surprisingly tricky).
  3. Generate a noise for the heights. This noise will determine the height of each vertex. We use a Perlin noise with a random seed, then enrich it with several "octaves". This means that after generating the first noise, we do several pass on it, each time with a lower scale and strength ("lacunarity" and "persistence"). This creates smaller details that make the terrain rougher in different ways (mountains, fields, smaller erosion, etc). With these options we can make either a smooth terrain or one with a lot of irregularities.
  4. Vertically position vertices and normal from the noise, with a heigth's scale attribute. This defines the strength of our generated noise on the final result, and how high we want our mountains to rise.
  5. Draw color based on altitude. We use a gradient to determine the color used on the lowest surface, and the color of the highest ones. This allows to make the mountains green on the base and white on the top!

This method already allows to obtain neat terrains, and easily iterate by twitching the options!

The in-editor tools make it easy to experiment different settings

Early landscape obtained with this method

Onward!

Now that I am able to generate a single chunk of terrain, it's time to move forward! To scroll through an infinite landscape, I used the classic old trick: I instantiate two chunks, a "current" one and a "next" one, and I make them move while the camera stays fixed. Then once the current chunk gets behind the camera, it is deleted, the next chunk becomes the current, and a new "next" chunk is instantiated behind the remaining one. It's very simple, and the illusion of a seamless movement is working! To adapt the noise for each chunk, all of them use the same seed, with an offset matching their position.

There are two chunks moving here. If you focus on a point on the left, you can see it persist to the right.

But wait! There's a catch. Remember how we generate our mesh? We randomly select points to position vertices. This means that two adjacent chunks won't fit together, since their vertices don't match. Sure, their heights will be consistent, because they use the same noise. But they won't be joint by common vertices, and will clearly appear as two separate pieces.

The heights are correct, but the horizontal misalignment of the vertices make the normal separated

To fix that, I had to alter a bit the generation of points. Before randomly positioning points, I take the last line of the previous chunk, and add it as the first line of the next one. Thus they share the same connection! Then to avoid triangulation moving this line of points on the next step, I only place the new point at a certain distance of the border. Now we have an almost seamless transition between chunks! I say "almost", because since our chunks are squares, if we're attentive enough, we can see their horizontal edge. It's like the random generation suddenly decide to align all points on a perfect line! But to notice it, you have to look at them from high above, which won't be the case in game. So it's safe to say that we're good!

The render in-game, sped up

Better noises

Although I put away Map Magic 2 to build my own terrain instead, using it allowed me to discover something: cellular "Voronoi" noise creates more interesting shapes than Perlin noise! It wasn't a complete waste of time after all. Thus I wanted to experiment with different types of noise. I only had to change the third step of the algorithm above. Unity provides an advanced math module to generate different kinds of noise, but for reasons I'll explain later, I opted for the Fast Noise Unity plugin. This allowed me to experiment with several noises, and mix them up in way that would have been too laborious in code. I eventually found a nice balance between a large exponential cellular noise for the landscape, and a bit of Perlin to smoothen it and add details. It results in neat valleys and mountains range that look more natural and create beautiful views!


This however comes with a price: Voronoi noise is more expansive to generate than Perlin. This is why I turned to Fast Noise in the first place: the Unity math module was not optimized enough to compute the noise value for all vertices in a mesh! And although Fast Noise is more efficient, there would still be a perceptible freeze when generating a new chunk. Yikes, so much for the smooth scroll! I tried to generate the noise asynchronously, using Unity's routines, but to no avail. The only solution was to reduce the number of vertices in a mesh, so that the calcul is not too demanding. Since we're in low-poly, the result is still consistent.

However now that I can take a step back, I realize I could a use a better method. Instead of using CPU, the noise could have been generated by the GPU with a shader. The vertices vertical position could have been updated more efficiently by a vertex shader! I could even have used Unity's Shader Graph for this, which would have allowed me to experiment with all kind of noises. It's something I have never done before, but that I would like to learn. I might experiment this method for other elements of the game. There will be plenty of visual effects to make, having a basic understanding of shaders would certainly help!

Make it rise

The reason I had to build my own solution for terrain generation was to animate it. It's time to do it! The basic idea is that the terrain will start flat on the first layer, then mountains will rise on the second. Making a really impactful transition! We only have to intervene on step 4 for that. Once the mesh is generated and the noise calculated for each vertex, we use a height scale value to determine how high our mountains are. Using a tween, we can incrementally update this value, and reposition the vertices and normal on each update of the animation. In the same way, I also update a color scale to use the lower values of the gradient first, and only apply the higher ones at the end of the animation.

But that's not all! I was also able to update the color and lights of the scenes! We start from a dark blue background to transition to a light green one, kinda like if the sun was rising on a distant planet. Instead of the default Unity skybox, I use a skybox plugin to customize a basic gradient. I also draw a basic 2D background, that I scale during the animation to follow the mountain's rise. And finally, I also change the environment's fog color to fit with the scene's ambient. Here is the result:

Colors are not definitive

The game finally starts to look like something! I'm really happy with the result I've achieved, especially in such a short time. I hope I won't lose this momentum.

The next step will be to add more elements to the environment, and more animations. Expect some other neat looking visuals for the next entry. There will be a lot of experimentation, I hope it will lead to pleasant results.

Leave a comment

Log in with itch.io to leave a comment.