Commands and timing

Not much to show this week due to I3D/GDC trip planning, along with trying to approach the problem of commands, and related issues.

As a header, I’m using variant of ECS, where the Systems don’t really do mass processing as usual, but are rather logic hubs, emitting/receiving events, and storing unique data. I also want the player and non-player characters and creatures to use the same underlying machinery for performing actions, using abilities, etc. The difference would be that the player needs to choose an available action and set the parameters via a UI (e.g choose ranged attack, select a tile), whereas the non-player entities would need to run some AI code that figures out what to do and how it should set the parameters. So, the common resulting part, which is the parameterized action, is the command.

An entity command  is the most basic bit of logic that does something, operating on an entity. A player can build a command via UI, while all other entities build them using AI. An entity command holds no state – it reads/writes state from/to the entity – therefore one of each is stored in the registry and are retrieved and used as flyweights. Such commands just return a bool that indicates if the command actually did something.

A timed entity command is a composition of a reference to an entity command plus some timing information. Now this bit is important, as I believe it will give some flexibility and increase the potential for strategy in the game. An entity command can be in 3 stages:

  • Inactive
  • Pre-Execution
  • Recovering

When the command is started, the stage switched from inactive to Pre-Execution and we wait for some time. The execution happens instantly after the pre-execution time has expired, and the entity immediately switches to the Recovering stage, regardless if the execution succeeded or failed. Also, while in the pre-execution stage, the command can be interrupted.  This requires the following additional information:

  • Pre-Execution duration
  • Recovery duration
  • Interrupt recovery duration
  • Interrupt difficulty class (DC)
  • Interrupt strength

So, if an entity can execute a command that, if applied to another entity, can interrupt it if it’s in the pre-execution stage. To check for successful interruption, the strength should exceed the difficulty class.

Some commands should be instant. In that case, all durations are set to zero.

That’s it for now – next time I’m going to describe how such timed commands fit together with a finite state machine (for simple AI) and a game turn manager.

Application States

Definition

Application states are simply the states an application can be in. A rough definition would be: a state is the set of data and logic that is responsible for handling input, and displaying output at any given time. State examples are:

  • Main menu
  • Options
  • Credits
  • Overworld
  • Dungeon
  • Selecting a single target tile
  • Selecting a single target entity ( several entities can lie on a tile, e.g. items on the floor, a creature and a trap)

Note: Another name would be “game stack”, “game states” and so on. Of course these can all have several other meanings depending on who you talk to, but that’s irrelevant)

Desirable traits

  • Active state stack
    • Top of the stack is running, others are paused
  • State hierarchy
    • A state can be a state machine. For example, a state of “select 5 targets” can be a state machine which 5 transitions of type select_target
  • Arbitrary data exchange between states
    • States have a blackboard of sorts ( a flat collection of map< string , type>, one per type ) that can store data used to communicate to parent/child states
  • Configurable via JSON
    • Should be able to define states and state machines, complete with transfers

System design

There are two main actors in this system: the Application State Manager and the Application State. The public interface related to transitions should be very simple.

The manager is responsible for creating states, providing the current active state and execute one of the three transition operations:

  • Push(state, next_state, transfer_request) : Pauses the state, pushes next_state to the stack and starts next_state. When next_state finishes successfully, it will use the transfer_request to transfer data to state.
  • Finish(state, success) : Stops the state, marking the state execution as successful or not
  • SwitchTo( state, next_state) : A convenience function for finishing a state successfully and starting another state

States own a root widget and a blackboard. States can do their custom input handling and rendering in addition to the widgets (done before/after or completely ignoring the widget-related functionality)

Example

I used the above concepts to create a simple example, which is demonstrated in the below video.  We have 5 states:

  • Menu: Start the application at this state. It has it’s own gui with two buttons: Play and Quit. Play button leads to “Loading” state. Quit quits the application
  • Loading: This state shows a gui with a timer – after 1 second the state is switched to “Game”
  • Game: This state has again its own gui, and shows a map with a playable lich character, which can be moved with the arrow keys. ‘L’ key changes the state to “TargetSingleTile”, while ‘T’ key changes state to “TargetMultiTile”
  • TargetSingleTile: This state does not have its own gui – it uses whatever gui was there before. It adds a selection of highlighted tiles, centered at the character. When the user clicks on a tile that is within the selection, the state completes successfully and transfers the selected tile to the parent. At the tile, a skeleton appears.
  • TargetMultiTile: This state doesn’t have its own gui either. It is a state machine, and calls TargetSingleTile successively. When the state machine is finished, the locations of all selected tiles are sent to the parent state (Game). At those tiles, wraiths appear

