
今年初頭に私たちは、Apple の新しい Arcade サブスクリプション サービスのローンチ タイトルの 1 つとして『Dodo Peak』をリリースするという栄誉にあずかることができました。Apple Arcade のセールスポイントには、ゲームが Mac を含むすべてのデバイスで動くということがあります。Apple Arcade で『Dodo Peak』をリリースするために私たちは、iCloud や Game Center といった Apple のサービスとともに、Apple の Mac App Store エコシステムと統合する必要がありました。
Mac をターゲットにするのであれば、そのような統合をしない手はありません。その理由は次のようになります。
- Mac App Store で販売することによって、ゲームに特化したデジタル ストアに立ち寄らない人たちに対しても、組織だってアピールできる。
- iCloud または Game Center を使用してリーダーボードやクラウド保存などを扱うと、サードパーティーのインフラストラクチャをアプリに統合することなく、無料で信頼できるファーストパーティー製のサービスに依拠することができる。
- Apple Arcade には Game Center と iCloud が必須であるため、異なるデバイスからクラウド保存に必ずアクセスできるようになる。
iOS をターゲットにする場合、Unreal は、そのままで Game Center と iCloud をサポートしています。これは素晴らしいことですね。ただし、macOS をターゲットとする場合には、残念ながら同様のサポートが組み込まれていません。最近まで、Mac App Store をターゲットする Unreal のゲームは一般的ではありませんでした。
幸いなことに、そのような機能上のギャップは誰でも手動で回避できます。ここ数か月にわたって、図らずも私は Unreal のゲームを Mac のための Apple サービスと統合をするエキスパートとなりました。そこで、それによって得られた知識をすべて公開したいと思います。そうすることによって、皆さんが自分で知識を集めてつなぎ合わせる必要がないようにしたいと思います。このガイドでは、Mac 向けゲームを Apple サービスと統合する方法、および、Mac App Store に出品するためにゲームをパッケージングする方法について解説します。

Apple Developer ポータルで Mac 向けアプリをセットアップする
Apple のサービスと Mac App Store をゲームが使えるようにするためには、まず、Apple のバックエンドでアプリを設定しなければなりません。その方法を教えるガイドはたくさんあるのですが、適切に遂行できるようにするために、必要となる作業の簡単なチェックリストを以下に示します。- Apple Developer Program に参加する。 (年間 99ドルの登録料が必要です。)
- Certificates, Identifiers & Profiles のページの Identifiers セクションから App Identifier を作成する。
- アプリのための Bundle ID を作成する。例: “
com.mycompany.mygame
” - iCloud および Game Center、その他必要な機能を有効にする。
- 注意: すでに iOS 版のゲームのために有効な App ID を作っている場合は、Mac の Identifier を自動生成させることができます。その場合は、iOS App の Identifier の設定項目から Mac の capability を選択します。
- アプリのための Bundle ID を作成する。例: “
- Certificates, Identifiers & Profiles のページの Certificates セクションでアカウントの Certificate (証明書) を作成します。
- Certificate Signing Request (証明書署名要求) をローカルで作成する。
- これを使用して、次のための Certificate を作成およびダウンロードする。
- Mac Development
- Mac App Distribution
- Mac Installer Distribution
- Certificates, Identifiers & Profiles のページの Devices セクションで、所有しているテスト用または開発用の Mac を追加する。
- System Report 機能によって Mac の UDID を見つけることができる。
- (任意) Certificates, Identifiers & Profiles のページの Identifiers セクションで iCloud コンテナを作成する。
- App Identifier を編集するとともに、iCloud 機能の横にあるボタンを使ってコンテナを割り当てる。
- 関連する Provisioning Profile を作成およびダウンロードする。
- Mac Development Provisioning Profile を作成する。
- 必ず、新たに作成した App Identifier および Certificate、Device を選択する。
- Mac App Store のための Provisioning Profile を作成する。
- Mac Development Provisioning Profile を作成する。
- 新たな macOS app を App Store Connect で作成し、新たな Bundle ID を選択する。
- Unreal でプロジェクトを開く。[Project Settings](プロジェクト設定) で、[Platforms](プラットフォーム) > [iOS] を開き、Bundle Identifier に新しい Bundle ID を設定する。
以上は退屈な作業ですが、すべて後でゲームをパッケージングまたはテストするときに役立つものです。次のセクションでは、Game Center や iCloud などの機能に関わるコードを実際に記述する方法について説明します。

