Data-Driven Design
For an RPG that you want to scale alongside your development, data-driven design really is pretty essential. I attribute much of the ease in which 'red' was developed to data-driven design and most of my design regrets are only that I didn't make more use of it. Basically, this is referring to the fact that the game maps, the sprites, the data and functionality of the items, equipment and spells, the data behind the enemies, the effects used for attacks etc., the scripts for pretty much every character you talk to, every cutscene, most of the puzzles etc. exist outside of MZX and can be modified independently of the game. You could probably take this stuff and make a completely different RPG with different characters, story, items, enemies etc. to 'red' without touching the MZX file (except to replace the title screen).
The benefits to this approach are difficult to overstate; much of it comes from the fact that you can describe the things you are putting into the game in a domain-specific way- you don't need to throw in all sorts of extra stuff that could be needed if you were writing things directly into Robotic. Even if you can ameliorate this somewhat with subroutines and/or handler robots (which can pose their own problems) it's still far messier. Compare:
:npc {Townsperson} "Hi, how are you doing?" @end
: "npc" set "$textbox" "{Townsperson} \"Hi, how are you doing?\"" goto "#textbox" end
Over the course of writing a long game with many characters and more complicated handling, this sort of thing will add up and at the very least reduces maintainability. The goal is to be able to work basically at the same level of logic as the stuff you're doing requires. This may involve having multiple ways of expressing things, multiple mini-"languages" to cover different aspects of the game. 'red' basically had two main ones:
vdata. 'variable data', what an inspired name. This is the stuff found in the vdata_ files in the game's directory. These are text files that consist of lines, a field and value on each line. The main structure of these vdata files was typically 'name: {the name of the thing}' followed by lines that cover the stats of said thing. At startup 'red' goes through the directory and reads every file starting with vdata_ into counters and strings. Some fields are counters, in which case the value is either the number or the name of another counter to read the value from, while some fields are strings, in which the value is text used directly. Which fields are counters and which fields are strings is actually hardcoded into the MZX, which in hindsight was probably a mistake, but at the time made sense as I was only going to have 1-2 string fields. A better way would probably be just to use the standard string prefix, $, and use '$name: blah' instead. Some fields were also handled in other special ways, again hardcoded into the MZX. 'name', for example, would advance a counter for the particular kind of object we are reading data for as well as store the index of this object in the counter {object type}[{object name}] (this object type comes from the filename; e.g. files starting with 'vdata_art' read in objects that are given the prefix 'art' and advance an 'arts' counter with each name) while other fields were set to work with mini-lists; for example:
eff: ART_CURE_EFFECT eff_a: EFFECT_POISON eff: ART_CURE_EFFECT eff_a: EFFECT_SLEEP eff: ART_CURE_EFFECT eff_a: EFFECT_LOCK
Here, 'eff:' is a field that maintains its own advancing counter (art#eff) and writes the values that are associated with these fields into art#eff#, art#eff_a# etc. to allow giving multiple effects to an art (in this case, the curing of poison, sleep and locking) without needing to maintain a list. I don't even make use of this consistently; for example, the battle arts associated with weapons are actually stored like this:
art0i: art[Battle Cry] art0req: 4 art1i: art[Defensive Pose] art1req: 8 art2i: art[Bisect] art2req: 25 arts: 3
Here I basically do the same thing, except I have to manually keep track of the lists. This is another thing related to expressing your design as close to the logic as you can- don't keep track of things that you can write code to keep track of. Your work will be less error-prone and it will save you time in the long run. The only reason I didn't do this was because storing battle arts with weapons is one of the earlier things I did in 'red' and I hadn't implemented the auto-advance fields at that point. Implementing them then would have required rewriting code and rewriting the weapons I had already added so I didn't bother. Other than the specifically-named auto-advance fields, string fields and name:, the other aspects of the vdata_ file pack all the necessary logic required into the files themselves rather than into the .mzx. For example, given a typical filename:
vdata_item[2]weapons.txt
the 'vdata_' portion marks the file as a vdata file so that the game sees it when scanning the directory and knows to read it in. The text following vdata_ but preceding the opening square bracket is the object name. In this case, item, so each object that appears in this file will increment the 'items' counter and those objects will fill the counters item#{field name} and strings $item#{field name}. The text after the closing square bracket is just a comment- it tells me that there are weapons in this file. This part isn't actually used by the game at all. The number inside the parentheses is read order- I read vdata files with lower numbers first. This is necessary as I need to be able to reference counters that are written in other vdata files, which means I need to be able to depend on those counters being written first. For instance, the enemy Shell Wisp casts the spells 'drain life' and 'unwound', as represented here:
name: Shell Wisp ... art: art[Drain Life] art: art[Unwound]
Now, the 'art:' fields refer to counters, not strings, and because the value field contains text it is read as a counter, so a counter called "art[Drain Life]" must already be set. However, we also store arts in vdata files.
name: Drain Life desc: Drain life from one foe type: ART_TYPE_SPIRIT ….
Unless we see this file first, art[Drain Life] will not be set. So we store Drain Life in vdata_art[1]spirit.txt and we store the Shell Wisp in vdata_enemy[3].txt. Why not vdata_enemy[2].txt? This is because I use [2] for items, and enemies need to be able to refer to items (as some enemies drop items), while items also need to be able to refer to arts (because some items cast arts as part of their use- in fact, most of them do because that's a straightforward way of implementing the behaviour of most items in the game. Arts that are only cast by items and aren't actually available for use as spells are in vdata_art[1]potions.txt and vdata_art[1]items_2.txt).
There are some cases where this solution is not perfect. For example, while enemies need to be able to see spells, some spells need to be able to see enemies; the enemy spell 'Batckup' spawns the enemy 'Cave Bat' but because spells are loaded before enemies 'Cave Bat' doesn't exist at this point. One solution could be to give 'Batckup' specifically a higher load order so it appears later; but this means every enemy that uses 'Batckup' needs to be have an even higher load order itself, hence things spread over even more files and it becomes harder to organise things. My solution in these cases was to store the string 'Cave Bat' instead and look the enemy up dynamically. It would be possible to use this solution for everything and remove the need for dependencies, but it would require more coding as I'd need to basically dereference all these strings when looking everything up and that can be a bit of a pain in MZX since you can only use && brackets for interpolating strings and those can't be nested.
The other language used in 'red' is the 'npc' language, the scripts of which are all located in the 'npc' directory. As the name suggests, this was originally intended to be used just for NPCs but gradually grew in scope until it was a general-purpose scripting language used for almost all event scripting in the game. While I do regret somewhat that I didn't design it as a cleaner language to begin with (the code for NPCs is pretty clean, but instances where I was basically coding puzzles in it look really messy. One good example of this is npc/dungeon2.txt, which has the code for all the puzzles in that dungeon, including the light road puzzle. Things like the pushable blocks and boulders were also written in this language.) I think it really helped the design of the game overall and I don't regret using it so much one bit. If anything, I regret that the language wasn't more powerful so I could use it for every bit of event coding- there were still some bits that either had to be done in MZX or were only done in MZX because I hadn't decided to use the npc language to implement them at that stage. Having a specialised language that can work at the domain level of the events is important; everything is so much more consistent and so much less error-prone. To make Sophia walk one tile west, with animation and everything, all I need to do is @plr_w(1, 3, 3). This made those little cutscenes so easy to write. If there was anything I found I was doing a lot, I could just write another method to handle it.
Working in the domain isn't the only benefit to data-driven design. You can also modify pretty large sections of the game while you're in the middle of playing it, and for something like an RPG being able to edit things as you play really is a necessity because you need the game to scale to the player's party throughout and getting this drastically wrong can ruin your game. I'm just going to say that I have no idea how djtiesto made Kikan because it's also a long RPG and is pretty much the diametric opposite of data-driven design – and yet it still manages to maintain balanced throughout. I can only imagine it was a lot of work. It also means that when you fix bugs related to low-level implementations of certain things, the fixes tend to propagate well and you don't need to rewrite a heap of code. Imagine trying to transition from a subroutine-based textbox model to a call-another-robot-based textbox model after you've created five locations, thirty NPCs and three cutscenes, all containing goto "#textbox" and a textbox implementation across a dozen robots.
Graphical Limitations
I've tried different ways in the past to try and make as much use of the limited charset and palette MZX gives you as possible.
In Ultima IV: Quest for the Avatar[ I basically didn't have much of a choice. I settled on tile sizes early on that made it possible to visually approximate the original game screen and the game data contains the graphics of the tiles and the designs of the maps and I simply had to make do with the chars I had to display. I dynamically allocated the entire charset- I needed to be able to write text into the console on the right and draw tiles into the display on the left and I would only store the font characters and the tile graphics I specifically needed into the charset. I dynamically allocated the palette as well, but this was less important as I was never in danger of running out of colours. Now, most of the game was fine with this- the game has 256 different graphical tiles and while every tile on the screen could be different (and impossible to store in the charset at once since these tiles required 6 chars each) in practice this just wasn't the case. However, at some points it was possible for the charset to fill up and there would simply be no way of displaying that screen in MZX. I tried a few tricks to deal with this- some tiles I modified so that I could display them with fewer chars, like the water tiles and other heavily repeating tiles. I also modifed some really tile-intensive parts of the game, just changing them visually (parts with a lot of those nasty text tiles, usually) but it's not perfect and occasionally this collapses, if only momentarily.
A number of years ago I worked on a solo DoZ.. can't remember the title, it was something to do with 'memory'. The game was very incomplete and a pretty big failure, but it was my first SMZX game and I attempted to deal with the same issue there; the issue was that each enemy and other graphical effects would take up their own space in the charset and palette (so I could do fancy things with colour, transparency etc.) hence I needed some way of handling the instance in which they fill up the screen. I opted to deal with this in the NES way by flickering some of them off on alternating frames when I went over the limit. This isn't too bad a solution but it's pretty obvious when it happens (whereas I hope that the few cases in Ultima 4 where the graphical model breaks go unnoticed by players- you kind of have to be looking for them.)
In a non-SMZX case, Thanatos Insignia 2 also allocates a unique portion of the charset to every enemy so each enemy can have any number of animations, including fancy death fades etc. The enemies were allocated their own portion of the charset, so the entire charset wasn't dynamically allocated; only the enemy portion, but there was still a chance that the enemies could fill up the charset and this would cause problems. My solution for Thanatos Insignia 2 was to only 'remember' a relatively small number of enemies (so enemies would rapidly spawn 'offscreen' and die just as quickly when you wander away from them) and to only keep enemies around when there is space in the charset for them. If there isn't enough space for an enemy, I don't spawn that enemy. This approach had all sorts of interesting side effects, like the fact that you could 'clear' a boss arena so the boss can appear just by wandering around it until you've offscreened all the enemies enough to vanish them.
Anyway, I gave these examples basically to show how much of a colossal pain the dynamic management of char and palette resources is. They compromise gameplay (TI2), they compromise graphics (Ultima 4), they compromise the solidity of the experience (that DoZ game I mentioned) or all three. Basically, it's a huge pain and if you can avoid it it's best to avoid it. With 'red' I avoided it entirely; there is no dynamic allocation of any graphical resources whatsoever. I designed the graphical style of the game to entirely use the charset and palette.
For the field, this meant creating a charset for each location, although it was essentially an SMZX charset- I worked on it in low resolution and had a conversion utility find the most efficient way of packing the colours used into the palette. To create the tileset for a location I would draw directly on this charset and if it failed because the converter couldn't pack the colours I used into the 256-colour palette I would modify it and run it until it could.
For the battle engine, this meant partitioning up my character set and palette and dedicating specific roles to them to create the desired graphical results. These limitations were then built into the conversion utilities used for creating backgrounds, enemy sprites and graphical effects and these utilities forced the limitations. The result is that content was created in a fairly restrictive way – I only had 24 tiles to create areas from (compare with Ultima 4's 256!) and was always battling the colour limitations – but the game had a consistent look and I never had to worry about running out of chars and colours in the middle of the game where I would have to programmatically make tradeoffs instead of being able to decide at the point of content creation which tradeoffs to make. This means I couldn't do a few things, like have a particularly plain background and use the space chars and colours to make a fancier enemy, but in the end this all worked out quite well. Thanks again to Otto Germain and Lachesis for drawing enemy sprites for me – battling char/colour limits to draw something nice is incredibly tedious and they managed to draw great stuff.
RPG Mechanics and Balancing
Mechanics in an RPG are pretty easy to get sorta right and very hard to get exactly right. There's a reason a lot of games like to lift mechanics directly from established pen and paper RPGs – because those games have been tested over a long period of time and have probably gotten a lot of things right.
I didn't do that here, but I tried to design mechanics that were simple and reasonably intuitive and I managed that with some success, given some definition of the word 'success'. The numbers that appear when you hit enemies at the start of the game are pretty good and the numbers that appear when you hit enemies at the end of the game are pretty good, so that's what ultimately matters. You're designing systems that have to scale across a game people are playing for 15-20 hours, providing a steady advancement so players always feel they are making progress, so the random battles they have to fight hundreds of don't feel entirely pointless etc. and so players are always finding cool stuff that makes the battles easier and the game more enjoyable. I think a good RPG can be the crack cocaine of video games; I find it scary how I can dump 100 hours into Sengoku Rance, 100 hours into Yumina or 100 hours into Skyrim and still want to keep playing. While I think 'red' is a long way from that, I made my attempt here.
In the game thread, I listed some things I'd change if I were to make the game again:
Quote
- Art multipliers would no longer go below 1.00
- For battle arts, when you master a weapon (unlock all arts) you permanently learn one of the arts and can use it with any weapon. Dunno if I'd let you pick the art or just have a predefined one with every weapon (and hopefully different for each weapon!)
- Battle arts would be further multiplied by your damage multiplier (and the more powerful battle arts probably nerfed a bit to accommodate this).
The first two are basically designed to reduce one of the penalties associated with using arts, particularly the powerful ones. Basically, if you use those jewel arts or greater arts too much, your multipliers for the other kinds of arts (that are more 'general use' and where having a good multiplier is more important) will drop. This is not an inadvertent or unforeseen side-effect; I designed the multiplier system with these penalties in mind. However, I'm convinced now that it's the wrong way of doing things.
Greater arts should be powerful no matter what. If you were using other kind of arts throughout the game before getting your first greater art, the greater art multiplier for your main spellcaster is probably along the lines of 0.01. Now, I designed most of the greater arts to not be particularly damage-focused (Blaze Moment is one of the exceptions) so it ended up not mattering too much in that respect. Unfortunately, it was a different story with jewel arts. Jewel arts are expensive throughout considering gems are a limited and valuable commodity. You can't just buy them and they take ages to 'farm' from gem hoarders, the only renewable source of them. This means you don't use them very much; only for emergencies, which means your jewel art multiplier probably goes towards 0.01x just like the greater art multiplier. This is fine for stuff like Prismatic, Grand Mirror and Timecross which will work fine no matter what your multiplier, but it meant the attacking spells weren't all that great, especially for the cost. The problem the multiplier system was intended to solve here (overuse of those powerful arts) was never really an issue – jewel arts are expensive by nature and the high energy costs and long cooldowns of greater arts means you're not using them in every battle anyway.
Battle arts were another thing; they were associated with the weapon (you never actually 'learn' battle arts- I just keep track of the number of battles each character has used each weapon in and dynamically fill spell lists with the available arts based on what arts are associated with the currently-equipped weapon; elegant design, but possibly a mistake) but couldn't be carried across and to be honest I didn't really know what to do with this balance-wise. As a result, there were like 3 battle arts in the game and nearly all weapons had none at all until I got to the end of the game, realised I had really neglected this particular aspect and went in adding a ridiculous number of them and assigning them fairly haphazardly to the weapons. I never balanced these and in some cases these had pretty horrible bugs that I didn't know about because I never tested them. These arts didn't carry across between weapons and each time you switched weapons you had to learn a new set of battle arts (you may have learned how to bisect enemies with your scimitar, but you need to learn it again for the broadsword!) - as a result, I made them quite powerful to compensate but trying to specialise in battle arts was still a really limiting role. The latter two changes were designed to redress this somewhat. The last change in particular, since there were certain cases (particularly if your battle art multiplier wasn't great) where you could use a particular battle art corresponding to some fancy sword technique and have it do less damage than just attacking normally with the same weapon, which is all kinds of wrong. Siphon was a good example in its original form- it cost 10 energy to use and would regularly draw in 4-5 points of energy from enemies. really useful!!
Story
Actually, I don't have much to say about story, except I'd recommend having a better idea of your story (and I mean the whole thing- how it starts, how it ends and how you plan on getting there) before starting real development on things like characters, events and locations. Perhaps you can tell, but I didn't do this. This can manifest in a story that is weaker than it could have otherwise been due to needing to work around the architecture you've already built.
Suffice to stay, I'm still trying to work out how to write and more importantly TELL a good story. I don't have it yet, but I think story is damn important so I'm going to keep at it.