first development update: Building an Action Roguelike from the Ground Up

A deep dive into the systems powering my 2D platformer dungeon crawler.


Two months of development, lets wrap up what I have so far on my unnamed action roguelike project. What started as a simple 2D platformer concept has evolved into procedurally generated dungeon crawler that focuses on tight controls and responsive fighting.

For those just tuning in, I am building this entirely in Godot Engine 4.5, targeting a retro aesthetic with pixel graphics rendered at 640×320 and scaled up to 1920×1080.

The Foundation: Player Movement That Feels Right

Every great action game lives or dies by its controls I think. In a singleplayer experience where the player will be running through procedurally generated rooms, the movement needs to feel perfect from the first jump and there shouldn’t be a moment where poor controls get in the way.

Quality of the controls is absolutely non-negotioable.

I have spent considerable time refining the player physics system. The challenge with 2D platformer movement isn’t just making a character move left and right, it’s about creating a “game feel” that feels good to interact with. Here’s a glimpse at the acceleration system:

Code snippet representing the acceleration and deceleration of my player object.

The magic happens in that turnaround multiplier. When you reverse direction, you get 1.8x acceleration, making the character feel snappy and responsive. Combined with different acceleration values for ground (1200) versus air (700), the platforming has weight while maintaining control.

But the real game-changer was implementing coyote time and jump buffering. These are invisible systems that make platforming feel fair and more responsive:

Coyotee time implementation to allow jumping after the platform has been left for a short period.

I have set the coyote time togives players 0.15 seconds to jump even after walking off a ledge. Jump buffering remembers your jump input if you press it slightly before landing. These tiny details are what separate a casual platformer from a precision action experience. I am not building a precision platformer, in fact, this is not a core pillar for my game but I’d rather give the player the tight controls and they can decide how to use them.


Combat Systems: Three Weapons, Well Four Actually

An action RPG needs satisfying combat, and this week I hit a major milestone: all three melee weapons are fully functional with proper animations and sound effects for both their primary and secondary attacks.
This was not an easy task because the sprites are sliced up and they are positioned individually to create one coherent sprite.

The Weapon Trinity

The game features three distinct weapon types: Hammer, Sword, and Axe and each with primary and secondary attacks. The breakthrough to scale this so I can add say 20 of each type, came when I extracted all weapon animations into a separate scene, creating a modular system that handles all the complexity:

Each weapon has it’s own animation and set of rules because each weapon has an unique moveset.

Each weapon has different sprite positions for left/right facing, different animation timings, collisions and unique sound effects. The WeaponAnimator class manages all of this through position dictionaries and dedicated animation players. Still, the class would become bigger if I add 20 swords, hammers and axes, but the underlying logic does not change, just the assets will increase.

Attack Buffering System

One feature I’m particularly proud of is the attack buffering. If you press attack while already attacking, the game remembers it:

Attack buffering primitve implementation, but does the job.

When the current attack finishes, the buffered attack executes immediately. For an action roguelike, responsive combat where inputs never feel dropped, feels like the right thing to have. The system prevents button mashing from feeling chaotic while rewarding deliberate timing. This system is a bit primitve now but does the job, will see how and if it will evolve further.

I have gone a step further and have included the seconday attack as a follow up to the primary attack but also would give the player the possibility to perform the secondary attack on it’s own. Playtesting will tell me if that is correct or not.

Sound Design Integration

Simple xample showing how sword impact sound is handled. With two sounds and pitch altercations, we can produce a good set of varying sounds.

Random pitch variation prevents repetitive audio, and the impact sound is triggered at the exact animation frame where the weapon connects. That visceral feedback is essential for survival in a roguelike I feel, my aim is the player to feel every hit and each hit to have its own sound variation. Many roguelikes use screenflashes and screenshake to convey this, and I will add it too, but I do want to explore an angle where sound takes a front seat and see how that goes.

The Hand Cannon: Ranged Combat

Simple example of how bullets are used and added to the game.

The system has smart constraints – you can only shoot while grounded, can’t shoot during melee attacks (although some animations can be cancelled!), and can’t spam fire if the gun animation is already playing. This creates meaningful tactical choices: do you stay grounded for ranged safety or jump for mobility or safety? The reason for this is to enhance the feeling of weight even further – imagine you are swinging a large hammer with your two handes, well you wouldn’t be able to do anything else during the swing right? At least that is the idea, again, we will see if that is actually a good idea 🙂

Controller support was essential here. The hand cannon maps to the right trigger, giving that satisfying pull and fire feel you’d expect from a modern action game while maintaining our retro aesthetic.


Enemy Composition: Multiple Approaches to Performance

This was a bit tricky and here’s where things got interesting. A roguelite needs a good enemy counts, but performance matters. The solution? Multiple enemy implementations optimised for different scenarios but will probably not use them all, at least not yet.

Five Enemy Types, One Goal

I did not know exactly whay the enemy behaviour should be so I decided to explore and implement a few types and test them to see what works and also I did not want to constrain myself to one specific enemy implementation.

The game contains five distinct enemy classes, each with different performance characteristics:

  1. Enemy (CharacterBody2D) – Full physics simulation with gravity and collision. Uses move_and_slide() for proper physics interactions. Perfect for platforming enemies that need to walk on surfaces.
  2. OptimisedEnemy (Node2D) – No physics engine overhead. Uses direct position manipulation in _process() instead of _physics_process():
