XMLGameEngine

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

View the Project on GitHub beefviper/XMLGameEngine

38. Qt front end

Status: built (gui/, scripts/cmake/executables.cmake; Engine::step()/render(), Game::setVariable()/getStates())

Decision

XGEGUI is a Qt 6 Widgets application. The game is on the left; on the right, from the top, are the Play/Pause, Step and Reset buttons, a line saying the current state, the frame number and whether it is running, and a tree of everything in the game.

Showing the game

Options were to embed one of the existing windows (SFML and SDL2 can draw into a window handle Qt gives them; raylib cannot take a foreign window at all), or to write one more Window backend that draws the picture itself. The second is built, and the first was tried and later removed (39, 40): QtWindow (gui/source/qt_window.cpp) implements Window like the SFML, raylib and SDL2 backends, draws every object with QPainter into an image, and GameView shows that image as large as fits with the game’s own proportions. One backend then works the same on every platform and is resized by Qt, rather than three platform-specific embeddings, only one of which would be complete. It is in gui/, not lib/, so the engine library needs no Qt. The other backends open windows of their own (40): the Options dialog chooses between them and this one.

The engine is driven from a Qt timer instead of Engine::loop(), at the game’s own frame rate (a game moves a fixed amount a frame, so that is also its speed). The timer wakes every 2 ms and plays however many frames the clock says are due (at most four, so a window drag does not make the game race to catch up), then draws once; a timer of the frame’s own length (16 ms for 60) drifted against the clock and ran frames early, late and in pairs. The frame is drawn with one QPainter, not one per object. GameView is an ordinary QWidget; it was a QOpenGLWidget (which presents on the display’s vertical blank) until a video library’s OpenGL context turned out not to share the thread with Qt’s (40). loop() is now step() (read the keys, move, check conditions) followed by render() (draw without moving anything), so a frozen game can be redrawn after an edit. Pause really stops the simulation, which is separate from a game’s own pause state; Step advances one frame while frozen; Reset is Game::resetAll().

Keys go to the game only while the game view has the focus (click it); the buttons do not take the focus, so pressing one does not stop the keyboard working. Keys held when the focus leaves are released.

Choosing the libraries

The Options dialog (File menu) picks the video library, the Qt renderer by default, and the XML parser, Xerces by default, and can change the video library of a running game; see 39. The Qt renderer above is one of the choices; the others draw in a window of their own, and the View menu and the Options dialog move the game there (40).

The tree

The tree is laid out like the XML:

A value that can change has an editor on its own row: a spin box with up and down arrows for a number (type a value or click), a check box for yes/no, a text box for text. A change is made on the running object at once, and the picture is redrawn straight away when the game is frozen. Editable: class, visible, a sprite’s size, color and text, position, velocity, acceleration, collision on/off, every object variable (through Game::setVariable(), so a text showing it follows), and the window background. Values read from the game every tenth of a second keep the tree current while it runs; a box being typed in is left alone.

Editors are only made when their parent row is first opened, because a game has thousands of values (Frogger has 110 objects) and most are never looked at.

Finding the games and assets

The engine reads assets/ (the font, images) relative to the working directory, and the games are in games/. XGEGUI and XGECLI look for a folder that has both in the working directory, then next to the program, then in the folder above it (a Visual Studio build puts the program in build/Debug and the copies of games/ and assets/ in build/). The first one found becomes the working directory, so the program can be started from anywhere, and the file dialog opens in its games/. A game named on the command line is found the way XGECLI finds one (a bare name gets .xml; then as given, its file name alone, then in games/). The search is one piece of code in the library (lib/include/data_folder.h, tested in tests/test_data_folder.cpp), so the two programs cannot drift apart; main.cpp of each only calls it.

Not done