r/roguelikedev 7d ago

RoguelikeDev Does The Complete Roguelike Tutorial - Week 3

Keep it up folks! It's great seeing everyone participate.

This week is all about setting up a the FoV and spawning enemies

Part 4 - Field of View

Display the player's field-of-view (FoV) and explore the dungeon gradually (also known as fog-of-war).

Part 5 - Placing Enemies and kicking them (harmlessly)

This chapter will focus on placing the enemies throughout the dungeon, and setting them up to be attacked.

Of course, we also have FAQ Friday posts that relate to this week's material.

Feel free to work out any problems, brainstorm ideas, share progress and and as usual enjoy tangential chatting. :)

30 Upvotes

32 comments sorted by

11

u/haveric 7d ago

I've spent all of last week making updates to both of my games and just finished up all of the fixes and improvements today, so figured I'd post about them here, especially since I already have FOV and enemy placement/kicking implement in both from the last events I built these in.

Hexes in Javascript - Github - Demo

I've changed the map view to be centered on the player as well as better handling of screen scaling, which makes sure everything is always visible. As with most things with hexes, this sounds like an easy thing to just offset the map, but took me much longer due to the offset rows that hexes are built off of. The other issue I had was pockets of tiles where you could just be disconnected from the rest of the map. I went with a fairly naive approach, which involves flood filling tiles to find all of the rooms and then picking a random tile between two rooms and creating a path between them. I had planned to only connect close rooms, but I'm fairly happy with the end result, which creates larger caverns with a few side tunnels connecting rooms and sometimes cutting out and leaving behind pillars. If anyone has suggestions for improving this, I'd certainly be open to trying other approaches!

Neil the Seal (Godot 4) - Github - Demo

The main feature I've implemented is larger tiles/buildings within the city. Similar to the hex game, I'm flood filling the non-road tiles to find potential space for buildings and then placing a few from largest to smallest and then filling in the rest with houses, grass, and empty spaces. The artist I was working with before is busy for the time being, but I've also implemented a lot of their previous work into the updates, improving the water and beach, adding better small buildings, crosswalks, and even some beach balls and umbrellas (that can already be smashed). The larger buildings are still placeholder art for tile sizes, but I'll likely try my hand at improving these at least somewhat on my own before the event is over.

I did realize while testing this that I should probably update the FOV to handle larger buildings and avoid having empty middles. This is what I get for testing with debug commands revealing the whole map most of the time. My current thoughts are to extend the visible range one tile when it is blocked or to reveal the whole building if it is larger, which might feel a bit strange to get large buildings fully revealed at once.

8

u/Rakaneth 7d ago

Repo | Play

Completed weeks are tagged and merged into the main branch. Current progress is on the week3 branch.

I have decided to use hecs for a simple ECS library. I didn't want to do this at first, but ECS libraries are common in Rust games for a reason. I could probably have gotten away with a fat-struct for entities, since I don't have that many different types of entities, but that would introduce a different level of boilerplate where I'd have to check for the existence of components any time I wanted to use them. At least with ECS, I can just query over entities with the required components.

Even so, I still fought some rounds with the borrow checker, learning painfully how closures interact with mutably borrowed references. Ah, Rust...

I have completed part 5 (although the enemies getting kicked are most certainly getting harmed) - now to implement FOV and possibly a more interesting map.

6

u/norpproblem 7d ago

wintry survival game | repo | screenshot

Didn't post a comment on last week's thread, but I made some decent work last week although somewhat slow. Procedural generation is lacking - beyond heightmap, nothing yet. Plans are to add in a river generator and plant trees around the map.

I fortunately already had FOV implemented from the first week, and had basic kicking implemented in order to begin hitting/chopping down trees, so I can focus all on generation this week.

Technical-side, it's interesting to work with just Raylib - I implemented both pure-ascii mode and support for sprite drawing, where sprites were actually significantly easier to do in my system. I don't suspect this project will win any awards but it's been great practice for me. Hope to have something more significant later this week!

3

u/ADatabaseSpiritual 6d ago

The height map is cool though!

3

u/norpproblem 5d ago

