XMLGameEngine

An engine for describing games built around Xerces, exprtk, and SFML.

View the Project on GitHub beefviper/XMLGameEngine

XMLGameEngine: current design

This document describes what the engine does today, as read from the source on 2026-10-01 (before that, games were written as function-call attribute strings; see designs/03). For the reasoning behind these choices, the alternatives that were considered, and ideas that are not built yet, see designs/00-designs.md.

XMLGameEngine is a video game description language (VGDL) written in XML, plus a C++ engine that loads a game description and runs it. A game is one .xml file. The description is declarative: there are no loops and no if statements in it, and no function calls either. Behavior comes from a fixed vocabulary of verbs (the tags <bounce />, <stick />, <die />, …) that the engine knows how to carry out. In this document, and in the design notes, a verb is sometimes written as bounce() for short; in a game file it is always the tag.

Running a game

XGECLI                       # loads "pong"
XGECLI breakout              # a bare name gets ".xml" appended
XGECLI pong.xml              # a name with an extension is used as given
XGECLI -g pong -w sdl2 -x tinyxml2
XGECLI --game pong --window raylib --xml pugixml

The game is a bare argument or -g / --game; both are looked for the same way: as given (a path, or a name in the current directory), then its file name in the working directory, then its file name in games/ of the data folder (the first of the working directory, the program’s folder and the folder above it that has both games/ and assets/; the program then runs from there, so it can be started from anywhere). If it is not found the program prints an error and exits. -w / --window picks the window library (sfml3, raylib, sdl2, opengl; default sfml3) and -x / --xml the XML library (xerces, tinyxml2, pugixml, rapidxml; default xerces), not case sensitive. A short option takes its value attached or after a space (-gpong, -g pong); a long option needs the space (--game pong, not --game=pong). Each option can be given once, the game only once (bare or with -g), and -h / --help prints the usage. The program starts by printing the file, window library and XML library it chose, one to a line. See design 37. Shipped games: games/pong.xml, games/breakout.xml, games/spaceinvaders.xml, games/frogger.xml, games/spacerace.xml (two players, W/S and Up/Down, first to two points), games/kaboom.xml (A/D or Left/Right; catch bombs in three waves, three missed bombs end the game, 60 points win), games/freeway.xml (two players, W/S and Up/Down, first to five crossings), games/depthcharge.xml (A/D or Left/Right to move, Space to drop, Space to start; sink all nine submarines before eight charges are wasted) games/astrosmash.xml (A/D or Left/Right to move, Space to fire, Space to start; shoot 20 rocks before five land) and games/lunarlander.xml (Up or W for the main thruster, Left/Right for the side ones, Space to start; set the lander down on the green pad slower than the safe speed, with fuel to spare, and do not touch anything else).

The XML and window libraries are chosen in C++ with Game(file, XmlBackend) and Engine(game, WindowBackend); XGECLI passes what -x and -w named, Xerces and SFML3 when they are not given. XGEGUI (the Qt application) takes the game the same way, XGEGUI pong, or opens a file dialog in games/ when none is named; its Options dialog picks the video library and the XML parser. See Backends.

A game file that is wrong (it does not match the schema, an expression will not evaluate, a command names a state or object the game does not have) stops the load with a message saying where; XGECLI prints it and exits, XGEGUI shows it and carries on.

Game file layout

A game file has one <game> root with exactly four children, in this order, as enforced by assets/xmlgameengine.xsd:

<game xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:noNamespaceSchemaLocation="../assets/xmlgameengine.xsd">
  <window name="..."> ... </window>
  <variables> <variable name="...">...</variable> ... </variables>
  <objects>   <object name="..."> ... </object> <group name="..."> ... </group> ... </objects>
  <states>    <state name="..."> ... </state> ... </states>
</game>

Attributes and content

The rule the whole format follows: an attribute names or picks something; everything else is the content of an element.

Values

Wherever the content of an element is a number (a position, a velocity, a size, a variable’s value, a threshold, a step) it is a value, and a value is one of two things:

<velocity>
  <x><random min="-7" max="7" /></x>
  <y><random min="-3" max="3" /></y>
</velocity>

A value holds text or a tag, not both, and not two tags (<x>5 <random .../></x> is an error naming the element). A value tag that is evaluated at load gives one number for the whole run: a reset() puts an object back to the position and velocity it started with, not a new draw.

The names an expression can use:

