Aereven Lunar Wake's Postmortem
Legends of Aereven: Lunar Wake is a 2D Zelda-like I made in 2020. As I continue my gamedev journey with other projects, I wanted to take the time to look back on it.
The Engine
Why C#?
As a kid, I learned programming so I could make games. I started with Basic-inspired languages (first the game-oriented DarkBasic, then the general purpose PureBasic) before switching to C++ in highschool. In many ways, the inherent complexity of the language was a distraction: while I learned a lot about programming in general (and a lot of bullshit only applicable to C++), I wasn’t making a lot of progress on the gamedev front.
After a few years of making prototypes with C++ and Irrlicht, I had to use C# for a school project (because csc.exe was the only compiler already installed on the school computers, as part of .NET)... and I fell in love. C# was everything C++ wasn’t: simple, clean, elegant (and it was before C++11, which added another layer of complexity and legacy considerations to the pile).
I played with an Irrlicht .NET binding for a few months, but then came Microsoft’s XNA, and it was another revelation. XNA was lower-level than Irrlicht, yet not as low as SharpDX. But more importantly, like C# itself, it was clean and elegant. The API made sense. With C# and XNA, programming games was all pleasure and no pain.
There was no looking back.
GameLib and SPF
Flash forward a decade or so, and XNA is dead, because Microsoft seems to get a kick from killing their best products and initiatives.
The rising star at the time was Unity, but I hated it from the start. I hated the philosophy (framework vs toolkit) as I felt constrained by the engine. And I hated the execution: back in 2010, the Unity Editor would frequently crash, and I would lose work. Once my trust was lost, there was no recovering it. Add to that the poor Visual Studio integration and the old Mono runtime, the non-idiomatic use of C# and the lackluster support for 2D... Coming from XNA, the Unity experience was miserable.
So, what were my alternatives? I tried MonoGame and FNA, both open source reimplementations of XNA. They were fine. A few bugs here and there, and the shader pipeline of MonoGame was a bit cumbersome, but it was workable. I used FNA for a couple of years.
Around that time, Sony took a page from Microsoft and launched the Playstation Mobile initiative, which meant I could use a fork of MonoGame on the PS Vita. And I very much wanted to! But while XNA worked with the same types and namespaces across PC, XBox 360 and Windows Phone, here I had to use different (yet 99% identical) types for the PC and Vita targets.
That’s how GameLib came to be: I extracted the math classes from MonoGame in a separate assembly, so my gameplay code would be independent of the actual XNA-fork I would use for the rendering. And then I started adding stuff, from general utilities functions to very specific gameplay behavior. After five years it snowballed to the point where I had a full Tactical RPG engine hidden somewhere in GameLib… Oops?
But Sony took another page from Microsoft and killed Playstation Mobile before I could do anything with it. By then I was a bit tired of FNA, which was intended to be a 1:1 reimplementation of XNA for archive purposes and would never receive new features.
I don’t need something so general, I thought. So I started p/invoking Win32 and OpenGL directly to write a “platform” library. All this interop quickly became a pain, so I turned around and wrote the whole thing in C++ and only exposed my own API to C#. Also, I ditched Win32 to build on top of SDL2 and SDL_mixer. The platform handled window management, input handling, rendering (with a built-in sprite batch), audio and I even added Bullet for some simple physics stuff. I called it “SPF” for “Simple PlatForm”, but two shipped games and a dozen prototypes later, I’m not sure the “Simple” adjective fits anymore. I intended it for 2D pixel-art projects and now I’m using it to make 3D stuff with multiple render passes, custom shaders and stencil buffer abuse.
I still actively maintain SPF, which is open source: https://github.com/Dreamnoid/SPF/
Dream
The problem with GameLib was that it provided reusable blocks, but you had to glue them together.
For every new project, I had to write a lot of boring boilerplate code just to get to something playable. Sure, the controller input and collision routines were provided by SPF and GameLib, but how many times do I have to write the same damned velocity or gravity code to bridge the two? Map loading and autotiling are in GameLib, as well as a Camera2D class, but how many times must I write the same tilemap rendering code with scrolling considerations? That’s not a new problem, that’s not fun.
I grant you, it’s only an issue because I have no discipline and I often start new projects on a whim. But it’s easier to change your tech than your nature. So that’s what I did.
Enter Dream. When it started, Dream was 99% GameLib with the namespace changed. But the goal of Dream was to provide more than reusable classes, but reusable glue too.
Yet, it had to be optional, otherwise I would end up with another Unity. I would have built a prison tailored for me, but it would still be a prison.
Dream is architectured in modules, and only one is mandatory: Dream.Core, which is the direct descendant of GameLib. It defines a lot of utility classes and functions (math, collisions, serialization, generic data structures, etc) that can be used in a standalone fashion, but also interfaces necessary to describe what I call a "Platform" (IWindow, IInputProvider, ITexture, IRenderer, IFilesystem, etc).
Then I have a Dream.SPF module that acts as a "Platform" implementation using SPF. The Platform interface was described with SPF in mind, so it's almost a 1:1 bridge between SPF and Dream. Those are the two modules I basically use every time.
Then you have two other kinds of modules: those dedicated to certain kind of games or gameplay features (eg: Dream.Vino provides staging and dialog features for Visual Novel or RPG games) and those used for tools and pipelines (eg: Dream.Editor is an IMGUI-type tool-builder and Dream.Drawing includes classes to manipulate bitmap or raytrace sprites from 3D models, etc.)
There are currently 13 modules in Dream!
ECS
So, reusable blocks is easy and all, but how do you reuse the glue between them?
I was first made aware of the Entity/Component (EC) and Entity/Component/System (ECS) concepts through the “Evolve your Hierarchy” and T-Machine blogposts, and it was eyes-opening. Around the same time, the notion of Data Oriented Programming was also gaining traction, with the Sony paper blasting OOP or Mike Acton’s post-its. Problem is: the two ended up confused in my mind (and to be fair, in a lot of literature too).
The full ECS system was always described to me as a performance thing. Entity are just IDs! Components are stored sequentially! Cache friendly! Except I couldn’t wrap my head around it. Wouldn’t you need more than one component to do anything useful? Wouldn’t that cause random accesses? Surely I was missing something. (As it turns out, yes, I missed the fact that in that kind of design, data is constantly shuffled around in memory). But the games I was making could run on a GBA, so what did I care for cache misses?
In 2017, the Overwatch team gave a GDC talk about their ECS implementation, and in 2019 it was finally made available outside of the Vault. It was everything I hoped it would be (I had tracked a Chinese transcript before, but Google Translation butchered it). They didn’t talk about performance. They said ECS helped with side effects. ECS helped with order of updates. ECS helped with coupling. ECS made it easier to organize gameplay code. Finally an ECS talk about my concerns!
And with that, another side became apparent: ECS would help reusability. Components don’t need to know about each other, as they’re entirely piloted by higher-level systems. It’s a bit like Duck-typing, where the mere presence of properties or methods on an object is enough to make it eligible for some behavior, without the need for a rigid agreed-upon interface.
The ECS would be my glue.
So, how does that work in Dream?
Entities are real. Not just nebulous IDs, sorry data-oriented purists. They come with a few flags (like Terminated), a position (a Vector3 that can be used in both 2D and 3D contexts) and a list of components. (Only one of each type is allowed.)
Components are barely more interesting. They only store data. I do allow ease of use methods, like setters that will change multiple properties at once in a coherent manner, but that’s it. They also have an Enabled flag. If disabled, it’s like the component never existed. It won’t be queried by the systems. So, let’s say I have a ghost enemy that periodically disappears while still moving around: I just need to enable/disable the Sprite component to make it eligible or not for rendering.
Scenes are basically a collection of entities and registered systems. You can queue spawn operations at any time, but entities are only created at the beginning of a frame, and only removed at the end. That way I don’t have to worry about half-updated entities.
And then we have systems. Systems do not hold state (though they may hold configuration settings or resources). Instead, they query the scene every frame for tuples of components. Like ‘give me all entities with both a Physics and a Sprite component’, and then the components are accessed from the entity. That system may for example change the sprite to reflect the current physics state. In practice, with the position stored directly in the entity, I never had to query more than 4 components at once.
(Now is a good time to tell you I don’t do multithreading. Because I don’t need to. But if I did, I would need to be a bit more strict about separating reading and writing operations, so I can run the systems in parallel.)
Each frame, the scene will run every system in the order they were registered. So what happens when stuff need to be updated in a specific order? You could try to register the systems carefully, in order, but it’s a losing battle. Instead, the ISystem interface provides a few callbacks that act as ‘passes’:
- Gather is called first to get info on the current state of the game. Like the list of actors that will contribute collisions. Or syncing the player’s entity health component with the HP written in the save file.
- Controller is a chance to decide how entities will move before the physics pass. It’s here that I translate the user inputs into a velocity vector. It’s also here that enemies AI decides what to do.
- Physics will try to resolve all movements and collisions. It will handle gravity, if enabled, check if an entity is grounded, etc. The beauty of ECS is that you move all entities at once, so you avoid the bugs that usually come with some entities being fully moved while others have yet to start (moving platforms are straightforward with ECS).
- Interact is another chance to decide what entities will do, but once physics is resolved. Typically, I will use this pass to handle interactions (like a ‘Talk’ prompt), triggers or hitboxes.
- Lifespan is near the end of the frame, opposite to Gather. It’s where I decide which entities should be terminated or continue to live (like removing a played out FX).
- Animate comes at the end to update the graphics so they can reflect all that happened in the frame.
- Render is used by RenderSystems like Dream.Pixel and Dream.Polygon, but also by the UI RenderSystem.
The Gather method will be called for each systems, then the Controller method, then Physics, etc. So order-dependent code is always in different passes.
One last thing: scenes have components too. They’re usually a different set of components than those used on entities. More like the ‘singleton components’ of Overwatch. Those components are directly accessed from the scene, always in a GetOrCreate way. Think of it as a way to exchange data between systems without them having to know about each other. For example, the CameraSystem will write to a Camera2D singleton component that will later be read by the RenderSystem to render the correct viewport. If I do not register the CameraSystem, the RenderSystem will get a Camera2D component with default values, so everything continues to work fine. I can replace the CameraSystem with a different one, or add other systems between the two to change those data (ScreenshakeSystem!).
Hopefully you’re starting to see how it can help modularity ;)
Okay, the actual last thing: System bundles. I’ve found that some systems are ubiquitous. Everything dealing with the lifetime of entities, or their hierarchy, or triggers, etc. Every games need them. So I bundle them together to register them all at once. The CoreSystem is the most obvious SystemBundle, and I always register it. But you can also have an RPGSystem that will bundle a collection of systems that work well for making an RPG, for instance.
Subroutines
ECS is great for organizing what happens in a frame, but games often have to coordinates stuff happening over multiple frames. For that I use subroutines, made easy in C# thanks to the yield keyword.
I often use subroutines as coroutines. Mostly for entity behavior. If you remember the example about the ghost becoming invisible, that behavior can be driven by a coroutine that will enable/disable the components after some time has passed, or if the player is near, or randomly.
So when is a behavior a component/system and when is it a coroutine? My rule of thumb is that if the behavior is reusable, it’s a component/system. If it’s unique, it’s a coroutine. A HorizontalPatrolComponent can be used for both Goombas and Koopas, but a Bowser boss fight would be a unique coroutine.
Like I said, the whole ECS thing is an option. Scenes are game states, but game states need not be scenes. Most of them aren’t, and are instead driven by subroutines. The most notable examples are menus.
Some states are both. For example, if I were to make a 3D turn-based JRPG battle, I would have a scene with actors for every fighter, rendered and animated with their respective systems, but the logic would be driven by a subroutine. Turn-based systems are usually a better fit for subroutines than ECS.
Pool allocator
C# is a garbage collected language, which is great but also terrible. Great because when you don’t care about memory, you don’t have to. But terrible because when you do care about memory, there’s not much you can do.
Thankfully, you usually don’t need to do much.
The bane of every C# game is when the GC decides it’s a good time to clean up house, right in the middle of a frame. .NET’s GC is fast, but not 60 FPS fast. So how do we make sure it doesn’t run during gameplay? By not allocating memory.
That’s why the Entity class and all components implement an IRecyclable interface that moves their initialization from their constructor to an explicit Recycle() method. New instances are requested through a global pool allocator, that will only call ‘new’ if no instance is ready to be reused. When the code is done playing with the object (e.g. an entity has despawned), it is sent back to the pool to be recycled. That simple system removes most of the allocations happening during gameplay. (What’s left is due to .NET being allocation-happy, and usually remedied by reusing large-capacity lists, caching delegates or avoiding AddRange and Sort.)
(And yes, I do allocate memory… up until I don’t need to anymore. .NET allocates memory in pages, so a single new is pretty fast. My memory consumption curve starts linear but quickly ends up flat. And I manually run the GC during scene transitions, while the screen is black, just in case.)
Prefabs
Creating an entity is as simple as calling scene.Spawn() and adding components to the newly recycled entity.
Prefabs rely on that. They’re semi data-driven: built in code, as functions that can receive extra information. The prefabs can use that extra info to adapt using any code imaginable.
It can be really simple, like the Chest prefab offering a different item depending on the input. Or a door requiring a different key. But it can get more complex, like the NPC prefab that will completely change in appearance and behavior according to the NPC definition.
I can also run logic to make the spawning conditional. It was super helpful for Lunar Wake because the main source of bugs after release was props respawning at the same position as the player, making them stuck:
It’s a simple yet powerful system.
Since then, I tried making it fully-data driven. It simply wasn't worth it: I ended up complicating things by orders of magnitude and it required extensive tooling. Turns out, for a lone programmer, writing game-specific code is okay! That being said my current approach is still a lot more data-driven than what I had for Lunar Wake. The code only defines reusable broad behaviors and everything else is configured in data.
Anyway, the reason I can do data-driven stuff is thanks to the...
Serialization
Serialization is nothing new in .NET, but I don’t use the builtin system. You may wonder why. Sometimes, I do too.
After The Lightkeeper, I briefly prototyped a 2D JRPG. I wanted to use Lua for the scripting, but the .NET bindings were a pain in the ass. And I didn’t need the full power of Lua anyway. And you may have gathered by now that I like reinventing the wheel (and why wouldn’t I, it’s fun!). Even worse: I love writing programming languages. So I wrote RPGScript. RPGScript is a simple, lightweight, 100% managed Lua replacement. (It’s also open source: https://github.com/Dreamnoid/RPGScript)
For a few projects, I used RPGScript for both data definition and scripting. But I quickly (and painfully) realized that mixing code and data together could be problematic. At first you’re very reasonable, only using code to describe behaviors like what happen when you talk to an NPC. But then you start abusing that power to develop a macro system and everything goes to shit.
So for Lunar Wake I started from RPGScript but removed functions. And then I changed the syntax to look exactly like JSON.
The way I do automatic (de)serialization is by having an IData interface exposing a Visit(IVisitor) method. This method will describe the properties of the object (usually name+type, but it can be a bit more involved if need be).
Two examples of Visitors are the serializer and deserializer, but the Dream.Editor module can also leverage that to create automated UI.
Beyond that extra flexibility, my solution has the advantage of not relying on reflection, which means it should be a bit faster (it’s certainly a lot easier to use). Also, you don’t need to reference NewtonSoft packages.
Scripting
Now that I have replaced RPGScript with JSON, what of the scripting?
All languages are trees before being compiled, but in RPGScript the AST was executed directly, without being converted to opcodes. After migrating to JSON, I quickly realized I could write my code using only lists. It works, but the syntax is unwieldy, especially when you’re using an AZERTY keyboard.
Writing code with nested lists? If only I knew a language that worked that way… Brackets are hard to type, so maybe if I switched to parentheses… Removing unnecessary double quotes… Hmm... That DOES sound familiar.
Yeah, it’s freakin’ Lisp!
My Lisp parser actually creates JSON lists under the hood, so the virtual machine didn’t even have to change, but the syntax became a lot nicer.
So... did it work?
It did!
It really did.
As we will discuss later, this project went through a few false starts before it became a Zelda-like. So by the time I started working on Lunar Wake proper, Dream was pretty much up to speed. Map rendering, collisions handling, hitboxes interaction: all that was already working.
The most code I had to write at first was the PlayerControllerSystem that handled the particular Zelda-like movement, sword swing and other tools use. It got gradually more complex when I implemented advanced gameplay systems like falling in pits, swimming, skidding on ice, crossing chasms with the hookshot, etc. But none of those features took more than a day or two to implement.
Likewise, implementing enemies was a breeze. I could go from a rough idea to a working prototype in very little time. And the ECS even suggested new gameplay ideas! When you have a bunch of systems ready to go, you're tempted to mash them together to see what happens. That's how the Ice Rod in the Ancient Forge extension came to be: I could already spawn platforms, make them temporary and turn their surface slippery, so the idea came naturally.
So yeah, big win for ECS and reusability. All of my current prototypes and more serious projects use Dream, and they reuse a lot of code. I regularly promote code written for a specific game to Dream.Core or a dedicated module.
The Tools and Pipeline
The Approach
I’m the lone developer on my games, and I’m a programmer. It means I’m not afraid of writing code to get the results I want. Thus, as discussed in the previous part, my current approach is not fully data-driven.
But it still is to a large extent, because some things are a pain to express in code. Sure, you can write the stats of your RPG characters directly in a C# file, even derive them using very advanced formulas (aka ‘code’), but when you want to just visualize them neatly in a row, it’s difficult. You need tools. If you’re going to build them, you may as well do everything in them.
Most of my games have a ‘Database’. Not the SQL kind, but something similar to what RPG Maker 2000 had, at the very beginning of my gamedev journey. If it works, it works!
The database is a big JSON file with definitions for almost every element found in the game. Items. Maps. Warp points. Characters. Spells. Etc.
Of course, I don’t edit it manually. Instead, I’ve built an editor, in C#, using Dream and leveraging the IMGUI system in Dream.Editor. This editor has three responsibilities:
- Editing the database (with either generic or custom UI, depending on the use case)
- Editing the tile maps
- Launching scripts and process, like the Validator
Sharing data
The editor is a separate executable from the game. A different project in the same solution. I didn’t want to include the editor inside the game (even as a separate mode) because I wanted to be able to recompile the game without closing the editor. If they used the same .exe, that wouldn’t be possible.
But I still needed a way for the two executables to share data. The proper .NET solution would have been to create a third assembly for the shared code. It seemed a bit overkill, so instead I relied on a nice Visual Studio feature to reference the same source file in both projects. All data definition went to a Shared.cs file used in both compilations. So the only time I needed to close the editor was when I updated that file (but then that usually implied that the editor would change to reflect that) or Dream itself.
Turns out, I change Dream quite often, actually. So I often had to close the editor anyway. For new projects, I’ve decided to bite the bullet and just have the editor in the same executable, only accessible through an “editor” command line argument. You can try it with Tower of Ordal: start the executable with the “editor” flag and happy modding!
Map Editor
For years (scratch that, for more than a decade now) I’ve been using a little WinForm+XNA tool named Blacksmith for all my projects. Reckless Squad used it. The original Tower of Ordal used it. The Lightkeeper used it. It’s old, but it works. (On my PC, anyway. The XNA Runtime dependency becomes more cumbersome with every new version of Windows…). Best return on investment ever.
I didn’t use it for Lunar Wake.
First, Blacksmith has some problems. Number one: it doesn’t have an Undo command. But more importantly, it’s game agnostic. Very generic. Which, in many ways, is a strength! I wouldn’t have used it for so long and for so many projects if it wasn’t. Still, I want to be able to customize the editor for each project. I want to be able to preset the layers, tile size, tileset, grid measurements, etc. I want to be able to place prefabs directly indexed from the game code. And some projects could require custom features, etc.
For years, I meant to develop a “Blacksmith 2.0” in WPF, with plugins support. The idea never took off, because it was the wrong approach.
The right one was Dream.Editor.Blacksmith.
This namespace in the Dream.Editor module is a toolkit to quickly build map editors, using the other IMGUI controls. And almost everything can be composed or extended in different ways.
For Lunar Wake, I still use the BSM file format from Blacksmith. It means I can still open and edit my maps with the good ol’ Blacksmith editor. It was my safety net while working on this new map editor. Nowadays it is robust enough that I can go with specific file formats, but still… code rot is a thing, and using a well-supported file format saved a few of my old projects.
But for Lunar Wake, I still needed to store more data than BSM allowed. So I used the “Name” field on my maps to store arbitrary JSON (that would include the actual map name!). There, done, problem solved.
What I did not want was to choose the size of the map before laying the first tile. Because at that point, I have no idea how big it’s going to be. Infinite maps is a thing, Tiled does it, but it’s a pain to implement and that wasn’t really necessary. I went for something easier: maps are created small (the size of a single screen) but can be easily extended by another screen in any direction, or cropped. It’s a bit like sculpting: sometimes you add clay, sometimes you remove some.
Something I added that I never had in the original Blacksmith is Undo/Redo. I handled it in a very brute-force way, by making a full snapshot of the tilemap everytime a draw operation is completed (= when the mouse button is released) and storing it in a circular buffer. Not memory efficient by any mean, but it worked, and it was super helpful. Misclicking with the paint bucket used to be apocalyptic, but not anymore.
Prefabs preview
Prefabs being described in code does not preclude them from being listed in the editor. The Lunar Wake editor will read the game assembly and list all prefabs (identified by an attribute) to present them in a swatch.
It will also instantiate one of each and look for a Sprite component to display some kind of ‘preview’. It was a handy feature, but it introduced a few headaches, because spawning outside of the game, with no active scene or player, would not work for every prefab. I had to handle those situations, check if I was in the game or the editor, and change the prefab accordingly. Not great.
For my current projects, prefabs are a bit more data-driven. The behavior is usually written as code, but the appearance of each entity is configured in the editor (because a visual editor is best for visual stuff, who knew?), so it can easily be used by the map editor to preview the prefab.
The Validator (not starring Arnold Schwarzenegger)
The other feature that I knew I wanted from the start was the Validator.
It’s a pretty simple script that will browse all the data in the game (both in the database or in the map files) and check that everything is coherent. While doing ‘level design’ (scare quotes added to not insult my level designer colleagues) I would often place a chest without deciding on a specific reward. The validator would then print me a list of every empty chest in the world. It would check that entities were correctly configured, that IDs actually referenced things that existed, that no data was missing, etc.
The Validator quickly evolved into a general-purpose reporting tool. Beside errors, it would report stats on the world, like how many times each enemy was used (when making The Lightkeeper, I created an enemy that ended up being used only once, which was quite a waste), how many piece of hearts were to be found in the world (and making sure it was a multiple of 4), and a guess about how much money the player could reasonably collect in a playthrough, which helped me balance the shop prices.
The Validator would run on the press of a button in the editor, and output a simple text file presented as a list.
It was simply invaluable. So much that it acted like a secondary To-Do list in addition to my Trello board.
Conclusion
As a professional Tools Programmer, this is a very important topic for me, even for my hobby projects. A lot of what I exposed here has been refined with The Trespasser and will continue to change in the future.
Still, that approach for Lunar Wake was a great starting point. The Ancient Forge extension was produced quite quickly and painlessly thanks to those tools.
The Concept and Design
Concept
When I tell my ‘how I became a game developer’ story, I usually say that playing Mario 64 was when I realized I would play video games all my life... and Ocarina of Time was when I knew I would be making them as well. I played games before, but those two really captured my imagination with their 3D worlds. Needless to say, the Zelda franchise played an important role in my childhood. To this day, Majora’s Mask is still my favorite game ever.
But short of learning Japanese, moving to Kyoto and being hired by Nintendo, I’m never going to work on a Zelda game. Unless I make my own.
Though my love is mostly for the 3D Zelda games (because exploring a 3D world is just plain better), I still thoroughly enjoy the 2D games, and they’re quite easier to make for a solo programmer. It probably helped that Link’s Awakening Remake was out six months before, and was still fresh in my memory.
But I didn’t initially set out to make a Zelda-like.
First, I tried my hand at another side-scrolling Metroid-like, a spiritual sequel to The Lightkeeper, but with bigger sprites, more like Metroid Fusion. But I wasn’t feeling it.
So I switched to an action-adventure platformer, like Zelda 2. It was already in the Aereven universe, and tentatively called Witch Hunt. But I usually prefer top-down view to sideview, because exploration is more natural on the ground (the sideview constraint is why so many 2D Metroidvanias take place in space stations, castles or caves).
So I switched to a JRPG, still taking place in the Aereven universe, but with a different story. Never gave it a name, but it was about a Healing Fountain and a Pirate King. I went pretty far. It looked a bit like Golden Sun (but less pretty), with lots of puzzles. I actually implemented all cities, with NPCs, story progression, and a few puzzle-y dungeons. But every time I try to make an RPG I struggle with the battle system. As a player, I tolerate the traditional turn-based menus (I can even love them in some rare cases), but I don’t enjoy designing them. Also I hate numbers, and math, and stuff! So when it came to designing the battle system, I quickly lost steam and dropped the whole project.
It had to be a top-down action-adventure game. Basically a Zelda-like. The story changed once again, but I reused most of the graphics. And all four games, the three abandoned prototypes and Lunar Wake, used Dream and shared code. You see now why I needed a modular engine!
Aereven
Around 2004, when I was a highschooler with more ambition than skill, I teamed up with an internet friend to make what was sometimes a JRPG, sometimes an MMORPG (sticking to a scope wasn’t exactly our forte).
You guessed it, that project was called Aereven. You probably also guessed that it went nowhere. (Beside the insane scope, it was during my C++/Irrlicht phase, so: lot of learning, very little results.) But I kept trying to make an Aereven game in the years after. I think you can still find some old attempts with a Google search.
(Thanks Wayback Machine! "Not definitive" indeed! It only took more than a decade :p)
What was it that appealed to me? The world I guess. I still have my old worldbuilding documents, and let me tell you: they’re bad. Very cringey. You can tell I was a 14yo boy who had just read The Silmarillon. Still, something about a world-ocean, creepy moon-devoted elves and fish people who are masters of both water and fire never stopped appealing to me, even after another 15 years.
One of the strength of Aereven is that it’s made to be infinite. The whole world is an ocean, with countless continents, islands and uncharted territory. I can tell an infinite number of stories in that world. Like Zelda, it was made to be subtitled. There’s no one game called “Aereven”. It’s a vibe and a handful of shared elements, leaving me plenty of freedom for each installment. They don’t even have to be all the same genre.
Initial flowchart
So okay, I knew I was making a 2D Zelda-like game set in the Aereven universe. Now what?
It’s at that point that I abandon most projects. Funny that. It should be the best part of the project, the actual creative step. The chance to lay down all the cool things you want to do. But it’s also the step where you go from a perfect blank page to a potentially irredeemable design. In other words, I often get choice paralysis.
But just like The Lightkeeper before it (and Tower of Ordal back then), I avoided that fate by randomly ‘designing’ Lunar Wake. No second guessing allowed. It may end up being mediocre, but it will still be something. It's a similar process to pantsing a novel: you write a first draft on instinct and fix it later when you have the big picture.
I started with a flowchart, listing the different places and the key/lock relationships that gate the adventure:
It took me all of five minutes. It was enough to start. I later changed stuff around, added more dungeons and obstacles, but not before implementing the initial version and testing it.
It also gave me a clear scope, written as a text file with checkmarks:
Production
Then it was just a matter of implementing every area, preferably in a sequential order so I could make sure every mechanic was properly introduced. ‘Level design’ (again with the scare quotes) was a spur of the moment thing: you add some tiles and go “Oh! That would go well with water here. And a cliff! And here is a chest!”.
The problem with that approach is that it may end up being “too small”. You create your environments with what little gameplay elements you have, and when you finally add a new one to the game you find out you have no room left for it. My Blacksmith editor makes it relatively easy to move stuff around, but it’s still harder than calling it quit and not changing it. It’s also a bit difficult to take stock of the pacing when you don’t have the full picture. I had to insert two extra mini-dungeons to the game in the last few days of development!
To mitigate that, I tried to design as many enemies and puzzles as I could before drawing the first map. It gave me a good initial palette, and new additions could be introduced in later content.
("Improvising" dungeons is pretty difficult, too. The forge above is the most complex dungeon in the game, and I started it with no big idea or plan...)
It sorts of work.
But I feel like it lacks intention. I also write novels, and even though I discover a lot of neat ideas as I write the scenes, I usually have a better plan. Things are more intentional and I always rework scenes to better fit with what comes before or after. Sure, it’s easier to move a chapter around or rewrite a paragraph than it is to re-implement an entire section of a game, but I’m still looking for a way to do that with game development.
One solution may be better tools, to have a way to see and edit the game in a macro fashion. Procedural generation is pretty good for that too, as it lets you worry about the systems as a whole while the minute details are (re)generated, but I have yet to find a way to really integrate it in my workflow for fully authored games.
Iteration
I abandon most of my projects after completing the “vertical slice”. Those that survive do because I managed to get a first draft completed before losing steam.
If I can get past this point, then I’m pretty sure to finish the thing. At that point it’s only a matter of iteration: I will play the game with a text file open, and write down my starting and ending time to get a feel for the length of each playsession. I try to play it as a newcomer would, following the main path and pretending to be surprised when I find a closed door instead of going straight for the key like a speedrunner may do. While I play, I write down notes. This is too fast. This is too hard. Too easy. I don’t have enough money to buy the next item. That enemy glitched. I managed to sequence break this thing. Etc. I write my notes fast to make sure I don’t get out of the flow. Just state the problem. There will be a time later to come up with a solution.
I wish I could have playtested Lunar Wake with other people, but I made most of it in isolation during the first COVID lockdown, so that wasn’t really possible.
The Z Dimension
To zoom-in for a moment, I want to talk about one of the regrets I have with this game: it’s 2D.
I mean, sure, that was the whole point: a 2D Zelda game. But starting from A Link to the Past, no Zelda game have been truly 2D. These games have a hidden Z component. When you walk up stairs, it’s not just a matter of the ¾ projection tricking you into thinking you’re going up like most regular 2D JRPGs: Link’s Z component is actually increased. An arrow fired from a platform will have a higher Z than the enemies under them, and thus will go over their head.
Lunar Wake works differently. It’s entirely 2D. No Z component. Probably because it took off from that JRPG experiment that didn’t need it. Most difference of elevations act as walls, for the player character but also for projectiles. It makes no sense! If two platforms are facing each other, I should be able to stand on one and fire an arrow at the switch on the other one. It’s a classic Zelda puzzle! But in Lunar Wake the arrow will stop at an invisible wall.
I make it sound like it's a big deal, but it’s not really noticeable. I designed the game around it, using specially-designed pits for that kind of puzzle. Zelda 1 works the same way. It’s more of a missed opportunity, really.
Except...
I wanted this arrow-from-a-platform-to-the-switch-on-another-platform so bad that I faked it for the Ancient Forge expansion. How? I added invisible entities that would act just like the puzzle-platforms that only appear if you turn on a switch, or the ice platforms over lava. Except they’re always on, but not for the player character. Kind of like a tunnel that only allows projectiles!
That’s not consistent design, but I couldn’t help myself!
Looking back (and then forward)
All in all, I'm quite proud of Lunar Wake. The development was fun and relatively painless, and I recently replayed the whole game to check for regressions after introducing input remapping, and it felt good! I had fun, and I thought the Aereven vibe was conveyed pretty well despite the minimal story, the programmer art and the public domain soundtrack. I do not think it rivals any of Nintendo or Capcom's installments, but I hope Zelda fans will enjoy spending an hour or two on Falern Island.
So, what's next?
Obviously, I want to create more Aereven games. I've been trying to complete one for almost two decades now, and Lunar Wake did scratch that itch a little. Like putting an old regret to rest. But there's so much more I could do with it. Locations I've dreamed of for years, waiting to be explored. Characters waiting to be introduced. Old ideas eager to resurface.
Ideally, I would like for the next installment to be 3D. Those games are built around a sense of place first and foremost, and 3D is way more immersive. There's a lot more you can do with a third dimension. But it's also a lot more work, more complex tech-wise (though I like the challenge), and less forgiving. It's way harder to make a 3D game look good, or feel good to play!
So I don't know. Four years later, I still don't know. I have 3D prototypes, but they feel both unimpressive and too much work for a solo developer unwilling to spend the next 10 years on it. I'm hoping to find a good angle, a cheap art style that still looks competent and a game design that's fun to play without involving too many animations or stuff like that. The answer is not obvious, to say the least.
But I'm not giving up.
So maybe we will see each other again in the world of Aereven. ;)