The Ebonheim Chronicle

Development Blog for Chronicles IV: Ebonheim

All the actions work has been manual to test for a while, so I finally got over the hump of attaching them to Ability assets which attach to Actor assets so an Actor can dynamically give you abilities to use in your hotbar which you can click to select and use!

The ability name also shows a nice UI window in the corner :host-joy:

#gamedev #chron4

| 🌐 | πŸ™‹β€ | @britown@blog.brianna.town

Finally took a minute to lay out the basics of the normal gameplay UI using my β€œEGAUI” ImGui-facsimile

Here we get an idea of the actual full resolution of the game and why I worry constantly about things being readable. The combat UI has also gone through another iteration, can you guess how this turn will play out? I'm still not totally happy but it continues to get to a better and better place.

One of the new inventions is a grey graph-node graphic that shows the expected positional changes of the actors to make it clearer where they'll be acting from:

#gamedev #chron4

| 🌐 | πŸ™‹β€ | @britown@blog.brianna.town

It's time to build out a new type of action for the first time. True to its influences, a key type of user ability is pushing enemy actors around the board to achieve tactical advantage. Because this uses the new system, target requests can be filled out fairly arbitrarily for different types of push abilities. Pushes can be directed relative to the source or given an explicit direction.

More examples after the jump!


Here we side-push the enemy at the top so that they hit their friend on their turn:

Here we push all adjacent enemies away from us:

Here the enemies move first in the turn order and pushed away after they approach:

The highlighting, arrows, and messaging has also been improved and is in a better place right now, although still in need of a lot of iteration.

EDIT: Surprise late night addition: projectile targeting!

#gamedev #chron4

| 🌐 | πŸ™‹β€ | @britown@blog.brianna.town

Very much enjoy the moments of just throwing colors around the pixel editor and seeing what sticks. The grass field was getting a little stale.

Honest question, what do these little green guys look like to you?

#gamedev #chron4 #pixelart

| 🌐 | πŸ™‹β€ | @britown@blog.brianna.town

Chronicles IV: Ebonheim attempts to mix the β€œinstant-turn-based” combat of a classic roguelike (what do we call that? a dungeon crawler? a Nethack-like?) with more complex targeted abilities like a traditional grid-based tactics RPG such as Disgaea and also have perfect-ish deterministic turn information like Into the Breach.


Defining the Problems

Into the Breach's Prediction Model

ItB is really smart and innovative because it doesn't only show what the AI units intend to do on their turn, it also shows the consequences of the player's decision prior to committing to it. This would normally be a really complex thing to get right but not for ItB because the prediction problem is heavily simplified by a very key design decision: the board can't change between decision and execution.

Since all enemy units act together on their turn stage, they can never interrupt a player's plan. The player can decide what a unit will do, calculate it's outcome by only simulating one action (based on the current board which is guaranteed to be accurate), and then instantly execute it, modifying the board.

There's no turn-order in the classic sense that requires a player decision to be delayed and executed at a later point when the board may have been modified by other units acting first.

Chronicles' Design Constraints

Chronicles wants to have this nice feature of showing the intended decisions of AI as well as the consequences of player decision prior to committing. However, there are several design constraints for the game that make this a lot harder to predict and message:

  • On every turn, every actor acts in a calculated turn-order.
  • When deciding an action, for both player and AI, the decision should be based on the predicted game-state at that point in the turn-order, taking into consideration previous actors' intended decisions.
  • Abilities must be able to have complex, multi-stage, multi-tile, dependent targeting options.
  • For each stage of targeting, the game should render the outcome of making that decision by simulating the full turn.
  • All turn decisions must still be able to function relative to the actual game state at time of execution, even if it doesn't match the predicted state at time of decision.

I also wrote out a long list of abilities I already intend to implement. Some of those are a surprise but many of the basics are:

  • Pushes & Pulls that knock a target actor in a direction, potentially causing damage
  • Redirects which modify the already-decided direction of an actor's target decision
  • Teleports which zip-zap actors around the board
  • Stuns that interrupt an actor's decision before they are able to execute