Name Value
window.top, window.left 0
window.bottom, window.right window height, window width
window.width.center, window.height.center half the width, half the height
any global <variable> its value. They are worked out in the order written, so a variable can use the ones above it
any objectName.variableName that object’s variable (available regardless of the order objects appear in the file)
objectName.width, objectName.height the width and height of any object (its sprite’s footprint); an object refers to itself by its own name, like any other field. Meant for <position>. An object’s own <variable> named width or height wins over its size. A circle’s or rectangle’s size is known from its sprite, so the position is exact at load. A text’s or image’s size is only known once the window has measured it, so its position is finished then, and worked out again whenever the size changes (a score gaining a digit); until then printGame shows it as unknown.

The other exprtk math functions (min, max, sqrt, …) exist because exprtk brings them, but the game files do not use them and nothing about the format depends on them.

<window>

<window name="Pong"> (the title), then in this order:

Element Meaning
<width>, <height> Size in pixels. Plain whole numbers: the expressions in the rest of the file are worked out against the window’s size, so these cannot be expressions themselves
<background> A color name, e.g. color.black
<fullscreen> true or false
<framerate> Frames per second (0-255)

<variables>

Global named numbers. Each <variable name="margin">30</variable> becomes a constant that any expression in the file can use (for example margin, ball.radius). Names may contain dots. The content is a value, so <variable name="b">a * 2</variable> and <variable name="start"><random min="1" max="3" /></variable> both work. Declaring a name twice keeps the later value.

<object>

An object is anything that can be drawn, moved, or collided with: a ball, a paddle, a score readout, a title. Attributes: name (required, how other elements refer to it) and class (optional, a group label that collision rules and conditions can match, like a CSS class).

Children, in this order:

Element Required What it does
<sprite> yes What the object looks like: one shape (see Sprites), or a <grid> of them. An object with an <animation> has several, each with a name
<animation> no Which of the object’s sprites are shown, in what order, and for how many seconds each: see Animation. Objects, and groups and their members
<position> yes Starting position, in pixels from the top-left: <x> and <y>, each a value. May use an object’s own size by its name, title.width and title.height, to place it by its size, for example a text called title centered: <x>window.width.center - title.width / 2</x>
<velocity> yes Starting velocity, in pixels per frame: <x> and <y>, each a value
<acceleration> no A constant pull: <x> and <y>, each a value, added to the velocity once every frame before anything moves, for as long as the object is shown. Gravity is an acceleration with only a <y>. <stop /> takes it away and a reset gives it back. Objects only, not groups
<heading> no The way the object faces, in degrees clockwise from straight up (0 is up, 90 is right). It lets <turn> and <thrust> work, and the sprite (of lines or a <bitmap>) is then drawn turned to the heading, to the nearest whole degree, so a pixel collision follows it. A collision <reset /> puts it back. Objects only
<drag> no A value from 0 up to (not including) 1: the fraction of its velocity the object loses every frame, after its thrust is added. Objects only
<hidden> no true: the object starts out of play (not drawn, no collisions, not counted by a condition’s remaining) until a <release> or a <fire> brings it in. Objects, and groups (after <velocity>)
<collisions> yes <enabled> (true/false), an optional <lockstep> (true), an optional <type> (box, the default, or pixel), then zero or more <collision> rules. See Collisions
<actions> no Named actions the object can perform, each <action name="up"><move direction="up">step</move></action>: the commands are what it does. States bind keys to these names
<variables> no Variables owned by this object, each <variable name="score">0</variable>. Other expressions refer to them as objectName.variableName, e.g. paddle1.score

An object with class="projectile" starts invisible (used for bullets).

<group>

A <group> is several objects that share a description, written once: a lane of logs, a row of debris, the pads along the top of Frogger. It sits beside <object> under <objects>. It holds the parts its members have in common, in the same order as an object (<sprite>s, an <animation>, <position>, <velocity>, <collisions>, <actions>, <variables>, each optional except <collisions>), then one or more <member>s. Attributes: name (required) and class (optional, and every member’s class).

A <member> says only what is its own: its <sprite>s and <animation>, a <position> and a <velocity>, and an optional name. Whatever a member leaves out it takes from its group, and after that it must be as complete as an <object> (a missing part is a load error that names the member). A <position> or <velocity> can give just an <x> or just a <y>, so a lane gives its row once and each member gives where along it it starts:

