Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update MacOS packaging_games.md #58

Merged
merged 7 commits into from
Sep 29, 2024
Merged
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 81 additions & 21 deletions articles/getting_started/packaging_games.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,21 +13,17 @@ To publish desktop games, it is recommended that you build your project as a [se

From the .NET CLI:

`dotnet publish -c Release -r win-x64 /p:PublishReadyToRun=false /p:TieredCompilation=false --self-contained`
`dotnet publish -c Release -r win-x64 -p:PublishReadyToRun=false -p:TieredCompilation=false -p:PublishAot=true --self-contained`

> [!IMPORTANT]
> Note we are making use of the `PublishAot` option. Using `Aot` has some restrictions which may require changes to your game code. Especially if you are using Reflection.

You can then zip the content of the publish folder and distribute the archive as-is.

If you are targeting WindowsDX, note that players will need [the DirectX June 2010 runtime](https://www.microsoft.com/en-us/download/details.aspx?id=8109) to be installed on their machine for audio and gamepads to work properly.

### [macOS](#tab/macos)

From the .NET CLI:

```cli
dotnet publish -c Release -r osx-x64 /p:PublishReadyToRun=false /p:TieredCompilation=false --self-contained
dotnet publish -c Release -r osx-arm64 /p:PublishReadyToRun=false /p:TieredCompilation=false --self-contained
```

We recommend that you distribute your game as an [application bundle](https://developer.apple.com/library/archive/documentation/CoreFoundation/Conceptual/CFBundles/BundleTypes/BundleTypes.html). Application bundles are directories with the following file structure:

```text
Expand All @@ -37,23 +33,40 @@ YourGame.app                    (this is your root folder)
            - Content           (this is where all your content and XNB's should go)
            - YourGame.icns     (this is your app icon, in ICNS format)
        - MacOS
- amd64 (this is where your game executable for amd64 belongs, place files from the osx-x64/publish directory here)
- arm64 (this is where your game executable for arm64 belongs, place files from the osx-arm64/publish directory here)
- YourGame (the entry point script of your app, see bellow for contents)
- YourGame (the main executable for your game)
     - Info.plist            (the metadata of your app, see bellow for contents)
```

The contents of the entry point script:
So first lets create our directory structure.

```
mkdir -p bin/Release/YourGame.app/Contents/MacOS/
mkdir -p bin/Release/YourGame.app/Contents/Resources/Content
```

Next we need to publish our application for both `arm64` (Apple Silicon) and `x64` (Intel). From the .NET CLI:

```cli
dotnet publish -c Release -r osx-x64 -p:PublishReadyToRun=false -p:TieredCompilation=false -p:PublishAot=true --self-contained
dotnet publish -c Release -r osx-arm64 -p:PublishReadyToRun=false -p:TieredCompilation=false -p:PublishAot=true --self-contained
```

> [!IMPORTANT]
> Note we are making use of the `PublishAot` option. Using `Aot` has some restrictions which may require changes to your game code. Especially if you are using Reflection.

Next we need to comibne the two binaries into one Universal Binary which will work on both arm64 and x64 machines.
We can do this using the `xcode` utility `lipo`.

```cli
lipo -create bin/Release/net8.0/osx-arm64/publish/YourGame bin/Release/net8.0/osx-x64/publish/YourGame -output bin/Release/YourGame.app/Contents/MacOS/YourGame
```

The above command will combine the two output executables into one.

```sh
#!/bin/bash
Copy over your content

cd "$(dirname $BASH_SOURCE)/../Resources"
if [[ $(uname -p) == 'arm' ]]; then
./../MacOS/arm64/YourGame
else
./../MacOS/amd64/YourGame
fi
```
cp -R bin/Release/net8.0/Content bin/Release/YourGame.app/Contents/Resources/Content
```

The `Info.plist` file is a standard macOS file containing metadata about your game. Here is an example file with required and recommended values set:
Expand Down Expand Up @@ -104,14 +117,35 @@ The `Info.plist` file is a standard macOS file containing metadata about your ga
For more information about Info.plist files, see the [documentation](https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Introduction/Introduction.html).

After completing these steps, your .app folder should appear as an executable application on macOS.
However it does need an icon. So we need to create an `.icns` file. We can use online tools to do this or you can use the following

```cli
mkdir -p bin/Release/YourGame.iconset
sips -z 16 16 Icon.png --out bin/Release/YourGame.iconset/icon_16x16.png
sips -z 32 32 Icon.png --out bin/Release/YourGame.iconset/[email protected]
sips -z 32 32 Icon.png --out bin/Release/YourGame.iconset/icon_32x32.png
sips -z 64 64 Icon.png --out bin/Release/YourGame.iconset/[email protected]
sips -z 128 128 Icon.png --out bin/Release/YourGame.iconset/icon_128x128.png
sips -z 256 256 Icon.png --out bin/Release/YourGame.iconset/[email protected]
sips -z 256 256 Icon.png --out bin/Release/YourGame.iconset/icon_256x256.png
sips -z 512 512 Icon.png --out bin/Release/YourGame.iconset/[email protected]
sips -z 512 512 Icon.png --out bin/Release/YourGame.iconset/icon_512x512.png
sips -z 1024 1024 Icon.png bin/Release/YourGame.iconset/[email protected]
iconutil -c icns bin/Release/YourGame.iconset --output bin/Release/YourGame.app/Contents/Resources/YourGame.icns
```

Note this code is expecting an `Icon.png` file to be in the same directory. This file should be `1024` x `1024` pixels.

For archiving, we recommend using the .tar.gz format to preserve the execution permissions (you will likely run into permission issues if you use .zip at any point).

### [Ubuntu](#tab/ubuntu)

From the .NET CLI:

`dotnet publish -c Release -r linux-x64 /p:PublishReadyToRun=false /p:TieredCompilation=false --self-contained`
`dotnet publish -c Release -r linux-x64 -p:PublishReadyToRun=false -p:TieredCompilation=false -p:PublishAot=true --self-contained`

> [!IMPORTANT]
> Note we are making use of the `PublishAot` option. Using `Aot` has some restrictions which may require changes to your game code. Especially if you are using Reflection.

You can then archive the content of the publish folder and distribute the archive as-is.

Expand All @@ -123,6 +157,32 @@ We recommend using the .tar.gz archiving format to preserve the execution permis

.NET proposes several parameters when publishing apps that may sound helpful, but have many issues when it comes to games (because they were never meant for games in the first place, but for small lightweight applications).

### PublishAot

This option optimises your game code "Ahead of Time". It allows you to ship your game without the need to JIT (Just In Time compile).
However you do need to currently add some additional settings to your .csproj.

```
<ItemGroup>
<TrimmerRootAssembly Include="MonoGame.Framework" />
<TrimmerRootAssembly Include="mscorlib" />
</ItemGroup>
```

The `TrimmerRootAssembly` stops the trimmer removing code from these assemblies. This will on the whole allow the game to run without
any issues. However if you are using any Third Party or additional assemblies, you might need to add them to this list or fix your code to be `Aot` compliant.
It is recommended that you publish using AOT as it simplifies the app bundle.

See [Trim self-contained deployments and executables](https://learn.microsoft.com/en-us/dotnet/core/deploying/trimming/trim-self-contained) for more information.

There are some known area's you need to watchout for.

1. Using `XmlSerializer` in your game will probably cause issues. Since it uses reflection it will be difficult for the Trimmer to figure out what needs to be kept.
It is recommended that instead of using the `Deserialize` method, you write your own custom deserializer using `XDocument` or `XmlReader`.
Alternatively you can use the Content Pipeline and create a custom `Processor` and `Reader` to convert the Xml into a binary format that can be loaded via the usual `Content.Load<T>` method.
2. Dynamically loading assemblies via `Assembly.LoadFile`.
3. No run-time code generation, for example, System.Reflection.Emit.

### ReadyToRun (R2R)

[ReadyToRun](https://docs.microsoft.com/en-us/dotnet/core/whats-new/dotnet-core-3-0#readytorun-images) is advertised as improving application startup time, but slightly increasing binary size. We recommend not using it for games, because it produces micro stutters when your game is running.
Expand Down