Mac 向けゲームを Apple API と統合する
エンジンには、macOS でゲームが iCloud または Game Center と通信するために必要なフックが用意されていないため、自分で実装する必要があります。Apple のドキュメントにざっと目を通すと、オペレーティングシステムの API の殻を破り、栄養豊富なその中心に到達する唯一の方法は、Objective-C または Swift を使用することであることがわかります。Unreal は C++ を使用しているため、これには少し怖気づきますね。それでどうやってやればいいのでしょうか?静的ライブラリをコンパイルしてリンクするのは大変でしょうか?いいえ!
このことは、Unreal Engine のソースと同じ方法で回避できます。Objective-C++ という Xcode の強力でありながら、あまり知られていない機能を使用するのです。
Objective-C++ とはどのような言語か?
Objective-C++ は、名前どおりの言語です。すなわち、C++ の内部に Objective-C コードを埋め込んだり、その逆を行ったりすることが可能です。C++ も Objective-C も C のスーパーセット言語です。つまり、これらの言語は、標準的な C 言語の構文に付加的な機能を加えたものをサポートしている、ということになります。両言語が衝突する場合もありますが、たいていは、Xcode で両方を使っても動きます。Objective-C++ のより詳しい解説を探している方には、この Medium article の記事をおすすめします。Objective-C の参照カウントベースのメモリ管理には回避すべき危険がいくつかありますが、ほとんどの場合は大丈夫です。とりあえず、お待ちかねのコードサンプルを掲載します。
Mac で Game Center をサポートする
Apple には、Game Center のためのコーディング方法を解説した独自のガイドがありますが、要約すると次のようになります。- プレイヤーを認証する
- 認証されたプレーヤーを使って、アチーブメントやリーダーボードなどを呼び出す。
次のコードのいずれも、コンパイルする前には、Unreal のビルドシステムを介して、パッケージに必要なライブラリを示す必要があります。 ゲームのBuild.csファイルで、コンストラクターに次のロジックを追加します。
if (Target.Platform == UnrealTargetPlatform.Mac) {
PublicFrameworks.AddRange(new string[]{"GameKit"});
}
以下に、Objective-C++ を使ってプレーヤーを認証する方法のサンプルを示します。なお、Objective-C のコールバックを authenticateHandler に渡しますが、そのコールバック関数内のコードには C++ を含めることができます。
#include <GameKit/GameKit.h>
void UMyBlueprintFunctionLibrary::GameCenterSignIn() {
#if WITH_EDITOR
// エディタでは実行しない。
#elif PLATFORM_MAC
// GameCenter に非同期にログインする。コードの中で、
// デリゲートまたは UBlueprintAsyncActionBase を使って書くことによって、
// ログインに時間がかかる場合に対処できるようにすることをおすすめします。
dispatch_async(dispatch_get_main_queue(), ^{
[[GKLocalPlayer localPlayer]
setAuthenticateHandler:^(NSViewController *_Nonnull viewController, NSError *error) {
if ([[GKLocalPlayer localPlayer] isAuthenticated])
{
// 成功
return;
}
if (error)
{
// 失敗
}
else if (viewController)
{
// ログインを表示
GKDialogController *presenter = [GKDialogController sharedDialogController];
presenter.parentWindow = [NSApp keyWindow];
[presenter presentViewController:(NSViewController * _Nonnull) viewController];
}
}];
});
#endif
}
以下は、アチーブメントのロックを解除するコード例です。
#include <GameKit/GameKit.h>
void UMyBlueprintFunctionLibrary::WriteAchievement(FString ID, float Percent)
{
#if PLATFORM_MAC
if (![[GKLocalPlayer localPlayer] isAuthenticated])
return;
// FString を NSString に変換する。
NSString *nsID = [NSString stringWithUTF8String:TCHAR_TO_ANSI(*ID)];
GKAchievement *achievement = [[GKAchievement alloc] initWithIdentifier:nsID];
achievement.percentComplete = Percent;
achievement.showsCompletionBanner = YES;
[GKAchievement reportAchievements:@[ achievement ]withCompletionHandler:^(NSError *error) {
if (error != nil)
{
NSLog(@"%@", [error localizedDescription]);
}
}];
#endif
}
次は、Game Center のリーダーボード API を呼び出すコードです。
#include <GameKit/GameKit.h>
void UMyBlueprintFunctionLibrary::WriteScoreToLeaderboard(FString LeaderboardID, int Integer) {
#if PLATFORM_MAC
if (![[GKLocalPlayer localPlayer] isAuthenticated])
return;
// FString を NSString に変換する。
NSString *nsID = [NSString stringWithUTF8String:TCHAR_TO_ANSI(*LeaderboardID)];
GKScore *score = [[GKScore alloc] initWithLeaderboardIdentifier:nsID];
score.value = Integer;
[GKScore reportScores:@[score] withCompletionHandler:^(NSError *error) {
if (error != nil)
{
NSLog(@"%@", [error localizedDescription]);
}
}];
#endif
}
最後の警告として:Unreal Editor 内のコードから Game Center の関数を呼び出すと、奇妙な動作が生じる可能性があります。そのため、Game Center のコードを
#if WITH_EDITOR
マクロでラップするか、パッケージングされていないバージョンのゲームにおいて何らかの方法でこれらの機能を無効にすることをおすすめします。
クラウド保存のために iCloud を利用する
このガイドでは、iCloud の複雑な詳細についてまで説明していません。Apple には、入門的な解説がいくつか用意されているので、それらをご覧になってから、iCloud を使用してタイトルにクラウド保存のサポートを追加してください。その目的上、iCloud はプレーヤーが読み書きできる大きなキーバリューストアと考えることができます。iCloud の保存データをモデル化する方法はいくつかあります。
- 保存オブジェクトの各フィールドを iCloud における独自のフィールドとして扱う (たとえば、プレーヤーのレベル フィールドは「レベル」という整数、キャラクターの名前は「名前」という文字列など)。
- 保存オブジェクトをバイナリデータにシリアル化し、保存データ全体を1つのバイナリグロブとして単一の iCloud フィールドにアップロードする。
『Dodo Peak』について言えば、私のチームは 2 を選択しました。理由は、Unreal がディスクに保存データを書き込む場合と同じシリアル化ロジックを使用できるようになるからです。
ここでも、コードを実行する前に、CloudKit ライブラリを使用するようにビルドシステムに指示する必要があります。
if (Target.Platform == UnrealTargetPlatform.Mac) {
PublicWeakFrameworks.Add("CloudKit");
}
次のコードは、SaveGame オブジェクトをシリアル化し、iCloud にそれをアップロードする例です。
#include <CloudKit/CloudKit.h>
#include "GameFramework/SaveGame.h"
#include "Kismet/GameplayStatics.h"
#include "Serialization/MemoryReader.h"
#include "Serialization/MemoryWriter.h"
#include "Serialization/ObjectAndNameAsStringProxyArchive.h"
void UMyBlueprintFunctionLibrary::WriteSaveToCloud(USaveGame* MySave) {
TArray<uint8> ObjectBytes;
FMemoryWriter MemoryWriter(ObjectBytes, true);
FObjectAndNameAsStringProxyArchive Ar(MemoryWriter, false);
MySave->Serialize(Ar);
CKContainer *defaultContainer =
[CKContainer containerWithIdentifier:@"iCloud.unrealtutorial.mygame"];
if (defaultContainer == nil)
{
// 初期化失敗
return;
}
else
{
CKDatabase *DB = [defaultContainer privateCloudDatabase];
CKRecordID *recordId = [[[CKRecordID alloc] initWithRecordName:@"save_game_id"] autorelease];
// RecordType "SaveGame" は Apple のオンライン iCloud ダッシュボードで設定される。
CKRecord *record = [[CKRecord alloc] initWithRecordType:@"SaveGame" recordID:recordId];
// Unreal のデータ配列を NSData バイトに変換する。
NSData *data = [NSData dataWithBytes:ObjectBytes.GetData() length:ObjectBytes.Num()];
record[@"SaveData"] = data;
// CKModifyRecordsOperation を使って、既存のレコードを更新できるようにする。
CKModifyRecordsOperation *modifyRecords =
[[CKModifyRecordsOperation alloc] initWithRecordsToSave:@[ record ]
recordIDsToDelete:nil];
modifyRecords.savePolicy = CKRecordSaveAllKeys;
modifyRecords.qualityOfService = NSQualityOfServiceUserInitiated;
modifyRecords.perRecordCompletionBlock = ^(CKRecord *results, NSError *error) {
if (error != nil)
{
NSLog(@"icloud save error: %@", error);
}
else
{
NSLog(@"icloud save success: %@", results);
}
};
[DB addOperation:modifyRecords];
}
}
次は、iCloud から SaveGame データをロードし、バイナリデータを新しい SaveGame オブジェクトに読み込むためのサンプルコードです。
#include <CloudKit/CloudKit.h>
#include "GameFramework/SaveGame.h"
#include "Kismet/GameplayStatics.h"
#include "Serialization/MemoryReader.h"
#include "Serialization/MemoryWriter.h"
#include "Serialization/ObjectAndNameAsStringProxyArchive.h"
UCustomSaveGame* UMyBlueprintFunctionLibrary::LoadFromCloud() {
CKContainer *defaultContainer =
[CKContainer containerWithIdentifier:@"iCloud.unrealtutorial.mygame"];
if (defaultContainer == nil)
{
return nullptr;
}
else
{
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"TRUEPREDICATE"];
CKDatabase *publicDatabase = [defaultContainer privateCloudDatabase];
CKRecordID *recordID = [[CKRecordID alloc] initWithRecordName:@"save_game_id"];
__block NSData *data = nil;
// このブロックの結果を保持して、予期せずに GC されないようにする。
__block CKRecord *holdResults;
// 注意:これは、デリゲートやセマフォ、その他の手段を使用して同期する必要があります。
[publicDatabase
fetchRecordWithID:recordID
completionHandler:^(CKRecord *results, NSError *error) {
holdResults = results;
if (error != nil)
{
NSLog(@"icloud load error: %@", error);
}
else
{
NSLog(@"icloud load success: %@", results);
data = [[NSData alloc] initWithData:(NSData *) results[@"SaveData"]];
}
}];
// Sleep と同期させないこと。これは例のためだけに置いている。
usleep(3000000); // 非同期ロードを待機する。
// データを保存オブジェクトに読み込む。
TArray<uint8> ObjectBytes;
ObjectBytes.AddUninitialized(data.length);
FMemory::Memcpy(ObjectBytes.GetData(), data.bytes, data.length * sizeof(uint8));
FMemoryReader MemoryReader(ObjectBytes, true);
FObjectAndNameAsStringProxyArchive Ar(MemoryReader, true);
UCustomSaveGame *LoadedSave = Cast<UCustomSaveGame>( UGameplayStatics::CreateSaveGameObject(UCustomSaveGame::StaticClass()));
LoadedSave->Serialize(Ar);
return LoadedSave;
}
}
上記のコードは、叩き台にすぎません。ニーズはプロジェクトによって異なります。ゲームにクラウド保存を完全に実装するためには、次のようなことを考慮する必要があります。
- ゲームはどれくらいの頻度で保存されるか?どのくらいの頻度でロードするか?保存とロードのたびにクラウドと通信する必要があるか、それともその一部でのみ通信する必要があるのか?
- 複数のデバイスが同じ iCloud レコードへの書き込みと読み取りを行うという機能をサポートする必要があるか?
- オフラインでの進行状況をどのように扱うか?
- プレーヤーのローカル保存がクラウド内のデータと競合する場合、どのように対処するか?
エディタから、または、アプリをエクスポートした後に、Mac iCloud のコードを実行しようとすると、資格がないことを示す例外がスローされることに注意してください。Game Center と同様に、エディタでの実行から iCloud のコードを除外することをおすすめします。
次のセクションでは、Development ビルドでその iCloud のコードを実際に実行する方法を概略的に説明します。