<group name="logrow3" class="logs">
  <sprite>
    <rectangle>
      <width>3 * cell - 2 * inset</width>
      <height>body</height>
      <color>color.brown</color>
    </rectangle>
  </sprite>
  <position>
    <y>3 * cell + inset</y>
  </position>
  <velocity>
    <x>-1</x>
    <y>0</y>
  </velocity>
  <collisions>
    <enabled>true</enabled>
    <collision edge="horizontal">
      <wrap />
    </collision>
  </collisions>
  <member><position><x>20</x></position></member>
  <member><position><x>272</x></position></member>
  <member><position><x>524</x></position></member>
</group>

A member is an ordinary object: it is loaded as one object of its own (logrow3.1, logrow3.2, logrow3.3: the group’s name, a dot, and its number counting from 1 in the order written, unless the member says name="..."), it is drawn, moved and collided with on its own, and each <wrap /> or <die /> happens to that one member. A group only shares what is written. Members are drawn in the order written, at the place in the file where the group stands. The group’s name means every member wherever the file names an object (<show object="logrow3" />, object="pads" in a rule or condition, <reset object="pads" />), and a member can be named on its own (object="logrow3.2"). A group with <lockstep>true</lockstep> in its <collisions> moves as one block, like the cells of a <grid>.

A <grid> and a <group> differ in what they repeat: a <grid> makes identical cells on a regular pattern from one sprite, a <group> lists members that can each differ in place, shape or speed and share everything else.

Sprites

A <sprite> holds one shape. Colors are named (color.red, below); a <color> left out is color.white. A sprite may be given a name, which an object needs only when it has several sprites for an <animation> to choose between.

Shape Contents Draws
<circle> <radius>, <color> A filled circle
<rectangle> <width>, <height>, <color> A filled rectangle
<text> <content> (a fixed label) or <number> (a value), then <size>, <color> Text. <content>PONG</content> is written as is. <number>paddle1.score</number> shows a number: when the value is exactly one owner.variable it is live and redraws when that variable changes; any other value (paddle1.score + 1, a <random>) is worked out once
<image> <path>, then <flip> (horizontal or vertical, optional) An image file
<line> (one or more) <from> and <to> (each an <x> and a <y>), then <color> and <thickness> (both optional) Straight lines, all in one sprite. See Lines
<bitmap> one or more <row>s of . and *, then <scale> and <color> (both optional) A picture written as rows of text. See Bitmaps

Lines

A sprite of one or more <line>s is a drawing. Each <line> goes from a point to a point, in pixels from the top left of the sprite (so the coordinates are never negative), and may have a <color> (default color.white) and a <thickness> in pixels (a value, at least 1, default 1):

<sprite>
  <line><from><x>10</x><y>2</y></from><to><x>20</x><y>2</y></to></line>
  <line><from><x>20</x><y>2</y></from><to><x>25</x><y>7</y></to><color>color.red</color><thickness>2</thickness></line>
</sprite>

The lines are drawn once, when the game loads, in the order written (a later one covers an earlier one where they meet), into a bitmap the size of what was drawn: as far right and down as the furthest endpoint plus the thickness, from 0, 0. Endpoints are rounded to whole pixels and a line is drawn with no gaps, stamping a square of its thickness along it. Every pixel nothing was drawn on is transparent. That bitmap is the sprite: the window backends show it as a picture, its size is the object’s size (so name.width and name.height work as for any shape, with no window needed), and a collision of type pixel looks at the same pixels, so what is drawn is exactly what is tested. A <random> in a coordinate is drawn once, so the picture and its size agree.

A sprite of lines is not repeated by a <grid>, and cannot be mixed with another shape. One object can be a whole drawing: Lunar Lander’s moon is one object of fifteen lines, its lander another, its pad a single thick line.

A <grid> repeats a shape as a grid of separate objects (Breakout bricks, Space Invaders): <columns>, <rows>, an optional <padding> (with <x> and <y>), then the shape, which may be a <circle>, <rectangle>, <text>, <image> or <bitmap>.

<sprite>
  <grid>
    <columns>11</columns>
    <rows>5</rows>
    <padding><x>15</x><y>15</y></padding>
    <rectangle>
      <width>width</width>
      <height>height</height>
    </rectangle>
  </grid>
</sprite>

Bitmaps

A sprite of one <bitmap> is a picture written as rows of text, one character to a pixel: a period (.) is clear and an asterisk (*) is solid, drawn in the sprite’s <color> (default color.white). Every character becomes a block of <scale> by <scale> real pixels (a value, a whole number of at least 1, default 1), so a few characters make a chunky sprite:

