As one of three programmers my main contribution in the project was the combat system. This included both the enemies and the surrounding system to support the turn-based combat. Another part of the system was the pathfinding, which used a tweaked version of Dijkstra's algorithm, that helped the player and enemies navigate in combat. Beyond this I was also responsible for the player movement controller and the player input implementation.
My primary challenge during the project was ensuring that the systems I built were easy for designers, artists, and other programmers to tweak and extend. This required exposing tile states through ScriptableObjects and keeping the playing field flexible enough for free room decoration and seamless integration with other gameplay systems.
Musspleheim is a 2D dungeon crawler where the player delves deeper and deeper into bug-infested tunnels in hope of reaching the end and defeating the centipede that controls them all. The game focuses on exploration and turn based combat.
Team: 8
Duration: 9 weeks
Engine: Unity
Keywords: Unity, C#, Pathfinding, Dijkstra's algorithm, Tilemaps
The playing field detects any potential obstacles as soon as combat begins and adapts to them.
AOE tiles stack, becoming less and less transparent as more enemies share an AOE position.
The team decided early on that we wanted the game to have the tactile feel of a board-game. To achieve this feeling in combat, we settled on a tile-based design where both the NPCs and player planned their turns through a dynamically changing set of moves. Because the in‑game environments were randomly generated, the playing field needed to accommodate many different room layouts. Due to this, the final system built on Unity Tilemaps and used custom tiles.
The custom tiles were divided into three functional groups:
Terrain tiles - used by the pathfinding algorithm to determine which tiles were occupied.
Path tiles - used by the player and NPCs to visualize their intended movement and actions.
AOD (Area Of Damage) tiles - used by NPCs to show how far they can reach to harm the player at the end of a path.
Each custom tile could change appearance based on its current state. Of these the path tile had the most variations, since it was used to represent both player and enemy movement as well as the player's other combat actions. These states and their associated images were exposed to the design and graphics teams through ScriptableObjects, allowing them to assign visuals to each state and iterate freely without programmer involvement.
The pathfinding was used by both the player and enemies during combat. The enemies used it to determine which path to choose to get closest to the player while it assisted the player in planning their turn. Each turn the player had to plan their path using a varying set of movement- and action tiles. To decrease the amount of clicks needed the path was automatically laid out to their chosen point on the playing field, showing whether the player had enough movement actions to allow it.
To achive this system, Dijkstra's algorithm was chosen over A*. This choice was made since all playing fields were small and the design team planned to introduce several terrain types with different movement costs. As such, A* did not provide a meaningful performance advantage, and its heuristic would require fine-tuning to remain reliable.
The game used two variations of Dijkstra’s algorithm. The first variation could detect and plan a path to a tile blocked by an NPC, while the second was a true Dijkstra that could not. As such the first one could be used when the player or enemies needed to plan a path to eachother, such as when the player planned to hit an enemy. The other variant was used at any other time. If no path to the player could be found by an enemy, there was also a timeout function to ensure the game always continued.
The path is transparent until it is selected by the player.
The closest path to a tile is automatically calculated by the game. If a tile cannot be reached with the current amount of movement it, and every unreachable tile on the path to it, will be marked with an x.
When the last enemy dies the combat ends and input swaps back to the exploration Action map.
Because the game exclusively used button input, we needed a system that allowed the same buttons to trigger different actions depending on the current game state. I implemented a context‑aware input router on top of Unity’s new Input System, ensuring that only the correct functions were called at the correct time. Each major part of the game, exploration, combat and inventory, had their own action maps which the router activated or deactivated based on the current state.
This approach resulted in some duplicate code between exploration and combat, since they shared many of the same inputs, but it gave the designers full freedom to iterate on keybindings without affecting other systems. It also made the tutorial implementation faster, as it could reuse the same input while being routed through a separate tutorial‑specific manager.
I’m continuing to refine this pattern in current projects to reduce code duplication while keeping the flexibility of state‑based input routing.