Introduction
This project was born from the 2D version of this game, which I present separately here. I always wanted to make games and had undertaken several attempts in the past that all ended up being glorified tech demos, without any actual concept or idea for gameplay. With this project (and the 2D version of it) I intended to change that, follow the good advice of a great friend of mine and try to first implement a minimal playable game play loop on which I could then iterate.
Idea and gameplay concept
For Fablefarm, I actually had a concept, an idea for the game play loop: It would be a non-violent third person survival game with farming and base building functionality. The player farms and collects food and building materials, catches and cares for magical creatures that in turn reward them with magical items that enable the player to catch the next bigger creature. The end goal of the game is to revive the mother of the player, who has been petrified into a stone statue. The player must collect 4 dragon eggs, hatch them and grow them into adult creatures, so they would reward them with their magical essence which can be used to craft a potion to lift the curse from the mother.
Limitations in 2D
While I really liked the look and the feeling of the 2D game, I realized that some of the things I wanted to achieve were very hard to do in 2D. I will briefly summarize the limitations I faced when trying to make this game in 2D
Obstruction of view
I wanted to have large forest areas where the player would have to collect resources and items between bushes and trees. I wasn't able to convey the feeling I had imagined. Trees, rocks and bushes would obstruct the view of the player constantly preventing them from collecting items effectively. The straight-forward solution, which is to use transparency on objects in front of the player didnt fully satisfy me.
Limited build system
Additionally, I wanted to have a build system in the game which would allow the player to place buildings. This was hard for me to shape out in 2D, in the end I just implemented placing simple AI generated premade buildings, which wasn't enabling the creative expression that I had envisioned for the player.
Character animation
I also struggled a lot with character animation. At first I used an asset from the Unity asset store; a sprite based character that had some simple walking and idle animations. I knew I wanted to have a custom character though, and so I generated a front and side view of a character using generative AI and then I tried to do the animations in Blender. It was very hard to make this look good, in the end it always looked like a piece paper being folded. To make better animations in 2D, I realized most game studios create 3D models and then render them from different points of view to create sprites for 2D animations. This was basically what ultimately pushed me to move to 3D; if i had to animate in 3D, why not do the whole thing in 3D.
Challenges of the third dimension
Apart from the obvious challenge of everything having an additional dimension that needs to be considered, the main challenge in 3D for me were the assets. I could no longer use the strategy from the 2D version, which was to use the recent rise of generative AI to just generate my trees and bushes, I now needed actual 3D models. I did a few attempts at making my own models in Blender, but quickly realized that this was slowing me down immensely. I wanted to make the game, not the assets. I would still be able to customize some of the assets later on, once I reached a playable state in the project. So I went to the Unity asset store and quickly fell in love with the low poly assets that were available there. There were many, and they were looking great. Going low poly also meant that I didnt need to use a lot of textures, which would further simplify things. It also would reduce the need for performance optimizations due to the lower poly count of those assets, which would further speed up development.
Procedural world
I wanted the world to be infinite, and different for every playthrough. I had found an amazing tutorial series for procedural landmass generation in Unity by Sebastian Lague, which I used already in the past to create one of my tech demo projects. At the time, I really struggled when it came to placing entities efficiently on the world, such as trees, rocks and most importantly grass. I used to place each grass instance as an individual Unity game object, which was putting way too much pressure on the CPU and it ultimately made me give up on that attempt.
Chunk component system
In this version I implemented a versatile system for loading and managing chunks of the procedural world. The world is split up into chunks that are Unity game objects, so they can be enabled or disabled once loaded to save processing resources. The world chunk then has a list of so-called world chunk components. These represent different features of the world. Examples for world chunk components are
- Terrain component
- Water component
- Tree component
- Bush component
- Rock component
- Grass component
- Ambient particles component
These components implement a common interface, which allows for efficient loading. Each component implements a load
function and when it is done loading, it fires a loaded
event. The world chunk is then notified of each loaded
event and once all components have loaded, it fires its own chunkLoaded
event notifying the world generator. Loading happens asynchronously and some processes are offloaded to separate threads, for example noise generation.
Seed
At the root of the world generation lies the seed, a random number, which is determined by the user at the start of a new game. It is passed to the noise generation algorithm that lies at the root of the whole procedural generation. The world generator uses many different noise maps that are ultimately all based on the same seed, just offset with some hardcoded integers. This makes sure we dont end up getting the same noisemap for terrain height as we get for vegetation masking.
Grass
I spent an insane amount of time on the grass rendering. As mentioned earlier, the first approach using game object did not allow me to have the grass density I was looking for. So I fell down a rabbit hole and ended up implementing my own custom grass shader pipeline. It consists of a compute shader, that just receives the vertex positions of the terrain of the chunk the grass component sits on. then it computes the vertex positions of grass blades for each terrain vertex and writes them into a buffer that is then forwarded directly to the graphics shader that takes care of rendering the grass onto the screen. This way, the CPU is only minimally involved in the process. All it does is sending the terrain vertices to the shader and then updating a couple of uniform variables at runtime for player and animal positions. Those are then used in the compute shader to displace the grass around moving entities.
Procedural fences
One key aspect of the game is to capture and tend to animals. So I clearly needed fences to allow the player to create animal enclosures. I didnt want this to be grid based or free placement of prefab fences, because I found it looked boring. I wanted the players to be able to draw the fence on the ground and then render and update it as they go. I was heavily inspired by the game Tiny Glade here, which has amazing procedural generation for fences, walls and all other kinds of building parts.
Build system
I wanted the player to be able to build their own farm house. I found an amazing asset on the Unity asset store for modular low poly buildings. I then implemented a system which places snap points on these building parts and does ray casting and allows the building parts to be "intelligently" placed, at least it think it is more or less intelligent. This system is highly inspired by Valheim which has a quite finnicky but very versatile build system that allows the players to be creative and make insane buildings. I wanted to allow creative expression in the same way in my game.
Animals
Animals are a core part of this game. I found some amazing low poly fantasy animal assets in the Unity asset store which I bought. I used Aaron Gransbergs A* pathfinding project to create a moving grid graph around the player that updates in realtime while the player moves through the procedural world, enabling the NPC entities to do pathfinding around the player. The player wouldn't be interested what happens outside of their view anyways, so this was a perfect solution for my problem. I implemented a basic deer AI, which consists of a state machine that transitions between resting, idleing, grazing, and fleeing state when the player is in the field of view of the animal.
Multiplayer
At some point, I became too inspired by Valheim and other cooperative multiplayer games and decided my game had to support that as well. So I did a large scale rewrite that would make the system work using Netcode for game objects. It was a giant undertaking, that made pretty much every single feature of my game much more complicated than it originally was. I might also just call it a mistake, because I think this is pretty much the reason I haven't continued with the project now for quite some time.
Performance considerations
One thing I learned during this project is that making 3D games is hard. It's very easy to do something seemingly harmless and completely tank the performance of your game. You constantly have to monitor the frame rate with each and every change you make and see how it impacts that. There are a couple of things I did to make the game run smoothly on my machine, at least.
LODs
Every model needs level of detail models (LODs). The further away from a tree the player is, the less detailed the model will be. I use this quite agressively, reducing most importantly the amount of poligons rendered for each tree, for example The same concept is very agressivley applied to the grass. The further away a terrain vertec from a player, the less grass blades are rendered on it. After some distance, no grass at all is rendered. This can be hidden very well in a third person setting, since the player normally looks at the world at an angle, that makes it impossible to see far away grass anyway.
Culling
Everything that cannot be seen should not be rendered. The easiest way to achieve this in a chunk based world is to just simply turn off world chunks that are not within the view frustum of the player. For memmory management, world chunks that are outside a certain distance treshold from the player can be destroyed altogether, freeing up the corresponding memory
Object pooling
I use object pooling for all entities that are spawned in the world. This means that I dont destroy and create entities when they are needed, but rather just move them around and disable or enable them. This is especially important for things like trees or rocks, of which we need to draw many in the vicinity of the player, but much less so far away from the player. By simply moving entities around rather than instantiating new ones, we can use the same memory for a given entity for as long as it is instantiated. This leads to less memory framgentation and less garbage collection calls being required. It also gives us a good estimate of the memory usage of the game, since we can put hard limits on these object pools, effectively preventing the game from instantiating more than foreseen.