Finally, let's also throw in some architecture design constraints that will make the final system scalable into the future:

  • Actions need to be a Game Asset. This carries the same constraints all Chronicles Assets have which is that they must be editable in-engine and edits must affect the running game instance live.
  • Actions need to be able to have features stapled onto them ad infinium without increasing the overall complexity of the architecture.
  • Adding new action functionality should never have to modify the prediction engine.
  • Actions should be able to, in the future, support looping, branching, and more complex flow control.

The Solution

Breaking it Down

To try and build a unified theory of Actions that satisfied all constraints it was helpful to start by separating concerns; first by user. Who are the consumers of this system? 1. The Developer: Writes C++ and adds new features to the system like pushes and branching and looping. Cares most about the ease of adding and tweaking types of actions with minimum boiler-plate. Doesn't want to ever have to touch the prediction code ever again. 2. The Content Creator: Uses the in-engine editor to define arbitrary actions for use in all the different abilities. Likes having tons of knobs and clickers to tweak everything and make unique abilities. Expects everything to have tactile UI and doesn't want to write scripts. 3. The Game Renderer: Needs to be able to inspect a given action against an immutable game state and then draw both decision-time and execution-time UI and messaging. Really doesn't want to have to care about the underlying assets, enjoys being real stupid and just simulating component parts to draw. 4. The Game Logic Step: Needs to be able to modify itself based on the actions. Also doesn't want to care about the underlying asset, just wants to loop through the actions and call Do(). Likes long walks on the beach and trivial copyability.

I realized that satisfying the Content Creator's stories is a fairly isolated set of problems. Most of what makes their life easiest doesn't need to touch the other users. What they need is Game Assets that serialize and deserialize, have UI, and are able to be referenced in immediate-mode from the asset directory. So this is where I split the problem into two distinct parts: Actions and ActionTokens.

Actions

An Action is a pure-virtual interface (C-style vtable in my implementation but still). Because this is a Game Asset, these are completely const and immutable during gameplay. This interface has the functions create(), destroy(), serialize(), deserialize(), and, most importantly, doUI() and compile().

doUI() uses ImGui's immediate-mode idiom for rendering out a complete frame of UI for modifying the content of that Action. A lot of my UI is also driven by code-generated reflection so it's extremely easy to throw together some rapid UI for modifying a new type of action.

compile() spits out a set of ActionTokens, which are consumed by the other users.

What makes the Action Interface really slick is that you can make an Action that is, itself, a list of Actions. The ActionList implementation just holds onto a list of child Actions and calls the virtual functions on each of its children. This makes all Actions reusable, modular, and embeddable!

Right now there are 3 action implementations: DeclareTarget, MoveAttack, and ActionList. In the future, ActionList can be expanded to have options for looping and conditional branching, and of course new Actions are easy to add like pushes, teleports, or AoE damage.

With the Game Asset side of the problem completely fleshed out and implemented, the content-creator is happy and the Developer is happy because all the UI code and boiler plate for ser/deser is contained in a separate module that never touches game state.

ActionTokens

static void _actionList_Compile(Action const* self, ActionTokenSet &tokens) {
   auto data = (ActionDataActionList*)self->data;

   auto tok = tokens.tokenGen.alloc();
   tokens.tokenType[tok] = ActionTokenType_BeginScope;
   tokens.tokens.push_back(tok);

   for (auto&& a : data->actions) {
      actionCompile(a, tokens);
   }

   tok = tokens.tokenGen.alloc();
   tokens.tokenType[tok] = ActionTokenType_EndScope;
   tokens.tokens.push_back(tok);
}

For how we ended up at ActionTokens, let's talk a little bit about what a program is versus what a programming language is. When you write a program in C, you have all sorts of bog-standard utilities like looping, conditions, scoped variable declarations, stacks, heaps, memory referencing, etc. You can think of a program as a series of expressions. Every expression has different behavior and different sets of inputs but ultimately the written pre-compiled program is one big expression that contains expressions that contain expressions all the way down.

