与其他 Unity 服务集成

为了充分发挥云代码的潜力,您可以通过 JavaScript 服务 SDK 或 REST API 将其与其他 Unity 游戏服务连接。

JavaScript SDK 更简单,并提供更一致的体验。但是,如果您要使用的 UGS 服务尚未提供云代码 JavaScript SDK,您仍然可以通过其 REST API 与其连接。

使用 UGS SDK

您可以直接将云代码 JavaScript SDK 导入到您的云代码脚本中。

要检查哪些服务集成可用,并查找每项服务的详细 SDK 文档和变更日志,请参阅云代码服务 SDK 文档。

Note: The return objects of methods for other Unity services contain additional information about the request, such as the headers of the request or the response. To access the payload of a response, reference the data property of the object.

有关云代码 JavaScript SDK 及其文档的完整列表,请参阅下表

服务SDK 文档SDK 变更日志
云保存云保存 SDK云保存 SDK 变更日志
经济经济 SDK经济 SDK 变更日志
远程配置远程配置 SDK远程配置 SDK 变更日志
VivoxVivox SDKVivox SDK 变更日志
大厅大厅 SDK大厅 SDK 变更日志
排行榜排行榜 SDK排行榜 SDK 变更日志
匹配器匹配器 SDK匹配器 SDK 变更日志
多人游戏多人游戏 SDK多人游戏 SDK 变更日志
好友好友 SDK好友 SDK 变更日志
玩家名称玩家名称 SDK玩家名称 SDK 变更日志
玩家身份验证玩家身份验证 SDK玩家身份验证 SDK 变更日志

云代码脚本允许您通过组合不同的 UGS 服务来与跨玩家数据交互。有关更多信息,请参阅有关如何与跨玩家数据交互 的文档。

使用云保存 SDK

您可以使用云保存 服务将游戏中的持久玩家数据(如游戏进度)保存到云端,并可以从任何地方和任何设备访问玩家数据。这意味着您可以使用云保存来减少玩家更换设备或重新安装游戏时的数据丢失。

读取和写入数据

您可以使用云代码保存和读取云保存中的数据。

示例代码演示了如何使用不同的访问修饰符让玩家和自定义实体访问和修改云保存数据

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;
  }
};

查询数据

您可以使用云代码查询云保存数据。

在使用下面的示例之前,您需要云保存服务 中创建查询。

导航到Unity 云控制台 中的云保存服务,并创建具有以下参数的查询

  • 对具有默认访问类的键 health 的玩家实体进行升序查询。
  • 对具有私有访问类的键 difficulty 的自定义实体进行降序查询。
  • 对具有受保护访问类的键 level 的玩家实体进行升序查询。

示例代码演示了如何使用不同的访问修饰符让玩家和自定义实体查询云保存数据

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;
  }

};

使用经济 SDK

经济 服务允许您使用以下资源在游戏中创建、管理和发布经济系统

  • 货币允许您的玩家拥有一种或多种货币/面额的游戏内余额。
  • 库存项目表示您作为配置的一部分创建的资源定义。玩家可以拥有库存项目的实例(例如,同一把剑、盾牌、帽子的多个副本),并且与货币不同,您可以为每个实例赋予其自己的属性。
  • 购买允许您的玩家使用不同的游戏内货币和库存项目(虚拟购买)购买游戏内货币和库存项目,或者通过数字商店使用真钱

使用云代码,您可以直接从脚本操作货币、库存项目和购买。

经济 SDK 玩家库存

下面的脚本将 SWORD 项目添加到玩家的库存中。

在使用下面的示例之前,您需要经济服务 中创建类型为 SWORD 的库存项目,并发布您的新配置

Note: If you published your inventory configuration but the inventory item isn't found, you might need to refresh your playerId. Alternatively, update the script to use a config assignment hash.

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;
  }
}

经济 SDK 玩家货币

下面的脚本设置玩家的货币余额,然后将其递增和递减。

在使用下面的示例之前,您需要经济服务 中发布货币。

下面的脚本接受一个必需的字符串参数 currencyId

Note: If you published your currency configuration but the currency is not found, you might need to refresh your playerId. Alternatively, update the script to use a config assignment hash.

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.org.cn/Packages/[email protected]/manual/Authoring/javascript_project.html)
//
// module.exports.params = {
//   currencyId: { type: "String", required: true },
// };

使用经济 SDK 虚拟购买

下面的脚本进行虚拟购买,然后从玩家的库存中删除该项目。

在使用下面的示例之前,您需要经济服务 中创建虚拟购买,并发布您的新配置

该脚本将 purchaseIdcurrencyId 作为参数。currencyId 是玩家需要完成购买的货币的 ID。

Note: If you published your virtual purchase configuration but the purchase isn't found, you might need to refresh your playerId. Alternatively, update the script to use a config assignment hash.

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.org.cn/Packages/[email protected]/manual/Authoring/javascript_project.html)
//
// module.exports.params = {
//   currencyId: { type: "String", required: true },
//   purchaseId: { type: "String", required: true },
// };

将经济与游戏覆盖结合使用

