Highlight
Apple 推出了 Unity 版 StoreKit 和 Background Assets 插件,支持本地化资源包自动分发,新增 Steam 资源转换工具帮助游戏开发者迁移现有构建。
核心内容
做手游开发最头疼的是什么之一——包体太大。玩家下个游戏就要等半天,下载几百 MB 的资源,其中一大半是自己用不到的。比如英文玩家下载了包含中日韩法德西俄等十二种语言的音频包,实际只用到英文那一份。这既浪费玩家时间,也浪费存储空间。
Apple 在 iOS 27 给 Managed Background Assets(后台资源管理)加了一个关键能力:本地化资源包。系统现在能识别玩家在设置里选择的首选语言,只下载对应语言的资源。如果没有提供对应语言的资源包,系统会自动回退到最接近的语言变体。
这个功能直接解决了多语言游戏的包体膨胀问题。一个支持 10 种语言的游戏,每个玩家只需要下载 1/10 的语言资源。
另一个现实问题是:很多游戏已经上架 Steam,资产都以 depot 形式管理。要迁移到 Apple 平台,重新整理这些资产是巨大的工作量。Apple 提供了一个命令行工具,可以直接把 Steam depot 转换成 Asset Pack Manifest。
对于 Unity 开发者,Apple 还正式发布了 StoreKit 和 Background Assets 的 C# 插件。以前要在 Unity 里用原生 Apple 框架,得自己写桥接代码。现在有了官方插件,直接用 C# 调用就行。
详细内容
本地化资源包
(01:35)
在资源包 manifest 中加入 language 字段,就能标记这个资源包的语言版本。
// Asset pack manifest
{
"assetPackID": "voice-english",
"downloadPolicy": { /* ... */ },
"language": "en-US",
"sourceRoot": ".",
"fileSelectors": [ /* ... */ ],
"platforms": [ /* ... */ ]
}
关键点:
language字段使用 BCP 47 语言标签格式(如en-US、zh-Hans)- 系统会根据用户在「设置 > 通用 > 语言与地区」的选择匹配资源包
- 如果没有精确匹配,会回退到基础语言(如
en-GB回退到en-US)
Steam 资源转换工具
(03:13)
把 Steam depot 转换成 Apple 资源包只需一条命令:
# 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
关键点:
--asset-pack-id指定资源包唯一标识-l指定语言标签(可选)--on-demand设置下载策略(还有prefetch等选项)- 输入是 Steam 的
.vdfmanifest 文件 - 输出是 Apple 的 JSON manifest
生成 manifest 后,再打包成资源包归档:
# Convert an asset pack manifest to an asset pack archive
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)
{
// Unlock access to purchased content
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;
// 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();
}
}
关键点:
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)) {
// 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
}
}
关键点:
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 三步搞定,不用自己写 Objective-C/Swift 桥接。
-
下载进度可视化:用 Background Assets 插件的
DownloadStatusUpdatesAsync做一个精美的下载进度条,让玩家知道内容正在加载,而不是让他们盯着黑屏等待。 -
横屏支付体验:iOS 27 的系统支付界面支持横屏模式,设计游戏内购买流程时可以考虑让玩家保持横屏状态完成支付,体验更连贯。
关联 Session
- 210 In-App Purchase — App Store 内购最佳实践和最新功能
- 223 Live Activities — 用实时活动展示游戏下载和内容更新进度
- 258 Xcode 27 — Xcode 新功能,包括 StoreKit Testing 增强
- 277 WidgetKit — 用小组件展示游戏内容和更新
- 319 PCC Foundation Model — Private Cloud Compute 相关的安全实践
评论
GitHub Issues · utterances