My Cool Playdate Mech RPG

Hi everyone! I’ve been putting off sharing anything about the game until it was Visually Presentable, but finally accepted this is still years away. So let’s document the progress as it happens. Alright, let me channel my best nintendo direct impression:

My Cool Playdate Mech RPG (working title) is a game of Strategy, Romance, and Giant Robots for the Panic Playdate video game console. Set in a sci-fi world of space feudalism, where great Noble Houses wage civil war over scraps of imperial periphery, create your own Mech Pilot and assemble a ragtag team of Nobles, Mercenaries, Pirates, and more! Customize your fleet of Mechs and take it into tactical, turn-based battles, both planet-side and in outer space. Strengthen bonds between your squadmates by having them fight side-by-side on the battlefield, and in the off-time get to know them through visual-novel style story scenes. Forge frienships, rivalries, and maybe something more? Mount your Mechs and shape the fate of the galaxy in My Cool Playdate Mech RPG (working title), coming to Panic Playdate at uhhhhh some point. Like 2029 or something

Screenshots (literally all of the visuals are currently placeholder)

select unit

engagement preview

dialogue

options

Ok enough of that bit. MCPMRPG(wt) is an SRPG, chiefly inspired by Fire Emblem and Lancer. It has character creation, grid-based tactical combat, build-crafting through equipment, and cross-unit relationships (both player-companion and companion-companion). It will have dialogue options for role-playing purposes, but most likely no significant branching in the story.

I’ve started on this project in May of 2024. Been wanting to make something for the Playdate since basically it’s announcement, and wanted to make something mech-related, and playing Fire Emblem Fates at the time I thought “Here is a simple and exciting idea – Fire Emblem + Mechs! It’s just a series of stand-alone maps with straight-forward turn-based mechanics, how long could it possibly take? Like 2-3 years tops?”. Turns out, unfortunately, a game even of this modest complexity still takes one person working part-time like 5-6 years (and that’s still an optimistic number at this point).

By the end of 2024 I’ve had both combat and dialogue systems pretty much working. And since then I’ve been stuck making everything else around it! Menus, turns out, take a really long time.

List of what’s currenty in the game:

  • Main menu
    • With the ‘Press Any Button’ message! Surprisingly satisfying to implement one yourself, makes me think about video game conventions, how arbitrary they actually are, and yet how we continue re-create and perpetuate them. Every ‘Press Any Button’ screen in every game before did not magically appear on its own. A human looked at other games, thought “we need one too”, and diligantly re-crafted it from scratch in the image of those other games. I kinda love that
  • Save/load menu
    • With support for multiple characters, manual saves, and separate categories of auto-saves (before starting mission, before deploying to combat, start of each turn)
  • Basic character creation
    • Currently you can enter your name, mech name, and choose both third- and first-person pronouns. A handy preview demonstrates an example sentence to show how those will look in-game. Editing your character’s visuals will happen at some point.
  • Mission selection screen
    • Currently just a list of names of available missions. At some point I plan to make it more visually interesting
  • Mech equipment management menu
    • Unit can equip a number of weapons, Systems (passive-ish bonuses, aka perks aka feats etc.), and a Frame (determines stats + can come with pre-installed Systems)
  • Relationship menu
    • Check relationship levels and access unlocked scenes
  • Combat
    • So many things implemented here, attacking, aiding, pathfinding, status effects, enemy ai, VFXs, etc.
  • Cutscenes
    • I’ve ported my dialogue system from Godot to Playdate, and so can use my Dialogue Editor to write branching dialogues. Additionally I’ve implemented a veeery simple scripting language to direct simple cutscenes (move units around, move camera around, etc) and integrated it into the dialogue system.

List of things curiously still missing :

  • All of the art
  • All of the sound
  • All of the writing

Whoops! Turns out I made the rookie mistake of getitng stuck in pre-production hell and am yet to start actually making the actual game. All the visuals including UI are currently placeholder. Current mech sprites are by blobertson. I did technically have some writing done – world-building and a rough outline, but those hardly count.

Thank you for reading! I have several posts planned about technical and design aspects that I found neat, and will try to log my future progress as it happens.

