PlayFab(旧版 Economy)、Unity IAP 和 Android 入门
重要
经济 v1 API 处于维护模式,将不会收到任何新功能,只有 bug 修复。 v1 API 将在可预见的未来进行维护。 请参阅“Economy v2 概述”,详细了解 PlayFab Economy 的下一版本!
本教程介绍如何使用 PlayFab、Unity + IAP 服务和 Android Billing API 设置应用内购买 (IAP)。
开始之前
设置 IAP 可能很乏味,如果不确定不同的服务应该如何集成和协作,就更是如此。
下图说明 Android Billing API 和 PlayFab 如何协作从而为客户提供稳定的 IAP 体验。
首先通过 PlayMarket 设置产品 ID 和价格。 开始时,所有产品都面貌不明 - 它们是玩家可以购买的数字实体,但是对于 PlayFab 玩家没有任何意义。
要使这些实体有用,我们需要在 PlayFab 物品目录中对它们进行镜像。 这样,可使“面貌不明”的实体成为捆绑包、容器和独立的物品。
每个物品都具有自己独特的面貌:
- 游戏
- 说明
- 标记
- 类型
- 图片
- 行为
所有这些都通过共享 ID 链接到市场产品。
访问可供购买的实际货币物品的最佳方式是使用 GetCatalogItems 和 GetStoreItems。 这些是免费货币商店使用的 API 方法,因此,过程是熟悉的。
物品 ID 是 PlayFab 和任何外部 IAP 系统之间的链接。 因此,我们向 IAP 服务传递物品 ID。
此时,购买过程开始。 玩家与 IAP 界面交互 - 如果购买成功 - 则会获得收据。
然后 PlayFab 可以验证收据并注册购买,向 PlayFab 玩家授予其刚刚购买的物品。
这是 IAP 集成的大致工作方式,下面的示例说明了在实际应用中的大部分情况。
设置客户端应用程序
本节介绍如何配置一个非常基本的应用程序,从而测试使用 PlayFab、UnityIAP 和 Android Billing API 的 IAP。
先决条件:
- 一个 Unity 项目。
- 已导入 PlayFab SDK 并将其配置为处理游戏。
第一步是设置 UnityIAP:
- 导航到 “服务”。
- 确保选择 Services 选项卡。
- 选择 “统一服务” 配置文件或组织。
- 选择 “创建” 按钮。
- 接下来,导航到 “应用内购买 (IAP)” 服务。
通过设置 简化跨平台的IAP 切换,请确保启用 “服务”。
然后选择 “继续” 按钮。
将显示一个插件列表页面。
- 选择 “导入” 按钮。
继续 Unity 安装和导入过程,直至导入所有插件。
- 确认插件是否已就位。
- 然后,创建一个名为 AndroidIAPExample.cs 的新脚本。
AndroidIAPExample.cs
包含下面所示的代码(请参阅代码注释以获得进一步说明)。
using PlayFab;
using PlayFab.ClientModels;
using PlayFab.Json;
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Purchasing;
public class AndroidIAPExample : MonoBehaviour, IStoreListener {
// Items list, configurable via inspector
private List<CatalogItem> Catalog;
// The Unity Purchasing system
private static IStoreController m_StoreController;
// Bootstrap the whole thing
public void Start() {
// Make PlayFab log in
Login();
}
public void OnGUI() {
// This line just scales the UI up for high-res devices
// Comment it out if you find the UI too large.
GUI.matrix = Matrix4x4.TRS(new Vector3(0, 0, 0), Quaternion.identity, new Vector3(3, 3, 3));
// if we are not initialized, only draw a message
if (!IsInitialized) {
GUILayout.Label("Initializing IAP and logging in...");
return;
}
// Draw menu to purchase items
foreach (var item in Catalog) {
if (GUILayout.Button("Buy " + item.DisplayName)) {
// On button click buy a product
BuyProductID(item.ItemId);
}
}
}
// This is invoked manually on Start to initiate login ops
private void Login() {
// Login with Android ID
PlayFabClientAPI.LoginWithAndroidDeviceID(new LoginWithAndroidDeviceIDRequest() {
CreateAccount = true,
AndroidDeviceId = SystemInfo.deviceUniqueIdentifier
}, result => {
Debug.Log("Logged in");
// Refresh available items
RefreshIAPItems();
}, error => Debug.LogError(error.GenerateErrorReport()));
}
private void RefreshIAPItems() {
PlayFabClientAPI.GetCatalogItems(new GetCatalogItemsRequest(), result => {
Catalog = result.Catalog;
// Make UnityIAP initialize
InitializePurchasing();
}, error => Debug.LogError(error.GenerateErrorReport()));
}
// This is invoked manually on Start to initialize UnityIAP
public void InitializePurchasing() {
// If IAP is already initialized, return gently
if (IsInitialized) return;
// Create a builder for IAP service
var builder = ConfigurationBuilder.Instance(StandardPurchasingModule.Instance(AppStore.GooglePlay));
// Register each item from the catalog
foreach (var item in Catalog) {
builder.AddProduct(item.ItemId, ProductType.Consumable);
}
// Trigger IAP service initialization
UnityPurchasing.Initialize(this, builder);
}
// We are initialized when StoreController and Extensions are set and we are logged in
public bool IsInitialized {
get {
return m_StoreController != null && Catalog != null;
}
}
// This is automatically invoked automatically when IAP service is initialized
public void OnInitialized(IStoreController controller, IExtensionProvider extensions) {
m_StoreController = controller;
}
// This is automatically invoked automatically when IAP service failed to initialized
public void OnInitializeFailed(InitializationFailureReason error) {
Debug.Log("OnInitializeFailed InitializationFailureReason:" + error);
}
// This is automatically invoked automatically when purchase failed
public void OnPurchaseFailed(Product product, PurchaseFailureReason failureReason) {
Debug.Log(string.Format("OnPurchaseFailed: FAIL. Product: '{0}', PurchaseFailureReason: {1}", product.definition.storeSpecificId, failureReason));
}
// This is invoked automatically when successful purchase is ready to be processed
public PurchaseProcessingResult ProcessPurchase(PurchaseEventArgs e) {
// NOTE: this code does not account for purchases that were pending and are
// delivered on application start.
// Production code should account for such case:
// More: https://docs.unity3d.com/ScriptReference/Purchasing.PurchaseProcessingResult.Pending.html
if (!IsInitialized) {
return PurchaseProcessingResult.Complete;
}
// Test edge case where product is unknown
if (e.purchasedProduct == null) {
Debug.LogWarning("Attempted to process purchase with unknown product. Ignoring");
return PurchaseProcessingResult.Complete;
}
// Test edge case where purchase has no receipt
if (string.IsNullOrEmpty(e.purchasedProduct.receipt)) {
Debug.LogWarning("Attempted to process purchase with no receipt: ignoring");
return PurchaseProcessingResult.Complete;
}
Debug.Log("Processing transaction: " + e.purchasedProduct.transactionID);
// Deserialize receipt
var googleReceipt = GooglePurchase.FromJson(e.purchasedProduct.receipt);
// Invoke receipt validation
// This will not only validate a receipt, but will also grant player corresponding items
// only if receipt is valid.
PlayFabClientAPI.ValidateGooglePlayPurchase(new ValidateGooglePlayPurchaseRequest() {
// Pass in currency code in ISO format
CurrencyCode = e.purchasedProduct.metadata.isoCurrencyCode,
// Convert and set Purchase price
PurchasePrice = (uint)(e.purchasedProduct.metadata.localizedPrice * 100),
// Pass in the receipt
ReceiptJson = googleReceipt.PayloadData.json,
// Pass in the signature
Signature = googleReceipt.PayloadData.signature
}, result => Debug.Log("Validation successful!"),
error => Debug.Log("Validation failed: " + error.GenerateErrorReport())
);
return PurchaseProcessingResult.Complete;
}
// This is invoked manually to initiate purchase
void BuyProductID(string productId) {
// If IAP service has not been initialized, fail hard
if (!IsInitialized) throw new Exception("IAP Service is not initialized!");
// Pass in the product id to initiate purchase
m_StoreController.InitiatePurchase(productId);
}
}
// The following classes are used to deserialize JSON results provided by IAP Service
// Please, note that JSON fields are case-sensitive and should remain fields to support Unity Deserialization via JsonUtilities
public class JsonData {
// JSON Fields, ! Case-sensitive
public string orderId;
public string packageName;
public string productId;
public long purchaseTime;
public int purchaseState;
public string purchaseToken;
}
public class PayloadData {
public JsonData JsonData;
// JSON Fields, ! Case-sensitive
public string signature;
public string json;
public static PayloadData FromJson(string json) {
var payload = JsonUtility.FromJson<PayloadData>(json);
payload.JsonData = JsonUtility.FromJson<JsonData>(payload.json);
return payload;
}
}
public class GooglePurchase {
public PayloadData PayloadData;
// JSON Fields, ! Case-sensitive
public string Store;
public string TransactionID;
public string Payload;
public static GooglePurchase FromJson(string json) {
var purchase = JsonUtility.FromJson<GooglePurchase>(json);
purchase.PayloadData = PayloadData.FromJson(purchase.Payload);
return purchase;
}
}
- 创建名为 “代码” 的新 “游戏” 对象。
- 向其添加
AndroidIAPExample
组件 (2 步过程)。 - 向它添加
AndroidIAPExample
组件。 - 确保保存场景。
最后,导航到 “编译设置”。
- 确认场景是否已添加到 “编译中的场景” 区域。
- 请确保已选择 Android 平台。
- 转到 “玩家设置” 区域。
- 指定 “程序包名称”。
注意
请务必提供 自己 的程序包名称,以免造成任何 PlayMarket 冲突。
最后,像往常一样生成应用,并确保 APK 安全。
我们目前不会对其进行测试。 首先,我们需要按如下面几节所述,配置 PlayMarket 和 PlayFab。
为 IAP 设置 PlayMarket 应用程序
本节介绍如何为 PlayMarket 应用程序启用 IAP 的具体信息。
注意
设置应用程序本身已超出本教程的范围。 我们已假设 有 一个应用程序,它配置为至少发布 Alpha 版本。
有用的注意事项:
- 要达到目的,需要先上传 APK。 请使用我们在上一节中构建的 APK。
- 当系统提示上传 APK 时,可以将它上传为 Alpha 或 Beta 应用程序从而启用 IAP 沙盒。
- 配置 Content Rating 将涉及有关如何在应用程序中启用 IAP 的问题。
- PlayMarket 不 允许发布者使用或测试 IAP,因此,请选取另一个 Google 帐户进行测试,并将其添加为 Alpha/Beta 版本的测试人员。
应用程序版本发布后:
- 从菜单中选择 In-app products。
- 如果提示提供 Merchant Account,请链接或创建一个。
- 选择 Add New Product 按钮。
- 在下面显示的屏幕上,选择 “托管产品”.。
- 为它提供描述性 产品 ID。
- 选择继续。
PlayMarket 要求填写 Title (1) 和 Description (2)。 不过,在我们的示例中,不太会用到它们。
我们将仅从 PlayFab 服务获取 Data Item 数据,并且只需 ID 匹配。
- 进一步滚动并选择 Add a price 按钮。
- 输入有效的价格(注意价格是如何针对每个国家/地区/区域独立转换的)。
- 选择 Apply 按钮。
- 最后,滚动回到屏幕顶部,将物品的状态更改为 “可用”。
尽管这样就结束了应用的配置,我们还是需要再进行一些优化。 首先,我们保存 Licensing Key(这样便于链接 PlayFab 与 PlayMarket)。
- 导航到菜单中的 Services & APIs。
- 然后,找到并保存 “密钥” 的 Base64 版本。
下一步是启用 IAP 测试。 尽管 Alpha 和 Beta 版本自动启用了沙盒,我们还是需要设置获得授权可以测试应用的帐户:
- 导航到“主页”。
- 在左侧菜单中查找并选择 “帐户详细信息”。
- 找到 License Testing 区域。
- 验证 测试账户 是否在列表中。
- 请确保 许可证测试响应 设置为 RESPOND_NORMALLY。
别忘记应用设置!
现在,集成的 Play Market 端的设置就完成了。
设置 PlayFab 游戏
最后一步是配置 PlayFab 作品,以反映我们的产品,并与 Google Billing API 集成。
- 选择 “附加内容”。
- 然后选择 Google 加载项。
- 填入 程序包 ID。
- 填写在上一节中获取的 Google 应用许可证密钥。
- 通过选择 Install Google 按钮提交更改。
下一步是在 PlayFab 中反映我们的 Golden Sword 物品:
- 选择 Economy。
- 确认选择了 Catalogs 子选项卡。
- 选择 “新目录”。
- 提供 “目录版本” 名称。
- 选择 “保存目录”。
如果目录没有物品,则会将其 自动 删除。 这就是为什么所有新目录都带一个名为 One 的预添加物品。
我们可以随时 创建新 物品,但为了清楚起见,我们修改现有的 One 物品。
- 选择该项目来编辑 项目 ID,使之与 PlayMarket 中的 ID 相匹配。
- 现在,编辑“显示名称”,并添加 “说明”。
注意
请注意,这些数据与 Play Market Item Title 和 Description无关 - 是完全 独立的。
- 为 项目 指定 价格。
- 选择 “保存项目“ 以提交更改。
注意
在本教程中,IAP 主要指实际货币购买。 这就是为什么我们会使用 RM - 特殊的实际货币。 PlayFab 金额是以美分为单位确定的。
- 在 Item ID 列表中观察物品。
PlayFab 作品的设置到此结束。
测试
为了进行测试,请下载使用 Alpha/Beta 版本的应用。
- 请务必使用测试帐户和真实 Android 设备。
- 启动应用后,应该看到 IAP 初始化,以及一个表示物品的 按钮。
- 选择该按钮。
将启动 IAP 购买。 按照 Google Play 说明操作直至购买成功。
最后,在 PlayFab “游戏管理器” 仪表板中导航到作品,找到 “新事件”。
这意味着已成功将购买提供、验证和传送到 PlayFab 生态系统。
现在,已成功将 UnityIAP 和 Android Billing API 集成到 PlayFab 应用程序中。