When you compile, these expressions are translated. Loops and Branches become GOTOs/JUMPs, memory value referencing turns into a whole ton of MOVEs and PUSHes, and you end up with a machine-readable completely linear list of instructions. Your PC doesn't need to know anything about C, as long as the instruction set is compatible with the CPU.

You might be catching on that this is a great metaphor for Actions! If Actions are the expressions of a program, ActionTokens are the instructions! Before our Actions-as-defined-by-our-Game-Assets can be used by the other two users, Step and Render, we have to compile it to a linear list of instructions.

Data-Flow

One problem I ran into immediately is that while we have a token for declaring a target-request with a specific ID, it's actually really tricky to determine what targets are available at different points in the execution. Similarly it is difficult to get the decisions for each request stored in an appropriate place so that the tokens that reference the decisions are able to resolve their targets. Here's an overly-complicated situation you could run into with embeddable Action Lists:

Begin Scope
   Declare Target "T1"
   Declare Target "T2" relative to "T1"
   Move/Attack "T2"
   Begin Scope
      Declare Target "T1"
      Move/Attack "T2"
      Declare Target "T3"
   End Scope
   Move/Attack "T3"
End Scope

While looping over our tokens prompting the user for decisions on all of the target requests, you need these name-resolutions to work the way they would with scoped variables in a normal programming language!

And so in addition to the const, immutable TokenSet we got from the compiled Action, we now also need a very mutable TokenMemory for keeping track of all of this! With TokenMemory we can iterate through our token set and define memory addresses to all of these target references so we don't need to worry about scoped name resolution anymore.

Begin Scope
   Declare Target "T1" -> 0x00: New Target
   Declare Target "T2" relative to "T1" -> 0x01: New Target referencing 0x00
   Move/Attack "T2" -> referencing 0x01 relative to 0x00
   Begin Scope
      Declare Target "T1" -> 0x02: New Target
      Move/Attack "T2" -> referencing 0x01 relative to 0x00
      Declare Target "T3" -> 0x03: New Target
   End Scope
   Move/Attack "T3" -> Error, unresolved name in scope
End Scope

With the links made between all the tokens in the memory object, we can get decisions from the player or AI, and assign them to the appropriate place to be referenced by the other tokens.

Consuming a TokenSet

Now we have everything we need for our token set to actually affect things, how do we actually use them? If we think back to our list of design constraints, a TokenSet is only the things performed by a single actor. That actor might be going 3rd in a turn-ordered list of 7 actors. That actor might be the player-character which means all the other actors have already decided what they will do. Therefore, your target-highlighting needs to convey to you the consequences of that particular target decision at the point in the turn order that the player acts. How do we do that?

This brings us all the way back to functional programming, inline state modification, immutability, and, chiefly of all, trivially-copyable Game State.

When I first started trying to tackle this system months ago I very naively attempted to make little snapshots of the board state that I could pass around to different places to make decisions relative to the expected board. This runs into a lot of issues as you start to scale up in complexity and the amount that you need to be correct and in-sync in these little snapshot objects starts to grow into a medusa.

So hey, what if you just copied your literal entire game state and then applied every token to it in-order until you reach your targeted simulation point.

And that's exactly what we do:

void _renderActorDecisionsUnderActors(EGATexture& target, GameState const& g) {
   auto gPrev = g; // copy game state to preview game state
 
   // loop over all turn members in turn-order
   for (auto actor : g.turn.members) {
      // at this point actor should have the full list compiled AND memory should be filled with decisions (except for the player)
      auto &actorState = *g.turn.actors.find(actor);
 
      size_t idx = 0;
      auto &set = actorState.tokens;
      auto &mem = actorState.tokenMemory;
 
      while (idx < set.tokens.size()) {
         // if we reach the player in the turn-order and they're still deciding this frame, skip them
         if (actor == gPrev.player_controlled && turnMemberTokensNeedDecisions(g, actor) && idx == actorState.tokenIndex) {
            // we're at the player's current decision node, break
            break; // go on to next actor
         }
         // render the token messaging (show arrows, highlight squares, show damage)
         _renderActionTokenUnder(target, gPrev, actor, set.tokens[idx], set, mem);
 
         // apply the token to the PREVIEW game state
         gameStateApplyActionTokenForPreview(gPrev, actor, set.tokens[idx], set, mem);
         ++idx;
      }
   }
}

