Chapter 25: Packaging Your Game for Distribution
Learn how to package your game for distribution across Windows, macOS, and Linux platforms.
After all of our work creating Dungeon Slime, we need to prepare the game for distribution to players. Properly packaging your game ensure it runs correctly on different platforms without requiring players to have development tools installed.
In this chapter you will:
- Learn how to prepare your game for release.
- Package your game for Windows, macOS, and Linux platforms.
- Create platform-specific distributions with appropriate configurations.
- Understand important publishing parameters and their impact on game performance.
- Address common cross-platform distribution challenges.
- Learn about third-party tools that can automate the packaging process.
Understanding Game Packaging
When developing with MonoGame, you are working in a .NET environment that abstracts away many platform-specific details. However, when distributing your game to players, you need to ensure they can run it without installing the .NET runtime or other development dependencies.
Self-Contained Deployments
The recommended approach for distributing MonoGame games is to use self-contained deployments. This approach packages your game with all necessary .NET dependencies, resulting in a larger distribution but ensuring your game runs without requiring players install additional runtimes.
A self-contained deployment offers several advantages:
- Players can run your game without installing the .NET runtime.
- Your game will always use the exact version of the runtime it was developed with.
- Distribution is simplified with fewer external dependencies.
The main trade-off is a larger distribution size compared to framework-dependent deployments, but this is usually worth it for the improved player experience.
Preparing Your Game for Release
Before packaging your game for distribution, you should take some preparatory steps:
- Set Release Configuration: Ensure your build configuration is set to "Release" rather than "Debug" for better performance and smaller executable size.
- Update Game Information: Verify your game's title, version, and other information in the project's properties file (
.csproj
). - Final Testing: Perform thorough testing in Release mode to catch any issue that might not appear in Debug mode.
- Asset Optimization: Consider optimizing larger content files to reduce the final package size.
Platform-Specific Packaging
Now that we understand the general packaging concepts, we will explore how to create distributions for Windows, macOS, and Linux. Each platform has specific requirements and tooling that we will need to navigate. Choose the instructions below based on the platform you are using.
Important
The packaging instructions for each platform are designed to be executed on that same platform. This is because each operating system provides specific tools needed for proper packaging (like lipo
on macOS or permission settings
on Unix-based systems). When building on Windows for macOS or Linux, the executable permissions cannot be set since Windows lacks these concepts.
While you will need access to each platform for the steps below, do not worry if you do not have all these systems available. At the end of this chapter, third party libraries provided by MonoGame community members are included that can automate these processes for you without requiring you to own each type of machine.
Windows is the most straightforward platform to target since MonoGame development typically happens on Windows machines.
Building for Windows
To create a self-contained application for Window, open a new command prompt window in the same folder as the as the main game project (in our case the folder with the DungeonSlime.csproj
file) and execute the following .NET CLI command:
dotnet publish -c Release -r win-x64 -p:PublishReadyToRun=false -p:TieredCompilation=false --self-contained
This command specifies:
-c Release
: Builds in Release configuration for better performance.-r win-x64
: Targets 64-bit Windows platforms.-p:PublishReadyToRun=false
: Disables ReadyToRun compilation (explained later).-p:TieredCompilation=false
: Disables Tiered Compilation (explained later).--self-contained
: Includes the .NET runtime in the package.
The output will be placed in a directory like bin/Release/net8.0/win-x64/publish/
, relative to the game's .csproj
file. This directory will contain the executable and all necessary files to run your game.
Note
If your base game project is created with dotnet 9.0 (which at the time of writing is the default), the above folder will be more like bin/Release/net9.0/win-x64/publish/
, just so you are aware.
Noting the change in folder from net8.0
to net9.0
.
Creating a Windows Distribution
Once you have created a build for Windows, to create a Windows distribution, you can simply:
- Zip the entire contents of the publish folder.
- Distribute the ZIP file to your players.
- Players can extract the ZIP and run the executable directly.
Note
If you are using the WindowsDX platform target, players may need to install the DirectX June 2010 Runtime for audio and gamepad support. If you are targeting this platform, consider including this information in your game's documentation.
Important .NET Publishing Parameters
When publishing your game, there are several .NET parameters that can significantly impact the performance of the game. In the above sections, these are all set to the recommended values, however, we will examine them in detail below.
ReadyToRun (R2R)
ReadyToRun is a feature in .NET that pre-compiles code to improve startup time. This sounds like a good thing on paper, however, for games, it can lead to micro-stutters during gameplay.
This happens because ReadyToRun-compiled code is initially of lower quality, and the Just-In-Time (JIT) compiler will trigger periodically to optimize the code further. These optimization passes can cause visible stutters in the game.
For games, it is recommended to disable ReadyToRun by setting -p:PublishReadyToRun=false
in your publish command, which we have already included in our examples.
For more information on ReadyToRun, refer to the ReadyToRun deployment overview documentation on Microsoft Learn
Tiered Compilation
Tiered compilation is another .NET feature that works similarly to ReadyToRun. It initially compiles code quickly at a lower optimization level, then recompiles frequently-used methods with higher optimization.
While this improves application startup time, it can also cause stutters during gameplay as methods are recompiled. It is recommended to disable tiered compilation by setting -p:TieredCompilation=false
in your publish command, which we have already included in our examples.
For more information on Tiered Compilation, refer to the Tiered compilation section on Microsoft Learn.
Native AOT (Ahead-of-Time) Compilation
Native AOT compilation (specified with -p:PublishAot=true
) compiles your entire application to native code at build time, eliminating the need for JIT compilation during runtime. This can provide better performance and a smaller distribution size.
However, AOT has limitations:
- No support for runtime reflection.
- No runtime code generation.
- Some third-party libraries your game uses may not be compatible.
For MonoGame game, AOT can work well if you avoid these limitations.
Note
Native AoT is recommended for mobile platforms due to its performance benefits and smaller binary size, which are important for mobile devices with limited resources. Additionally, it is mandatory when targeting console platforms (Xbox, PlayStation, Switch) as these platforms typically do not support JIT compilation for security and performance reasons.
For more information on Native AOT, refer to the Native AOT deployment overview documentation on Microsoft Learn.
Trimming
Trimming (specified with -p:Trimming:true
) removes unused code from your distribution to reduce size. It is automatically enabled when using AOT.
While trimming can significantly reduce your game's size, it may remove types that appear unused but are accessed indirectly through reflection or generics causing runtime errors.
Important
Trimming can cause issues with content pipeline extensions that are used at runtime. When the compiler cannot detect that certain types are used (especially with reflection or generic collections), thy might be trimmed away, resulting in "type not found" exceptions when loading content.
If you encounter runtime exceptions about missing types when loading content with trimming enabled, you can resolve this by ensuring the compiler recognizes the types being uset at runtime by making the following call:
ContentTypeReaderManager.AddTypeCreator(typeof(ReflectiveReader<ReaderType>).FullName, () => new ReflectiveReader<ReaderType>())
Where ReaderType
is the ContentTypeReader
of the content pipeline extension to be preserved. This call should be made somewhere in your code before loading content that uses these types.
For more information on Trimming, refer to the Trim self-contained applications documentation on Microsoft Learn.
Single File Publishing
Single file publishing packages your entire application into a single executable. While this sounds convenient, it is essentially a self-extracting archive that extracts to a temporary directory at runtime.
This can significantly increase startup time for larger games and may fail on system with restricted permissions of limited storage. For this reason, it is not recommended to use this option for games.
For more information on Single File Publishing, refer to the Create a single file for application deployment documentation on Microsoft Learn.
Cross-Platform Considerations
When distributing your games across multiple platforms, be aware of these additional considerations:
File Paths
Different operating systems use different path separators (Windows uses backslashes, macOS and Linux use forward slashes). Always use Path.Combine
in your code rather than hardcoding path separators.
// Incorrect approach - will fail on some platforms
string path = "Content\\images\\atlas-definition.xml";
// Correct approach, works on all platforms
string path = Path.Combine("Content", "images", "atlas-definition.xml");
Case Sensitivity
Windows is case-insensitive for filenames, but macOS and Linux are case-sensitive. Ensure your asset references use the exact case that matches your files for maximum compatibility.
// If the content path on disk is:
// images/Atlas.xnb
// On Windows, this would work fine since windows is case-insensitive.
// On macOS and Linux, this would fail since they are case-sensitive.
Texture2D text = Content.Load<Texture2D>("images/atlas");
External Dependencies
Try to minimize external dependencies. If your game requires additional libraries or runtimes, document these requirements clearly for players.
Note
When publishing to distribution platforms and app stores (such as Steam, Epic Game Sore, App Store, or Google Play), you are typically required to disclose all external dependencies in your privacy policy or a dedicate dependencies section. This includes third-party libraries, analytics tools, and any software components that your game depends on.
Check specific requirements for each distribution platform you plant to target, as well as requirements by third-party libraries for using them, as disclosure requirements may vary.
Mobile Platforms
While this tutorial series has focused on creating a 2D game for desktop platforms, MonoGame also offers support for mobile development on Android and iOS. The game we have built throughout this series could be adapted for touch controls and distributed through mobile app stores with additional work.
Mobile deployment involves several considerations beyond those of desktop platforms:
- App store submission process and platform-specific requirements.
- Platform-specific signing and certification procedures.
- Extensive device compatibility testing across various screen sizes and hardware.
- Optimization of touch input controls (replacing our keyboard and gamepad input).
- Power consumption management and performance optimization for mobile hardware.
For the Dungeon Slime game, adapting to mobile would require:
- Implementing touch controls to replace the keyboard/gamepad movement.
- Potentially rethinking game mechanics to suit mobile play patterns.
Note
Mobile deployment for MonoGame games is significantly more complex than desktop deployment and typically requires platform-specific development environments (Android Studio for Android and Xcode for iOS). A comprehensive guide to mobile deployment will be covered in a future tutorial.
If you are interested in extending the Dungeon Slime game, or future games, to mobile platforms after completing this tutorial series, these resources provide a good starting point:
Third-Party Packaging Tools
While the platform-specific packaging steps outlined in this chapter give you complete control over the distribution process, they require multiple commands and potentially access to different operating system. Fortunately, the MonoGame community has developed several tools that can automate these packaging steps across platforms.
GameBundle
GameBundle is a .NET command-line tool created by Ellpeck that simplifies packaging MonoGame and other .NET applications into several distributable formats. This tool can automatically bundle your game for Windows, Linux, and macOS platforms, create applications bundles for macOS, and handle various packaging configurations with a single command.
For more information about GameBundle, including installation and usage instructions, visit the official repository on GitHub
MonoPack
MonoPack is a .NET command-line tool created by ShyFox Studio designed specifically for MonoGame projects. According to its documentation, "MonoPack is a dotnet tool used for MonoGame projects to package the game for Windows, Linux, and/or macOS".
Key features include:
- Cross-platform packaging capabilities (build for any OS from any OS).
- Automatic creation of macOS application bundles.
- Appropriate compression formats for each target platform for distribution.
For more information about MonoPack, including installation and usage instructions, visit the official repository on GitHub
Conclusion
In this chapter, you learned how to package your MonoGame project for distribution across Windows, macOS, and Linux platforms. You now understand how to create self-contained deployments for each target platform, the impact of various .NET publishing options on game performance, and important cross-platform considerations.
Whether you choose to use the manual platform-specific packaging steps or automate the process with tools like GameBundle or MonoPack, you now have the knowledge to ensure your game runs smoothly for players across different platforms without requiring them to install additional dependencies.
Test Your Knowledge
Why is it recommended to use self-contained deployments for distributing MonoGame games?
Self-contained deployments package your game with all necessary .NET dependencies, ensuring players can run the game without installing the .NET runtime. This simplifies distribution, guarantees your game uses the exact runtime version it was developed with, and provides a better player experience despite the larger package size.
Why should ReadyToRun and Tiered Compilation be disabled when publishing games?
ReadyToRun and Tiered Compilation both initially produce lower-quality code that gets optimized during runtime. This dynamic optimization process causes micro-stutters during gameplay as the Just-In-Time compiler triggers to improve code quality. Disabling these features results in slightly longer startup times but provides smoother gameplay without performance hitches.
What is the purpose of the
Info.plist
file when creating a macOS application bundle?The
Info.plist
file contains essential metadata about the macOS application, including the bundle identifier, application name, version, copyright information, minimum system requirements, and other configuration details. macOS requires this file to properly recognize and display the application in the Finder and Dock, and to associate the correct icon and file types with the application.What is the advantage of using a tar.gz archive over zip file when distributing for macOS and Linux?
A tar.gz archive preserves Unix file permissions, which is crucial for maintaining the executable permissions set on game files. Without these permissions, users would need to manually set execute permissions before running the game. ZIP files do not reliably preserve these Unix-specific permissions, which could prevent the game from running directly after extraction on macOS and Linux platforms.
What is the purpose of creating a universal binary for macOS distributions?
A universal binary combines executables for multiple CPU architectures (Intel x64 and Apple Silicon arm64) into a single file. This allows the game to run natively on both older Intel-based Macs and newer Apple Silicon Macs without requiring separate distributions.