Post-mortem: Ghost in the Swamp


This article is also available on GitHub.

The swamp is vast and infinite.

You are the half-ghost guardian of a peaceful swamp. Humans invaded your homeland and stole three precious treasures. You need to spook invaders, retrieve lost items, and finally hide them away in Risky Boots’ secret island. You lose the game if you are exposed in human sight without Mana Points, sink in the swamp, or cannot take any valid actions.

Ghost in the Swamp is a turn-based, coffee break Roguelike game made with Godot engine. A successful round takes about 5 to 10 minutes. It is available on GitHub and itch.io.

According to GitHub’s commit history, I started this project a year ago. But I think I spent at most 8 months in development. The other 4 months are occupied by work, life and video games. I released the game at the end of August, and I intended to write this article in September. However, I “accidentally” downloaded Morrowind from Steam and you know, live and drink, outlander.

This post-mortem is composed of three parts: (1) designing game mechanics, (2) casting rays to detect environment, and (3) combining 9 small blocks into a whole dungeon.

Design Game Mechanics for Ghost in the Swamp

In Game Mechanics: Advanced Game Design, the author points out that in order to create an emergent game, that is, a game with a few rules and many more possibilities, you need to build feedback loops that interactive with each othter on different levels. A feedback loop involves the generation, conversion and consumption of resources. Starting from these ideas, I drew a few mechanics diagrams on paper, and then import them into computer using draw.io.

Figure 1: Game mechanics: the whole picture.

Above are the game mechanics for Ghost in the Swamp. They are used as early stage inspiration, which show the relationships between resources, but are not quantitive. Besides, I do not strictly follows the signs used in Game Mechanics. In my diagram, circles are resource pools, squares are sources or sinks, triangles are converters, and diamonds are triggers, which you can think of as a button, when you click it, something happens.

Figure 2: Final goal.

The final goal is a lock-key system. In order to sail to the final island, you need three items and mana points. In order to get these items, you need to spook NPCs with mana points. Therefore, the most part of the game is to keep the MP engine running smoothly.

Figure 3: Mana point engine.

Mana point source constanlty generates mana points. There are two factors affect the generation amount. NPCs have a negative impact on generation. Lighting a harbor with a ghost can boost the generation. You may cost extra mana points in the process, which partly depends on player’s skills, so I use a percent sign instead of +1. Ghost and mana points are intertwined in two more ways. The amount of mana points have a negative impact on ghost generation. If you are possessed by a ghost, you need more mana points to spook an NPC.

Figure 4: Three items: Rum, Parrot & Accordion.

The more itmes you have, the more mana points are consumed when spooking an NPC. In addition to this rule, all three items affect mana point generation and consumption in their own ways.

Rum increases the upper limit of mana point source pool, which means a skillful player may be able to accumulate more mana points.

Parrot allows PC to swap position with an NPC. This action itself costs no mana point. However, by using this action, if PC can spook an NPC from behind or side, it costs fewer mana points than attacking head-on. This action may also move an NPC to a remote place, who needs more time to collide into another NPC. The collision between NPCs slows down mana point generation. You are not guaranteed to benefit from having Parrot, so I use two percent signs here.

Accordion let PC enter a harbor and sail a pirate ship. If you can move to an advantage point, the Accordion can reduce spook cost, just as Parrot. Besides, since NPCs cannot enter swamp, if you can sail into a harbor by swamp and light it with a ghost, you completely avoid conflicts on land and save mana points.

Cast Rays to Detect Environment

Whenever player presses Space, the game shows available ablities in four directions. The game also needs to calculate mana point generation and NPC’s line of sight based on PC’s surrounding environment. All these requirements are handled by detecting rays.

At the start of PC’s turn, cast four rays in four cardinal directions. Check every grid’s terrain and NPC (if there is one). Once the ray hits an obstacle, return the end point data. The core function looks like this:

func cast_ray(cast_from_land, cast_from_swamp) -> Dictionary:
    pass

cast_from_land and cast_from_swamp are two functions that defines obstacles based on PC’s position. The returning Dictionary contains four pairs of data:

{
    LEFT: EndPointData,
    RIGHT: EndPointData,
    UP: EndPointData,
    DOWN: EndPointData,
}

EndPointData is a custom object that stores everything the game needs. If you are interested in the details, in my GitHub repository (see above), search CastRay.gd.

Combine 9 Small Blocks into a Whole Dungeon

The whole dungeon is divided equally into 9 (3x3) blocks, see blow. Each block has three variants, which contains both constant and random terrains. During world generation, the game picks 9 small blocks, then flip the whole dungeon horizontally and/or vertically. If you’d like to learn more about technical stuffs, search these files in the GitHub repository: InitWorldHelper.gd, DungeonPrefab.gd and FileIoHelper.gd.

Dungeon blocks:

a0 a1 a2
b0 b1 b2
c0 c1 c2

The benefit of this approach is that I only need to design 27 (3x9) map pieces, which results in more than 19683 (3^9) dungeon worlds. However, I highly recommend to use a hand-drawn map most of the time. I’ve tested almost everything (PC abilities, NPC AI, interacting with buildings, etc.) on a mostly static map before starting to generate a more dynamic world.

I use REXPaint to draw maps. Each map must meet three requirements:

  • Connection: A map must be connected with its neighbors by roads.
  • Function: Most blocks must have exactly one harbor.
  • Variation: Maps in the same block should look different from each other.

Let’s take c2 series as an example.

Figure 5: c2_0.

Map c2_0 is a blank template. It defines two grids that must be roads (-) leading to its neighboring blocks.

Figure 6: c2_1.

The backbone of c2_1 is two parallel horizontal roads and decorated roads (=), connected by a vertical path. PC can teleport to the harbor (H) from a2. There might be an impassable bush (+) in the lower right corner. There are also two roads of random length: E is the starting point, X is the middle point, and Z marks the maximum possible length (3 grids long).

Figure 7: c2_2.

In c2_2, the backbone is two long horzontal paths. The harbor is reachable from c1, rather than a2. There are more potential bushes than c2_1.

Figure 8: c2_3.

In c2_3, the backbone is L-shaped. If both roads of variable length are 3 grids long, there will be a loop in this block, which makes it easier for PC to get rid of NPCs.

Get Ghost in the Swamp

Leave a comment

Log in with itch.io to leave a comment.