We can do this every frame for our Render user who never needs to modify the actual source GameState!

This method of simulation is so intensely simple and intuitive and it solves all simulation problems. Need to cancel target request 2-of-3 after noticing that request 1-of-3 was too short? re-simulate. Need to use an ability that has Turn Priority and makes the player suddenly go first? re-simulate.

In the end, both the Render user and the Logic-Step user are the same! They're just interpreters of the compiled token set. That's really all there is to it!

Out of everything we've gone through and decided up to this point, the beauty is that the turn prediction engine is actually the simplest and smallest section of code out of them all.

Closing Thoughts

Why didn't you use LUA for your action scripts?

I want to use LUA! For... some things. The truth is that scripts are data is logic is data. I could pretty easily replace the Game Asset Action with a LUA script and let people go crazy with loops and branching and everything. But then I lose all this cool UI! ImGui lets me do scripting without actually writing scripts and that is COOL.

Preview vs Execution

Because we're making a roguelike and perfect knowledge would ruin the fun, we have two different execution functions for the different ActionTokens. ApplyForPreview will do things that may modify the board state but provide imperfect information. ApplyForExecution is when we actually perform all the actions and loop through all actors and modify the base game state with all the results. The difference between these two is going to be a bit of a grey area and will require a lot of iteration! All I know is sometimes you need to see an ogre and for it to say β€œDoing ???? to you.” and for you to need to change your pants.

A Very Small Example

After I got the system working last night, I rigged up a version of the move attack that asks for you to decide on three consecutive target-requests, each one relative to the previous. The Move-Attack at the end would then act upon the final selected tile. The following gifs were made with zero code changes only modifying the Action assets:

You made it all the way to the end!

:eggbug: Good Job!

#gamedev #chron4 #longpost

| 🌐 | πŸ™‹β€ | @britown@blog.brianna.town

Hey it works! Every step of my gamestate updating saves to a history and I can just roll back through history with a simple slider. These aren't saved images replaying, it's the complete data for rendering the map: all rendering is completely immutable.

This lets me debug state at specific paused points as well as replay series of events.

With Live++ I could see a bug in combat, pause, roll the history back, fix the bug, live-recompile, and hit play on it again.

#gamedev #chron4

| 🌐 | πŸ™‹β€ | @britown@blog.brianna.town

A friend sent me this old email from John Carmack talking about how his habits had changed over time to be less modular and less reusable to combat a primary source of bugs: invalid input state.

http://number-none.com/blow/blog/programming/2014/09/26/carmack-on-inlined-code.html


This has honestly connected a lot of dots for me and makes a ton of sense. Something that has been holding me back with my engine implementation for Chronicles had been attempting to modularize and snapshot and delegate everything to keep it clean.

Part of my combat actions system requires some extremely complex operations involving simulating entire game states from potential decisions and part of what was making that really hard was not just treating game state as a pure functional input every frame.

Hard to get detailed on this without just doing a code review. I'm just kind of excited to do some fairly straightforward refactor to get my board state into the sort of place where I can start using it as a trivially copyable object.

This whole thing gives me some much needed guidance on just where to put everything and what the general rules are for how data is supposed to be stored and when it's allowed to be modified. It makes me a lot less anxious about scaling upward!

Maybe next week I can show some of this working with some really complex combat simulations,

Wouldn't that be cool considering I first started working on combat actions in mid-September.

Edit: Follow-up

Happy to say that this refactor is going very well. Turning my game state into a trivially copyable object with limited rules for modification opens up a ton of possibilities for existing systems, future systems, and debug tools.

One neat thing is I can pause the game live, and step it forward one frame at a time. And as a debug utility I can just store every frame and have a slider that just let's me roll back to a previous state. Wild! You see this in emulators and such but never imagined having it for a personal game project. I'll have to show this working with cool gifs soon.