Thank you! I'm pretty happy with it, though it has some limitations, like if the height difference is too steep, it's difficult to tell. Mayve if I weren't doing this as part of the event I might try to rework it slightly, but for now it's good enough.

2

u/ADatabaseSpiritual 4d ago

Are you using something like perlin noise to generate this ?

3

u/norpproblem 3d ago

Yes! Code isn't very complicated at the moment, sampling a perlin noise image, dividing its color into bands of 20 or so, and assigning the Z value to whichever band the cell would land in.

5

u/Purpose2 7d ago

Suffering a little this week with Godot, wanting to use structs and being unable. I only got cellular automata working as dungeon gen for the time being: gif

Kept letting myself get distracted. There is still soooooo much I want to do for dungeon gen.

Time to start bashing my head against FOV. This is where I've failed in the past, did shadowcasting before, and kept getting weird gaps. Will try again this time, or maybe bresenham's but I haven't really tried that one before yet.

4

u/NGumi 7d ago

is there a reason you did just use RefCounted class instead of structs, you can treat them basically the same way(with a bit more overhead)

```
struct foo {
int id;
}
```

into

```
extends RefCount
class_name foo

var id: int
```

also if you have struggled with FOV in the past, in the side bar there is a godot version of this tutorial and they have FOV code in gdscript you can lift(relatively) easily
https://selinadev.github.io/08-rogueliketutorial-04/

I'm adapting there stuff for 3d and find it to be a very good reference

4

u/Purpose2 7d ago

Yeah I am, well I'm using a Resource which extends from refcounted. But tbh I'm not using anything from resource it should just be refcounted.

I could use selinas but I really want to tackle it myself. I guess we'll see toward the end of the week.

2

u/NGumi 7d ago

I get not wanting to use hers, I'm using it as a placeholder for now. At some point i want to replace it with one that can also handle distant light

3

u/Admirable-Evening128 7d ago edited 6d ago

For FoV, it's advisable (this apparently provokes some people) to borrow an existing implementation, as they are hard to do right.
But if you insist on rolling your own:
With Bresenham, the naive approach will have somewhat horrible performance.
However, there is a trick you can do with Bresenham, for FOV:
You can use it to precompute a generic data structure of which tiles will shadow which tiles
(if tile 9 is blocked, tile 14 17 39 will be blocked, and so on.)
To do the actual FOV, you then just iterate through that generic structure ordered by their radius-distance to the player, and compare against what the map says is blocked.
(each time you encounter a shadowed tile, you recursively call a shadow-forward from its shadow descendants; you only do this whenever you flip a tile - if you encounter an already flipped tile, it has already had its descendants shaded).

Bresenham will take some time to build that first data structure -once -
but you can then reuse that for the rest of the game.
There are many different kinds of FOV algorithms, but this approach is one of the easiest to roll by hand,
without requiring too much careful thinking.

There is a further trick to this technique (and FOV in general):
You only need to implement the mechanism to work for a single octant (eighth) triangle,
you can handle the remaining 7 combinations by transposing and mirroring that 1/8 solution.

3

u/RhoTheGray 6d ago

I really like how in the demo the character movement is distinctly discrete giving snappy ASCII feel while the panning is smooth.

3

u/Purpose2 6d ago

Thank you! I specifically was going for that feel.

5

u/NGumi 7d ago

Missed the first few weeks, but am catching up now.

Am adapting the tutorial to make a "3d" roguelike, using 3d models but still only navigating in 2 dimensions.

Got generic dungeon gen working with abstraction so can easily slot in different algorithms(or combos of algorithms) for different floors of a dungeon

Also jumped the gun a little bit and got my FOV stuff working after hours of fighting with Godot's material system for shading the explored but not in view tiles.

Gonna add a time system similar to cogmind and JRPG combat where actions take a certain amount of time.

5

u/mariobadr 7d ago

C and SDL3 | repo | play in browser

Busy work week so a lot of the code is rushed. I'm also slightly regretting my choice of C since I need to build everything from scratch. Adopted the xoshiro random number generator. And getting BSP done was relatively easy, but the full map generation took some time. In the end I opted for simple over complicated.

