I want to write a technical postmortem of what went well and what didn’t, but realise a lot of that’s not going to make sense to anyone reading it without some understanding of what the design goals I was aiming for, and spectacularly falling short of. So this is that missing piece.
The tagline is “Quazatron/Paradroid as a roguelike”, but really Quazatron was the version I played and loved as a child. So the gameplay was intended to be very Quazatron-like, ie you upgrade by taking parts from defeated enemies rather than taking them over wholesale, while the graphics would by top-down 2D (in true roguelike fashion) like Paradroid (rather than Quazatron’s isometric 3D). OK, there was a much earlier design in my head which would have been 3D, but 7DRL came along and changed my direction on that. And also introduced the idea of making it turn based, which seemed like it should be a good fit.
The basic concept is that the systems in an automated mining facility have become corrupt, and in order to perform a clean reboot of the central systems all the robots in the facility need to be “forcibly deactivated” otherwise they’ll just recorrupt the central system when they reconnect. The only way to get in undetected is by remote control of a very basic robot, but once inside you’ll be able to scavenge parts from defeated enemies to upgrade.
Ironically, the mini-game of the originals which allows you to take over/disable an enemy was the first thing I decided to descope, before even starting the jam. I was unconvinced about how it could be made properly roguelike (despite not being a player of the genre, I have strong views about What Counts, and being turn-based is an absolute requirement in my book), and realised that it was a lot of development effort I could save by just implementing the effect as another weighted random event. And on the back of that decision, I decided that the player should be able to scavenge from any defeated enemy, not just those defeated in this way. (Although, of course, if you’ve shot an enemy to death it’s going to have fewer usable parts.)
The other big difference in making it a roguelike was to have procedural generated levels. The overall Quazatron design of multiple levels of an underground city with tougher opponents further down is a very natural fit, it’s just that it uses static level design. Which is not something I’m into. (As I joked in the first entry in this series, I don’t know much about roguelikes. I’m just here for the procedural level generation. (And possibly the permadeath.))
If I hadn’t been working within a constrained time frame, I would probably have written my own level generation code from scratch. As it was, I decided pulling in somebody else’s map-generation GDScript was within the bounds of the jam rules, especially as I was intending to tweak it. There are two level generation algorithms there: BSP Tree to create rooms, and cellular automata to create caves. The presence of both of these led to my decision to make the setting a mining complex, with the idea that as a level is mined out it is turned into rooms (why robots need rooms, I don’t know) using the BSP tree algorithm while working levels are more cave like using cellular automata.
Following the “try and get a 7 in” maxim of 7DRL, I decided to create a tree structure 7 levels deep, with 7 cave levels at bottom, with intermediate levels either being a room set with one cave and one room level below, or a cave level with just another cave level below. This makes for 28 levels total, which seemed like a reasonable number.
Which brings me to the first point in which I needed to feel my way around a lack of roguelike experience. From the ascension stories of friends (the ones who instilled the very determined ideas about what makes a roguelike), 28 seemed like a reasonable number of levels. But what size should each level be? Here, I went way back to the origins of the genre, and thought about a non-scrolling 80×24 single-character-tile map fitting on terminal. However, I had no intention of doing this character based — that’s one trad roguelike rule I disagree with. I grew up with 8-bit gaming, and we put up with lousy graphics because we didn’t have better options. We can do better now, and should do so. Or, to put it another way, I really don’t get the retro aesthetic, either in obviously pixelated art (let alone continuing to use glyphs rather than sprites) or 8-bit music. Which means using a nice tile set, with a scrolling view, and any level size I wanted.
Tileset and other assets
Probably my biggest limitation when it comes to game creation is a serious lack of skill in creating graphical assets, whether 3D or 2D. Quite often, what I write is determined by finding an asset I really like. So in the run-up to 7DRL, i poked around at the 2D art assets available from itch.io to look for anything that would fit into my vague plans.
What I came up with was a suitably cavernous tileset and a nice robot as 48×48 sprites. This seemed to me to hit a nice point between detailed-enough graphics and being able to fit a decent number of tiles on screen (22 full tiles vertically at 1080p, which is my default resolution for working to). I then gave myself a crash-course in aseprite for tweaking and created a couple of variants of the robot (krita also got used for its global fill functionality) and some weapons. More weapons and robot variants were in the plan, but I stopped because it was starting to feel like I was doing too much work on the game in advance.
One thing I had no qualms about preparing in advance was the combat system, because that was completely a legitimate design phase. Rather than trying to produce an exact copy of the inspiration, I designed something heavily influenced by it. Critical stats are power, chassis and logic (CPU in the original) — if any of these drop to zero, you’re deactivated. You also have stats for strength (how effective your weapon is), protection (how effective your armour is) and speed (how many moves you can make in a turn). “You” here applies to all the robots in the game — enemies and player alike. On top of that, you have a drive class, which effectively determines whether you’ve got enough power to move at maximum speed, and an armour class which determines whether your armour stands any chance against the weapon being used against you.
Different weapons, as well as being more or less countered by different armour classes, inflict different types of damage at different chances. One general type inflicts damage to all stats with a probability of attackers strength to defenders protection, modified by armour; another inflicts a random amount of damage (determined by the attackers strength modified by defenders armour) to a number of stats (determined by the attackers strength) starting with the lowest stat (or the highest, depending on weapon type) and working through in order. Other weapons specifically target logic (logic probe), chassis and armour (rail gun) or logic and power (EMP). Some types of ranged weapons have splash damage: lasers keep going and also inflict damage to enemies behind the initial target, plasma barrages and ion cannons also inflict damage to any enemies within a one tile (including diagonals) of the target, and EMPs are non-directional and affect any robot within range.
I took this design and coded up some Python to play off robots with different stats (character/monster classes) without a random element, but assuming a maximum hit from the attacker, chucked the results into a spread sheet and went to work tweaking stats and formula to try and achieve balance. What I was after was a level 0 (initial player state) robot being immune to being one-shotted by any level 1 enemy, and in turn being able to take any of them down (eventually), checking that the same applied for level 1 v level 2, and then being confident it would apply from thereon up (although not actually planning to go beyond level 3). I then figured out how to arrange the 12 classes by 3 levels over the 7 levels of the game in increasing order of threat.
Robots can also have one of a range of special components fitted as additional equipment, which let them do things like masquerade their true stats, always have the advantage of initiative in mini-game-substitute combat, and provide protection against immobilisation (speed dropping to 0 but not deactivated).
One added wrinkle: if the player is allowed to mix-and-match components from defeated enemies, why shouldn’t the enemy robots do the same? Given the concept, the idea was that the robots at the deepest levels had become corrupt first and started scavenging from each other to create random hybrids, at which point the systems sent their toughest units down to deal with them, before they got corrupted too, and then the central systems, explaining why you’ll find dangerous military hardware at the lower levels of a mining operation.
As well as taking explicit damage in combat, a robot can take implicit damage to its chassis if its weight (drive + armour) exceeds the chassis’s capacity (with adjustment to stop it spiralling out of control) and to its drive if its power is insufficient. Power can be restored at charging points scattered across the level.
How It Started
- So at the start of the week (13:00 Saturday, local time) I had lined up:
- the tile assets, including charging point and doors for going up and down (at the start of the week I wasn’t sure whether these were going to be lifts or teleports, but almost immediately settled on lifts because the tree structure of the levels just didn’t make sense if you could teleport)
- the original robot asset
- three variants I’d created: one without the “horns” on the head, meant to indicate a robot without any special equipment, which would be used for the player, and duplicates of these with green tinted eye strips (which, thanks to krita, added a green cast to nearby body work) which would be used for enemies
- dead form and idle animations for all four robot variants
- two weapon animations (idle and firing) for two-and-a-half weapon classes (plasma barrage is just plasma beam doubled up)
- level generation code
- Python code for deterministic combat
How It Went
… see next entry