Sample code showing how to move without using physics.

This is significantly faster when you don’t need physics interactions.

3. UltraLightEnemy – Takes optimisation further with static player references:

All this enemy needs is reference to the player and the movement happens by manipulating the position (not shown).

When you have 50+ enemies on screen, finding the player node 50 times per frame adds up. Static caching means one lookup total, ever.

4. CulledEnemy – Adds health system and pooling support for wave-based spawning with proper activate/deactivate methods for reuse.
5. CrawlerEnemy – Flying enemy with special navigation layer configuration for aerial movement. This one is in a very early prototype but I do like the sound of having an enemy that can crawl on walls and just move like the xenomorph from the move Alien 🙂

Orbital Behavior (experimental): Making Enemies Feel Intelligent

I am also exploring orbital behavior that makes them feel smarter than basic “chase the player” AI:

Orbital behaviour implementation.

When an enemy gets close, instead of piling onto the player’s exact position, it navigates to the opposite side. This creates natural swarm behavior where enemies surround you rather than clumping. It’s the kind of emergent gameplay that makes me think about what I actually want from the enemy, not sure if it will end up in the game as of yet.

Staggered Navigation Updates (experimental)

To prevent CPU spikes when 50 enemies all update their paths on the same frame, updates are staggered:

Attempt to controll the staggering of the enemeis to reduce performance penalties.

Each enemy gets a random initial delay, spreading the computational load across multiple frames. Combined with the 0.5 second update interval, this keeps pathfinding costs manageable even with large enemy counts.

This will probably not end up in the game because at this point path finding becomes difficult to manage and have to take into account other things too and I really don’t need sophisticated path finding, but it was cool to explore.


Wave-Based Spawning: The Roguelite Structure

The wave system is built around two key components: WaveSingleton (global coordinator) and WavePocket (local spawner).

WavePocket: Localized Enemy Pools

Each combat room has a WavePocket that manages its own enemy pool:

Enemy poop defining variables.

During initialisation, it pre-creates the entire pool:

Shows how we populte the pool with inactive enemies. We will later activate them, we just want the pool ready to be used.

Twenty enemies instantiated once, hidden and disabled. No runtime instantiation, no garbage collection pressure, great for performance.

Smart Wave Progression (experimental)

The wave system has progression logic:

Example how waves work. Not full implementation shown.

When you clear enemies down to the threshold, the next wave starts spawning. This keeps pressure constant and you never get a boring “clear room, wait for next wave” moment. The action stays intense, which is exactly what an action roguelite needs. This is one of my favourite experiments and it will probably end up in the game in some shape or form.

This is getting pretty long now but finally, I want to include the procedural generation logic overview to highlight how actually the levels come together to give that roguelike feeling.


Procedural Level Generation: LDtk Integration

The level generation system uses LDtk (Level Designer Toolkit) for hand-crafted chunks that are assembled procedurally.

The Three-Part System

  1. ChunkData Resources – Define individual level chunks with connectors:
Chunk resource variables that make it.

2. LevelGraph – Creates a blueprint of connected nodes:

Example of a blueprint for a level and it’s connectors, this is what describes our level.

3. LevelAssembler – Instantiates chunks and aligns them via connectors:

Chunks are instantiated and then snapped together by their connector points. Think of them as lego blocks.

Chunks snap together perfectly at connector points. Each chunk is hand-designed in LDtk for quality, but the order and connections are procedural.

As we add more chunks to the library, the procedural combinations grow exponentially. Ten chunks? Thousands of possible levels. Twenty chunks? Tens of thousands. That’s replay value and the only constraint here is time to create the chunk is LDtk.


Recap

The progress so far has solidified the basic foundations: move, attack, procedural level generations and smart enemy spawning. The action feels basic and yet tight, the platformer physics feel fair, and the roguelite structure promises really long replay value. I am still testing every piece of each mechanic as much as I can to find any potential issues, quality is absolutely non-negotiable.

Building an action roguelite is a constant balancing act. I want the adventure to feel epic, but each run needs to be digestible (I am targeting 5-10 minute runs and with more developed builds, maybe a 2 minute run). The action needs to be intense but fair. The procedural generation needs to surprise without frustrating – I want it to be different but familiar so players can traverse the levels without getting lost or blocked.

Every system I build keeps replay value front and center. The modular weapon system means I can add new attack types without rewriting the controller. The chunk-based level generation means I can continuously expand content. The multiple enemy implementations mean I can keep throwing new threats at players without performance concerns and mix and match depending on what experience I am aiming for.

To me, this is what makes roguelite development so compelling because every feature is designed to be remixed, recombined, and replayed and yet feel fresh.

I am concious there is not video or gifs in the post so that would probably be my next post where I show everything mentioned in more detail.


Development continues daily. Follow along for more updates as I turn this unnamed project into a fully fledged action roguelike experience. Got questions about the systems described here? Drop them in the comments!

Leave a Reply

Hey!

I am Kris, nice to meet you! You can find here my progress updates about the unnanounced roguelike/lite game I am working on. Feel free to connect with me and share any and all feddback!

Register interest

Signup for early access invites, new content reveals, and major announcements. No spam.

Discover more from Double Damage

Subscribe now to keep reading and get access to the full archive.

Continue reading