经济系统接入指南
经济系统接入指南
1. 基本概念
用于构建和管理游戏中的经济系统,包含以下三大模块:
- 资源:经济系统的资源分为 物品、虚拟货币 和单独存在的真实货币 “人民币” 三类。其中,可自行创建资源为:
- 物品:指玩家可以获得、携带和使用的虚拟对象。物品可以是武器、装备、道具、药品、任务物品等。
- 虚拟货币:指游戏中的一种交易媒介,用于购买物品、服务或其他游戏内的内容。虚拟货币通常是游戏内部的一种计数单位,虚拟货币可以是钻石、金币等。
- 商品:指的是玩家可以购买或交易的物品或者虚拟货币。这些物品可以是装备、道具、消耗品、宠物、坐骑等。游戏中的货币也可以作为商品出售,例如玩家可以使用真实货币购买虚拟货币,然后用虚拟货币购买游戏物品。商品模块主要功能为:
- 商品目录:可以按照不同的分类进行组织,例如按物品类型分类成不同的商店,以便玩家更方便地浏览和筛选所需的商品。
- 商品发布:商品创建后,需要发布才可以被正式对玩家可见和可使用。
- 商品下架:发布后的商品,若需要停止销售,可以下架该商品,下架的商品意味着玩家将无法再通过正常途径获取或购买该商品。
- 交易:玩家可以使用不同资源购买游戏内的商品。
2. 功能配置与管理
创建资源
创建物品
在 UOS Passport 服务 「经济系统 -> 资源 -> 物品」 处点击 「立即创建」,创建一个名为“小刀”的物品:
配置说明:
- 唯一标识(SlugName):物品唯一标识,同一个UOS APP下可以唯一定位一个物品,仅支持字母、数字、-、_的组合
- 名称:物品的显示名称
- 类别:物品所属的类别,例如武器、装备、道具、药品等,默认为"default"
- 数量上限:物品在角色背包内的数量上限,默认值为0,代表无上限
- 物品可堆叠:物品在角色背包内是否可堆叠,默认值为true,代表物品在放入背包的时候按slug合并
- 自定义属性:自定义物品属性
创建虚拟货币
切换至 「虚拟货币」 栏并点击 「立即创建」,按照同样的方式,创建一个名为 “金币” 的虚拟货币:
配置说明:
- 唯一标识(SlugName):虚拟货币唯一标识,同一个UOS APP下可以唯一定位一个虚拟货币,仅支持字母、数字、-、_的组合。 默认存在唯一标识为“CNY”的“人民币”资源(不属于物品及虚拟货币,单位为分)
- 名称:虚拟货币的显示名称
- 类别:虚拟货币所属的类别,例如货币、宝石等,默认为"default"
- 数量上限:物品在角色背包内的虚拟货币的上限,默认值为0,代表无上限
- 虚拟货币可堆叠:虚拟货币在角色背包内是否可堆叠,默认值为true,代表虚拟货币在放入背包的时候按slug合并
- 自定义属性:自定义虚拟货币属性
物品是具体的游戏内对象或实体,而虚拟货币是游戏内的一种货币形式,用于购买和交易物品。
创建商品
点击 UOS Passport 服务 「经济系统 -> 商品」 进入商品页面,此时商品以及商品目录均为空,首先需要创建一个商品目录, 点击「立即创建目录」:
输入目录的唯一标识和目录名称,点击「创建」即可,自定义属性可根据实际需求添加:
配置说明:
- 唯一标识(SlugName):商品目录唯一标识,同一个UOS APP下可以唯一定位一个目录,仅支持字母、数字、-、_的组合
- 名称:商品目录显示名称
- 自定义属性:自定商品目录源属性
切换至新创建的目录下,点击「立即创建」可以在该商品目录下创建商品:
首先,输入商品的唯一标识和名称(以 小刀(商品) 为例),点击「下一步」:
配置说明:
- 唯一标识(SlugName):商品唯一标识ID,同一个UOS APP下可以唯一定位一个商品,仅支持字母、数字、-、_的组合
- 名称:商品的显示名称
- 目录:商品的所属目录
- 限购数量:商品限购数量,默认值为0,代表不限购
- 自定义属性:自定义商品属性
商品默认售卖时间从当前开始并一直有效,默认不开启商品重置周期,二者均可自行更改。
配置说明:
- 开始售卖时间:商品开始售卖的时间,默认为当前时间
- 结束售卖时间:商品结束售卖的时间,默认为空,即一直可售卖
- 自动重置周期:可选择“每日”、“每周”或“每月”限购,并指定具体的日期和时间
最后设置购买商品需要消耗的资源(或货币)及数量和可以获取到的资源及数量(注意:人民币不可与其它资源同时消耗。):
至此,我们创建一个归属于“示例目录”的“示例商品”,购买它需要消耗 人民币*1分,并可以获取物品 小刀*1,此时点击 「发布」 将商品“上架”,发布状态的商品才可以售卖:
对于商品,我们可以在创建或者编辑时配置商品的折扣活动信息。处于折扣时间内的商品购买时会按照折扣后价格进行交易。
配置说明:
- 折扣开始时间:商品折扣活动开始的时间,默认为当前时间
- 折扣结束时间:商品折扣活动结束的时间,为空,即一直可售卖
- 折扣比例(%):折扣价格占原价的比例,80%代表8折,即原价×80%
交易记录
点击 UOS Passport 服务 「经济系统 -> 交易记录」 进入交易记录页面,在客户端创建交易后可看到交易记录:
虚拟交易只保留最近6个月的历史数据,超出的将会定期清除。
交易详情说明:
- 类型:交易类型,分为现金交易和虚拟交易
- 角色ID:购买该商品的角色ID,必须是真实存在的角色ID
- 商品:所交易的商品
- 状态:交易状态,分为付款完成、交易完成和交易失败三个状态
- 交易时间:交易发生的时间
背包
点击 UOS Passport 服务 「经济系统 -> 背包」 进入背包页面,可以在此看到归属于角色的资源信息:
背包详情说明:
- ID:背包资源ID
- 角色ID:背包资源所属角色ID
- 资源名称:背包资源名称
- 资源类型:背包资源类型
- 数量:该资源的数量
玩家背包资源条目数默认上限为5000,其中可堆叠的同种资源仅计为一条,不可堆叠的每个独立资源均单独计数。如需更改上限,可以联系我们。
至此,您已了解经济系统资源、商品和交易的相关内容,可在此基础上根据游戏的实际需求进行更多样化的操作。
3. 在 Unity 中接入 SDK
在接入 SDK 前请确保完成 「功能配置」
在接入 SDK 前,请确保在代码中引入了 Passport 的命名空间:
using Unity.Passport.Runtime;接入用户登录模块
参考 「外部账号系统登录」 正确接入外部ID系统。 确保在调用 经济系统 相关方法之前 已完成外部ID系统的接入
- 参考 「Passport 登录」 正确接入 Passport Login,确保在调用 经济系统 相关方法之前 用户已经登录完成。
- 安装并初始化 Feature SDK
- 在 UOS Launcher 服务列表中,找到 Passport Feature, 点击 「Install SDK」,将服务 SDK 安装到当前项目中
- 初始化 Feature SDK
try { await PassportFeatureSDK.Initialize(); } catch (PassportException e) { Debug.Log($"failed to initialize sdk: {e.Message}"); throw; }
- 在 UOS Launcher 服务列表中,找到 Passport Feature, 点击 「Install SDK」,将服务 SDK 安装到当前项目中
1. 获取商品目录列表
Economy.ListCategoriesResponse categoryList = await PassportFeatureSDK.Economy.ListCategories();
// 将获取到的目录名称打印出来
foreach (var category in categoryList.Categories)
{
Debug.Log(category.DisplayName);
}
public class ListCategoriesResponse
{
// 符合搜索条件的商品目录总数
public uint Total;
// 结果起始位置
public uint Start;
// 结果获取的数量
public uint Count;
// 查询结果,Category列表
public List<Category> Categories;
}
public class Category
{
// 商品目录ID
public string Id;
// IdDomain ID
public string IdDomainID;
// app下商品目录的唯一标识
public string SlugName;
// 商品目录的名称
public string DisplayName;
// 自定义商品目录属性
public Dictionary<string, string> CustomData;
// 创建时间(UTC)
public Timestamp CreatedAt;
// 创建用户
public string CreatedBy;
// 最后修改时间(UTC)
public Timestamp ModifiedAt;
// 最后修改用户
public string ModifiedBy;
}2. 搜索商品/获取商品详情
// 查找目录唯一标识为“weapon”的商品(查找weapon目录下的所有商品)
Economy.SearchProductsSDKResponse productList = await PassportFeatureSDK.Economy.ListProducts("weapon");
// 将搜索到的商品名称打印出来
foreach (var product in productList.Products)
{
Debug.Log(product.DisplayName);
}
// 获取商品唯一标识为“knife”的商品详情
Economy.ExpandedProductSDK productInfo = await PassportFeatureSDK.Economy.GetProduct("knife");
public class SearchProductsSDKResponse
{
// 查询结果,Product详情
public ExpandedProductSDK Product;
}
public class SearchProductsSDKResponse
{
// 符合搜索条件的总数
public uint Total;
// 结果起始位置
public uint Start;
// 结果获取的数量
public uint Count;
// 查询结果
public List<ExpandedProductSDK> Products;
}
public class ExpandedProductSDK
{
// IdDomain ID
public string IdDomainID;
// 商品slug
public string SlugName;
// 商品名称
public string DisplayName;
// 商品目录
public Category Category;
// 购买该商品需要消耗的物品或者货币详情
public List<ProductEntityDetail> Costs;
// 购买该商品可以获得的物品详情
public List<ProductEntityDetail> Rewards;
// 商品开始售卖的时间
public Timestamp StartTime;
// 商品结束售卖的时间
public Timestamp EndTime;
// 商品限购周期类型(EveryDay/EveryWeek/EveryMonth),例如商品每周限购2次
public Economy.LimitPeriodType LimitPeriodType;
// 商品限购周期的具体日子,如果周期类型是EveryWeek,则可以配置0-6(Sun-Sat);如果周期类型是EveryMonth,则可以配置日期(1-31)
public string LimitPeriodDay;
// 商品限制周期的具体时间,可配置成0-23
public string LimitPeriodHour;
// 商品限购数量
public uint LimitCount;
// 如有购买限制, 返回已消耗的购买次数
public uint ConsumeCount;
// 当前是否能购买
public bool Purchasable;
// 商品自定义属性
public Dictionary<string, string> CustomData;
// 创建时间(UTC)
public Timestamp CreatedAt;
// 创建用户
public string CreatedBy;
// 最后修改时间(UTC)
public Timestamp ModifiedAt;
// 最后修改用户
public string ModifiedBy;
// 目录下的展示顺序
public uint Sequence;
// 当前折扣信息
public List<Discount> Discounts;
}
// 关联的资源Resource的相关信息
public class ProductEntityDetail
{
// IdDomain ID
public string IdDomainID;
// 资源类型
public Economy.ResourceType ResourceType;
// 资源唯一标识slug
public string SlugName;
// 资源名称
public string DisplayName;
// 资源数量上限
public uint MaxValue;
// 资源自定义属性
public Dictionary<string, string> CustomData;
// 资源数量
public uint Quantity;
// 资源类别/命名空间
public string Namespace;
// 原价,打折前资源数量,仅在商品有折扣时会显示
public uint OriginalQuantity;
}
public class Discounts
{
// 打折开始时间
public Timestamp StartTime;
// 打折结束时间,空则表示永久
public Timestamp EndTime;
// 折扣后购买该商品需要消耗的资源或者货币
public List<ProductEntity> Costs;
// 折扣自定义属性,包括折扣比例
public Dictionary<string, string> Properties;
}
public class ProductEntity
{
// 资源唯一标识slug
public string ResourceSlug;
// 资源数量
public uint Quantity;
}3. 搜索角色背包内资源
// 搜索角色背包内资源
Economy.GetPersonaInventoryResponse personaInventories = await PassportFeatureSDK.Economy.SearchPersonaInventory();
// 将获取到的角色背包内的资源名称打印出来
foreach (var inventory in personaInventories.Inventory)
{
Debug.Log(inventory.Resource.DisplayName);
}
public class GetPersonaInventoryResponse
{
// 符合搜索条件的总数
public uint Total;
// 结果起始位置
public uint Start;
// 结果获取的数量
public uint Count;
// 查询结果
public List<ExpandedInventoryItemSDK> Inventory;
}
public class ExpandedInventoryItemSDK
{
// 背包资源ID
public string InventoryItemId;
// 数量
public uint Quantity;
// 背包资源的类别/命名空间
public string Namespace;
// 资源详情
public ResourceInfo Resource;
// 背包资源的自定义数据
public Dictionary<string, string> CustomData;
}
public class ResourceInfo
{
// app下资源的唯一标识
public string ResourceSlug;
// 资源类型(Item/VirtualCurrency)
public Economy.ResourceType ResourceType;
// 资源名称
public string DisplayName;
// 资源类别/命名空间
public string Namespace;
// 资源可以存放在背包内的最大值
public uint MaxValue;
// 资源的自定义属性
public Dictionary<string, string> CustomData;
}4. 获取角色背包内所有资源
using Unity.Passport.Runtime.Model;
// 获取角色背包内所有资源
ListPersonaInventoryResponse personaInventories = await PassportFeatureSDK.Economy.ListPersonaInventory();
// 将获取到的角色背包内的资源名称打印出来
foreach (var inventory in personaInventories)
{
Debug.Log(inventory.Resource.DisplayName);
}
public class ListPersonaInventoryResponse
{
// 背包资源数量
public uint Total;
// 背包资源列表
public List<ExpandedInventoryItemSDK> Inventory;
}
public class ExpandedInventoryItemSDK
{
// 背包资源ID
public string InventoryItemId;
// 数量
public uint Quantity;
// 背包资源的类别/命名空间
public string Namespace;
// 资源详情
public ResourceInfo Resource;
// 背包资源的自定义数据
public Dictionary<string, string> CustomData;
}
public class ResourceInfo
{
// app下资源的唯一标识
public string ResourceSlug;
// 资源类型(Item/VirtualCurrency)
public Economy.ResourceType ResourceType;
// 资源名称
public string DisplayName;
// 资源类别/命名空间
public string Namespace;
// 资源可以存放在背包内的最大值
public uint MaxValue;
// 资源的自定义属性
public Dictionary<string, string> CustomData;
}5. 虚拟购买
// 虚拟购买一个唯一标识为“knife”的商品
Economy.VirtualPurchaseResponse virtualPurchaseRes = await PassportFeatureSDK.Economy.VirtualPurchaseAndGetInventory("knife", 1);
public class VirtualPurchaseResponse
{
// 此次购买的交易信息
public Transaction transaction;
// 更新后影响到的玩家背包相关资源信息
public List<InventoryItem> InventoryItems;
}
public class Transaction
{
// 交易流水号
public string TransactionUUID;
// IdDomain ID
public string IdDomainID;
// 角色ID
public string PersonaId;
// 用户ID
public string UserId;
// 交易类型(RealMoneyPurchase/VirtualPurchase)
public Economy.TransactionType TransactionType;
// 商品唯一标识slug
public string ProductSlug;
// 交易商品快照
public ExpandedProduct Product;
// 交易状态 (Charged/Fulfilled/FulfillFailed)
public Economy.TransactionStatus Status;
// 自定义属性
public Dictionary<string, string> CustomData;
// 支付时间
public Timestamp PaidAt;
// 交易完成时间
public Timestamp FulfilledAt;
// 交易商品个数
public uint Quantity;
}
public class InventoryItem
{
// 背包资源ID
public string InventoryItemId;
// app下资源的唯一标识
public string ResourceSlug;
// 资源在更改后的现有数量
public uint Quantity;
// 自定义属性
public Dictionary<string, string> CustomData;
}6. 消耗角色背包内资源
// 消耗玩家背包内背包资源ID为"123"的资源2个,"124"的资源1个,背包资源ID可以通过ListPersonaInventory/SearchPersonaInventory获取
Dictionary<string, uint> consume = new Dictionary<string, uint>();
consume["123"] = 1;
consume["124"] = 2;
Economy.ConsumeInventoryItemsResponse consumeInventoryItemsRes = await PassportFeatureSDK.Economy.ConsumeInventoryItems(consume);
public class ConsumeInventoryItemsResponse
{
public List<InventoryItem> InventoryItems;
}
public class InventoryItem
{
// 背包资源ID
public string InventoryItemId;
// app下资源的唯一标识
public string ResourceSlug;
// 资源在更改后的现有数量
public uint Quantity;
// 自定义属性
public Dictionary<string, string> CustomData;
}7. 增加角色背包内资源
需在app下Passport模块 - 经济系统 - 资源 页面开启「允许客户端增加背包资源」后才可访问此客户端SDK接口:
// 增加玩家背包内资源slug为"item1"的资源2个
List<Economy.DepositResource> depositResourcesRequest = new List<Economy.DepositResource>();
var customData = new Dictionary<string, string>
{
{ "Key1", "Value1" },
{ "Key2", "Value2" }
};
var depositResources = new Economy.DepositResource
{
ResourceSlug = "item1",
DepositQuantity = 2,
CustomData = {customData}
};
depositResourcesRequest.Add(depositResources);
Economy.DepositResourcesResponse depositResourcesResponse = await PassportFeatureSDK.Economy.DepositResources(depositResourcesRequest);
public class DepositResourcesResponse
{
public List<InventoryItem> InventoryItems;
}
public class InventoryItem
{
// 背包资源ID
public string InventoryItemId;
// app下资源的唯一标识
public string ResourceSlug;
// 资源在更改后的现有数量
public uint Quantity;
// 自定义属性
public Dictionary<string, string> CustomData;
}8. 更新背包资源自定义属性
// 可以通过SearchPersonaInventory/ListPersonaInventory的返回值中获得对应的需要修改的InventoryItemId
string itemId = "111";
Economy.UpdateInventoryItemResponseSDK response = await PassportFeatureSDK.Economy.UpdateInventoryItem(itemId, new Dictionary<string, string>
{
{"key1", "value1"}
});
public class UpdateInventoryItemResponseSDK
{
public InventoryItem inventoryItem;
}
public class InventoryItem
{
// 背包资源ID
public string InventoryItemId;
// app下资源的唯一标识
public string ResourceSlug;
// 资源在更改后的现有数量
public uint Quantity;
// 自定义属性
public Dictionary<string, string> CustomData;
}9. 获取资源列表
Economy.SearchResourcesSDKResponse res = await PassportFeatureSDK.Economy.SearchResources();
public class SearchResourcesSDKResponse
{
// 符合搜索条件的总数
public uint Total;
// 结果起始位置
public uint Start;
// 结果获取的数量
public uint Count;
// 查询结果
public List<Resource> Resources;
}10. 获取所有资源
Economy.ListAllResourcesResponse resAll = await PassportFeatureSDK.Economy.ListAllResources();
public class ListAllResourcesResponse
{
// 总数
public uint Total;
// 资源列表
public List<Resource> Resources;
}11. 查找交易记录
// 其中交易类型TransactionType(RealMoneyPurchase/VirtualPurchase)为必填字段
Economy.SearchTransactionsSDKResponse res = await PassportFeatureSDK.Economy.SearchTransactions(TransactionType.RealMoneyPurchase);
public class SearchTransactionsSDKResponse
{
// 符合搜索条件的总数
public uint Total;
// 结果起始位置
public uint Start;
// 结果获取的数量
public uint Count;
// 查询结果
public List<Transaction> Transactions;
}
public class Transaction
{
// 交易流水号
public string TransactionUUID;
// IdDomain ID
public string IdDomainID;
// 角色ID
public string PersonaId;
// 用户ID
public string UserId;
// 交易类型(RealMoneyPurchase/VirtualPurchase)
public Economy.TransactionType TransactionType;
// 商品唯一标识slug
public string ProductSlug;
// 交易商品快照
public ExpandedProduct Product;
// 交易状态 (Charged/Fulfilled/FulfillFailed)
public Economy.TransactionStatus Status;
// 自定义属性
public Dictionary<string, string> CustomData;
// 支付时间
public Timestamp PaidAt;
// 交易完成时间
public Timestamp FulfilledAt;
// 交易商品个数
public uint Quantity;
// 线上支付商户订单号
public string OutTradeNo;
// 线上支付方式
public string PaymentMethod;
// 线上支付状态
public string TradeState;
// 创建时间
public Timestamp CreatedAt;
// 订单的支付链接超时时间
public Timestamp ExpiredAt;
// 订单支付总金额,单位为分
public uint TotalAmount;
}4. 附录
核心类和接口
namespace Unity.Passport.Runtime
{
public partial class PassportFeatureSDK
{
public class Economy
{
// 获取商品目录列表
// displayName: 商品目录名称,可模糊查询
// start: 查询起始位置 (按照创建时间降序排序, 最后创建的在前),默认为0
// count: 查询获取的数量,默认为10,最大为50
public static async Task<ListCategoriesResponse> ListCategories(string displayName = null, uint start = 0, uint count = 10){}
// 获取商品列表
// categorySlug: 商品目录slug
// start: 查询起始位置 (按照目录slug_name、目录下的展示顺序升序、创建时间降序排序),默认为0
// count: 查询获取的数量,默认为10,最大为50
public static async Task<SearchProductsSDKResponse> ListProducts(string categorySlug = null, uint start = 0, uint count = 10){}
// 获取商品详情
// slugName: app下商品的唯一标识
public static async Task<ExpandedProductSDK> GetProduct(string slugName){}
// 搜索角色背包内资源
// start: 查询起始位置 (按照创建时间降序排序, 最后创建的在前),默认为0
// count: 查询获取的数量,默认为10,最大为50
// resourceNamespace: 背包内资源的类别/自定义命名空间
// resourceSlug: 需要查询的资源唯一标识slug
public static async Task<GetPersonaInventoryResponse> SearchPersonaInventory(uint start = 0, uint count = 10, string resourceNamespace = null, string resourceSlug = null)
// 获取角色背包内所有资源
// resourceNamespace: 背包内资源的类别/自定义命名空间
public static async Task<ListPersonaInventoryResponse> ListPersonaInventory(string resourceNamespace = null){}
// 虚拟购买
// productSlug: 商品的唯一标识slug
// quantity: 购买的商品的数量,不可为0
// customData: 自定义属性,大小默认限制为0.5KB
public static async Task<VirtualPurchaseResponse> VirtualPurchaseAndGetInventory(string productSlug, uint quantity, Dictionary<string, string> customData = null){}
// 消耗角色背包内资源
// inventoryItems: 需要消耗的背包资源ID及数量
public static async Task<ConsumeInventoryItemsResponse> ConsumeInventoryItems(Dictionary<string, uint> inventoryItems){}
// 增加角色背包内资源(需在app下Passport模块 - 经济系统 - 资源 页面开启「允许客户端增加背包资源」配置)
// depositResourcesRequest: 需要增加的资源唯一标识slug及数量列表
public static async Task<DepositResourcesResponse> DepositResources(List<DepositResource> depositResourcesRequest){}
// 更新背包资源自定义属性
// inventoryItemId: 背包资源ID
// customData: 自定义属性,不可为空,覆盖式全量更新,大小默认限制为0.5KB
public static async Task<UpdateInventoryItemResponseSDK> UpdateInventoryItem(string inventoryItemId, Dictionary<string, string> customData){}
// 获取资源列表
// start: 查询起始位置 (按照创建时间降序排序, 最后创建的在前),默认为0
// count: 查询获取的数量,默认为10,最大为50
// resourceType: 资源类型
// displayName: 资源展示名称
// resourceNamespace: 资源类别/命名空间
public static async Task<SearchResourcesSDKResponse> SearchResources(uint start = 0, uint count = 10, ResourceType? resourceType = null, string displayName = null, string resourceNamespace = null){}
// 获取所有资源
public static async Task<ListAllResourcesResponse> ListAllResources(){}
// 查找交易记录
// transactionType: 交易类型(RealMoneyPurchase/VirtualPurchase),必填字段
// productSlug: 商品slug
// productName: 商品名称,支持模糊匹配
// outTradeNo: 外部商户支付订单号
// startTime: 查询的起始时间
// endTime: 查询的结束时间
// start: 查询起始位置 (按照创建时间降序排序, 最后创建的在前),默认为0
// count: 查询获取的数量,默认为10,最大为50
// status: 交易状态
public static async Task<SearchTransactionsSDKResponse> SearchTransactions(TransactionType transactionType, string productSlug = null, string productName = null, string outTradeNo = null, Timestamp startTime = null, Timestamp endTime = null, uint start = 0, uint count = 10, TransactionStatus? status = null)
}
}
}服务端 API
客户端 API
错误码
参见 「Passport 错误码」 。