<sprite>
  <bitmap>
    <row>..*...*..</row>
    <row>..*****..</row>
    <row>.**.*.**.</row>
    <row>*********</row>
    <scale>5</scale>
    <color>color.green</color>
  </bitmap>
</sprite>

That picture is 45 pixels wide and 20 tall. The rows are measured from the top left, must all be the same length and may hold nothing but . and *: a space or any other character, an empty row, or rows of different lengths stop the load with a message that names the row and the character (row 3 of a bitmap has 'o' as character 2). A bitmap is drawn once, when the game loads, into the same kind of bitmap a sprite of lines is, so everything said there holds for it: its size is the object’s size (name.width and name.height work with no window), every window backend shows it as a picture, and a collision of type pixel tests exactly the solid pixels. Unlike a sprite of lines a <bitmap> can be repeated by a <grid>, and all the cells share the one picture. It has one color. On an object with a <heading> the picture is drawn once like this and then that finished picture is turned to the heading the object faces, to the nearest whole degree (360 headings, 0 and 360 being the same): each pixel of the turned picture takes the one pixel of the original that lies under it, so chunky pixels stay chunky and nothing is blended. The object keeps the original and the one picture it shows, and draws a new one only when its heading moves to another whole degree. Every heading comes out as the same square, big enough for the picture at any angle (for a bitmap 11 by 8 characters at <scale> 5 that is a square of 68), and that square is the object’s size, as with lines.

Animation

An object normally has one <sprite>. One that has several gives each a name and follows them with an <animation>, which says which are shown, in what order, and for how long:

<object name="crab">
  <sprite name="open"> <bitmap> ... </bitmap> </sprite>
  <sprite name="closed"> <bitmap> ... </bitmap> </sprite>
  <animation>
    <interval>1</interval>
    <frame sprite="open" />
    <frame sprite="closed" />
  </animation>
  <position>...</position>
  ...
</object>

<interval> is how many seconds each picture is shown (a value above 0, so 0.25 works), and each <frame sprite="name" /> picks one of the object’s own sprites by its name. There are at least two frames, and the same sprite may come up more than once (open, closed, open, wide). The object starts on the first frame and goes round and round. Seconds are turned into frames of the game when the game loads, with the window’s <framerate> (a second at 60 frames a second is 60 frames, and a window with no framerate has nothing to count seconds in); like the speeds in the game, which are in pixels per frame, the animation counts frames of the game and does not read a clock, so a game that runs slower than its framerate animates slower too.

The frames must all be pictures (a <bitmap> or <line>s, not a circle, rectangle, text or image) of the same size, and where one is repeated by a <grid> all of them are, the same way. Every sprite the object has must be shown by its animation, and several sprites without an animation are an error, since nothing would say when each is shown. An object with a <heading> can be animated too: whichever frame is showing is drawn at the heading the object faces, and the frames must come out as the same square when turned (equal sized bitmaps always do).

Only an object that is shown by the current state and in play (not dead, not hidden) moves on, so a pause or a menu holds the picture where it was, and a <reset /> puts it back on the first picture, from the start of its time. Every cell of a <grid> has a count of its own, and they all start together, so a block of aliens changes picture as one. A collision of type pixel tests the picture that is showing. In a <group>, a member that gives sprites of its own has those instead of the group’s, and one that gives an <animation> has that instead of the group’s; the names in an animation are looked up among the sprites the member ends up with. Space Invaders is the example: its three kinds of alien are the members of one group, each a <grid> of two named bitmaps with an animation, so the block marches, bounces and steps down together while each kind flaps on its own sprites.

Commands

Commands are tags, and where they are meaningful is what the table says. Any list of them (inside a <collision>, an <action>, an <input> or a <condition>) is run in the order written, for example <inc variable="paddle2.score" /> then <reset />.