Since you’ve also talked about the dialogue editor here I’d be interested to read about this process. How much of the original work was stuck in gdscript/c# etc? Does the work of “porting” mostly just mean implementing something that can parse files?

Also, importantly: what do you use the crank for in this?

Yay, RPG! How did you program the enemy AI?

I’m also here to ask how the crank is going to be incorporated!

I very much hear what you’re saying in terms of “my whole life is menus”, it feels like UI work is never-ending. I try to remind myself that the way players interact with the game UI kind of IS the whole game, particularly if you’re making a more menu-driven game like you’ve described.

Anyway, I’m looking forward to seeing how your work progresses!

Thank you for your intereset!

For the sake of porting, the dialogue system is three parts - the editor, the files it produces, and the runtime logic that works with those files. Editor logic remained basically untouched. Runtime-logic was ported over a long weekend of copy-pasting classes one by one from GDScript to Lua and adjusting the syntax lmao. As for files, I really did not want to parse Godot’s .tres format. So for the editor I implemented a JSON exporter, and then in Lua I parse those .json files into runtime data. The current workflow is: I open a Godot project (since i still haven’t had the time to fully sever my editor from it), work with “source” .tres files in my editor, and upon saving they are automatically exported as .json into a corresponding folder in my Lua project. Not the cleanest, but it gets the job done.

Enemy AI is nothing fancy, enemies don’t coordinate with each other nor think several moves ahead. I’ve implemented essentially a simple utility-based AI. There are two verbs - attack and aid. On its turn, unit looks at every target it could attack and every position it could do so from, same with aid. Then for each position it evaluates a scoring function. For example attacking score depends on how much health attacked unit will have left, whether you get counter attacked or not, how far you need to move to get there, etc. Then it just moves to the highest-scoring position and performs that action on target unit.

Game design-wise I’d say this is good enough. State of the board changes too dramatically turn-to-turn for multi-turn planning to be meaningful. And some complicated enemy coordination is not communicated to the player, so would remain barely noticible anyway. At most I’m considering a heuristic where units with buff/debuff abilities should act first, and regular attackers should act second.

Regarding the crank, currently there are no plans for it. I find it best suited for inputs that require analog precision, which is simply not a thing in a very discrete turn-grid-based game. I’ve considered it for maybe radial menus, or as a mini-game to charge up powerful attacks, but ultimately these all seem like gimmicks. Not particularly additive to the experience and could become tiresome, solutions in search of a problem.

Personally, I was never especially enthusiastic about the crank. Playdate instantly enamored me with its 1-bit screen, form-factor, and independent nature, but the crank, while fine, fun even, is just not useful for the types of games that I like and want to make.

I think you could make some truly creative crank-based attack-charging mini games if you wanted too, like in Paper Mario. But if that’s not the kind of game you want to make, then that’s totally fair!

I find games on Nintendo platforms to be frequently annoying because they try to find a use for Nintendo’s goofy hardware, whether it makes sense or not, and it tends to detract from the game. I see the crank as a similar thing, but even more weirdly specific. I make pretty straightforward games and unusual modes of input do not interest me, so I doubt I’d ever have a use for it.

Here comes the first of the posts I want to write about technical aspects of the game. It’s probably not anything revolutionary, but these approaches were novel and useful to me, so maybe they’ll be interesting to others.

Of Scenes And States

Playdate provides two SDKs - for C and Lua programming languages. Most of the game is written in Lua, with a little bit of C for more demanding things like pathfinding. While the SDKs are fairly fleshed out, they are still closer to a framework than an engine. I had to implement a ton of fundamental things myself, and so could shape those fundamentals to my liking.

Engines I’ve worked with before structure games around Scenes (aka Levels aka Rooms) and Actors (aka Game Objects aka Nodes). Actors hold logic and data, Scenes hold Actors and manage their lifecycle.

My issue with this approach is you often need “higher-level” logic that isn’t tied to an any one actor in the world. You may need to store meta-progression, or, say, flow of combat – whose turn is it, what part of the turn is it, etc. You have to either resort to an invisible dummy Actor which holds that data and logic, and now needs to be carefully migrated between scenes. Or global variables which exist outside the Actor/Scene system all together, and need their lifecycle managed separately. Both of these solutions seem a lot like hacks, even though this is a very basic need for most games.