有关更多信息,请参阅经济游戏覆盖与云代码 文档。

使用远程配置 SDK

远程配置 是一种云服务,您可以使用它来调整游戏设计,而无需部署新版本的应用程序。远程配置由一组命名空间的键值参数组成,您还可以选择定义一组值来覆盖或添加到这些参数。

您可以从 Cloud Code 脚本修改远程配置设置。

以下示例演示了如何将远程配置设置分配给玩家。

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:[email protected]",
    t: "sip:[email protected]"
  };

  try {
    return TokenApi.generateVivoxToken("TEST_KEY", o)

  } catch (err) {
    logger.error("Error while generating vivox token", {"error.message": err.message});
    throw err;
  }
});

使用大厅 SDK

大厅 允许您的玩家在游戏会话之前或期间连接。玩家创建具有简单游戏属性的公共大厅,其他玩家可以搜索、发现并加入。仅限邀请的大厅还允许玩家为仅供特定参与者使用的私人空间。

以下是如何使用 Cloud Code 脚本创建、更新和心跳 大厅的示例。

该示例使用服务令牌创建一个没有所有者的空大厅。然后它接收一个playerId参数,您可以使用它将玩家添加到大厅,以及一个serviceId参数,您可以使用它来识别拥有大厅的服务。

While you can use Cloud Code to make Lobby requests as a player, the default uses service authentication. You can give the serviceId that the sample uses below any value you want; it gives the Lobby service a unique identifier in case you want to differentiate among your service-owned lobbies.

以下示例创建一个私人大厅,更新大厅的名称,以模拟玩家的身份加入大厅,并检索大厅。

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.org.cn/Packages/[email protected]/manual/Authoring/javascript_project.html)
//
// module.exports.params = {
//   serviceId: { type: "String", required: true },
//   playerId: { type: "String", required: true },
// };

使用排行榜 SDK

排行榜 允许您为您的游戏创建和管理排行榜。

在使用以下示例之前,您需要在排行榜服务中创建一个排行榜。

以下示例演示了如何使用 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.org.cn/Packages/[email protected]/manual/Authoring/javascript_project.html)
//
// module.exports.params = {
//   leaderboardId: { type: "String", required: true },
// };

使用好友 SDK

好友 允许您在您的游戏中创建和管理玩家之间的关系。您可以使用好友来管理游戏内关系,发送和接受好友请求,控制状态,以及在玩家之间发送消息。以下示例演示了如何使用 Cloud Code 与好友一起发送好友请求,检索好友,检索通知令牌,设置和接收状态,以及发送消息。

要在Unity 云仪表板中测试此示例,请在两个不同的浏览器选项卡中以两个不同的玩家身份运行脚本。在一个选项卡中,运行使用第一个玩家 ID 的脚本,并在另一个选项卡中运行使用第二个玩家 ID 的脚本。

Note: Some of the calls below fail if the requested user hasn't accepted the friend request.

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.org.cn/Packages/[email protected]/manual/Authoring/javascript_project.html)
//
// module.exports.params = {
//   playerId: { type: "String", required: true },
// };

使用玩家姓名 SDK

玩家姓名 允许您检索您游戏中玩家的玩家姓名。您可以使用它为尚未设置姓名的玩家生成新的玩家姓名,或更新现有的玩家姓名。

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 的服务,这将非常有用。

身份验证

根据您的用例,您可以使用accessTokenserviceToken来验证 API 调用。如果您使用serviceToken来验证 UGS 客户端 API,请确保您要调用的服务支持服务令牌。有关支持服务令牌的服务和用例列表,请参阅服务和访问令牌支持文档。

Note: If the service you want to call provides a Cloud Code JavaScript SDK, you can use the SDK instead so you don't have to call the service API directly. For more information, refer to Cloud Code Services SDKs.

调用 API

以下示例演示了如何调用用户生成内容 API,创建自定义内容项目,检索项目,以及删除项目。

Note: Before you can use the sample below, you need to enable the User Generated Content service in your project.

JavaScript

const axios = require("axios-0.21");

module.exports = async ({ params, context, logger }) => {
  const config = {
    headers: {
      'Content-Type': 'application/json',
      Authorization: `Bearer ${context.accessToken}`
    }
  };


  const baseUrl = `https://ugc.services.api.unity.com/v1/projects/${context.projectId}/environments/${context.environmentId}/content`;

  let result;
  try {
    const payload = {
      name: "a festive treat",
      description: "banasco is a traditional british toast",
      visbility: "Unlisted",
    };

    const postResult = await axios.post(baseUrl, payload, config);
    logger.info(`Created content: ${JSON.stringify(postResult.data)}`);

    const getUrl = `https://ugc.services.api.unity.com/v1/content/search`;
    result = await axios.get(getUrl, config);

    const deleteContentUrl = `${baseUrl}/${postResult.data.content.id}`
    await axios.delete(deleteContentUrl, config);

    return result.data;
  } catch (err) {
    logger.error("Error while calling out to User Generated Content", {"error.message": err.message});
    throw err;
  }
};