ゲームへの署名とパッケージング
すべてをまとめる時が来ました。Apple API を使用して Unreal ゲームを実行し、Mac App Store にゲームをアップロードするための最終関門です。ゲームのコード署名、および、資格 (entitlement)、Plist を構成する必要があります。これらについてだけでもブログ記事を書くことができるでしょうが、以下に簡単に要約してみます。
- 実行可能コードが、実際にそれを作成した者によるものであること、および、コードが改ざんされていないことを保証するために、Apple はコード署名という手段を取っています。詳細はこちらでご覧になれます。
- 資格 (Entitlements) は、コード署名を行っている間にアプリに埋め込まれるキーと値のペアです。iCloud など、アプリが利用できる特定の安全な OS の機能を意味しています。詳細はこちらをご覧ください。
- Information Property List (情報プロパティ リスト ファイル) または Info.plist には、アプリケーションと App Store のための重要なコンフィギュレーション データが格納されています。たとえば、アプリのアイコンの場所やサポートされている言語などが含まれています。詳細はこちらをご覧ください。
iOS のためにプロビジョニング プロファイルと証明書を使用して Unreal がパッケージングする方法に精通しているなら、この項は、それとよく似たものとなりますが、Unreal と Xcode が通常行うステップを自分で行う必要があります。
ローカルで開発するためのパッケージングと署名
ゲームを App Store へと送り出す前に、確認にしておかなければならないことがあります。それは、ゲームが動作するということです。では、ローカルで実行できるビルドはどのように作成できるのでしょうか? 先に述べたように、iCloud Entitlement (資格) をともなってアプリケーションに署名していない場合は、iCloud 関数を呼び出そうとするとゲームがクラッシュします。そこで、欠落している entitlement (資格) を修正することにしましょう。このプロセスは簡単ですが、退屈です。Mac アプリケーションに新しい entitlement を導入するためには、アプリに Provisioning Profile を供給し、必要な entitlement をともなって署名する必要があります。方法は次のとおりです。
まず、Unreal から、ゲームをパッケージングします。次に、ターミナルで、新しくエクスポートされたアプリを含むディレクトリに移動します。Development Provisioning Profile (開発用プロビジョニング プロファイル) のコピーを作成し、名前を
embedded.provisionprofile
に変更します。その Provisioning Profile を YourGame.app/Contents/embedded.provisionprofile
” にコピーします。これを新しいコード署名とまとめるには、まず、アプリケーションに追加したい entitlement (資格) をともなった XML ファイルを作成する必要があります。そのファイルは次のようになります。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.application-identifier</key>
<string>TEAMID.com.mycompany.mygame</string>
<key>com.apple.developer.icloud-container-environment</key>
<string>Development</string>
<key>com.apple.developer.icloud-services</key>
<array>
<string>CloudKit</string>
</array>
<key>com.apple.developer.icloud-container-identifiers</key>
<array>
<string>iCloud.com.mycompany.mygame</string>
</array>
<key>com.apple.developer.game-center</key>
<true/>
<key>com.apple.security.app-sandbox</key>
<true/>
</dict>
</plist>
上記の XML ファイルが次のようになるように変更します。
com.apple.application-identifier
が完全なアプリ Bundle ID である (重要:Bundle ID は、チーム ID から始まるようにします。「3Y1CL48M1K」のように、文字列と数字から成り立っている必要があります)。com.apple.developer.icloud-container-identifiers
の配列を変更して、iCloud コンテナ ID が含まれるようにします。
このテキスト ファイルを
entitlements.plist
として保存します。(余談ですが、アプリケーションの entitlement (資格) は、codesign -dv --entitlements-AppName.app で表示できます。デバッグのときに便利です)。
アプリに適切に署名するためには、まず、すべての実行可能コードに署名してから、アプリ自体に署名する必要があります。このことは、以下のようにして 1 つのコマンドで一遍に実行できます。
codesign --deep -f -v -s "3rd Party Mac Developer:" --entitlements entitlements.plist MyGame.app
これでお終いです! Finder でアプリをダブルクリックすると、ゲームがプレイできるはずです。また、iCloud と Game Center のリクエストも行うことができるはずです!