The system is still a bit rough around the edges, and it looks like it can be converted to a generic state machine (which I could reuse perhaps for some basic AI), but it’s ok for now.

 

Widgets – Part 4

More widget work this week, but it’s shaping up reasonably well, so next week I might actually be moving on to something else.

TileGrid improvements

  • TileGrid rendering is now pixel-perfect for the single-rectangle case, so thankfully that bug is gone.
  • I’ve added configurable edge scrolling for TileGrids, so that by moving the mouse at the edge of the tile grid, the focal point is shifted towards that edge. The closer the cursor is to an edge, the bigger the adjustment. It can be seen in the video below

Widget cloning

Given a database of allocated, configured but inactive widgets, we should at any point be able to clone one and activate it, ready for use, for example when spawning floaters and modal widgets.

Floaters and Modal widgets

Floater and modal widget functionality is now implemented: handling focus, input, spawning and destruction.

Floaters do not get input or focus. In contrast,  modal widgets get full focus: while a modal widget is active, no other widget may get input focus, unless it’s under the modal widget’s hierarchy. For example, a modal ok/cancel dialog widget would allow input+focus for its text and buttons, but not for any other widget. Example below:

Resizable widgets

Widget dimensions get finalized at activation time. If widget dimensions are set to 0 or under, then we can calculate optimal dimensions based on what the widget really is, e.g. a text widget would calculate the optimal dimensions for the text that it will display. For padding the optimal dimensions, negative dimensions are interpreted as amount of padding to add to the optimal. Example: A textbox widget with dimensions (100, -16) will calculate the optimal box height given a width of 100, and will add 16 to that height: that will be the final height of the box on activation.

More widgets: Buttons and TileGrids

More widget work this week, implementing the specification for focus and input handling.

I now have a working Button, which is really a TextBox with special input hanlding: They send events when pressed/released, and can have a custom renderer for each case, so that for example a button can appear pressed when you click on it.

I also set up a working ListBox, which is a special container for buttons, which provides custom renderers for highlighted items and binds the number keys (1,2,3, etc) to the children buttons. This will be used for any dialog with options. I’m tempted to adjust the rendering logic so that each widget has a list of potential renderers (defaulting to a generic one) for any state combination:  in/out-of-focus for all widgets and pressed or not for buttons.

Finally, I set up a TileGrid widget that, as the name suggests, handles tile grids such as dungeons and overworld maps. The TileGrid displays a view window, which can be smaller than the whole grid, and can focus at any point in the grid (inside cells too), and can move smoothly (or not) from a focal point to another. Cells can be highlighted and clicked on, and the widget will send messages for such state changes. Rendering tilegrids is performed in 3 ways:

  • Dense tile rendering: Instanced rendering of visible tiles, tile locations are implicit given the view grid start and size. We can use this for rendering tiles with per-tile individual data. In the below video “Widget test 1”, this is how the tiles on the bottom-left are rendered.
  • Sparse tile rendering: Similar to the above, but we need to provide the tiles to be rendered explicitly, as a number of instances and per-instance data. In the below video “Widget test 1”, this is how the flashing tiles are rendered.
  • Single rectangle: When the tilemap to be displayed is just a single, continuous texture and there are no per-tile data, there’s no reason to use instancing. In this case, we can render just the part of the texture that’s visible in the view grid. In both the below videos, this is how the biome map and DCSS tile map are rendered.

Widget test 1

Here, I demonstrate the current version of my widget … zoo, with some animated margin corner tiles, button-pressing (that ugly little red box which goes darker) and tilegrids. For the tilemap display, I’m randomly setting the focal point with a keypress, and the widget refocuses appropriately. There is still some rendering bug with the selection square in the tilemap, where it is occasionally a bit off. Still eludes me, and I’ll possibly spend way too much time to fix it.

Widget test 2: Biome Inspector

This is an attempt to make something useful out of the widgets, very quickly. So this is the result: just 2 widgets, a tilegrid and a textbox, and the application listens for highlighted messages and updates the textbox: very simple and very effective. Below the video is the gui spec script

widget_biome.png