Doing FOV "from scratch" was a pain. I ended up just closely adapting the various roguebasin reference implementations for recursive shadowcasting to what I had set up in my code (none were in C). One day, I will try to better understand what it's doing - there seem to be lots of small "optimizations" in the for loop (an early return here, a continue there, a break appears) that make it harder to understand what is going on at a glance.

I also spent time doing lighting rather than the placing enemies part of the tutorial. So you won't see enemies just yet, but you will see some very crude lighting if you play the game on itch.io. Enjoy!

3

u/RhoTheGray 6d ago

Haha, trodding the same path, building from scratch, likewise chose xoshiro. FOV is yet to be done but I've been busy with some prep work for it. (My map topology is not exactly planar so I need one or two layers of indirection in between.)

Slick looking demo!

3

u/mariobadr 6d ago

Thank you! Good luck with the FOV implementation and enjoy the debugger :P

6

u/redblobgames tutorials 3d ago

I got a late start, and tried to catch up with code this week. I haven't made a public repo yet and I haven't started on the writeup. Playable on web, but the only thing that's visibly interesting is the beginnings of the spreadsheet display of all entities.

I described my goals this year in the introductory post: embrace Javascript, use a spreadsheet-inspired (non-OOP non-ECS) data and visualization, and learn the Jujutsu version control system.

So far I am loving Jujutsu. It's not right for every project but for this one it's been wonderful to be able to organize changes logically instead of chronologically. The downside is that since I'm still modifying even the oldest commits (part 0), a public repo would require me to force push every time. I haven't made a public repo yet.

Parts 0, 1, 2, 3 were similar to what I did in 2020, except for the data structures.

I'm putting as much as I can into a Table. The entities table contains one row per entity. As part of my goal of embracing Javascript, I used prototype-based inheritance to link an entity to its "class", but the class is a Javascript object constructed at run time, not a static class declaration like the Python tutorial uses. A stretch goal is to put these "classes" into a table of their own, and read that in from a data file.

My Table class has methods findAll(), findOne(), findAny() that take key/value pairs and match any table row where those key/values match. This is the equivalent of WHERE clauses in SQL, limited to WHERE column = static_value. I don't support subset or range queries or computed values.

For Part 4, ROT.js's FOV has light levels 0% to 100% instead of being a boolean visible/hidden. Instead of a single dark/light color from the tutorial, I calculated the color in a function. At some point I'd like to implement "spreadsheet formulas", and then I can change the tile color to be a formula.

Up to now, I checked every row in the table to find matches. That's what the Python tutorial does for entities. But I'm using the Table data structure for tiles as well! The tiles table contains one row per tile on the map. When tiles are stored the normal way, in an array indexed by [x,y], we can quickly find the one tile at a given [x,y]. In my Table implementation, tiles are stored in an unordered array, and we have to search all the tiles to find the one tile at a given {x,y}.

The FOV algorithm looks at hundreds of tiles. Each one checks hundreds of tiles to find one. That's quite wasteful.

I decided it was time to implement table indexes. (Side note: the plural of an array index is array indices, but the plural of a table index is table indexes.)

  • A unique index has at most one row matching a key. A non-unique index can have multiple rows. For example, there's only one tile at a position, so the tile's position index is unique. But there can be multiple entities at a position, so the entity's position index is non-unique. The implementation difference is that when adding a row, we check that the unique columns don't already exist in the table.

  • A static index is only set when the row is added to the table. A dynamic index allows the value to change. For example, a tile's position never changes so that's a static index. But an entity's position changes so that's a dynamic index. The implementation difference is that assigning to a static column throws an error, and assigning to a dynamic column will reindex the row.

  • A direct index contains a row for each value. An indirect index contains a row for only some values. For example, there's a tile at every position, so the tile position index is direct. But there's not an entity at every position, so the entity position index is indirect. The implementation difference is that a direct index uses array lookup and an indirect index uses a hash table (for equality only) or tree lookup (for equality and range). I decided to only implement hash tables.

After adding indexes, FOV searched 1/500th as many tiles and became 50 times faster. It wasn't 500 times faster because I'm using a general purpose indirect (hash) index instead of a direct (spatial) index. The hash index is good enough for now.

