Highlight
Apple introduced StoreKit and Background Assets plug-ins for Unity, automatic distribution for localized asset packs, and a new Steam asset converter that helps game developers migrate existing builds.
Core Ideas
One of the most painful problems in mobile game development is package size. Players often wait a long time to download hundreds of megabytes of assets, and a large share of those assets may never be used. For example, an English-speaking player might download an audio package that includes twelve languages, even though only the English audio is needed.
In iOS 27, Apple adds a key capability to Managed Background Assets: localized asset packs. The system can identify the player’s preferred language in Settings and download only the matching language assets. If no exact language pack is available, the system falls back to the closest language variant.
This directly addresses package bloat in multilingual games. For a game that supports 10 languages, each player may need to download only one tenth of the language-specific assets.
Another practical problem is that many games are already shipped on Steam, with assets managed as depots. Migrating to Apple platforms can require a large amount of asset reorganization. Apple now provides a command-line tool that can convert a Steam depot directly into an Asset Pack Manifest.
For Unity developers, Apple also officially released C# plug-ins for StoreKit and Background Assets. Previously, using native Apple frameworks from Unity required custom bridge code. With the official plug-ins, you can call them directly from C#.
Details
Localized asset packs
(01:35)
Add a language field to the asset pack manifest to mark the language version of that pack.
// Asset pack manifest
{
"assetPackID": "voice-english",
"downloadPolicy": { /* ... */ },
"language": "en-US",
"sourceRoot": ".",
"fileSelectors": [ /* ... */ ],
"platforms": [ /* ... */ ]
}
Key points:
- The
languagefield uses BCP 47 language tags, such asen-USandzh-Hans. - The system matches asset packs based on the user’s selection in Settings > General > Language & Region.
- If there is no exact match, it falls back to a base language, such as falling back from
en-GBtoen-US.
Steam asset converter
(03:13)
Convert a Steam depot to an Apple asset pack with a single command:
# Convert a Steam depot to an asset pack manifest
xcrun ba-package convert --asset-pack-id voice-english -l en-US --on-demand voice-english.vdf -o voice-english.json
Key points:
--asset-pack-idspecifies the unique identifier for the asset pack.-lspecifies the language tag and is optional.--on-demandsets the download policy. Options such asprefetchare also available.- The input is Steam’s
.vdfmanifest file. - The output is Apple’s JSON manifest.
After generating the manifest, package it into an asset pack archive:
# Convert an asset pack manifest to an asset pack archive
xcrun ba-package voice-english.json -o voice-english.aar
This tool is already available on macOS with Xcode 27. Linux and Windows versions are coming soon.
Unity StoreKit plug-in
(05:41)
Fetch products and purchase them:
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)
{
// Unlock access to purchased content
result.TransactionVerification.SafePayload.Finish();
}
}
Key points:
Product.FetchProductsfetches product information asynchronously.product.Purchase()opens the system purchase interface.IsVerifiedensures the transaction has not been tampered with.Finish()must be called to complete the transaction, otherwise it can be processed repeatedly.
Listen for transaction updates:
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;
// Consumables are not in CurrentEntitlements, so handle them inline
if (verifiedTransaction.ProductType == ProductType.ProductTypeEnum.Consumable) {
if (verifiedTransaction.RevocationDate != null) {
// Revoke the consumable identified by verifiedTransaction.ProductId
} else {
// Grant access to the consumable
}
} else {
// Non-consumables and subscriptions: re-read CurrentEntitlements as source of truth
await foreach (var verificationResult in Transaction.GetCurrentEntitlements()) {
if (!verificationResult.IsVerified) continue;
// Grant access to the product
}
}
verifiedTransaction.Finish();
}
}
Key points:
Transaction.Updatesis a sequence that fires whenever a transaction changes.- Consumable products need separate handling.
- For non-consumables and subscriptions, use
CurrentEntitlementsas the single source of truth. - The system automatically filters refunded, revoked, and expired entitlements.
Unity Background Assets plug-in
(07:13)
Download an asset pack and listen for progress:
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)) {
// Update download progress in the UI
}
}, tokenSource.Token);
await AssetPackManager.EnsureLocalAvailabilityOfAssetPackAsync(assetPack);
tokenSource.Cancel();
// Start the tutorial with locally available assets
} catch (Exception exception) {
// Handle the exception
}
}
Key points:
GetManifestAsync()fetches the asset pack manifest.DownloadStatusUpdatesAsyncreturns an asynchronous sequence that can update UI progress.EnsureLocalAvailabilityOfAssetPackAsyncensures assets are locally available, automatically downloading them if needed.
Testing tools
(08:37)
Test these features in Xcode 27:
- Create a StoreKit Configuration file and add test products.
- Select that configuration file in the Run options for the scheme.
- In the same interface, set the asset pack folder path for the Mock Server.
- Run the project. The Background Assets Mock Server starts automatically and attaches to the debugging session.
Key Takeaways
-
Slim down multilingual games: If your game supports multilingual voice or video, split localized content into independent Asset Packs immediately. A 500 MB game may require each player to download only 150 MB after splitting.
-
Shortcut for Steam game migration: Porting a Steam game to iOS? Use the new converter to reuse the existing depot structure directly and avoid the manual cost of reorganizing assets.
-
Zero-friction Unity in-app purchases: If Apple in-app purchases used to feel painful in Unity, the official plug-in now reduces the flow to Fetch, Purchase, and Finish, without writing Objective-C or Swift bridge code yourself.
-
Visualize download progress: Use
DownloadStatusUpdatesAsyncfrom the Background Assets plug-in to build a polished progress bar, so players know content is loading instead of staring at a blank screen. -
Landscape purchase experience: In iOS 27, the system purchase interface supports landscape mode. When designing in-game purchase flows, you can let players complete payment without leaving the landscape experience.
Related Sessions
- 210 In-App Purchase — Best practices and the latest features for App Store in-app purchases
- 223 Live Activities — Show game downloads and content update progress with Live Activities
- 258 Xcode 27 — New Xcode features, including StoreKit Testing enhancements
- 277 WidgetKit — Use widgets to show game content and updates
- 319 PCC Foundation Model — Security practices related to Private Cloud Compute
Comments
GitHub Issues · utterances