商店的基本操作
游戏内的商店通常会涉及 3 个基本操作:
本文档将阐释与每个操作相关的示例代码。 这源自 InGameStore 示例,该示例会不断更新以反映最佳做法。
准备调用 XStore API
所有 XStore
API 均通过使用 XStoreCreateContext 创建的 XStoreContextHandle 进行运行。
通过此上下文,可在主机上的指定用户和电脑上的默认可用用户上下文中执行商店操作。 在主机上,挂起或快速恢复事件后上下文无效。 为了安全地处理这些情况,建议关闭 XStoreContextHandle 并在游戏从暂停状态恢复时重新创建它。
1. 确定可购买的产品
游戏通常会提供购买的是附加内容。 以下代码演示了用于让游戏了解哪些产品可用的基本 XStoreQueryAssociatedProductsAsync API 调用。
此查询将自动返回与游戏相关联的可购买附加内容。 只要游戏在合作伙伴中心的产品关系设置部分中设置了与相应产品的“可销售”关系,则也可以在此调用中返回与同一发行商关联的不相关产品(即用同一合作伙伴中心帐户配置的产品)。
bool CALLBACK ProductEnumerationCallback(const XStoreProduct* product, void* context)
{
// Handle adding the product to the game
printf("%s %s %u\n", product->title, product->storeId, product->productKind);
return true;
}
void QueryCatalog()
{
auto async = new XAsyncBlock{};
async->queue = m_asyncQueue;
async->callback = [](XAsyncBlock* async)
{
XStoreProductQueryHandle queryHandle = nullptr;
HRESULT hr = XStoreQueryAssociatedProductsResult(async, &queryHandle);
if (SUCCEEDED(hr))
{
hr = XStoreEnumerateProductsQuery(queryHandle, async->context, ProductEnumerationCallback);
if (SUCCEEDED(hr))
{
printf("Enumeration complete\n");
}
XStoreCloseProductsQueryHandle(queryHandle);
delete async;
}
};
XStoreProductKind typeFilter =
XStoreProductKind::Consumable |
XStoreProductKind::Durable |
XStoreProductKind::Game;
HRESULT hr = XStoreQueryAssociatedProductsAsync(
m_xStoreContext,
typeFilter,
UINT8_MAX, // placeholder maximum, see Paging
async)
if (FAILED(hr))
{
delete async;
}
}
注意事项
- 只有可购买的产品才会返回
XStoreQueryAssociatedProductsAsync
;只通过捆绑销售的产品或其他不能独立购买的产品将不会被返回。 对于这些用法,请参阅下面描述的XStoreQueryProductsAsync
。 - 不会提前知晓将返回的产品数量,因此必须累计计数。
分页
现在,处理分页是可选的。 选择要传递的最大值,该值保证大于目录的生存期预期大小。 对于大型目录或希望对结果处理进行分段以显示进度,可以实现检测和处理分页。 有关更多详细信息 ,请参阅 XStoreQueryAssociatedProductsAsync 。
其他选项
如果知道商店 ID 或需要其他 actionFilters
,则 XStoreQueryProductsAsync 可用于查询特定产品。
“操作”是适用于产品的使用方案,其中包括 购买、 许可证、 礼品和 兑换等谓词。
XStoreQueryAssociatedProductsForStoreIdAsync 可用于查询其他游戏的关联产品。 例如,这对于交叉销售不同游戏的加载项非常有用。
XStoreQueryProductForCurrentGameAsync 仅用于查询当前正在运行的游戏的产品。
XStoreShowAssociatedProductsUIAsync 将使用户转换到 Microsoft Store 应用,以查看关联产品的视图,该视图可以按产品种类进行筛选。 这可以替代必须枚举可用产品以在游戏内界面中呈现。
2. 评估所拥有的产品
这将涉及与上述基本相同的代码,并具有以下替换项:
- XStoreQueryAssociatedProductsAsync → XStoreQueryEntitledProductsAsync
- XStoreQueryAssociatedProductsResult → XStoreQueryEntitledProductsResult
借助这些 API,结果将包含调用用户帐户特别授权的产品。 “授权”可以表示直接拥有,也可以表示通过拥有父级捆绑或订阅进行实现。
请注意,这也可以由 XStoreQueryAssociatedProductsAsync (和相关函数的结果确定) ,因为 XStoreProduct 包含一个 isInUserCollection
在授权时设置为 true 的字段。
易耗品所有权
XStoreProduct.skus[i].collectionData.quantity
中注明了易耗品数量。
通常,易耗的产品仅有一个 SKU。
也可以使用 XStoreQueryConsumableBalanceRemainingAsync 单独查询数量,但建议不要分别查询大量易耗品,因为每次查询都会引起服务调用。
此外,为了维护基于易耗品的生态系统的完整性,强烈建议对易耗品进行服务验证和兑换。 有关详细信息,请参阅 基于易耗品的生态系统。
耐用品所有权
仅检查帐户是否拥有该产品,不足以确定是否应授权其在游戏内使用该产品。 耐用品必须遵守游戏的产品共享模型中所述的内容共享策略。
XStoreAcquireLicenseForPackageAsync 用于有包装的耐用品,以根据内容共享的条款确定是否可向其发放许可证。
XStoreAcquireLicenseForDurablesAsync 用于没有包装的耐用品以执行相同操作。
XStoreQueryAddOnLicensesAsync 可用于数字许可的游戏,以返回可许可耐用品(没有包装)列表。
有关详细信息,请参阅管理和许可可下载内容和如何使用没有包装的耐用品。
3. 购买符合条件的产品
只需将 Store ID 传递到 XStoreShowPurchaseUIAsync API 中,即可显示可购买产品的购买流:
void MakePurchase(const char* storeId)
{
auto async = new XAsyncBlock{};
async->context = &storeId;
async->queue = m_asyncQueue;
async->callback = [](XAsyncBlock *async)
{
const char* = reinterpret_cast<const char*>(async->context);
HRESULT hr = XStoreShowPurchaseUIResult(async);
if (SUCCEEDED(hr))
{
printf("Purchase succeeded (%s)\n", storeId);
// Refresh ownership and update game
}
else
{
printf("Purchase failed (%s) 0x%x\n", storeId, hr);
if (hr == E_GAMESTORE_ALREADY_PURCHASED)
{
printf("Already own this\n");
}
}
delete async;
};
HRESULT hr = XStoreShowPurchaseUIAsync(
m_xStoreContext,
storeId,
nullptr, // Can be used to override the title bar text
nullptr, // Can be used to provide extra details to purchase
async);
if (FAILED(hr))
{
delete async;
printf("Error calling XStoreShowPurchaseUIAsync : 0x%x\n", hr);
return;
}
}
除了在异步回调中处理潜在购买的结果外,还可以通过明确切换到 Microsoft Store,在游戏之外进行购买。 还可在 Xbox.com、电脑、移动应用或其他专营店进行购买。 建议在游戏中有一个可按需可靠地刷新产品所有权的位置,例如过渡到游戏内商店或设置中的某个位置,而不仅仅是作为初始登录流的一部分。