2026 App ServicesApp Store, Distribution & Marketing
WWDC26 · 10 min · App Services / App Store, Distribution & Marketing
Unlock in-game content with StoreKit and Background Assets
Unlock native Apple In-App Purchases for your Unity game with the new StoreKit plug-in. Reduce download sizes with the new Background Assets plug-in, which delivers language-specific asset packs so each player gets just what they need. Plus, a new Steam Asset Converter helps you migrate existing builds.
Watch at developer.apple.com ↗Chapters
Code shown on screen · 7 snippets
Asset pack manifest for a localized asset pack
// Asset pack manifest
{
"assetPackID": "voice-english",
"downloadPolicy": { /* … */ },
"language": "en-US",
"sourceRoot": ".",
"fileSelectors": [ /* … */ ],
"platforms": [ /* … */ ]
//…
} Convert a Steam depot to an asset pack manifest
# 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 Convert an asset pack manifest to an asset pack archive
# Convert an asset pack manifest to an asset pack archive
xcrun ba-package voice-english.json -o voice-english.aar Fetch and purchase products with the StoreKit plug-in
// Fetch and purchase products with the StoreKit plug-in
using UnityEngine;
using Apple.StoreKit;
async void Start() {
var products = await Product.FetchProducts(new[] {
"com.thecoast.capecod"
});
} Fetch and purchase products with the StoreKit plug-in
// Fetch and purchase products with the StoreKit plug-in
using UnityEngine;
using Apple.StoreKit;
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();
}
} Listen for Transaction updates with the StoreKit plug-in
// Listen for Transaction updates with the StoreKit plug-in
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();
}
} Download asset packs with the Background Assets plug-in
// Download asset packs with the Background Assets plug-in
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 UI
}
}, tokenSource.Token);
await AssetPackManager.EnsureLocalAvailabilityOfAssetPackAsync(assetPack);
tokenSource.Cancel();
// Start tutorial with the locally available assets
} catch (Exception exception) {
// Handle the exception
}
} Resources
Related sessions
-
27 min -
25 min -
8 min