Command Where it is meaningful Effect
<bounce /> collision Reverses velocity away from the touched edge
<stick /> collision Clamps the object inside the screen edge it touched and stops only the velocity heading into that edge; the other axis keeps going, so an object pressed against the bottom wall still slides left or right. Re-applied after the frame’s move, so a stuck object never ends a frame outside the screen
<wrap /> collision (screen edge) Once the object has gone completely off the screen through that edge and is still heading that way, puts it back in from the opposite edge, one window width (or height) plus its own size along, so anything spaced along a lane keeps its spacing. While any of it is still in view nothing happens
<carry /> collision (another object) Lends the object the touched object’s velocity for that frame, on top of its own: a frog on a log rides along with it. Worked out again every frame from whatever is still being touched, so an object that steps off is at rest
<die /> collision Disables the object’s collisions and hides it; it stops moving and being drawn until something brings it back (<fire> re-launching a bullet, <reset />)
<reset /> collision (screen edge or another object) Puts the object back at its starting position
<reset /> state input or condition Full game reset: every object’s position, velocity, variables, visibility and collisions go back to how they started (a bullet in flight is put away, a dead alien is back), and the state stack collapses to the first state
<reset object="name" /> state input or condition Resets that one object (or every cell of a grid, for its grid name) the same way
<inc variable="owner.variable" /> or <inc variable="owner.variable">amount</inc> collision (screen edge or another object) Adds 1, or the amount (a value, so an expression works), to that variable and refreshes any text bound to it
<dec variable="owner.variable" /> or <dec variable="owner.variable">amount</dec> collision (screen edge or another object) Takes 1, or the amount, off that variable and refreshes any text bound to it. The variable may go below zero; a condition with <atmost> is what notices it has run out
<move direction="up">step</move> (also down, left, right) collision, or an object <action> In a collision: shifts the object (or everything in lockstep with it) once. In an object action: sets a held-key velocity (see Input). The content is a value
<hop direction="up">distance</hop> (also down, left, right) object <action> A one-shot jump of distance pixels for each press of the key (see Input). The content is a value
<accelerate direction="up" burn="fuel">amount</accelerate> (also down, left, right) object <action> A thruster: while the key is held, the object’s velocity changes by amount (a value) every frame in that direction, where <move> would set the velocity itself. The optional burn names one of the object’s own <variable>s; 1 is taken off it every frame the thrust is on (and a text bound to it follows), and while it is 0 or below the thrust does nothing. See Input
<stop /> collision (screen edge or another object) The object comes to rest where it is and stays there: its velocity goes to 0, and it is no longer pulled by its <acceleration> or pushed by a held <accelerate> or <move>, until a reset gives the acceleration back. A landing
<turn direction="left">degrees</turn> (or right) object <action> While the key is held, the object’s heading changes by that many degrees every frame (left is counterclockwise). Needs a <heading> on the object
<thrust burn="fuel">amount</thrust> object <action> Like <accelerate>, but along the way the object faces instead of along an axis; burn works the same. Needs a <heading> on the object
<release object="name">count</release> collision (screen edge or another object) Puts the first count (default 1) out-of-play objects of that name or group back in play, centered on the object running the rule, at their own starting velocity. Fewer left in the pool gives what is there. This is how a rock breaks into smaller ones
<push state="name" /> / <pop /> state input or condition Push a state / pop back. The state named must be one of the game’s. <pop /> with only the first state left does nothing
<trigger object="name" action="up" /> state input Runs one of that object’s named <action>s. The object and the action must exist
<fire object="projectile" /> object action Launches the named projectile object from the shooter’s top-center (the middle of the projectile over the middle of the shooter’s top edge), moving with the projectile’s own <velocity>. A projectile is not drawn, moved or collided with until it is fired, and is put away again by <die /> (hitting a target, or edge="all"). The name may be a <group>: the first member that is out of play is the one launched, so a group of four is four shots in flight. A shooter with a <heading> fires from its nose, along the heading, at the speed of the projectile’s <velocity>; with none, one projectile name can only be in flight once

A command the engine does not know, or one missing an attribute it needs, stops the game loading with a message that says where (object 'ball' > <collisions> > <collision>: unknown command <explode>). So does a command that names something the game does not have: a state (<push state="pasued" />), an object or one of its actions (<trigger>), a projectile (<fire>) or an object to reset (<reset object="...">). These are checked once every object and state has been built, so a name used before the thing it names appears in the file is fine.

Colors: color.black, color.white, color.red, color.green, color.blue, color.yellow, color.magenta, color.cyan, and the muted color.grey, color.darkgrey, color.lightgrey, color.brown, color.orange, color.purple, color.darkblue, color.darkgreen, color.forestgreen. Any other name is fully transparent.

<state>

A state is one screen: a menu, the playfield, a pause screen, a game-over screen. Attribute: name. Children, in this order:

Element Required What it does
<shows> yes A list of <show object="name" />. Only these objects are drawn and updated while the state is current
<inputs> yes A list of <input button="key">, each holding the commands that key runs, e.g. <push state="playing" /> or <trigger object="paddle1" action="up" />
<conditions> no A list of <condition>, checked every frame. See Conditions

The first state in the file is the starting state. States form a stack: <push state="name" /> pushes a state, and <pop /> pops back to the previous one. The starting state is never popped: a <pop /> with only it left does nothing.

Input

An <input button="w"><trigger object="paddle1" action="up" /></input> names a key and the commands it runs. Objects never mention keys, and states never mention what an action does, so remapping a key means editing one attribute in one state.

The shipped games share one convention: Space starts, pauses and unpauses, and plays again after a game over (in games where Space fires, such as Space Invaders, Astrosmash and Depth Charge, pausing is P or Escape, and Space still unpauses); player one plays on W, A, S and D (the arrow keys are a second way in the one-player games, and player two’s keys in the two-player ones). The games written after Pong and Breakout also pause on P, and most of them on Escape; in Pong and Breakout Escape leaves the settings screen (S on the main menu).

Key names are lowercase: a-z, num0-num9, numpad0-numpad9, f1-f15, space, enter, escape, backspace, tab, left, right, up, down, home, end, pageup, pagedown, insert, delete, pause, lshift, rshift, lcontrol, rcontrol, lalt, ralt, and a few more listed in lib/source/keycode.cpp.

While a key bound to a <move> in an object action is held, that direction’s step is recorded. Velocity is recomputed from all four directions on every key change, so holding Down and tapping Up cancels out and releasing Up resumes Down. Left/right and up/down are independent axes, so two keys can make a diagonal.

The current state decides what a held key means. When the state changes, keys that are still down stop driving whatever the old state bound them to, and start driving what the new state binds them to. In Breakout, holding Left in playing and pressing Space stops the paddle, because paused binds only Space; unpausing while Left is still down moves it again with no re-press, and a Left first pressed during the pause starts moving it on unpause. Letting go of a key always stops what it was driving. Only continuous bindings (an action’s <move>) resume this way; state changes and one-shot commands such as <fire> run only when the key is actually pressed, so holding Space through the main menu does not pause the game. Alternatives that were considered are in designs/10.

Accelerate. <accelerate direction="up">amount</accelerate> in an object <action> is held like a <move>, and is resumed the same way after a state change, but it adds to the velocity instead of setting it: each frame the key is down, every direction held contributes its amount (so opposite thrusters cancel, and two directions make a diagonal push), on top of the object’s own <acceleration>, before the frame’s move. What was gained stays: let go of the key and the object drifts on at the speed it reached. <thrust> is the same along the object’s <heading>, and <turn> changes the heading by a fixed amount a frame while held; an object’s <drag> then takes a fraction of the speed away each frame, so thrust has a top speed. With burn="variable" each thruster that is on takes 1 off that variable of the object’s every frame, and does nothing once it is gone.

Hop. <hop direction="up">distance</hop> (and down, left, right) in an object <action> is a one-shot jump, not a held move. Pressing the key queues a hop of distance pixels; the next frame’s move makes it before collisions are worked out, so the object is judged where it lands, and it is refused (the object stays where it is) if it would leave the window. Holding the key does nothing more and releasing it does nothing, and a state change never repeats it: only continuous bindings are resumed, so a key held through a pause has to be pressed again to hop. Two hops asked for in one frame keep the later one, so a hop is always one step in one direction.

Collisions

Each object has <collision> rules. A rule’s content is a list of command tags, run in order. There are two kinds:

Detection (pure geometry, CollisionDetector) is kept apart from response (CommandExecutor). Object-against-object collisions are swept: each object moves along its own path for the frame, and the detector finds the moment two of them first touch (rectangles as boxes, a circle against the other object’s box with rounded corners), so a small or fast object cannot jump over a thin one between frames. The earliest touch in the whole frame is handled first: everything moves up to that moment, the pair’s rules run, and the rest of the frame is played with whatever velocities they left, so a bounce spends the remaining part of the frame heading away. Each pair reacts at most once per frame. The detector reports which edge of the other object was touched, and each side of the pair then sees the edge from its own point of view. Screen edges are still checked by position, before the move.

