About

Master's Thesis project in Game Design and Technology at the University of Gothenburg

Experimenting with a new mix of game genres: extraction and deck-builder

Developed with Godot .NET in C#

Features generated dungeons

Various card and enemy types

Development period: Feb 2025 - May 2025

Status: Demo available

Download on itch.io
#1 Gameplay
Image showing the UI of Ice Adventure
Overview

The only resource in the game is cards: found inside chests and dropped from defeated enemies.

The game is turn based, with each action (moving, playing or discarding a card, etc.) counting as one turn.

Animation showing the movement of the player Animation showing player interaction with objects
Controls

The player moves using the arrow keys. Objects can be interacted by nudging them.

Holding down a key allows for continuous movement to speed up traversing long corridors.

Animation showing the movement of the player Animation showing player interaction with objects
Using cards

Cards are selected and played using drag-and-drop. Targets are picked using left-click for cards that need them. Targeting can be cancelled with right-click.

Cards can be discarded by drag-and-dropping them onto the discard pile.

Animation showing the movement of the player Animation showing the movement of the player Animation showing player interaction with objects Animation showing player interaction with objects Animation showing player interaction with objects
Enemies

The prototype features 4 common enemy types and 1 boss, each having their own logic, core stats, and spawn conditions.

Image showing the UI of Ice Adventure
Interactive main menu

This area serves both as the main menu and the safe zone. The player can interact with objects to perform actions like managing their inventory, changing the difficulty, viewing statistics, or exiting the game.

#2 Technical details

Utils.GenerateLoot(

  amount: 3

  dropRate: new Dictionary<Type, int> {

    { typeof(SmiteCard), 40 },

    { typeof(ScoutCard), 40 },

    { typeof(ChainCard), 20 },

    { typeof(TeleportCard), 20 },

    { typeof(WoodenKeyCard), 5 },

    { typeof(GoldenKeyCard), 5 },

  }

);

Loot

Loot is generated using the GenerateLoot() function, which takes a list of card types and their drop rates, and based on the amount parameter, returns a list of Card instances, where the distribution follows the dictionary values.

This is an elegant way for generating loot for ›chests and enemies.

Criteria: the input dictionary must contains at least one type that can be cast to Card.

Image showing the UI of Ice Adventure
Pretty walls

Walls are just solid cells by default. To make them visually more appealing, one can use a sprite sheet that contains textures for all possible wall orientations.

In some styles (such as in this game), the used texture also depends on the surrounding 8 tiles. Since each tile can be either wall (true) or empty (false), the total combination of the possible surroundings is 28, which is 256. However, some wall textures can be re-used for multiple scenarios, as their layout does not depend on certain cells.

The figure below illustrates how a certain wall texture can be applied in multiple scenarios. In the top-right corner of the image, surrounding cells are categorized as follows:

  • true: must be wall
  • false: must be empty
  • null: can be either
Image showing the UI of Ice Adventure

To address all 256 possible combinations, a Dictionary can be constructed, where keys are bitmasks representing the state of the surrounding tiles (empty ➝ 0, wall ➝ 1), and values are the corresponding wall texture's atlas coordinates.

Instead of populating the dictionary by hand, the above-mentioned patterns, consisting of the true, false, and null values, can be used to describe conditions for each texture found in the sprite sheet. Then, an algorithm can be used to generate the dictionary entries that satisfy the conditions.

AddBitmaskEntries(

  atlasCoords: new Vector2I(3, 1),

  pattern: new List<bool?>() {

    null,  falsenull,

    true,         true,

    falsetrue,  true

  }

);

This is what the AddBitmaskEntries() function does, which takes the atlas coordinates of a texture (atlasCoords) and a pattern of 8 nullable booleans, where each value represents the state of a surrounding tile in the order shown in the bottom-right corner of the image. The function will then generate all possible combinations of the surrounding tiles that matches the pattern, and add them to the dictionary.

var bitmask = GetWallBitmask(x, y);

var atlasCoords = bitmaskToWallAtlasCoords[bitmask];

WallLayer.SetCell(new Vector2I(x, y), 0, atlasCoords);

Finally, the dictionary can be easily used to access the appropriate texture by providing the bitmask that describes a cell's surroundings.

Animation showing the dungeon generation steps
Dungeon generation

The process is an extended version of the algorithm from Bob Nystrom.

First, a bunch of rectangular rooms are placed, which later get connected via mazes and doorways. Dead ends are removed to improve the exploration experience.

As the final step, enemies and objects are instantiated into each room. Their number and type depends on the room size, difficulty level, and other factors.