#gamedev #chron4

| 🌐 | πŸ™‹β€ | @britown@blog.brianna.town

Part of my last post broke down the actions system I'm working on. Part of this is being able to define arbitrary targeting concepts including directable patterns!

As always shout-out to ImGui which empowers me to whip up editors like this extremely quickly. I also have code-generated reflection for a lot of my structs so hooking up custom UI to specific data structures is really easy and further speeds up that effort.

#gamedev #chron4

| 🌐 | πŸ™‹β€ | @britown@blog.brianna.town

For my hot girl summer I sat down bright and early to get some Chronicles work done. It has been a few weeks since I've been able to touch it (where before I was working on it nearly every night) and I also left it at a point where the next task is fairly large and daunting.


Because it's mostly a grid-based tactics RPG, I want to create a generic actions system that I can use to define all of the different types of abilities with the following constraints:

  • Be able to describe all targetable actions for player and enemy from basic movement to complex abilities
  • Be able to query an action for targeting constraints (pattern, line, range, etc)
  • Be able to query any action for the resulting turn state prior to execution
  • Be able to build all actions as an asset in an editor

In game development these days I typically shy away from ultra-generic constructs because they tend to grow into more of a hassle as you scale in complexity but a large component of this project is for all game content on release to be defined in-engine so that non-devs can easily generate custom content.

In a perfect world I would love for a full editor that allows me to create complex abilities like teleporting player behind an enemy and pushing them counter clockwise. Multi-stage actions with multiple dependent targeting constraints will empower a ton of complexity and variety in the game's combat and so I really want to get it right.

Unfortunately! What that means! Is that I'm extremely blocked by analysis paralysis on starting in on this system. It's very big and has to be correct enough to be able to be molded and scaled into the future. It will probably be several sessions of coding before I even have something to show for it and so the whole thing puts me into a mindless coma of inaction.

So my dev-day consisted entirely of making the above little tile highlighting animation. I mean I want to be able to highlight tiles for targeting so you definitely need that! I dreamed of getting my entire actions system working at the end of the day yesterday but instead I have a little animation and a blog post about game development being hard.

All in all still very happy to have broken my fast on this project and I'm going to keep crawling into the breach until I finally get past this hump!

#gamedev #chron4

| 🌐 | πŸ™‹β€ | @britown@blog.brianna.town

I love Dear ImGui. I use it every day and it's probably the most important contribution to independent development in the last decade. So when it came time to start thinking about making in-game UI, why not just create a similar immediate-mode system so that I can just make all my EGA RPG text and buttons and interactions work with literally the same API I use for my tools.


The code for the popup dialog there is

   egaui::setNextWindowPos({ 380, 32 });
   egaui::setNextWindowSize({ 300, 100 });
   if (egaui::begin("test 2!")) {

      egaui::text("Testing some UI");

      egaui::indent();

      egaui::beginGroup();
         egaui::alignTextToFramePadding();
         egaui::text("A button");
         egaui::alignTextToFramePadding();
         egaui::text("Another button");
      egaui::endGroup();

      egaui::sameLine();

      egaui::beginGroup();
         egaui::button("Btn 1");
         egaui::button("Btn 2");
      egaui::endGroup();
      
      egaui::unindent();

      egaui::setCursorPosX(egaui::getContentRegionMax().x - egaui::calcTextSize("Right Aligned").x);
      egaui::alignTextToFramePadding();
      egaui::text("Right Aligned");
   }
   egaui::end();

What's great about this, is this API is simple enough that later down the road when I start thinking of LUA/Scripting integration, creating bindings to enable scripts to define arbitrary UI becomes trivial. Custom menus and dialog trees and everything will be able to developed in the same asset system everything else uses.

Of course the real fun of all of this is that this API exists running inside a game instance that is being rendered to a full ImGui window:

edit: added a better looking window frame :3

#gamedev #chron4 #imgui

| 🌐 | πŸ™‹β€ | @britown@blog.brianna.town