Type. <type> in <collisions> says how the object’s shape is tested: box (the default: a rectangle as a rectangle, a circle as a circle, everything else as its bounding box) or pixel. A pair in which either object is pixel is first swept as boxes, exactly as above, and then looked at more closely: from the moment the boxes touch, the pair is walked along its path half a pixel at a time until some pixel drawn by one lies on a pixel drawn by the other, and that moment (found to a fraction of a pixel) is the hit. If the pixels never meet, there is no hit, however long the boxes overlapped. Pixels are asked about at their centres, at the positions the objects really are at. A pixel object is its sprite’s bitmap (a sprite of lines); an object of any other type in the pair counts as solid all over its shape. A circle or a rectangle can be pixel too, which is solid as itself; text and images cannot (a load error), since what they look like is only known to a window backend. Each object says its own type, so a game with a pixel lander and pixel terrain gives both <type>pixel</type>. The same test decides unless= (is it touching something of that class right now).

The edge reported for a pixel hit is the one the motion came in through (the side of the other object that the two were moving toward each other across), or the boxes’ edge when they were already touching; a pixel touch knows where pixels met, not which way a surface faces, so <bounce /> on a pixel hit reflects along the axis of the relative motion.

Rules that apply:

Conditions

<condition class="paddle" variable="score">
  <atleast>15</atleast>
  <push state="gameover" />
</condition>

Checked once per frame while the state is current. It fires when any object matching class and/or object (both optional, combined with AND) has a variable named variable that has reached the <atleast> value (greater than or equal). The first match runs its commands and stops checking for that frame. The threshold is an ordinary value, so it is not limited to 0-255 and can be an expression.

The other forms:

<condition class="aliens">
  <remaining>0</remaining>
  <push state="gameover" />
</condition>

<remaining> counts objects instead of reading a variable. It fires when no more than that many of the matching objects are still in play, that is still visible (<die /> hides an object). 0 means they are all gone, which is how Space Invaders is won: class="aliens" covers every cell of the grid, and object="aliens.3.2" would watch a single one. <atmost> reads a variable from above: <condition object="frog" variable="lives"><atmost>0</atmost>...</condition> fires when the variable has fallen to that value or below, which is how a lives counter that goes down with <dec> ends the game (Frogger). A condition uses exactly one of <atleast>, <atmost> or <remaining> (the schema enforces this). If the filter matches no object at all, the game warns when it loads, since the condition would fire at once. Ending the game with a gameover state and starting over with a bare <reset /> on that screen works as in Pong; <reset /> also brings the dead aliens back.

What happens when a game runs

  1. Parse and validate. The XML backend loads the file. If the file names a schema, it is validated: Xerces does full XSD validation (“strong”); the other three backends use a small built-in validator for the subset of XSD this project uses (“weak”, xsd_lite). printGame() reports which one ran.
  2. Evaluate. exprtk evaluates every value once (an expression text, or a value tag such as <random>, which is drawn here). Objects, their variables, <grid> cells and states are built. Nothing here needs a window.
  3. Open the window. Engine creates the window backend and measures each object’s real size for drawing and collisions, finishes the position of any text or image that uses objectName.width or objectName.height, then pushes the first state. The program prints the game twice, once before this step (sizes and size-dependent positions shown as unknown) and once after.
  4. Loop. Each frame: read key changes and run the current state’s bindings for them; count the frame towards the next picture of every shown object that has an animation; change every shown object’s velocity by its acceleration and held thrust; run the screen-edge rules; make any queued hops; move every shown object by its velocity, and by what it is being carried at, running the object-against-object rules at each touch on the way (per frame, not scaled by time); check conditions; clear; draw shown objects; present.

Backends

Job Interface Implementations
Read XML XmlDocument / XmlNode (xml_document.h) Xerces (default), TinyXML2, PugiXML, RapidXML
Window, drawing, keyboard Window (window.h) SFML3 (default), Raylib, SDL2, OpenGL (GLFW)

Game and Engine only ever see the interfaces. Each interface has a factory that is the single place that knows every implementation. Build-time dependency selection is in scripts/cmake/ (see the FORCE_LOCAL_* options in options.cmake).

Every window backend opens a window of its own, which is what XGECLI uses. XGEGUI shows the game in one of two layouts (design 40): in one window, drawn by a renderer of its own that draws with Qt (QtWindow, the default), or in two, where the main window holds only the controls and the tree and the game is in a window of its own, opened by the chosen library (SFML3, SDL2, raylib or OpenGL) or by the Qt renderer. Engine::replaceWindow() swaps the window of a running game for another without touching the game, which is how the Options dialog changes the video library. A front end that has paused the game but keeps its window calls Engine::pump() so the window can still be moved and closed, and Engine::isWindowOpen() tells it when the user closed it.