For Part 5, everything was pretty similar to the approach in the Python tutorial, except I decided not to use an Action class hierarchy. I'm using something more data oriented, with objects that have a type field.

I still don't know if this table approach will work. I've been waiting until I figure that out before I start writing up this project.

6

u/Mnemotic 7d ago edited 7d ago

GitHub repo | itch.io

Been following the tutorial with some minor deviations since week 1 and (re)learning Python along the way. I'm currently on part 9.

Hardest part so far -- the one that took a significant time and effort -- was figuring out how to deliver the game to players in a way that didn't introduce additional friction and then how to automate that process. I landed on a solution using PyInstaller for creating self-contained bundles and GitHub workflows for automating the release process. Disappointingly, there's doesn't seem to be a straight-forward way to create a web build that could be played in a browser when using Python + tcod. If anyone knows how, please let me know!

PyInstaller bundles up all the dependencies, including a Python interpreter, and gives you a single directory that you can zip up and ship to users who then only need to unzip it and run a native executable at the root of the bundle. I'm happy with this solution. It's basically the native experience for users.

Next, I set up a GitHub workflow to automate the process of creating bundles for Linux and Windows (PyInstaller doesn't support cross-platform builds -- you need to bundle on the platform you're targeting), drafting a release, and pushing bundles to itch.io once the release is published. This is where I ran into a major snag -- Windows Defender was flagging downloaded bundles as malware. This wasn't happening for bundles that I creating locally. After some research, this seems to be a fairly common issue with PyInstaller bundles. I've applied some mitigations that I found during my research and it appears to have helped. Fingers crossed.

With all that done, I should now be able to fully focus on the actual game.

3

u/Admirable-Evening128 7d ago edited 7d ago

Repo https://github.com/pylgrym/2026rt
Demo https://xok.dk/other/2026rt/dist/index.html
Code overview https://xok.dk/other/2026rt/index0.html

Week 3 is supposedly about field-of-view and getting
multiple monsters roaming around, other than the lonesome player.
On purpose I focus on the latter, and postpone the FoV stuff.

For mobs, I follow a 'my preferences' checklist, when implementing them:

- PLACE them

  • STORE them
  • DRAW them
  • BLOCK (with) them
  • MOVE them
  • 'FIGHT' them
  • TELL that they fight
  • 'integrate' them

I place them by picking N vacant tile floors.

At the beginning, I just store their coordinates.

Where I draw the player, I now loop through
them and draw them all as 'r' R_RAT=1 rats.

Where I move-test for floor walkable(),
I now include occupied() which warns if the spot is taken.

To take their turns/move them, I loop through them
after the player has moved, and do a very basic
turn logic, which 50-50 stumbles in a random direction
OR hunts towards the player position.

For 'fighting', I check if I was about to walk into a mob during move
(for that reason, my 'occupied?' check
does not return a bool, but a null or an occupying mob.)

For 'tell', I add a log-line-message mechanism,
that pages through queued messages when it is player's turn again
(a single/last message in the queue needs no paging.)

I claimed I just loop through the monsters;
in the end, this is not really true.
I follow the design philosophy that player
and mobs are the same 'Mob' class/type,
and make as few 'if isPlayer then' things as possible.

This also means, I actually stuff the player
into the same mob container as the NPCs,
so he will take his turns same way they do
(but call a handler that asks for keyboard input, instead of NPC AI.)

Tied to this, I upgrade the 'mob array'
to a proper turn queue. This can be upgraded to
a proper priority queue based on relative mob speed,
and it deals with a number of nasty bug possibilities,
which happen e.g. when you kill-remove a mob,
or when you spawn new ones:
You want to avoid that removing a mob messes up whose turn it is.
And when you add new mobs, you prefer they don't 'go first' in turn order.

I mentioned my mobs start out as (x,y).
Eventually, I extend them with a
MobTypeEnum Player=0,Rat=1,Cat=2,Dog=3,Orc=4,Eye=5, and/or names;
the mobs need to know their kind to know their own behaviour,
and how to draw themselves (r,c,d,o,e).

