ハイライト
Apple は Unity 向けの StoreKit と Background Assets プラグインを導入しました。ローカライズされたアセットパックの自動配信に対応し、新しい Steam アセット変換ツールでゲーム開発者が既存ビルドを移行しやすくなります。
主要内容
モバイルゲーム開発で大きな悩みの1つは、パッケージサイズです。プレイヤーはゲームを始めるだけで長く待ち、数百 MB のアセットをダウンロードします。その多くは実際には使われない場合があります。たとえば英語プレイヤーが、日本語、中国語、韓国語、フランス語、ドイツ語、スペイン語、ロシア語など12言語分の音声パックをダウンロードしても、使うのは英語だけです。
Apple は iOS 27 で Managed Background Assets に重要な機能を追加しました。ローカライズされたアセットパックです。システムはプレイヤーが設定で選んだ優先言語を認識し、対応する言語のアセットだけをダウンロードできます。対応する言語パックがない場合は、最も近い言語バリアントへ自動でフォールバックします。
この機能は、多言語ゲームのパッケージ肥大化を直接解決します。10言語対応のゲームでも、各プレイヤーが必要とする言語アセットはそのうち1言語分だけで済む可能性があります。
もう1つの現実的な問題は、多くのゲームがすでに Steam で配信され、アセットを depot 形式で管理していることです。Apple プラットフォームへ移行するためにアセットを整理し直すのは、大きな作業になります。Apple は、Steam depot を Asset Pack Manifest に直接変換できるコマンドラインツールを提供しました。
Unity 開発者向けには、StoreKit と Background Assets の C# プラグインも正式に公開されました。以前は Unity から Apple のネイティブフレームワークを使うには、自前のブリッジコードが必要でした。公式プラグインにより、C# から直接呼び出せます。
詳細
ローカライズされたアセットパック
(01:35)
アセットパック manifest に language フィールドを追加すると、そのアセットパックの言語版を示せます。
// アセットパック manifest
{
"assetPackID": "voice-english",
"downloadPolicy": { /* ... */ },
"language": "en-US",
"sourceRoot": ".",
"fileSelectors": [ /* ... */ ],
"platforms": [ /* ... */ ]
}
重要な点:
languageフィールドはen-USやzh-Hansなどの BCP 47 言語タグ形式を使います。- システムは「設定 > 一般 > 言語と地域」でのユーザー選択に基づいてアセットパックを照合します。
- 完全一致がない場合、
en-GBからen-USへ戻るように、基礎言語へフォールバックします。
Steam アセット変換ツール
(03:13)
Steam depot を Apple のアセットパックへ変換するには、1つのコマンドで済みます。
# Steam depot をアセットパック manifest に変換する
xcrun ba-package convert --asset-pack-id voice-english -l en-US --on-demand voice-english.vdf -o voice-english.json
重要な点:
--asset-pack-idはアセットパックの一意識別子を指定します。-lは言語タグを指定し、省略可能です。--on-demandはダウンロードポリシーを設定します。prefetchなどの選択肢もあります。- 入力は Steam の
.vdfmanifest ファイルです。 - 出力は Apple の JSON manifest です。
manifest を生成したら、次にアセットパックアーカイブへパッケージします。
# アセットパック manifest をアセットパックアーカイブに変換する
xcrun ba-package voice-english.json -o voice-english.aar
このツールは macOS ではすでに利用可能です(Xcode 27 が必要)。Linux 版と Windows 版も近日公開予定です。
Unity StoreKit プラグイン
(05:41)
商品を取得して購入します。
using UnityEngine;
using Apple.StoreKit;
async void Start() {
var products = await Product.FetchProducts(new[] {
"com.thecoast.capecod"
});
}
async void Purchase(Product product) {
var result = await product.Purchase();
if (result.Result == PurchaseResult.ResultEnum.Success
&& result.TransactionVerification.IsVerified)
{
// 購入済みコンテンツへのアクセスを解放する
result.TransactionVerification.SafePayload.Finish();
}
}
重要な点:
Product.FetchProductsは商品情報を非同期に取得します。product.Purchase()はシステムの購入画面を表示します。IsVerifiedは取引が改ざんされていないことを確認します。Finish()は取引を完了するために必ず呼び出す必要があります。呼ばないと取引が繰り返し処理される可能性があります。
取引更新を監視します。
using UnityEngine;
using Apple.StoreKit;
public static class TransactionListener {
public static void Initialize() => Transaction.Updates += OnUpdate;
async void OnUpdate(VerificationResult<Transaction> result) {
if (!result.IsVerified) return;
var verifiedTransaction = result.SafePayload;
// 消耗型商品は CurrentEntitlements に含まれないため、その場で処理する
if (verifiedTransaction.ProductType == ProductType.ProductTypeEnum.Consumable) {
if (verifiedTransaction.RevocationDate != null) {
// verifiedTransaction.ProductId で識別される消耗型商品を取り消す
} else {
// 消耗型商品へのアクセスを付与する
}
} else {
// 非消耗型商品とサブスクリプションでは、CurrentEntitlements を信頼できる唯一の情報源として読み直す
await foreach (var verificationResult in Transaction.GetCurrentEntitlements()) {
if (!verificationResult.IsVerified) continue;
// 商品へのアクセスを付与する
}
}
verifiedTransaction.Finish();
}
}
重要な点:
Transaction.Updatesは取引の変化があるたびに発火するシーケンスです。- 消耗型商品(Consumable)は個別に処理する必要があります。
- 非消耗型商品とサブスクリプションでは、
CurrentEntitlementsを唯一の信頼できる情報源として使います。 - システムは返金済み、取り消し済み、期限切れの権利を自動的に除外します。
Unity Background Assets プラグイン
(07:13)
アセットパックをダウンロードし、進捗を監視します。
using Apple.BackgroundAssets;
using UnityEngine;
async void LoadTutorial(string language) {
try {
string assetPackId = $"tutorial-{language}";
AssetPackManifest manifest = await AssetPackManager.GetManifestAsync();
AssetPack assetPack = manifest.GetAssetPack(assetPackId);
CancellationTokenSource tokenSource = new CancellationTokenSource();
_ = Task.Run(async () => {
await foreach (AssetPackManager.DownloadStatusUpdate statusUpdate in AssetPackManager.DownloadStatusUpdatesAsync(assetPackId)) {
// UI のダウンロード進捗を更新する
}
}, tokenSource.Token);
await AssetPackManager.EnsureLocalAvailabilityOfAssetPackAsync(assetPack);
tokenSource.Cancel();
// ローカルで利用可能になったアセットでチュートリアルを開始する
} catch (Exception exception) {
// 例外を処理する
}
}
重要な点:
GetManifestAsync()はアセットパックのマニフェストを取得します。DownloadStatusUpdatesAsyncは UI の進捗更新に使える非同期シーケンスを返します。EnsureLocalAvailabilityOfAssetPackAsyncはアセットがローカルで利用可能であることを保証し、必要なら自動的にダウンロードします。
テストツール
(08:37)
Xcode 27 でこれらの機能をテストします。
- StoreKit Configuration ファイルを作成し、テスト商品を追加します。
- Scheme の Run オプションでその構成ファイルを選択します。
- 同じ画面で、Mock Server が使うアセットパックフォルダのパスを設定します。
- プロジェクトを実行します。Background Assets Mock Server が自動的に起動し、デバッグセッションに接続されます。
重要な示唆
-
多言語ゲームを軽量化する:ゲームが多言語の音声や動画をサポートしているなら、ローカライズされたコンテンツを独立した Asset Pack にすぐ分割しましょう。500 MB のゲームでも、分割後は各プレイヤーが 150 MB だけダウンロードすればよくなるかもしれません。
-
Steam ゲーム移行の近道:Steam 版を iOS に移植したい場合、新しい変換ツールを使えば既存の depot 構造を直接再利用でき、アセットを整理し直す手間を省けます。
-
Unity アプリ内課金のハードルを下げる:Unity で Apple のアプリ内課金を扱うのが面倒だと感じていたなら、公式プラグインにより Fetch、Purchase、Finish の3手順で済みます。Objective-C や Swift のブリッジを自作する必要はありません。
-
ダウンロード進捗を可視化する:Background Assets プラグインの
DownloadStatusUpdatesAsyncを使って、洗練された進捗バーを作ります。プレイヤーは黒い画面を見つめるのではなく、コンテンツが読み込まれていることを理解できます。 -
横画面の購入体験:iOS 27 のシステム購入画面は横画面モードをサポートします。ゲーム内購入フローを設計するとき、プレイヤーが横画面のまま支払いを完了できるようにすると、体験がより一貫します。
関連セッション
- 210 In-App Purchase — App Store のアプリ内課金に関するベストプラクティスと最新機能
- 223 Live Activities — Live Activities でゲームのダウンロードやコンテンツ更新の進捗を表示する
- 258 Xcode 27 — StoreKit Testing の強化を含む Xcode の新機能
- 277 WidgetKit — Widget でゲームコンテンツや更新情報を表示する
- 319 PCC Foundation Model — Private Cloud Compute に関連するセキュリティ実践
コメント
GitHub Issues · utterances