Matchstick (MGDF) is a framework designed to make developing, installing, distributing, and updating games on Windows (Windows 7+ supported) easier. The framework uses DirectX 11 for graphics, RawInput/XInput for user input (has full support for the XBox 360 controller), and OpenAL for audio (supports 3d positional audio and audio streaming using Ogg Vorbis). The framework also provides a virtual file system which allows easy access to resources stored in zipped archives (new archive formats can also be plugged in).
MGDF makes developing games in c++ faster and easier as you no longer have to build and rebuild the same boilerplate code for initializing DirectX/Audio etc, loading preferences and setting up a render loop every single time you make a game. This means you can spend more time making your game. It also makes distributing games easier as it includes an auto update mechanism with full versioning support.
MGDF is comprised of two main components (the second of which is not required for running games, but for online updating/distribution)
This is written in c++ and is the part of the framework which actually runs the games. The core works by initializing the graphics/input/audio devices, loading up user preferences and setting up a multithreaded render/simulation loop before loading the user supplied game dll (known as a game module) and passing control on to that module. Once up and running the game module can then access the framework's functionality via a series of c++ interfaces (see the API reference for more details)
These interfaces include the ability to create audio samples and streams which under the hood are powered by the openAL audio system. Also included is the ability to access user interfaces devices including the mouse, keyboard, and any connected xbox 360 controllers. File system access is performed by a virtual file system wrapper which allows simple access to normal files on disk as well as files within compressed archives. Unlike the previous services, Direct3D 11 is directly accessible by the game module so as to negate the need to wrap the entire Direct3D API, though a number of helper interfaces are made available to make changing graphics settings easier.
This is written in C# (.NET 4.8) and consists a windows forms application which provides a minimalist launcher that checks for game updates and displays friendly error messages if the core crashes. It is also responsible for sending game statistics if this feature is used. In addition to this, the GamesManager also can be used by an installer to handle the usual tasks of windows program installation such as adding a installed programs entry in the registry, a start menu entry, and a desktop icon (see the command line options for more details)
NOTE This component is optional. If you plan on using your own solution for launching the game and managing updates (Steam, Itch.io, etc.), you can omit this piece of the framework from your game and start your game by running core.exe directly instead of launching it via GamesManager.exe
MGDF takes a convention over configuration approach for the most part and assumes that a number of files will be provided by the game and that these files will exist in a standard folder structure inside the root MGDF install folder. The standard folder layout with the relevant locations highlighted are as follows.
NOTE The MGDF redistributable included in the SDK is already layed out in this format, all you need to add when building a new game is the contents of the /game folder.
/install root
core.exe
GamesManager.exe
GamesManager.FrameworkUpdater.exe
... other framework dlls
/resources
/dependencies
All games files should be placed inside this folder
All game executable dlls should be placed inside this folder
This dll is required and provides an entry point for the host to create an instance of the game module.
Any game data files should go in here. This folder serves as the root of the virtual file system and content outside this folder is not accessable
This JSON file contains metadata including the name, version, and preference settings for the game. This file is required and the game will not run unless it is present.
This 48x48 PNG file is used to display the game logo in the GamesManager UI as well as the installed programs registry entry, and the desktop icon. This file is optional, and if not specified a default MGDF icon will be used instead.
By default the PNG image above will be converted automatically into an icon file for display in parts of the system that cannot use PNGs. If you don't wish to use the automatically converted icon file (sometimes the conversion results in messy transparency), and instead want to provide one, you can do so here.
As mentioned above, this file is responsible for telling the GamesManager and core everything they need to know about running your game. A basic manifest file is shown below. For full information on all the fields and their meanings, check out the API reference for more information.
Module.dll is the actual entry point into your game. This dll requires a number of functions to be exported, and must be able to provide the mgdf host with an instance of the IMGDFModule interface in order for the host to be able to run your game code.
MGDF is multithreaded and runs two main threads, the Sim thread and the Render thread. The Render thread is responsible for all rendering, and the Sim thread is responsible for handling input, audio, game logic. It is important to handle the sharing of data between these threads in a threadsafe manner for a game to work correctly. For the most part this means minimizing the amount of shared state between the Sim and Rendering parts of your game and ensuring that appropriate locking strategies are in place.
One method for managing this is to use a Double Buffered game state, i.e. the Sim thread modifies the game state and on each tick makes a copy of the current game state (or as much of a copy as the renderer needs to do its job) and copies it into a buffer which the Render thread can pick up when it's ready to render the next frame. In this way the Sim and Render threads can operate mostly independantly of each other without having to worry about concurrency issues. By default the Render thread will not run more than once per Sim tick, so you don't have to worry about buffer underrun in this case either (though this behaviour is configurable - if you have a very slow Sim framerate and want to interpolate a single game state over several render frames - check out the host.interpolateFrames preference option in the Game.json API reference)
NOTE The SDK includes a sample visual studio game project in src/samples/EmptyGame. This project contains a game.json file and will create a compatible module.dll with a stub module class.
Below is a stub implmentation of the IMGDFModule interface. All MGDF interfaces are COM objects and inherit from IUnknown, so it is the users responsibility to release them when no longer required. Once a module instance is created, the host communicates events to the module by calling into the various methods provided. You may notice that all methods except Panic are prefixed with ST or RT - RT methods will be called by the host from the render thread, and ST methods will be called by the host from the sim thread. Panic may be called from either thread.
Now that we have a module implementation, we need to make it available to the host. Below is a sample main.cpp file for a module dll. When the host starts up, it will first call the GetCompatibleFeatureLevels function. It will then call GetModule, and will invoke events on that module instance for the rest of the hosts lifetime.
When distributing a game to users, one simply has to put the game content and module in the prescribed locations inside the framework redistributable (as shown here). Users can then run the game by running GamesManager.exe.
However, having to keep your game inside the redistributable folder can be annoying during development, so there are a few other options available to make running the game in development easier.
Firstly, in development don't bother running GamesManager.exe, run core.exe directly instead. The GamesManager only wraps core.exe and does update checking and some other services that are not needed for testing development builds. Also running core.exe directly allows you to easily attach the Visual Studio debugger when running your game.
Secondly, you can actually run a game that exists anywhere on disk by passing in the following command line parameters when running core.exe
core.exe -gamediroverride "some folder on disk"
Provided that the folder specified has the usual /bin and /content structure that the /game folder in the framework would usually have, things will work as if the folder was in the /game folder in the framework redistributable folder.
Another useful commandline flag is
-userdiroverride
This changes the location that MGDF stores logs and per user data such as saved games and preferences from its usual folder in %appdata%/local/MGDF/1/<GameUid> to a /user folder in the parent folder of the current /game directory. For example, if the -gamediroverride was set to c:\mygame\game, and -userdiroverride was also set, then c:\mygame\user would be used to store all user data.
In order to distribute your game to users it is recommended to bundle your game into an installer, rather than just provide a .zip archive containing a framework redistributable combined with your game module and content files.
The main advantages to bundling everything in an installer is that the GamesManager depends on the .NET framework 4.8 runtime, which is not guaranteed to be present on all users systems. Because of this an installer may be necessary to install the framework prior to running the GamesManager
The general procedure for integrating an MGDF game with an installer is as follows
The installer should then invoke the following command
GamesManager.exe -register
This will cause the GamesManager to
;Includes !include "MUI2.nsh" !include "FileFunc.nsh" ;-------------------------------- ; The name of the installer Name "New game" OutFile "Setup.exe" ; The default installation directory InstallDir "$PROGRAMFILES\NewGame" RequestExecutionLevel admin ;-------------------------------- ;Constants !define CSIDL_APPDATA '0x1A' ;Application Data path ;-------------------------------- ;Pages !insertmacro MUI_PAGE_DIRECTORY !insertmacro MUI_PAGE_INSTFILES !insertmacro MUI_UNPAGE_CONFIRM !insertmacro MUI_UNPAGE_INSTFILES ;-------------------------------- ;Languages !insertmacro MUI_LANGUAGE "English" ;-------------------------------- ; check for and install .net 4.8 Section "Install .NET Framework 4.8" IfSilent Ignoredotnetsetup Call CheckAndInstallDotNet Ignoredotnetsetup: SectionEnd ;-------------------------------- ; Remove lastupdate file so we can check for updates as ; soon as we next start up (this installer may be out of date) Section "Remove lastUpdate check" Delete $LOCALAPPDATA\MGDF\1\NewGame\.lastupdate SectionEnd ;-------------------------------- ; the core engine components Section "Install NewGame" ;--- core engine components --- SetOutPath $INSTDIR File /r "path to the MGDF root folder" ;--- write out the add/remove program's registry keys & install any framework dependencies ExecWait '"$INSTDIR\GamesManager.exe" -register' SetRegView 64 WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\MGDF1_NewGame" "UninstallString" "$INSTDIR\Uninstall.exe" ${GetSize} "$INSTDIR" "/S=0K" $0 $1 $2 IntFmt $0 "0x%08X" $0 WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\MGDF1_NewGame" "EstimatedSize" "$0" WriteUninstaller $INSTDIR\Uninstall.exe SectionEnd ;-------------------------------- ; uninstall the core engine Section "Uninstall" ;-- remove registry keys, shortcuts etc. ExecWait '"$INSTDIR\GamesManager.exe" -deregister' ;--- remove uninstaller --- Delete $INSTDIR\Uninstall.exe RMDIR /r $INSTDIR SectionEnd Function CheckAndInstallDotNet ;--- https://docs.microsoft.com/en-us/dotnet/framework/migration-guide/how-to-determine-which-versions-are-installed#net_d ClearErrors ReadRegDWORD $0 HKLM "SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full" "Release" IfErrors NotDetected ${If} $0 >= 528040 DetailPrint "Microsoft .NET Framework 4.8 is installed ($0)" ${Else} NotDetected: DetailPrint "Installing Microsoft .NET Framework 4.8" SetDetailsPrint listonly SetOutPath $TEMP\Junkship File ndp48-web.exe ExecWait '"$TEMP\Junkship\ndp48-web.exe" /q /norestart' $0 ${If} $0 == 3010 ${OrIf} $0 == 1641 DetailPrint "Microsoft .NET Framework 4.8 installer requested reboot" SetRebootFlag true ${EndIf} SetDetailsPrint lastused DetailPrint "Microsoft .NET Framework 4.8 installer returned $0" Delete $TEMP\Junkship\ndp48-web.exe ${EndIf} FunctionEnd
The MGDF GamesManager can check for available updates to either the game or framework upon launching the game (this update check will occur at most once per day). To enable this feature, you need to add an updateservice property to your game.json file, listing a url pointing to a game update manifest file. The JSON format for the manifest url is shown below, listing the latest version of your game, and the MGDF framework version your games supports.
There are three main objects in the update manifest, the first is the framework object which allows you to specify which version of the MGDF framework your game supports, this allows you to upgrade the framework independently to your own game module and content. The url used for this object should point to an MGDF redistributable zip file (such as those included in the SDK)
The other two objects are the latest object which should point to a zipped game package (more on how to create these packages shortly). Finally the updateOlderVersions is an optional object which lists an array of zipped partial update packages that include only the files to upgrade from a particular version (or range of versions) to the version specified in the Latest object.
As mentioned above, in order to provide updates to your game, it has to be packaged into an update package. There are two types of update packages, full, and partial. A full update package is just a zip file containing everything in the /game folder whereas a partial update package contains only files which are different between two full update packages.
In order to make it easier to create these packages, the SDK includes a tool that can generate either full or partial update packages. In the simplest case of creating a full update package, the usage is as shown below (for more information see the API reference)
PackageGen "framework /game folder" -o "game package file"
NOTE The program output includes the version number and MD5 hash of the generated package, which you can use to fill out the field information in the game update manifest.
MGDF also has the ability to record and upload statistics from users play sessions. The user is prompted if a game wishes to gather statistics and can allow or deny the statistics gathering.
If permitted, statistics are recorded as key/value pairs. These pairs also include a timestamp which represents the time in seconds since that game session began. All statistics gathered in a game session also include a unique non-identifiable session identifier id so that all the statistics that were posted in a given session can be correlated.
In order to enable statistics tracking you need to add the following properties to your game.json file
You can then upload statistics (as key value pairs) using the IMGDFStatisticsManager API. The GamesManager will upload any statistics recorded once a users play session has ended.