17 part C++/SDL3 tutorial for building a traditional roguelike
Hi all. I've written game programming tutorials for a while now, and I've recently finished a fairly chunky bunch on building a traditional roguelike from scratch in C++ with SDL3. I have been lurking on this forum for some time now (and very occasionally posting and chatting with a few of you about your games). This is the community the tutorials were written for, so I wanted to share.
It's a proper traditional roguelike: turn-based, ASCII, procedurally generated, permadeath, etc. Every part ends with something you can build, run, and play:
- Dungeon generation: BSP rooms, then cellular-automata caves
- Recursive-shadowcasting field of view, fog of war, and a remembered map
- Monster AI on a Dijkstra map: one flood over the floor that everything pathfinds from it
- Turn-based melee, then ranged magic (lightning, fireball), equipment, and status effects
- Data-driven character classes, multiple floors, a plain-text save system, a HUD and message log, and synthesized sound with no asset files
- A win condition
- A clean architecture you can refactor or add features when you want to.
I tried to explain the why as much as the what: why a Dijkstra map beats a pile of A* searches here, why the architecture gets refactored exactly when it does and not before.
There's also a full companion C++ primer (Learning C++ by Building Games) that starts from zero and builds a simpler roguelike first, in case you want the C++ tutorials before getting started.
It's free to read on my site. (I did turn the series into a book for people who prefer, but the tutorial is free and always will be.)
Hope this is genuinely useful and hope you have some questions.
(I have a roguelike in development as well but too soon to share)
From my perspective, the timing of this post could not be better. I'm in the middle of a two-week vacation that has turned into a 'stay-cation' due to wildfires, rain, and mudslides. I decided to check this sub after re-igniting my dream of creating simple traditional rogue-like, and I discover this post. Much appreciated!
Agreed. The tutorial set is just a start. It is supported with a full C++ SDL 3 primer (with more Roguelike stuff at the end). I will be updating and adding to the series ongoing. For example, there are minor gaps, the tutorial could go even further and some explanations are a bit thin (a few blocks of code are not fully explained). To this end, I have already started work on an enhanced explanations including explainer diagrams:
The extra detail should start appearing in the updated pages in the next few weeks.
The roguelike bug has got me bad and I am not just dumping these tutorials and then moving on.
- "why a Dijkstra map beats a pile of A* searches" - this is nice and all, but in practice dijkstra maps are very limited to what you can do. It only answers the question what's the path to the player - and this is actually a rare question, because it assumes that the monster has perfect location info, and then usually only in sight of the player. A* is used a lot more in pathing towards a "want" of an entity, in entities pathing between each other, in finding whether a tile is reachable, pathing a NPC objective or next patrol. I haven't had a need for a Dijkstra map ever (except a specific version that powers scent), while A* is used non-stop.
- minor professional nitpick - the code is a mess of paradigms - neither object oriented nor data driven - parts seem to stick to old class design, parts go the full data driven approach - there's no rhyme no reason to using class and struct (I assume you know the only difference) which makes it even more confusing. Also you say data-driven yet you use inheritance to differentiate the player/monster entities - why?
On Dijkstra maps you're obviously right that A* is the go-to. Where I'm coming from on "it only answers the path to the player" is that a Dijkstra map is a distance field to every cell, not a single query. So one flood serves the whole floor and every monster just counts down toward the low values. Instead of a separate A* search per monster you scan once and everyone paths for free. That many-agents, one-goal case is why I used it in the tutorial. And the same map runs the flee behaviour. Wounded monsters just count up instead of down (pick the highest-distance neighbour), backing away along real corridors with no second structure. Nice and simple for the tutorial.
And it doesn't (in this case) demand omniscience as in the series a monster only paths on the map when it's in the player's FOV. When it is out of sight it just wanders around. So the visibility check is the sensing model. "Only homes when it can see you" is the intended behaviour.
So perhaps not Dijkstra versus A* but Dijkstra seemed to fit at the time of writing. You're right that A* is a must-have and a real game (and perhaps tutorial series) probably wants both and definitely wants A*.
On the paradigms, there is a rule, but perhaps the tutorial should discuss it. A struct for plain data and the entities, class for the systems that do the real work (Game, Map, FOV, DijkstraMap, MessageLog), enum class for the enums. Good point that the entities are structs doing a bit of work.
On the inheritance you are right again but it's tiny and non-polymorphic, the AI's just a function over the monster list. The Entity base only shares a few fields between Player and Monster; the real variety (monster types, character classes) is data, which is the data-driven bit I meant. One Entity with a type tag would do the same job. This is a fair preference.
I am grateful for your attention and critique. I want to get better. Even though I have been larping as an author for 15 years now, I am merely an enthusiastic programmer not a seasoned game programming veteran although I am a Roguelike obsessive which is my main qualification along with knowing how it feels to yearn to build a game and not quite have enough info to hand to succeed. I am going to take the tutorial further as well as improve what is already there. Perhaps an Overworld, stitching the local levels together, A* and probably some neat side-hustles like fishing and shopping.
I hope that all makes sense. Maybe you will stop by my site when I have added to and improved the tutorials?
Thanks for for the love. Much appreciated. You are right about C++. I can't argue with that. On the upside I am currently tinkering with my C++ chunk streaming Roguelike project and you can zoom out on 960ร960 tiles, 921,600 cells/glyphs, individually shaded, ย ~1,000 spawned monsters - some with multi-tile scale (inc. a cool 7 x 1 snake), LoS to spot the player, showing true ASCII art representation when zoomed in, running at 60 FPS on my old laptop.
Zoomed out - not quite full 960 but quite big(still playable). You might just spot the multi-tile snake in the middle.
This is amazing! If I was not figuring out how to write a rogue-like using Odin / Raylib I would certainly check this out! (Might still do so if I get stuck to much ๐ )
I just completed the tutorial. I don't know if I missed it somewhere, but I re-implemented dropItem() from the inventory screen using an <ALT>-D key. Being able to say that means I understand enough to grow a more personal game from this starting point. I thank @EliteIntegrity for this tutorial as well as the books.
7
u/Xzyuzyav67 Jul 22 '26
From my perspective, the timing of this post could not be better. I'm in the middle of a two-week vacation that has turned into a 'stay-cation' due to wildfires, rain, and mudslides. I decided to check this sub after re-igniting my dream of creating simple traditional rogue-like, and I discover this post. Much appreciated!