Grimmfog // Devlog #1

Published:

Init

Some time ago I watched a SsethTzeentach video covering a game called Path of Achra. The game is a 2D grid-based roguelike wherein the only real controls are moving or standing still. The meat of the gameplay comes from character abilities, effect stacks, and prayers, all of which can trigger other effects in synergistic chains. I was instantly hooked.

Fast forward a bit and I've been working a lot with SDL2/3 lately on my image viewer and on building a basic renderer backend for microui. As I've started to get more comfortable with SDL, I decided I wanted to take a stab at creating a game that's inspired by the chained effect triggers of Path of Achra, but with my own take on the idea. Grimmfog is that project.

The Plan (Loosely)

Without laying out every single idea that has popped into my head while working on this, permit me to share some aspects of the overall direction I want to go with this.

Visuals

I'm using the 1-Bit Pack from Kenney.nl for my tilesheet. I like the simple style of it, and using only static sprites provides an interesting constraint: animations (once the animation system exists) must be implemented by modifying the transform and tinting, or swapping the sprite entirely. I'm curious to see how much expressive mileage I can squeeze out with just those options.

Gameplay

The core loop will be moving around a tile grid in cardinal directions, one turn at a time. Attempting to move into a tile occupied by an enemy will perform a melee attack (classic "bump attack" style). I'd like to add ranged weapons at some point, but I haven't decided on that for sure yet. Item pickups, doors, traps and all the rest of the classic fantasy roguelike tropes are all planned.

Levels

The overall theme is very much "classic dungeon crawler". Spawn into a level, explore, fight, loot, move down to the next floor. I plan to switch up the level generation algorithm between levels.

Effects

This is the real meat of the game that I'm very excited to work on, but is also probably the farthest away. I plan to author the effect mechanics in Lua with a sort of "command table" style approach, while the Odin engine owns the actual world data and handles resolution of trigger chains. The definition for an effect will return a table that describes the conditions and consequences of an effect. The shape of this table isn't known yet.

Using Lua this way allows me to have the effect definitions be primarily declarative (while allowing small bits of computation where necessary) and affords an easy way to reload effects at runtime for testing, balancing, and debugging later. In the finished game, they only need to be loaded once to establish the rules since the Lua isn't being executed to implement the effect, only to define it.

Current Status

My approach to building Grimmfog is that of an expanding vertical slice. Rather than trying to develop any single system to completion before moving on to the next, I'm getting as far as is required to start building the next and make them work together, even if it's just to render a random invalid map.

This approach is proving useful to expose friction at the API boundaries and mitigates me trying to enforce a shape on the project before I really know what the shape should be. Continuously integrating minimal pieces allows me to massage things into place while refactoring is still cheap.

Renderer

The renderer is (as far as I know at this point) mostly complete. It's fairly basic because it's really just a thin wrapper over SDL3. It provides a Backend struct that holds a collection of pointers to the various SDL types needed to make the renderer work. It provides a collection of procedures for drawing a sprite from a region of the atlas texture, drawing a basic rectangle, drawing text, etc.

One small thing I did on a whim that turned out to be pretty handy is that draw_text returns an FRect which is the bounding box of the text that was drawn. This means to layout two lines of text one below the other, you need only offset the y position of the second line by the h value of the rect returned from the first.

World Data

This is currently the most recently implemented piece and is only just starting to take shape. I'm taking a layer approach to level data. What this means is that the Level type is a struct of arrays where each array represents a different conceptual layer of the world. The code might explain it better than I just did:

 1Tile_Kind :: enum u8 {
 2    Invalid,
 3    Floor,
 4    Wall,
 5}
 6
 7Visibility_State :: enum u8 {
 8    Hidden,
 9    Explored,
10    Visible,
11}
12
13// assuming entities will just be an ID for now
14Entity :: distinct int
15
16MAX_SIZE :: 128
17Level :: struct {
18    // tile type: floor, wall, etc
19    terrain: [MAX_SIZE*MAX_SIZE]Tile_Kind,
20
21    // tile visibility
22    visibility: [MAX_SIZE*MAX_SIZE]Visibility_State,
23
24    // tile occupancy
25    entities: [MAX_SIZE*MAX_SIZE]Entity,
26}

Next Steps

Currently I'm looking to get some level generation algorithms written so I can generate some valid levels. Then I'll likely start working to figure out how I'm going to handle world state and entities. I wish I had more to talk about and show here, but my innate desire to do things the hard way means I'm having to build the engine for my game as I build the game, so it's slow going.

If you made it this far, thanks for reading. If you didn't, it's weird that you're seeing this.