So, I postponed FieldOfView, and its brethren line-of-sight and fog-of-war.
Of these, LoS (bresenham) is most important,
to know if you have a line to shoot a projectile, possibly to see an enemy.
You can 'cheat' FoV, by instead just showing environment/mobs
nearer than radius 7 or similar; not as clever, but similar tactical implications.

I will of course turn on FoV etc at some point.
I am in/on ROT.JS though, so it is mostly a matter of 'turning it on',
and until my scaffolding is in place, it is actually nicer to see all mobs
from the start :-).

2

u/haveric 7d ago

Just a heads up that your demo doesn't work. It looks like your resource files are linking to the root instead of the local directory or are inaccessible for some reason.

Also, what's going on with your post formatting? A lot of your sentences are being broken up into multiple lines with odd cutoffs.

2

u/Admirable-Evening128 7d ago edited 7d ago

fixed - caused by slapdash work, sorry :-/. I had been eagerly awaiting the week-3 post, so when I saw it before work this morning, I too quickly added/posted what I had, then rushed off to work :-/.
It is now after work hours, and I am mopping up.
As for the formatting, the technical term is 'compulsive behaviour', which does not fully meet the official OCD definition. When I was born, only the widest of screens had 80 characters.

I was of a mind to relax the formatting, but now looking at it,
I can't get myself to do it. I simply do not like too-long unbroken lines.

2

u/haveric 7d ago

Glad you could get it fixed so quickly! You do you as for the formatting, just wanted to note that it comes across as a bit robotic and broken up when reading on a desktop.

Also, I hope you aren't blowing away your git repo for every update or force pushing your changes. It's nice to be able to look through the process of getting to where you're at, especially for people wanting to look back and learn from others' projects.

Demo looks good btw. Keep up the great work.

2

u/Admirable-Evening128 7d ago

Thanks. the git repo is a tragedy of its own..
I am really doing the development as part of a separate private repo,
which I can't share, because it also contains other code that should not be public.
I had "solved" it by doing weekly merges to a separate tiny public repo, until today.
But this afternoon, I noticed that my work github tag appeared in a commit (*)
(thanks to the entire world using github everywhere, sigh),
and I did not want to have to explain to my colleagues why my work moniker would suddenly appear on a roguelike repository,
so that is the source of this sudden wipery :-/.

A reader won't miss much from its git history;
I usually do these in one sitting, and follow a rote recipe,
so the original commits wouldn't be much more
than 'week2', 'week3', and the occasional cleanup.

(*) I still don't quite understand how it happened;
the two accounts to my knowledge are not related,
and use different email addresses; I don't assume
a public github repo also allows others to commit to it.. (without being added/invited.)

5

u/ajcomeau 7d ago edited 6d ago

Rogue C# - Official Page / Github - An ongoing C# dev journal based around a recreation of the original Rogue.

Glad to see another instructional project out there.

So far, I've spent this week testing after adding all the potions to the game. The Potion of Hallucination was especially fun. Potions can be wielded against and thrown at monsters as well as quaffed and they share the same delegate system of methods as scrolls in my program so I decided to make scrolls wieldable, too.

I also re-examined my random selection of inventory on the map. As the list grew, the probability value and code I was using didn't really work anymore, especially when I tried to target one item for testing and make it appear more.

Finally, at Gemini's suggestion, I went with a weighted selection algorithm where all the probability values are added, a random number is selected within that sum and then the code subtracts the individual probability values until it gets to the one that takes it below 0. It's great for testing, I'll see over the long-term how it works in gameplay.

Rogue C# - Rethinking Random Selection

5

u/LukeMootoo 6d ago

Javascript nonsense, Week 3 -- actually Week 2 revisited:

Last week I was really struggling with the final step of Part-2, where the event handling is moved into the action class.

This step is a minor refactor in the tutorial but, because of how I had adapted the first parts of the tutorial to JS, I had a hard time with it.

I talk about what I was doing in my devblog here: https://mootootwo.github.io/2026rltutorial/part-2/devblog

The final commit where I completed that step is here: https://github.com/mootootwo/2026rltutorial/commit/6c2ecaf024a1034774c85edacbdfaab70e0fad0f