Nothing in XGEGUI uses OpenGL through Qt, so a library’s OpenGL context is the only one on the thread. (The game view was a QOpenGLWidget once, and the two kinds of context, which Qt tracks by its own record, drew into each other; see design 40.)

Building: library and programs

The engine (everything in lib/source/ and lib/include/) is a library, the XGELIB CMake target. Two programs use it: XGECLI (cli/) and XGEGUI (gui/, the Qt application, built only when Qt 6 is found), and so do the tests (XGETEST, in tests/). Every project has the same layout, a folder with source/ and include/ in it; XGEDATA is the target that copies games/ and assets/ next to the programs. The library knows nothing about the command line, so a different front end only needs its own main().

The library is static by default: each program has the engine’s code copied into it, so XGECLI is one self-contained file. -DXGE_BUILD_SHARED=ON builds it as a shared library instead (libXGELIB.so, or XGELIB.dll on Windows), which each program loads when it starts; a shared build needs the library file to be found next to the program or on the system’s library path. On Windows the DLL exports every class in the headers (WINDOWS_EXPORT_ALL_SYMBOLS) rather than each being marked by hand. The third-party libraries are PUBLIC dependencies of the library, because the engine’s own headers include theirs. See design 35.

Source map

File Responsibility
cli/source/main.cpp, cli/source/cli.cpp XGECLI, the command line program: read the options, find the game file, build Game and Engine with the chosen backends. The only code outside the engine library
gui/source/*.cpp XGEGUI, the Qt application (design 38, 39, 40): main_window (the window, the File and View menus, the question about two windows), game_session (a loaded game and its engine, run from a timer; play, pause, step, reset, and changing the libraries), game_stage (where the Qt renderer’s picture is: the left pane, or a window of its own), game_view (the widget a picture is shown in), key_queue (the keyboard, read by Qt), qt_window (the Window that draws with QPainter), options_dialog and session_options (the video library and XML parser choice), app_settings (xgegui.ini, next to the program), inspector (the controls and the tree of game data)
game_xml.cpp Walk the parsed XML tags into raw window/variable/object/state data (RawValue, RawCommand, RawSprite); a <group> is read here as one raw object per member
game_expr.cpp exprtk symbol table and evaluation of raw values into Objects and States
command.cpp Turn raw command tags into typed Commands
game.cpp Objects, state stack, per-frame update, collision pairs, conditions, resets
collision_detector.cpp Geometry only: box, circle and swept tests, and the pixel pass for type pixel
builtin_font.cpp The 8x8 font stored in the program (rasterizeText), which draws text into a bitmap when a backend cannot load its font file
bitmap.cpp Draws a sprite’s <line>s (rasterizeLines) or <bitmap> rows (rasterizeRows) into an RGBA bitmap: the pixels both the window backends and pixel collisions use
command_executor.cpp What each command does
engine.cpp Frame loop (loop(), or step() and render() for a front end that owns the event loop) and key handling
object.h, states.h, color.cpp, keycode.cpp Data model, named colors, key names
window_*.cpp, xml_*.cpp, xsd_lite.cpp Backends and the weak validator
tests/ Catch2 tests (opt-in with BUILD_TESTING): collision geometry and swept collision, command parsing, conditions, input resolution, stick(), collision rules, lockstep bounce, size expressions, engine key handling, object variables, the new verbs (dec, hop, wrap, carry, unless, atmost, colors), the tag format, its rejections and the names commands use (test_xml_format), groups (test_group: expansion, overrides, names, lockstep, errors, both schema checkers), lines, pixel collisions, acceleration, thrust and the speed filters (test_lines_and_pixels), bitmaps, animations and Space Invaders as written with them, with both schema checkers (test_bitmap_sprites; invaders_fixture.h keeps a copy of the first, plain Space Invaders for the tests that are about grids and not about that game), the built-in font, the command line and the data folder search, Engine::pump() and isWindowOpen() (test_engine_input), and Frogger, Space Race, Kaboom, Freeway, Depth Charge, Astrosmash, Lunar Lander and Asteroids (test_asteroids: headings, turning, thrust and drag, the pool of shots, release, wrapping, losing ships, winning) played frame by frame

Known limitations