与其他 Unity 服务集成
要充分发挥 Cloud Code 的潜力,可以通过 JavaScript Service SDK 或 REST API 将其与其他 Unity Gaming Services(Unity 游戏服务)连接起来。
JavaScript SDK 更简单,并提供更一致的体验。但是,如果要使用的 UGS 服务还没有 Cloud Code JavaScript SDK,仍可以通过其 REST API 与其连接。
使用 UGS SDK
您可以将 Cloud Code JavaScript SDK 直接导入到 Cloud Code 脚本中。
要查看可用的服务集成,并查找每个服务的详细 SDK 文档和变更日志,请参阅 Cloud Code Services SDK 文档。
注意:其他 Unity 服务的方法的返回对象包含有关请求的其他信息,例如请求的标头或响应。要访问响应的有效负载,请引用对象的 data
属性。
如需查看 Cloud Code JavaScript SDK 及其文档的完整列表,请参阅下表:
服务 | SDK 文档 | SDK 变更日志 |
---|---|---|
Cloud Save | Cloud Save SDK | [Cloud Save SDK 变更日志] |
Economy | Economy SDK | [Economy SDK 变更日志] |
Remote Config | Remote Config SDK | [Remote Config SDK 变更日志] |
Vivox | Vivox SDK | [Vivox SDK 变更日志] |
Lobby | Lobby SDK | [Lobby SDK 变更日志] |
Leaderboards | Leaderboards SDK | [Leaderboards SDK 变更日志] |
Matchmaker | Matchmaker SDK | [Matchmaker SDK 变更日志] |
Multiplay | Multiplay SDK | [Multiplay SDK 变更日志] |
Friends | Friends SDK | [Friends SDK 变更日志] |
Player Names | Player Names SDK | [Player Names SDK 变更日志] |
Player Authentication | Player Authentication SDK | [Player Authentication SDK 变更日志] |
Cloud Code 脚本允许您通过组合不同的 UGS 服务来处理跨玩家数据。如需了解更多信息,请参阅关于如何处理跨玩家数据的文档。
使用 Cloud Save SDK
您可以使用 Cloud Save 服务将长期的玩家数据(如游戏进度)从游戏保存到云中,并可以随时随地跨设备访问玩家数据。这意味着,当玩家更换设备或重新安装游戏时,Cloud Save 可用于减少数据丢失。
读取和写入数据
您可以使用 Cloud Code 将数据保存到 Cloud Save 以及从中读取数据。
以下示例代码演示了如何使用玩家和自定义实体的不同访问修饰符来访问和修改 Cloud Save 数据:
JavaScript
const _ = require("lodash-4.17");
const { DataApi } = require("@unity-services/cloud-save-1.4");
module.exports = async ({ params, context, logger }) => {
const { projectId, playerId, accessToken } = context;
const cloudSaveApi = new DataApi(context);
try {
// Default item data, readable and writable by the player
await cloudSaveApi.setItem(projectId, playerId, {
key: "defaultData",
value: "value"
});
// Custom item - non-player entities
// Record any game data!
const customId = "gameLevelAttributes";
await cloudSaveApi.setCustomItem(projectId, customId, {
key: "levelDifficulty",
value: "easy"
});
// Protected data can be read by player, but not written to
await cloudSaveApi.setProtectedItem(projectId, playerId, {
key: "protectedItem",
value: "verySecretData"
});
// Private data that should only be accessed by server/service accounts
await cloudSaveApi.setPrivateCustomItem(projectId, customId, {
key: "levelTimerInMinutes",
value: 25,
});
const result = await cloudSaveApi.getItems(projectId, playerId);
logger.info(`Retrieved default items from Cloud Save: ${JSON.stringify(result.data)}`)
const protectedDataResult = await cloudSaveApi.getProtectedItems(projectId, playerId, ["protectedItem"]);
logger.info(`Retrieved protected item from Cloud Save: ${JSON.stringify(protectedDataResult.data)}`)
const customDataResult = await cloudSaveApi.getPrivateCustomItems(projectId, customId);
logger.info(`Retrieved private custom items from Cloud Save: ${JSON.stringify(customDataResult.data)}`)
} catch (err) {
logger.error("Error while calling out to Cloud Save", { "error.message": err.message });
throw err;
}
};
查询数据
您可以使用 Cloud Code 查询 Cloud Save 数据。
在使用以下示例之前,您需要在 Cloud Save 服务中创建查询。
在 Unity Cloud Dashboard 中导航到 Cloud Save 服务,然后使用以下参数创建查询:
- 对于键
health
,对玩家实体进行升序查询,使用默认访问类。 - 对于键
difficulty
,对自定义实体进行降序查询,使用私有访问类。 - 对于键
level
,对玩家实体进行升序查询,使用受保护访问类。
以下示例代码演示了如何使用玩家和自定义实体的不同访问修饰符来查询 Cloud Save 数据:
JavaScript
const _ = require("lodash-4.17");
const { DataApi } = require("@unity-services/cloud-save-1.4");
module.exports = async ({ params, context, logger }) => {
const { projectId, playerId } = context;
const cloudSaveApi = new DataApi(context);
try {
// --------------------------------------------------------------------------------
// Query player data with the key `health` to find players with health lower than 100
// Set data for player
const requestData = {
data: [{
key: "health",
value: 95,
},
{
key: "stamina",
value: 20,
}
]
};
await cloudSaveApi.setItemBatch(projectId, playerId, requestData);
// Query and return player stamina for players with health less than 100
const query = {
fields: [{
asc: true,
key: 'health',
op: 'LT',
value: 100,
}],
returnKeys: ["stamina"]
};
const queryRes = await cloudSaveApi.queryDefaultPlayerData(projectId, query);
logger.info(`Query results: ${JSON.stringify(queryRes.data)}`)
// --------------------------------------------------------------------------------
// Query private custom data to retrieve game levels with easy difficulty
// Record two levels with different difficulty levels
const casteLevelId = "castleLevel";
const anotherLevelId = "forestLevel";
await cloudSaveApi.setPrivateCustomItem(projectId, casteLevelId, {
key: "difficulty",
value: "easy"
});
await cloudSaveApi.setPrivateCustomItem(projectId, anotherLevelId, {
key: "difficulty",
value: "hard"
});
// Query levels with easy levels only
const privateQuery = {
fields: [{
asc: false,
key: 'difficulty',
op: 'EQ',
value: 'easy'
}],
};
const privateQueryRes = await cloudSaveApi.queryPrivateCustomData(projectId, privateQuery);
logger.info(`Private query results: ${JSON.stringify(privateQueryRes.data)}`)
// --------------------------------------------------------------------------------
// Query protected player data for players that are over level 5
// Set data for player
const protectedRequestData = {
data: [{
key: "level",
value: 15,
},
{
key: "experiencePoints",
value: 20,
}
]
};
await cloudSaveApi.setProtectedItemBatch(projectId, playerId, protectedRequestData);
// Query players over level 5, and return their health
const protectedPlayerQuery = {
fields: [{
asc: true,
key: 'level',
op: 'GT',
value: 5,
}],
returnKeys: ["experiencePoints", "level"]
};
const protectedPlayerRes = await cloudSaveApi.queryProtectedPlayerData(projectId, protectedPlayerQuery);
logger.info(`Protected player query results: ${JSON.stringify(protectedPlayerRes.data)}`)
// --------------------------------------------------------------------------------
} catch (err) {
logger.error("Error while calling out to Cloud Save", { "error.message": err.message });
throw err;
}
};
使用 Economy SDK
Economy 服务允许您使用以下资源在游戏中创建、管理和发布 Economy 系统:
- 货币:允许玩家在游戏中拥有一种或多种货币/面额的余额。
- 背包物品:表示在配置过程中创建的资源的定义。玩家可以拥有背包物品的实例(例如,相同的剑、盾牌、帽子的多个副本),与货币不同,您还可以为每个实例指定各自的属性。
- 购买:允许玩家使用其他游戏内货币和背包物品来购买游戏内货币和背包物品(虚拟购买),或通过数字店面用真实资金进行购买。
借助 Cloud Code,可以直接通过脚本处理货币、背包物品和购买。
Economy SDK 玩家背包
以下脚本将 SWORD
物品添加到玩家的背包中。
在使用以下示例之前,您需要在 Economy 服务中创建类型为 SWORD
的背包物品,并发布您的新配置。
**注意:**如果您发布了背包配置,但未找到背包物品,可能需要刷新 playerId
。或者,请更新脚本以使用配置分配哈希。
JavaScript
const { InventoryApi } = require("@unity-services/economy-2.4");
module.exports = async ({ params, context, logger }) => {
const { projectId, playerId, accessToken } = context;
const inventory = new InventoryApi({ accessToken });
const addInventoryRequest = { inventoryItemId : "SWORD" };
try {
await inventory.addInventoryItem({
addInventoryRequest,
playerId,
projectId
});
const result = await inventory.getPlayerInventory({ projectId, playerId });
return result.data;
} catch (err) {
logger.error("Error while retrieving inventory items", {"error.message": err.message});
throw err;
}
}
Economy SDK 玩家货币
以下脚本将设置玩家的货币余额,然后增加和减少余额。
在使用以下示例之前,您需要在 Economy 服务中拥有已发布的货币。
以下脚本使用了必需的字符串参数 currencyId
。
**注意:**如果您发布了货币配置,但未找到货币,可能需要刷新 playerId
。或者,请更新脚本以使用配置分配哈希。
JavaScript
const { CurrenciesApi } = require("@unity-services/economy-2.4");
module.exports = async ({ params, context, logger }) => {
const { projectId, playerId, accessToken } = context;
const { currencyId } = params;
const currencies = new CurrenciesApi({ accessToken });
try {
await currencies.setPlayerCurrencyBalance({ projectId, playerId, currencyId, currencyBalanceRequest: { balance: 100 } });
await currencies.decrementPlayerCurrencyBalance({ projectId, playerId, currencyId, currencyModifyBalanceRequest: { amount: 50 } });
await currencies.incrementPlayerCurrencyBalance({ projectId, playerId, currencyId, currencyModifyBalanceRequest: { amount: 10 } });
const result = await currencies.getPlayerCurrencies({ projectId, playerId });
return result.data;
} catch (err) {
logger.error("Error while updating currencies", {"error.message": err.message}, {"currencyId" : currencyId});
throw err;
}
}
// Uncomment the code below to enable the inline parameter definition
// - Requires Cloud Code JS dev environment setup with NodeJS (https://docs.unity3d.com/Packages/com.unity.services.cloudcode@latest/index.html?subfolder=/manual/Authoring/javascript_project.html)
//
// module.exports.params = {
// currencyId: { type: "String", required: true },
// };
使用 Economy SDK 虚拟购买
以下脚本将进行虚拟购买,然后从玩家的背包中删除物品。
在使用以下示例之前,您需要在 Economy 服务中创建虚拟购买,并发布您的新配置。
此脚本接受 purchaseId
和 currencyId
作为参数。currencyId
是玩家完成购买所需的货币 ID。
**注意:**如果您发布了虚拟购买配置,但未找到购买,可能需要刷新 playerId
。或者,请更新脚本以使用配置分配哈希。
JavaScript
const { CurrenciesApi, PurchasesApi, InventoryApi } = require("@unity-services/economy-2.4");
module.exports = async ({ params, context, logger }) => {
const { projectId, playerId, accessToken } = context;
const { purchaseId, currencyId } = params;
const inventory = new InventoryApi({ accessToken });
const purchases = new PurchasesApi({ accessToken });
const currencies = new CurrenciesApi({ accessToken });
try {
// Give a player some currency to complete the purchase
await currencies.setPlayerCurrencyBalance({ projectId, playerId, currencyId, currencyBalanceRequest: { balance: 1 } });
const makeVirtualPurchaseResult = await purchases.makeVirtualPurchase({ projectId, playerId, playerPurchaseVirtualRequest: { id: purchaseId } });
const getPlayerInventoryResult = await inventory.getPlayerInventory({ projectId, playerId });
await inventory.deleteInventoryItem({ projectId, playerId, playersInventoryItemId: makeVirtualPurchaseResult.data.rewards.inventory[0].playersInventoryItemIds[0], inventoryDeleteRequest: {} });
return {
makeVirtualPurchaseResult: makeVirtualPurchaseResult.data,
getPlayerInventoryResult: getPlayerInventoryResult.data
};
} catch (err) {
logger.error("Error while calling out to Economy", {"error.message": err.message});
throw err;
}
}
// Uncomment the code below to enable the inline parameter definition
// - Requires Cloud Code JS dev environment setup with NodeJS (https://docs.unity3d.com/Packages/com.unity.services.cloudcode@latest/index.html?subfolder=/manual/Authoring/javascript_project.html)
//
// module.exports.params = {
// currencyId: { type: "String", required: true },
// purchaseId: { type: "String", required: true },
// };
Economy 与 Game Overrides 结合使用:
如需了解更多信息,请参阅 Economy Game Overrides 与 Cloud Code 文档。
使用 Remote Config SDK
Remote Config 属于一种云服务;借助此服务,无需部署应用程序新版本,即可调整游戏设计。Remote Config 由一组命名空间的键/值参数组成,您可以选择定义一组覆盖或添加到这些参数的值。
您可以通过 Cloud Code 脚本修改 Remote Config 设置。
以下示例演示了如何为玩家分配 Remote Config 设置。
JavaScript
const { SettingsApi } = require("@unity-services/remote-config-1.1");
module.exports = async ({ context, logger }) => {
const { projectId, playerId, accessToken } = context;
const remoteConfig = new SettingsApi({ accessToken });
try {
const result = await remoteConfig.assignSettings({ projectId, "userId": playerId, "attributes": { "unity": {}, "app": {}, "user": {} } });
return result.data;
} catch (err) {
logger.error("Error while assigning settings", {"error.message": err.message});
throw err;
}
}
使用 Vivox SDK
Vivox 为游戏提供基于语音和文本的通信服务。Cloud Code 允许您生成 Vivox 令牌,让游戏客户端可以安全地访问 Vivox 服务操作。
以下示例演示了如何使用 Cloud Code 脚本生成 Vivox 令牌。
JavaScript
const { TokenApi } = require("@unity-services/vivox-0.1");
module.exports = (async e => {
const o = {
iss: "gary.charlton.oogabooga",
exp: 1559359105,
vxa: "join",
vxi: 1,
f: "sip:.username.@domain.vivox.com",
t: "sip:confctl-g-channelname@domain.vivox.com"
};
try {
return TokenApi.generateVivoxToken("TEST_KEY", o)
} catch (err) {
logger.error("Error while generating vivox token", {"error.message": err.message});
throw err;
}
});
使用 Lobby SDK
Lobby 允许玩家在游戏会话之前或期间进行连接。玩家使用简单的游戏属性创建公共大厅,然后其他玩家可以搜索、发现和加入大厅。“仅限受邀者加入”的大厅还允许玩家仅为选定的参与者创建私有空间。
以下示例说明了如何使用 Cloud Code 脚本创建和更新大厅以及对大厅进行心跳检测。
此示例使用服务令牌创建一个没有所有者的空大厅。然后,接受一个可用于将玩家添加到大厅的 playerId
参数,以及一个可用于标识拥有大厅的服务的 serviceId
参数。
虽然您可以使用 Cloud Code 以玩家身份发出 Lobby 请求,但默认使用服务身份验证。您可以在所需的任何值下方给出此示例使用的 serviceId
;这样会为 Lobby 服务提供一个唯一标识符,以便区分您的服务拥有的大厅。
以下示例将创建一个私有大厅,更新大厅的名称,以模拟玩家的身份加入大厅,以及获取大厅。
JavaScript
const { LobbyApi } = require("@unity-services/lobby-1.2");
module.exports = async ({ params, context, logger }) => {
const lobbyApi = new LobbyApi(context);
const { serviceId, playerId } = params;
try {
// Create a private lobby without any initial players.
const { data: lobby } = await lobbyApi.createLobby(
serviceId,
null, {
name: "my new lobby name",
maxPlayers: 4,
isPrivate: true,
}
);
logger.info(`Created lobby: ${JSON.stringify(lobby)}`);
// Heartbeat the lobby to keep it active.
await lobbyApi.heartbeat(lobby.id, serviceId);
// Update the lobby's name and get the updated lobby.
const { data: updateResponse } = await lobbyApi.updateLobby(
lobby.id,
serviceId,
null, { name: "my updated lobby name" }
);
logger.info(`Lobby updated: ${JSON.stringify(updateResponse)}`);
// Join the lobby as an impersonated player.
const { data: joinResponse } = await lobbyApi.joinLobbyById(lobby.id, serviceId, playerId);
logger.info(`Player joined: ${JSON.stringify(joinResponse)}`);
// Get and return the lobby.
const getResponse = await lobbyApi.getLobby(lobby.id, serviceId);
return getResponse.data;
} catch (err) {
logger.error("Error while calling out to Lobby", {"error.message": err.message});
throw err;
}
};
// Uncomment the code below to enable the inline parameter definition
// - Requires Cloud Code JS dev environment setup with NodeJS (https://docs.unity3d.com/Packages/com.unity.services.cloudcode@latest/index.html?subfolder=/manual/Authoring/javascript_project.html)
//
// module.exports.params = {
// serviceId: { type: "String", required: true },
// playerId: { type: "String", required: true },
// };
使用 Leaderboards SDK
Leaderboards 允许您为游戏创建和管理排行榜。
在使用以下示例之前,您需要在 Leaderboards 服务中创建排行榜。
以下示例展示了如何向排行榜添加分数,以及使用 Cloud Code 脚本获取排行榜分数。此脚本使用了 leaderboardId
参数。
JavaScript
const { LeaderboardsApi } = require("@unity-services/leaderboards-1.1");
module.exports = async ({ params, context, logger }) => {
const leaderboardsApi = new LeaderboardsApi({accessToken : context.accessToken});
let result;
try {
await leaderboardsApi.addLeaderboardPlayerScore(context.projectId, params.leaderboardId, context.playerId, { score : 20});
const result = await leaderboardsApi.getLeaderboardScores(context.projectId, params.leaderboardId);
return result.data;
} catch (err) {
logger.error("Error while calling out to Leaderboards", {"error.message": err.message});
throw err;
}
};
// Uncomment the code below to enable the inline parameter definition
// - Requires Cloud Code JS dev environment setup with NodeJS (https://docs.unity3d.com/Packages/com.unity.services.cloudcode@latest/index.html?subfolder=/manual/Authoring/javascript_project.html)
//
// module.exports.params = {
// leaderboardId: { type: "String", required: true },
// };
使用 Friends SDK
Friends 可用于创建和管理游戏玩家之间的关系。您可以使用 Friends 来管理游戏中的关系、发送和接受好友请求、控制在线状态以及在玩家之间发送消息。下面的示例演示了如何将 Friends 和 Cloud Code 结合使用以发送好友请求、获取好友、获取通知令牌、设置和接收在线状态以及发送消息。
要在 Unity Cloud Dashboard 中测试此示例,请在两个不同的浏览器标签页中以两个不同玩家的身份进行身份验证,然后运行脚本。在一个标签页中使用第一个玩家的 ID 运行脚本,然后在另一个标签页中使用第二个玩家的 ID 运行脚本。
**注意:**如果请求的用户尚未接受好友请求,下面的某些调用将会失败。
JavaScript
const { RelationshipsApi, PresenceApi, MessagingApi, NotificationsApi } = require("@unity-services/friends-1.0");
module.exports = async ({ params, context, logger }) => {
// Send friend requests and retrieve friends
const relationshipsApi = new RelationshipsApi({ accessToken: context.accessToken });
try {
const payload = {
type: "FRIEND_REQUEST",
members: [{ id: params.playerId }]
};
const createRes = await relationshipsApi.createRelationship(true, true, payload);
logger.info(`New relationship initiated: ${JSON.stringify(createRes.data)}`)
} catch (err) {
// The call will fail if the friend request has already been sent
logger.error("Error while sending a friend request", { "error.message": err.message });
}
try {
const list = await relationshipsApi.getRelationships(10, 0, true, true, ["FRIEND_REQUEST", "FRIEND"]);
logger.info(`Retrieved relationships: ${JSON.stringify(list.data)}`)
} catch (err) {
logger.error("Error while retrieving relationships", { "error.message": err.message });
}
// -------------------------------------------------------------------------
// Retrieve a notification token
const notificationsApi = new NotificationsApi({ accessToken: context.accessToken });
try {
const auth = await notificationsApi.getNotificationsAuth();
logger.info(`Retrieved notifications subscription token: ${JSON.stringify(auth.data)}`)
} catch (err) {
logger.error("Error while retrieving notification subscription token", { "error.message": err.message });
}
// -------------------------------------------------------------------------
// Set and receive presence
const presenceApi = new PresenceApi({ accessToken: context.accessToken });
try {
const newStatus = {
activity: { // define any payload structure
location: "In Menu",
},
availability: "ONLINE",
};
const setPresenceStatus = await presenceApi.setPresence(newStatus);
logger.info(`Changed player presence status for player ${context.playerId}: ${JSON.stringify(setPresenceStatus.data)}`)
} catch (err) {
logger.error("Error while setting a presence status", { "error.message": err.message });
}
try {
// This call will fail if the requested user has not accepted the friend request!
const presenceStatus = await presenceApi.getPresence(params.playerId);
logger.info(`Retrieved player presence for player ${params.playerId}: ${JSON.stringify(presenceStatus.data)}`)
} catch (err) {
logger.error("Error while retrieving a presence status", { "error.message": err.message });
}
// -------------------------------------------------------------------------
// Send messages
const messagingApi = new MessagingApi({ accessToken: context.accessToken });
try {
const messagePayload = {
id: params.playerId,
message: { // define any payload structure
hello: "world",
custom: "sending you a message!"
}
}
// This call will fail if the requested user has not accepted the friend request!
const messageResponse = await messagingApi.message(messagePayload);
logger.info(`Sent a message to player ${params.playerId}`)
} catch (err) {
logger.error("Error while sending a message", { "error.message": err.message });
throw err;
}
}
// Uncomment the code below to enable the inline parameter definition
// - Requires Cloud Code JS dev environment setup with NodeJS (https://docs.unity3d.com/Packages/com.unity.services.cloudcode@latest/index.html?subfolder=/manual/Authoring/javascript_project.html)
//
// module.exports.params = {
// playerId: { type: "String", required: true },
// };
使用 Player Names SDK
Player Names 可用于获取玩家在游戏中的玩家名称。如果玩家尚未设置玩家名称,您可以使用此功能为玩家生成新的玩家名称,或更新现有的玩家名称。
JavaScript
const { PlayerNamesApi } = require("@unity-services/player-names-1.0");
module.exports = async ({ params, context, logger }) => {
const playerNamesApi = new PlayerNamesApi({ accessToken: context.accessToken });
try {
const playerData = await playerNamesApi.getName(context.playerId);
logger.info(`The received player data is ${JSON.stringify(playerData.data)}`);
const updatedData = await playerNamesApi.updateName(context.playerId, {name: "newCustomName"});
logger.info(`The updated player data is ${JSON.stringify(updatedData.data)})`);
} catch (err) {
logger.error("Error while calling out to Player Names", { "error.message": err.message });
throw err;
}
};
通过 REST API 与 UGS 进行连接
您还可以通过服务的 REST API 与服务进行连接。如果要使用一个尚无 JavaScript SDK 的服务,这将非常有用。
身份验证
根据您的用例,您可以使用 accessToken
或 serviceToken
对 API 调用进行身份验证。如果使用 serviceToken
对 UGS Client API 进行身份验证,请确保要调用的服务支持服务令牌。如需查看支持服务令牌的服务和用例的列表,请参阅服务令牌和访问令牌支持文档。
**注意:**如果要调用的服务提供了 Cloud Code JavaScript SDK,则可以改用该 SDK,这样就不必直接调用服务 API。如需了解更多信息,请参阅 Cloud Code Services SDK。