Well, it's been a long time since the last blog post. I figured a blog post to announce the release of the Darkness Never Dies demo wasn't really necessary, since I plastered it all over the rest of the website.
After releasing the demo, I took a bit of a break from working on DND to recharge my batteries and allow time for playtest feedback to come in. I've had a couple of playtesters provide bug reports and have applied a variety of the updates to the demo which, as far as I know, is in quite good shape now. Aside from ever-present work, I've been relaxing by playing games, reading books, and getting outside in the weather which is finally and belatedly turning from cold winter into beautiful spring; prepping for my next Dungeons & Dragons campaign; and gradually ramping productivity on Darkness Never Dies back up in preparation for starting work on the full game.
In particular, I've been drawing a lot of character artwork, both filling up the roster of selectable player character portraits (which is now up to 52) and portraits for the various NPCs. Aside from serving as character graphics in Darkness Never Dies, these portraits will also be usable in other fantasy projects I work on.
As my first blog entry in two months I decided, why not share some of the art I've been working on? First up, here's the spritesheet for all 52 character portraits (click on the thumbnail below to bring up a full-sized image):
Player character portraits for Darkness Never Dies.
In the full game, the player will be able to encounter other adventurers exploring the ruins at the edge of Vantia. They have different goals and motives, some noble, some wicked, some purely mercenary. Depending on the player's actions, some of these NPCs might become friendly -- perhaps even joining the player's party for a time -- while others may stand in the player's way, acting as sub-bosses. (I've always thought that fighting rival adventuring parties makes for awesome boss battles in RPGs.) Here's a sampling of the NPC adventurers you can encounter:
NPC adventurer portraits for Darkness Never Dies.
And of course, there are other NPCs as well, such as shopkeepers, villagers, soldiers, etc. Here's a small sampling of those:
Other NPC portraits for Darkness Never Dies.
While the monsters in-game are done in a 16-bit sprite style, I do have higher-fidelity concept art for some monster ideas:
Concept art for monster designs.
And finally, scenery and landscapes, although most of these are still in sketch form:
Concept art for scenery and landscapes.
I decided I have enough art to show off that it was worth creating sketchbook / portfolio page. Hopefully as time goes on I'll have more art to add to this page.
It took longer than I expected to get here, but the layout and puzzle scripting for the demo level are finally complete. Because the demo level is supposed to be a showcase, I'm not holding back at all on the features and complexity -- there are loads of traps, secret doors, dark zones, antimagic zones, multi-level labyrinths, and a wide variety of puzzles that cause dynamic changes to the map layout.
(I've always thought highly interactive maps with lots of puzzles and changing layouts make for much more interesting dungeons than maps that are rather static. The Zelda series, particularly Ocarina of Time, is a major influence here, even though as a third-person action RPG it's a pretty different beast from a first-person dungeon crawler.)
Redlining the system like this exposed a few more bugs that needed fixing, tweaks that needed to be made, and little features to be added here and there. The funnest (I know the grammatically correct term is "most fun" but that just doesn't sound right to me) feature add was the ability to manually trigger traps from a scripted event. Let's just say hitting the player with a trap if he guesses a puzzle solution wrong is a rather effective way to prevent attempts at brute-forcing puzzle solutions. 😈
On the bug front, the most significant was an actual rendering bug in Processing (I confirmed by checking Processing forums that it's a legit Processing bug and not a mistake in my code) that would cause the game to crash when I dynamically updated certain map textures. I had to fix it by adding an exception handler around the rendering code which, in layman's terms, basically intercepts the Processing bug, backpedals the rendering a bit, then continues executing instead of just crashing the program. It's not a 100% perfect fix -- it briefly causes a bit of screen flickering -- but it only happens for a fraction of a second and only under specific, rare circumstances, and it's a heck of a lot better than a crash, so I'll consider this an acceptable solution and move forward.
There's still plenty to be done -- I need to implement all the items that will appear in the demo (shouldn't take too long); implement all of the player character abilities that weren't already created during core engine development and testing (a bit longer); then create the graphics, stats, abilities, and AI scripts for the monsters (a significant undertaking). Still, progress continues to be steady.
I've been pretty stingy with screenshots up to this point, so now that the demo level's layout is complete, I'll conclude this update with a big screenshot dump. Despite the PlayStation 1-era style primitive 3D graphics, I think some of these locales look cool and succeed at conveying an immersive gloomy dungeon atmosphere:
An unexpected stumbling block has appeared while building out my demo level: Sudden frame rate drops. This never occurred while running tests while I was building the engine. It appears that, although those small test levels were adequate for finding bugs and testing functionality, they weren't sufficient to really redline the engine and test the performance.
Part of the reason this comes as a surprise is, this is a retro-style game, so in no universe could the graphics be considered computationally intensive -- or so you would think. It's difficult to know how much of the performance issues might be inherent inefficiency in Processing (the framework I'm using), if I might be using the graphical API in a non-optimal way, and how much is structural inefficiencies my code.
I'd be lying if I said this wasn't discouraging, because this means I have to dive back into the code, and I'd hoped I had left the main programming phase behind with the completion of the core engine. Nonetheless, I've poured so much work into the project and the game is so exciting and promising, it's giving me the motivation to tackle this issue and slog through it.
I haven't busted out detailed performance profiling tools (I could do that if it becomes really necessary, but I'd rather not if I can avoid it), so I've started by more or less just throwing darts. Given that I hadn't expected performance issues since it's a rather primitive, retro-style game, I had made only nominal efforts at writing optimized code, so there was some low-hanging fruit; i.e. some areas where I could apply some fairly easy, obvious optimizations.
One issue was, my core rendering loop just cast out a wide net in all directions from the player and rendered all the spaces within that area, even ones the player can't possibly see because they're behind him. So, I made the rendering loop a little smarter to only render spaces in a cone in front of the player, and also reduced the draw distance.
There was just one knot to that approach; namely that the player's "direction" is only updated after completing a turn (remember, this is a grid-based game, so the player can't face in arbitrary directions, but turns in 90-degree increments), which meant that while turning, the part of the dungeon in the direction of the player's turn wouldn't be rendered until the turn was completed. So, for the brief interval while the player is turning, the loop renders two cones -- one in the player's previous direction and the other in the direction the player is turning toward.
I can confirm for sure that the number of spaces being rendered was at least one source of slowdown, because when I first implemented this solution, the frame rate would dip while turning -- that brief interval when the number of spaces being rendered doubles. Reducing the draw distance so the loop doesn't render as much spaces out in front of the player helped to greatly reduce the performance drop even while turning.
Another thing that helped, which shows that another source of lag was the complexity of the geometry being rendered, was when I reduced both the quantity and complexity of the 3D models being rendered in the map. This one is a puzzler to me because any halfway-decent modern computer should have been able to render the number of polys I was pushing standing on its head. This means either there's some serious inherent inefficiency in Processing's underlying rendering engine, or I'm unknowingly using the rendering API in some kind of seriously suboptimal way. I'm not sure which, but for the time being I've solved that part of the issue by just scaling back the complexity of the geometry.
Another bit of low-hanging optimization fruit was cutting down the number of function calls in the core loop. Function calls are relatively computationally expensive. All of the map's surface textures are actually instances of my Sprite object because they can be animated -- examples would be pulsing magical lights, rippling water surfaces, and flickering torches. For this reason, every single surface within rendering range made a call to its Sprite's next_frame() method.
However, each surface doesn't store its own copy of its Sprite, because that would have been really inefficient. Instead, there's just a single copy of each Sprite in memory and each surface is simply a reference to that single Sprite object within the sprite bank. This presented another good optimization opportunity: instead of calling next_frame() on each surface, I rewrote it to just loop through the map texture sprite bank and call next_frame() on each of those, then in the core loop it just fetches the next animation frame directly, without a method call.
I'm not sure how much that particular optimization actually helped, though. I didn't notice much of a performance gain when I applied it, which could either mean that particular issue wasn't much of a bottleneck, or it could just be that whatever improvement I gained from that optimization got lost in the noise of all the others.
Finally, I was using my engine's "three-layered levels" feature to define multiple dungeon floors on a single map. Yet another measure I took to reduce the amount of geometry the engine has to render is to split up multiple floors into separate map files (where possible -- sometimes multiple floors are visible at once, and in those cases the multi-floor geometry has to be duplicated across map files).
With those improvements, the frame rate appears to stay at a smooth 60 fps while exploring a single dungeon level, but now I notice that whenever the player moves to another dungeon map, it chugs for a few seconds before running smoothly again. This almost certainly indicates that when a new map is loaded, the garbage collector spends a couple seconds cleaning up all the memory from the previous map.
To address this, my next optimization is going to be to make the entire dungeon map object fixed in memory (I'm already reusing the same object for the entirety of the program run), not new() anything (except when I create the object for the first time on program load), and just swap out references when a new map is loaded. Since the dungeon map assets are already held in fixed memory, this should drastically reduce the amount of memory cleanup that has to be performed when swapping maps, and just be a matter of changing reference counts. With any luck, this will eliminate the brief slowdown that occurs when changing maps. And after that, maybe the performance issues will have been deal with and I can resume building the actual level. Fingers crossed.
(And whatever my next project will be after Darkness Never Dies, I think this experience has finally convinced me to experiment with other frameworks and see if they have more efficient rendering engines than Processing -- though I'm still not entirely sure whether that's a bottleneck or not.)
I've been working on the Darkness Never Dies demo. I approach the creation of discrete parts of game content -- in this case, the opening cutscene, followed by a sample dungeon level -- incrementally. Typically, although there is room for variation, the workflow goes something like this:
I don't find that just diving in and creating a level, or a cutscene, etc., without a plan goes very well. So, the first phase is to come up with a general idea, a skeleton which I can subsequently flesh out. In this case, the question was, "What kind of dungeon level will best showcase the game?" I don't want the player to be playing with a 1st-level party in the demo, because I want to be able to show off some of the cool abilities that experienced characters get. But, I don't want to give away everything. So that means a mid-level party. I settled on 6th level as a good middle ground for a strong and capable party while still leaving lots of surprises in terms of PC (player character) capabilities for the full game.
This informs other decisions as well. The character creation system assumes the creation of 1st-level characters. I couldn't modify it to allow the creation of higher-level characters without making it significantly more complicated. Thus, it's necessary that the player be given a pregenerated party for the demo. I think this is fine -- it's totally OK to only show a subset of game features in a demo; in this case, the character creation screen will only be available in the full game.
Another fundamental question was whether to make the demo completely standalone, with no relation to the full game except for using the same engine and mechanics; or whether to tie it into the game's story. Since I do have a rough idea what the story will be for the full game, I decided I could come up with a more interesting scenario by connecting the demo to the full game's plot than by having it exist in a vacuum. (This also creates the potential for possibly reusing some of the demo material in the full game.)
With a 6th-level party, the demo would occur somewhere in the middle of the story. The very simplified version is, the party has been led by some kind of specter which can appear only to the PCs, and has been guiding them to recover fragments of its memory and find old records and devices in ancient dungeons. At the point in the story where the demo takes place, the party has just reached a deep level where a major device that the specter has been seeking is located. The goal of the demo scenario is to gain access to the chamber where the device is located.
With the basic idea in place, it's time to start fleshing it out. When designing an RPG, this involves one of my favorite activities: drawing dungeon maps. Here's a sketch of a draft design for the dungeon that will feature in the demo:
The mark of a true oldschool roleplaying nerd: dungeon maps drawn on graph paper.
One advantage of sketching out designs ahead of time is that it lets you explore ideas in a way where you don't lose a lot of time if you make a mistake or end up pursuing a dead-end idea. As I sketched out the map and thought of what challenges to throw at the player, there were many instances where I repeatedly erased and redrew map sections as I refined my ideas. This is a very low-cost way to do iterative design as opposed to going straight to implementing a level in the game, where having to junk and remake sections is far costlier in terms of time.
Sketching dungeon maps is also a lot of fun if you're into that kind of thing. And it gives me an excuse to do some creating away from the computer for a bit. That variety is good for keeping the creative juices flowing.
Another step I completed before starting implementation was to come up with a list of potential monsters to encounter in the demo, making sure they're both thematically appropriate and of the right power to challenge a 6th-level party. Once I had a good stable of monsters, I worked out some draft stats for them. I haven't created their graphics yet, but that will come as I continue iterating on the demo.
Once I have a design I'm happy with (though I'll typically continue to tweak and refine it as I start implementation), it's time to start actually implementing it in the game engine. Earlier content tends to be a lot more effort-intensive because I haven't created anything yet. So, before I could even start any of this, I needed to toss out the temporary test graphics I had been working with and assemble a workable selection of character portraits. As a time-saver, most of the portraits are currently character artwork I drew for old projects that never saw the light of day, plus a handful of brand-new portraits. I'll probably continue to expand and refine the selection of portraits as I go, but what I have already (in the neighborhood of 40 portraits to choose from) is certainly workable.
Then, of course, I had to create textures for the dungeon surfaces. Rather than creating every conceivable texture all at once, I'm taking an iterative approach of just creating textures as I need them. These will certainly need to be refined as I go as well, since a texture that looks good in isolation doesn't necessarily work well when inserted into the game engine.
Once I had enough graphics to get me started, I began transferring my graph paper sketch to an actual in-game representation of the dungeon using my in-house map editor. I'm not creating the entire map at once, because it's pretty large and mistakes are inevitable. So, it's better to create the dungeon in chunks, test them out, and fix mistakes as I go. I'm less likely to miss mistakes this way, and it's also much less overwhelming.
The first part of the demo dungeon as viewed in the DND map editor.
Developing iteratively means frequently stopping to test your creations. The screenshot below demonstrates that the engine is still working even as I'm subjecting it to more rigorous tests, and that the pregenerated party was created without problems (although I haven't assigned all their abilities yet).
Kind of hard to see anything...
From my initial test, the dungeon is too dark. There is a Light spell to brighten things up, but I don't want the player to be forced to cast Light in order to see anything at all. So, one of my first inclinations is that I'll tweak the lighting values until I find a good balance that makes the dungeon dark and creepy without making it impossible to see anything. Of course, this also depends on the gamma of the user's monitor, so I'll want to test it on different monitors and with tweaked gamma values to get an idea of how it looks across different hardware. Regardless, changing the lighting is nothing but a matter of tweaking a couple of numbers, so this isn't exactly a difficult thing to fix.
I'll continue to build out the dungeon in this way, constructing it section by section, adding more models and textures as necessary, and tweaking things and fixing bugs as I go. Right now, the dungeon is an empty, lonely place; but before long I'm going to begin drawing some monster graphics (the monster graphics I've been using in testing up until now were flagrantly ripped off from existing games, so I can't keep using them :P) and plugging their stats and AI into the game. I'll also have to implement a few more spells and abilities, both for the monsters and the PCs. But, one thing at a time.
Once a complete draft of the level is done, which can be played start to finish, the next step is to go through and fancy it up. Find areas that can be made to look cooler and add little touches and flourishes here and there to give it that extra shine. This step is technically optional since by definition you already have a fully playable level once you get this far. On the converse, this phase can continue for as long as the developer wants -- there's no limit to how much shine you can add to a game. It's all about striking the right balance between creating a product you can be proud of versus a realistic appraisal of how much time you've got to get the thing done. Projects that are behind schedule tend to skimp on the extras, whereas those that are finished ahead of schedule often have lots of polish, flourishes, and secrets added to them.
Listing this as a separate step is a little misleading since a good designer is constantly testing the level as it's being built, but that's only a start. A universal truth is that developers are relatively poor at testing their own creations. So once you think it's ready, the next step is to get your product into the hands of other people to put it through its paces. You need them to find as many bugs as possible so you can fix them, but also to test things like balance, playability, and fun.
The most ideal scenario is for the developer to be able to watch players testing the game live (preferably without any hints or input from the creator, in order not to bias the player's actions), as it can be very enlightening to watch players fumble with interfaces that you thought would work more smoothly, struggle with parts of a level that you thought would be easy, find game-breaking uses for abilities that you thought weren't that good, etc. ad infinitum.
Whenever watching a playtest live isn't possible, you still want to try to solicit the most detailed feedback you can get from your testers, whether that's by having them record their gameplay to watch after the fact (which is 95% as good as watching live), or just filling out a simple form reporting any bugs or other issues they found.
Either way, I'm looking forward to finishing the demo and getting it into the hands of as many players as possible, and hopefully getting lots of feedback so I can tweak Darkness Never Dies into the best game it can possibly be. I think that time is fast approaching now... probably another 4-8 weeks until the demo is ready to go.
And now, to take a break for the rest of the evening to recharge my batteries. Rest is an important part of work too. (:
Well... Long time no post. 😅 But there's a good reason for that. I've been hard at work on my dungeon crawler, and I thought it was more productive to continue making progress, rather than to write about making progress, at least until I hit a major milestone. That milestone has been hit, as all of the core features of the engine are now fully functional. It took a little longer to reach this point than I had anticipated, but if there's an ironclad rule about software development, it's that reaching feature completeness always takes longer than you think it will.
I'll be posting more about my dungeon crawler, which now has an official title, Darkness Never Dies (DND for short), in the coming weeks and months. But to celebrate reaching "feature complete" status on the core game engine, I'm starting with a post about the development I've been doing from a high-level, 10,000-foot view.
So what's it like programming a game engine? If you don't approach it carefully, the answer is, "overwhelming." The best approach is to follow the advice that you'll hear about completing any overwhelmingly large task: break it down into smaller, manageable chunks; make a to-do list; and tackle items on the list one at a time. Here's what the "master" to-do list for the DND engine looked like:
A lot of those items were actually fairly substantial tasks in themselves, so they would get broken up into their own to-do lists, such as the following. (Fortunately, not every item had to be broken up into sub-items like this, because that would have been madness):
I now have all the programming in place to begin building content for a pretty robust dungeon crawler loosely based on B/X D&D-esque rules (the same ruleset that Warriors of the Eternal Sun used), albeit with sufficient changes across the board that I could publish and sell the game without any danger of running into copyright infringement. 😅 A complete-ish list of the engine's features includes:
A screenshot of the character creation screen in Darkness Never Dies.
By far the most effort-intensive part of this endeavor was the battle system, because the sheer number of features in that particular subsystem practically makes it a game unto itself. After the battle system, the next most effort-intensive part was probably the menus, just due to the sheer number and complexity of them -- as with most RPGs, there are a lot of menus, and I tried to put some effort into making them easy to use. Some menus also end up with a lot of state to keep track of, the inventory/equipment screen being a good example. There are a lot of things that need to be checked when swapping a character's equipment: Do they meet all the equipment's prerequisites? Which slot does it go in? If it's two-handed, you have to make sure to unequip anything the character has in both hands. Likewise, if you equip a one-handed weapon into either hand, if the character is currently using a two-handed weapon, you have to make sure to unequip it from both hands. And so on. It's easy for bugs to creep in with a complicated situation like that, as is evident from the fact that there exist multiple RPGs with menu bugs where, with the right sequence of actions, the player can create endless duplicates of desirable items. I'm trying to avoid allowing any cheap exploits like that to creep in.
With a project like this, maintaining a reasonably good code design is a must, otherwise the project collapses under the weight of debugging and continuously adding new features. But at the same time, the ultimate goal is to just write code to get the job done, not to spend all your time trying to come up with the perfect design. I try to straddle the line by coming up with "good-enough" designs before I start coding, and keeping an eye out for pain points and refactoring (just a fancy word for redesigning and rewriting) parts of the code that are becoming burdensome to work with. Pretty much a fairly standard agile approach. I won't say my code is the most immaculate in the world by any stretch of the imagination, but I think the fact that I was able to create this feature-complete dungeon crawler system in the span of a few months, while working a full-time job, speaks to the effectiveness of my approach.
Now that the bulk of the programming is out of the way, next comes the fun part of game development: creating the content. I'm going to try to create a demo -- basically one large dungeon level or two -- that I can release well ahead of the main game, hopefully within a couple of months. This will also entail creating a lot of artwork, such as a wide variety of character portraits to choose from; monster sprites and map textures; and I'm going to take a stab at composing some of my own music tracks, though if that goes poorly there are plenty of licensable tracks out there to fall back on. 😅
Expect a lot more updates in the coming weeks and months as the game takes shape!