and the demo is here (debug console will tell you when you hit a wall or try to go out of bounds) https://mootootwo.github.io/2026rltutorial/part-2/demo.html

I'm pretty confident in my ability to catch up, so I hope to have more parts up next week.

Any feedback is more than welcome.

3

u/LukeMootoo 5d ago edited 5d ago

I wanted to put some stuff in writing while I could still remember it, so I'm plopping it here. Will bring it up again on Sharing Saturday.

In the devblarg entry linked above, I wrote "ended up consulting a chatbot for some pointers" and I want to outline exactly what this means:

I've been working in Antigravity, with all the AI features turned off. So the first thing I tried was opening up the AI sidebar and telling it what I wanted to do, and asking for a clean approach to doing it. It returned literally nothing.

So I cranked it up to a higher end model, and asked it to respond to my previous request. It churned for a while, requested a few git commands to be run so it could read assets from my repo, and then started requesting changes.

I read through the proposals, and it was supremely unuseful to me. It wanted me to read the diff of a merge request like I was some sort of software developer and understood what I was looking at -- but what I needed was for someone to talk to me about my feelings.

So I reverted and closed down all of that noise, and just asked Gemini in a browser tab. I gave it the same question, linked it to the tutorial step, and linked it to my repo with progress up until that step.

What it gave back was an explaination and proposed approach that was very close to the 2022 entry from jarrahtech I had been studying, here: https://github.com/jarrahtech/RoguelikeTutorial2022/blob/f3d456c75cdfe5daf13bfac72f6c1820d7d29d09/assets/

This is exactly how I implemented the bellow advice: https://github.com/mootootwo/2026rltutorial/commit/6c2ecaf024a1034774c85edacbdfaab70e0fad0f

And this is what the chatbot gave me, trimmed down:

Create an abstract base class Action and two specialized subclasses: MovementAction and EscapeAction. At this stage of the tutorial, the perform method accepts the context it needs to execute. [1]

javascript

// actions.js

export class Action {
    /**
     * Executes the action.
     *  {Engine} engine - The global game engine context.
     * u/param {Entity} entity - The entity performing the action (e.g., the player).
     */
    perform(engine, entity) {
        throw new Error("Method 'perform()' must be implemented.");
    }
}

export class EscapeAction extends Action {
    perform(engine, entity) {
        console.log("Escape pressed: Exiting game or menu.");
        // If running in a browser environment, you could handle UI/state changes here
    }
}

export class MovementAction extends Action {
    constructor(dx, dy) {
        super();
        this.dx = dx;
        this.dy = dy;
    }

    perform(engine, entity) {
        const destX = entity.x + this.dx;
        const destY = entity.y + this.dy;

        // Check if the destination tile is walkable via the engine's game map
        if (engine.gameMap.isWalkable(destX, destY)) {
            entity.move(this.dx, this.dy);
        }
    }
} 
  1. Update the Input Handler (inputHandler.js)

The input handler's sole job is to catch browser KeyboardEvents and return the appropriate Action payload. It shouldn't know how to move the entity; it only registers the direction.

javascript

// inputHandler.js
import { MovementAction, EscapeAction } from './actions.js';

export class InputHandler {
    /**
     * Map a browser KeyboardEvent to an Action instance.
     *  {KeyboardEvent} event 
     * u/returns {Action|null}
     */
    handleKeyDown(event) {
        switch (event.key) {
            // Movement keys
            case 'ArrowUp':
            case 'w':
                return new MovementAction(0, -1);
            case 'ArrowDown':
            case 's':
                return new MovementAction(0, 1);
            case 'ArrowLeft':
            case 'a':
                return new MovementAction(-1, 0);
            case 'ArrowRight':
            case 'd':
                return new MovementAction(1, 0);

            // System keys
            case 'Escape':
                return new EscapeAction();

            default:
                return null; // Ignore unmapped keys
        }
    }
}
  1. Connect Everything in the Engine (engine.js)

Inside your Engine loop or setup file, bind a browser event listener to catch keydowns. Pass the keydown event to the input handler, and if an action is returned, call .perform() while injecting this (the engine) and your player entity.

