WWDC Quick Look 💓 By SwiftGGTeam
Unlock in-game content with StoreKit and Background Assets

Unlock in-game content with StoreKit and Background Assets

Watch original video

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 language field uses BCP 47 language tags, such as en-US and zh-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-GB to en-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-id specifies the unique identifier for the asset pack.
  • -l specifies the language tag and is optional.
  • --on-demand sets the download policy. Options such as prefetch are also available.
  • The input is Steam’s .vdf manifest 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.FetchProducts fetches product information asynchronously.
  • product.Purchase() opens the system purchase interface.
  • IsVerified ensures 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.Updates is a sequence that fires whenever a transaction changes.
  • Consumable products need separate handling.
  • For non-consumables and subscriptions, use CurrentEntitlements as 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.
  • DownloadStatusUpdatesAsync returns an asynchronous sequence that can update UI progress.
  • EnsureLocalAvailabilityOfAssetPackAsync ensures assets are locally available, automatically downloading them if needed.

Testing tools

(08:37)

Test these features in Xcode 27:

  1. Create a StoreKit Configuration file and add test products.
  2. Select that configuration file in the Run options for the scheme.
  3. In the same interface, set the asset pack folder path for the Mock Server.
  4. Run the project. The Background Assets Mock Server starts automatically and attaches to the debugging session.

Key Takeaways

  1. 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.

  2. 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.

  3. 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.

  4. Visualize download progress: Use DownloadStatusUpdatesAsync from the Background Assets plug-in to build a polished progress bar, so players know content is loading instead of staring at a blank screen.

  5. 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.

Comments

GitHub Issues · utterances