Additionally, when I implement such “meta” logic, like the combat flow example, it often turns into one huge bloated mess of a class. There are no built-in affordances that would push me to break this logic up into smaller independant pieces.

Playdate SDK doesn’t come with any scene or actor management, so instead I chose to build everything around States and State Machines. I wanted to put primary focus on the over-arching flow and structure of the game, unlike the Actor/Scene paradigm that centers individual pieces of content.

Here’s how it looks like in my implementation:

state machine class diagram

State class sets up whatever it needs in enter(), performs logic in update() and input(), and cleans up in exit().

State Machine class stores State-s and has one of them be active. Importantly, it inherits from State, meaning that any State could potentially also be a State Machine with its own sub-States.

Instead of storing current state id as a singular int field, I use a stack of int-s (learned while writing this post that’s called a Pushdown Automaton). State on the top of the stack is treated as the current active one. setState() calls exit() for previous active state, replaces the top of the stack, and calls enter() for new active state. pushState() in my implementation allows you to activate another state “on top” of the previous one. New state is pushed onto the top of the stack and enter()-ed. Previous state also remains enter()-ed, but without update() and input() being called it becomes effectively paused. Then popState() calls exit() and pops the top of the stack, making previous state “unpaused”.

As a use example, say you want to open the settings menu during combat. You simply push SettingsState on top of CombatState. CombatState remains in memory and in the background of the screen, but CombatState.update() stops being called; menu is drawn on top via SettingsState.enter(), and all inputs are now directed to SettingsState.input(). Then on B button press you call popState()SettingsState.exit() erases menu from the screen, and CombatState once again becomes active, exactly as you left it. The stack is additionally useful because SettingsState does not need to know what it is opened on top of, so we can reuse same code for opening settings from for example both main menu and mid combat.

Game States

Here is roughly how my game is organized into States. Parent State Machine interacts directly with the SDK for things like input listening and updating every frame. Settings and Save/Load menus can be opened from both main menu and in-game, so they exist on the same level as those, and can be stacked on top by calling ParentStateMachine.pushState(). Playthrough-related data, like your custom player character, recruited units, narrative choices, etc., are stored within Game State Machine and not just floating in the aether of global variables.

I’ve previously wrote about what I call ‘top-down’ vs ‘bottom-up’ architectures. I would classify this State-centric approach as top-down, and Actor/Scene-centric approach as bottom-up.

In State-centric architecture, current State is at the top, and it is “smart”: it decides what objects to spawn, centrally processes all user inputs, tells those objects what to do based on inputs and State’s internal logic, and moves game to different states. This result in a very rigid, but therefore very predictable, stable, easy to track game flow.

Meanwhile, Actor/Scene approach encourages having many “smart” Actors at the bottom, each independantly listening for user input, and acting according to their attached logic. Scene is above Actors but is “dumb” – it spawns required Actors but has no further logic of its own. This results into a chaotic unpredictable web of interactions, unstable and hard to track. Such approach can produce the coveted Emergent Gameplay, but you don’t necessarily want emergent gameplay in, say, your menu flow.

For this menu-driven turn-based game, top-down State-centric architecture was absolutely the right decision. Code is easy to write and debug. The any-state-could-be-a-state-machine approach makes it very intuitive to break up larger logic into increasingly granular chunks. At my day job, we use the Actor/Scene approach, and every system listens to inputs independetly. And it is a never-ending nightmare of overlapping inputs, different priorities, listeners not unsubsubscribing at the right time, etc etc. Processing all inputs centrally within current state prevents all of these headaches. You program the exact subset of available inputs, and exactly what each input does.

For real-time action games, or a complex multi-window UI like in MMOs, this State-Machines-all-the-way-down model wouldn’t work very well, you’d need a more bottom-up approach with independent Actors. But those usually still exist within a simpler over-arching program flow, so I would advocate for at least a mix of the two. State Machines for overaching program flow, simple UIs, input handling; independent Actors below them for complex interactions with lots of moving parts.