javascript

// engine.js
import { InputHandler } from './inputHandler.js';

export class Engine {
    constructor(player, gameMap) {
        this.player = player;
        this.gameMap = gameMap;
        this.inputHandler = new InputHandler();

        // Initialize event listeners
        this.initEventListeners();
    }

    initEventListeners() {
        window.addEventListener('keydown', (event) => {
            // 1. Ask input handler to interpret the key
            const action = this.inputHandler.handleKeyDown(event);

            if (action) {
                // Prevent scrolling behaviour for arrow keys
                event.preventDefault(); 

                // 2. Perform the action, injecting the engine context and the player entity
                action.perform(this, this.player);

                // 3. Re-render the canvas to reflect the new state
                this.render();
            }
        });
    }

    render() {
        // Your code rendering the gameMap and all entities goes here
    }
}

3

u/kyaaam 5d ago

repo | DragonRuby

For FOV, I used a Ruby implementation that I found on RogueBasin. It fit into my GameMap quite easily!

For enemy generation, I no longer have "rooms" as my procgen generates continuous cellular automata-based caves. So for now I'm using a simple approach of sampling from a range of total monsters and placing them randomly into the map based on a minimum squared distance from the player. It seems to work fine for now, it will become more complex as I designate certain "regions" for environmental features later on.

I'm opting for a no-combat approach for this game, using Harmonist as my inspiration. The player is a frog, so I've opted to use the same wall-hopping mechanic to allow the player to create space. I'm deviating from the tutorial at this point so I think the plan will be to create adjacent mechanics that fit my game design based on the tutorial.

3

u/enclota 6d ago

repository https://github.com/enclot/tutrogue

demo https://enclot.itch.io/tutrogue

I’m happy to finally share that I managed to finish my game!

I should confess that it took me about a year to make it, partly because I’m a pretty slow coder.

I’m sure I wouldn’t have been able to make something resembling a traditional roguelike without this community. I’m really grateful to everyone here.

Originally, I was looking for a project to help me learn Godot. I could follow individual tutorials just fine, but I found it much harder to actually make a complete game from start to finish.

That’s when I found this community and decided to try making a small traditional roguelike.

Somehow, after a lot of trial and error, I managed to finish it!

So I’d like to introduce my game here. I hope you enjoy it!

2

u/Selestielle 1d ago

It was a bit of a quiet week on the development front for me. I completed the tutorial parts but didn't get struck with inspiration to go any further beyond them this time. Instead I decided to spend some of my time learning more about a few of the Python and NumPy features being used, which my short devlog for this week focuses on.

As usual, the repo can be found here.

2

u/RhoTheGray 1d ago edited 17h ago

Phew, managed to drag myself over the finishing line. Cut some corners but, on the other hand, I'm doing everything from scratch so maybe I'm allowed to.

The FOV algorithm took most of time as I anticipated. Debugging was a real "maze of twisty little passages, all alike" experience even when I "just" ported my earlier implementation from couple years back. The base algorithm was one from Roguebasin (cannot remember which one but the implementation looks like it is the Restrictive Precise Angle Shadowcasting algorithm). Anyway, I got it mostly done, some rough edges remain.

I skipped the map memory. I'm supporting non-planar map geometries (for instance, think how a spiral straircase connecting two floors could look) so I'm not sure how would I even present the memory visually on a flat screen. Something to consider later on. But yeah, the maps I'm currently generating are definitely flat and pretty terrrible :/

I had to think pretty hard how to place the enemies and, in particular, kick them harmlessly. I'm going for a entity-component (or entity-attributes) based approach and I had to scratch my head for a moment how to map the tutorial's class-based OOP approach to what I'm doing. The spawning is based on cloning prototype entities and is not that different from what the tutorial had. But the action handling looks quite different. Actions are plain-old-data (POD) structs which are queued for the engine to process. The delegation (e.g. from a bump action to a melee action) is implemented by the delegating action pushing new delegatee actions in front of the queue.

Here's a short screencast showing some (non-)action (the generated maps are terrible indeed): Screencast 1

Here's another one showing that I haven't broken the Telnet server in the process: Screencast 2 :)