配布のためのパッケージングと署名
Mac App Store へのアプリの送信も同じ手順に従いますが、若干複雑になります。これらの手順を一度手動で実行して理解してから、独自のスクリプトを作成して自動的に実行しましょう。まず、Distribution (配布) を有効にした状態で、Shipping (シッピング) パッケージとしてゲームをエクスポートします。
次に、plist を編集して、ストアページに送信したい追加情報を取得します。たとえば、サポートされているコントローラーを GCSupportedGameControllers で一覧表示し、CFBundleLocalizations を使用してサポートされている言語を設定します (App Store は Unreal のローカリゼーション サポートを自動的に検出できません)。また、バージョン番号と Bundle ID を手動で編集が可能です。LSApplicationCategoryType を設定することによって、アプリケーションが Application Loader で受け入れられるようにする必要があります。
Distribution Provisioning Profile をコピーして、名前を
embedded.provisionprofile
に変更します。この Provisioning Profile ファイルを YourGame.app/Contents/embedded.provisionprofile
にコピーします。Development ビルドに署名するのとは異なり、Mac App Store で承認されるためには、エクスポートされた .app を少しクリーンアップする必要があります。
まず、Mac App Store は 32ビットの実行可能コードをサポートしていません (macOS 10.15 以降のバージョンもサポートしていません)。Unreal は、32 ビットコードを含むダイナミック オーディオ ライブラリをゲームに自動的にバンドルするため、そのコードを削除する必要があります。幸いなことに、このことはコンピュータ サイエンスの歴史を通じてかなり一般的な問題であるため、そのためのツールがあります。lipo コマンドを利用して、次を実行します。
lipo MyGame.app/Contents/UE4/Engine/Binaries/ThirdParty/Ogg/Mac/libogg.dylib -remove i386 -output MyGame.app/Contents/UE4/Engine/Binaries/ThirdParty/Ogg/Mac/libogg.dylib
lipo MyGame.app/Contents/UE4/Engine/Binaries/ThirdParty/Vorbis/Mac/libvorbis.dylib -remove i386 -output MyGame.app/Contents/UE4/Engine/Binaries/ThirdParty/Vorbis/Mac/libvorbis.dylib
lipo MyGame.app/Contents/UE4/Engine/Binaries/ThirdParty/OpenVR/OpenVRv1_0_16/osx32/libopenvr_api.dylib -remove i386 -output MyGame.app/Contents/UE4/Engine/Binaries/ThirdParty/OpenVR/OpenVRv1_0_16/osx32/libopenvr_api.dylib
次に、Unrealは、独自の Bundle ID がすでに備わっている、エクスポートされたゲームに、単一のサブコンポーネントを追加します。これが、Application Loader を怒らせます。 このコンポーネントは
RadioEffectUnit.component
です。私が知る限り、これはオーディオ エフェクトなのですが、なぜビルド システムによってこのように扱われるのかは分かりません。しかし良い知らせがあります。RadioEffectUnit を使用していない場合は、次のようにして、単にこのコンポーネントをアプリから削除すればよいのです。
rm -rf MyGame.app/Contents/Resources/RadioEffectUnit.component
rm -rf MyGame.app/Contents/UE4/Engine/Build
このクリーンアップが完了したら、ビルドへの署名を開始できます。
コード署名を用意するには、まず、アプリケーションに追加したい entitlement (資格) を含む XML ファイルを作成する必要があります。そのファイルは次のようになります。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.application-identifier</key>
<string>TEAMID.com.mycompany.mygame</string>
<key>com.apple.developer.icloud-container-environment</key>
<string>Production</string>
<key>com.apple.developer.icloud-services</key>
<array>
<string>CloudKit</string>
</array>
<key>com.apple.developer.icloud-container-identifiers</key>
<array>
<string>iCloud.com.mycompany.mygame</string>
</array>
<key>com.apple.developer.game-center</key>
<true/>
<key>com.apple.security.app-sandbox</key>
<true/>
</dict>
</plist>
上記の XML を変更して、次のようになるようにしてください。
com.apple.application-identifier
が完全なアプリ Bundle ID である (重要:Bundle ID は、チーム ID から始まるようにします。「3Y1CL48M1K」のように、文字列と数字から成り立っている必要があります)。com.apple.developer.icloud-container-identifiers
を変更して、iCloud コンテナ ID が含まれるようにします。
このテキスト ファイルを
entitlements.plist
として保存します。署名しましょう!配布のためには、ゲームのバイナリ、および、すべてのダイナミック ライブラリ、そして最後に .app 自体に署名する必要があります。私たちは、この作業を以下のようにして行いました:
codesign -f -v -s "3rd Party Mac Developer Application:" --entitlements entitlements.plist MyGame.app/Contents/MacOS/MyGame
このコマンドは、
find
を使用してファイル内のすべての動的ライブラリ(.dylibs)に署名します。
find MyGame.app/Contents/ | grep .dylib | xargs codesign -f -v -s "3rd Party Mac Developer Application:" --entitlements entitlements.plist
さらに、次のようにして、アプリ全体に署名します。
codesign -f -v -s "3rd Party Mac Developer Application:" --entitlements entitlements.plist MyGame.app/
すべてが署名されたら、アプリをパッケージングできます。アップロード可能な .pkg を生成するには、次を実行します。
productbuild --component MyGame.app/ /Applications --sign "3rd Party Mac Developer Installer:" MyGame.pkg
次に、ご自分のシステムで Application Loader アプリを開きます。[Choose](選択) ボタンをクリックして、新しい .pkg ファイルを選択します。Application Loader は、パッケージをスキャンしてエラーを見つけ、App Store Connect ポータルにアップロードします。

よくあるパッケージングの問題
最後に、あなたの旅の途中にぶつかるかもしれない問題を簡単に指摘しておきます。1 つ目として、パッケージングで問題が発生した場合ですが、Unreal フォーラムでそれが議論されているスレッドは次のとおりです。https://forums.unrealengine.com/community/community-content-tools-and-tutorials/68346-how-to-create-the-proper-pkg-file-for-deployment-to-the-macstore/page2
2 つ目として、Application Loader は、以下の理由でアプリを拒絶する場合があります。「ERROR ITMS-90135: The executable could not be re-signed for submission to the App Store. The app may have been built or signed with non-compliant or pre-release tools. (エラーITMS-90135:App Store への提出のために実行可能ファイルに再署名することができませんでした。アプリが、正しいやり方に則ってビルドまたは署名されていない可能性、または、プレリリースツールを使ってビルドまたは署名された可能性があります)」。このエラーメッセージは、まさに悪夢です。なぜなら、このメッセージは「何かが間違っているけども、何が間違っているのか教えることはできない」と同義だからです。私のチームは、この問題を数日間ぶつかりました。私たちのケースでは、ビルドにデバッグ シンボルを含めていたため、アプリの処理が中断されていたのです。必ず、[プロジェクト設定] で [Include Debug Files](デバッグファイルを含む) のチェックを外すようにしてください。
最後に、私たちが最もよく見かけた Application Loader の拒否理由は、アプリケーションのアイコンに関係しています。仕様に応じた .icns ファイルを生成するためには、さまざまなツールと手法がありますが、私たちの場合は、App Wrapper 3 を使用して PNG ファイルからアイコン セットを生成するというワークフローに落ち着きました。