PlayFab の Economy V2 は、クライアント側インベントリ関連の API を呼び出し、ログインしているプレイヤー以外のプレイヤーのインベントリにアイテムを追加または参照しようとすると、NotAuthorized
エラーが発生します。
他のプレイヤーとアイテムを取引したり、プレゼントを送ったり、といった用途に利用したい場合は、CloudScript を利用してサーバーサイドで処理を行う必要があります。本記事では、Azure Functions を用いて、指定したプレイヤーのインベントリにアイテムを追加する方法について解説します。
TransferInventoryItems は、クライアントサイドでプレイヤー自身のインベントリ内のコレクション間・スタック間でのみアイテムを転送でき、他のプレイヤーのインベントリには適用できません。
作成する機能
Azure Functions を利用して CloudScript を実装し、指定したプレイヤーのインベントリにアイテムを追加する方法を説明します。
この機能では、対象プレイヤーのマスタープレイヤーアカウント ID (PlayFab ID) を事前に把握していることを前提とします。Azure Functions 呼び出しの際のパラメーターとして、対象プレイヤーのマスタープレイヤーアカウント ID と追加するアイテムの フレンドリ ID を指定します。
Azure Functions で PlayFab CloudScript を作成する方法は、以下のドキュメントを参照してください。
今回のサンプルの実装の Azure Functions、分離ワーカープロセス .NET 8 で作成しています。
処理の流れ
インベントリに追加する相手プレイヤーのタイトルプレイヤーアカウント ID を取得
インベントリに追加する際、プレイヤーのタイトルプレイヤーアカウント ID が必要になるため、PlayFab Server API の GetUserAccountInfoAsync
メソッドを利用して、クライアントから渡されたマスタープレイヤーアカウント ID (PlayFab ID) をもとに、プレイヤーのタイトルプレイヤーアカウント ID を取得します。
// アイテムを追加するプレイヤーのタイトルプレイヤーアカウント ID を取得する
var requestGetUserAccountInfo = new GetUserAccountInfoRequest()
{
PlayFabId = playerId,
};
var getUserAccountInfoResult = await serverApi.GetUserAccountInfoAsync(requestGetUserAccountInfo);
if (getUserAccountInfoResult.Error != null)
{
throw new Exception($"GetUserAccountInfoAsync failed. Error={getUserAccountInfoResult.Error.GenerateErrorReport()}");
}
// アイテムを追加するプレイヤーのタイトルプレイヤーアカウント ID
var titlePlayerAccountId = getUserAccountInfoResult.Result.UserInfo.TitleInfo.TitlePlayerAccount.Id;
インベントリにアイテムを追加する
AddInventoryItemsAsync メソッドを利用し、指定したプレイヤーのインベントリにアイテムを追加します。
その際 AddInventoryItemsRequest.Entity
に、アイテムを追加するプレイヤーのタイトルプレイヤーアカウント ID を指定します。
// アイテムを追加するプレイヤーのタイトルプレイヤーアカウント ID
var titlePlayerAccountId = getUserAccountInfoResult.Result.UserInfo.TitleInfo.TitlePlayerAccount.Id;
var playerEntity = new PlayFab.EconomyModels.EntityKey()
{
Id = titlePlayerAccountId,
Type = "title_player_account",
};
// Economy V2 API
var economyApi = new PlayFabEconomyInstanceAPI(apiSettings, authContext);
// 指定されたプレイヤーのインベントリにアイテムを追加する
var addInventoryItemsAsyncRequest = new AddInventoryItemsRequest()
{
Item = new InventoryItemReference
{
AlternateId = new AlternateId()
{
Type = "FriendlyId",
Value = itemId, // 追加するアイテムの FriendlyId を指定
},
},
Amount = 1,
Entity = playerEntity, // 追加するプレイヤーのエンティティを指定
IdempotencyId = idempotencyId,
};
var addInventoryItemsAsyncResponse = await economyApi.AddInventoryItemsAsync(addInventoryItemsAsyncRequest);
if (addInventoryItemsAsyncResponse.Error != null)
{
throw new Exception($"AddInventoryItemsAsync failed. Error={addInventoryItemsAsyncResponse.Error.GenerateErrorReport()}");
}
制限
AddInventoryItemsAsync
は、Entity にプレイヤーを指定しての呼び出しには、90 秒間に 60回の制限(スロットリング)があります。
制限以上呼び出すと、TooManyRequests エラーが返されます。
Server API は、10 秒間に 1,000 回の制限があります。Economy V2 のプレイヤー指定の制限は、それに比べて厳しいですが、Entity ごとの制限のため、サーバーでの処理でもプレイヤーがばらける用途なら、むしろ使い安いように感じます。
この制限に関しては、用途に応じた事前検証を推奨します。
Function 全体のコード
Function 全体のコードは以下の通りです。
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using Microsoft.Azure.Functions.Worker;
using PlayFab;
using PlayFab.EconomyModels;
using PlayFab.ServerModels;
using PlayFab.Samples;
namespace Samples.Function
{
public class AddPlayerInventoryItem
{
private readonly ILogger<AddPlayerInventoryItem> _logger;
public AddPlayerInventoryItem(ILogger<AddPlayerInventoryItem> logger)
{
_logger = logger;
}
[Function("AddPlayerInventoryItem")]
public async Task<IActionResult> Run([HttpTrigger(AuthorizationLevel.Function, "get", "post")] HttpRequest req)
{
_logger.LogInformation("C# HTTP trigger function processed a request.");
string body = await new StreamReader(req.Body).ReadToEndAsync();
_logger.LogInformation($"body={body}");
var functionContext = JsonConvert.DeserializeObject<FunctionExecutionContext<dynamic>>(body);
var args = functionContext.FunctionArgument;
// CloudScript の呼び出し元のプレイヤーの MasterPlayerAccountId
string masterPlayerAccountId = functionContext.CallerEntityProfile.Lineage.MasterPlayerAccountId;
_logger.LogInformation($"masterPlayerAccountId={masterPlayerAccountId}");
// クライアントから渡された PlayerId と ItemId を取得
dynamic request = null;
if (args != null && args["Request"] != null)
request = args["Request"];
string playerId = request.PlayerId;
if (string.IsNullOrWhiteSpace(playerId))
{
throw new ArgumentException("PlayerId is empty", "PlayerId");
}
string itemId = request.ItemId;
if (string.IsNullOrWhiteSpace(itemId))
{
throw new ArgumentException("ItemId is empty", "ItemId");
}
string idempotencyId = request.IdempotencyId;
_logger.LogInformation($"playerId={playerId}, itemId={itemId}, idempotencyId={idempotencyId}");
// PlayFab Server API のインスタンスを取得
var apiSettings = new PlayFabApiSettings
{
TitleId = functionContext.TitleAuthenticationContext.Id,
DeveloperSecretKey = Environment.GetEnvironmentVariable("PLAYFAB_DEV_SECRET_KEY", EnvironmentVariableTarget.Process),
};
var serverApi = new PlayFabServerInstanceAPI(apiSettings);
PlayFabAuthenticationContext authContext = new PlayFabAuthenticationContext()
{
EntityToken = functionContext.TitleAuthenticationContext.EntityToken
};
// アイテムを追加するプレイヤーのタイトルプレイヤーアカウント ID を取得する
var requestGetUserAccountInfo = new GetUserAccountInfoRequest()
{
PlayFabId = playerId,
};
var getUserAccountInfoResult = await serverApi.GetUserAccountInfoAsync(requestGetUserAccountInfo);
if (getUserAccountInfoResult.Error != null)
{
throw new Exception($"GetUserAccountInfoAsync failed. Error={getUserAccountInfoResult.Error.GenerateErrorReport()}");
}
// アイテムを追加するプレイヤーのタイトルプレイヤーアカウント ID
var titlePlayerAccountId = getUserAccountInfoResult.Result.UserInfo.TitleInfo.TitlePlayerAccount.Id;
var playerEntity = new PlayFab.EconomyModels.EntityKey()
{
Id = titlePlayerAccountId,
Type = "title_player_account",
};
// Economy V2 API
var economyApi = new PlayFabEconomyInstanceAPI(apiSettings, authContext);
// 指定されたプレイヤーのインベントリにアイテムを追加する
var addInventoryItemsAsyncRequest = new AddInventoryItemsRequest()
{
Item = new InventoryItemReference
{
AlternateId = new AlternateId()
{
Type = "FriendlyId",
Value = itemId, // 追加するアイテムの FriendlyId を指定
},
},
Amount = 1,
Entity = playerEntity, // 追加するプレイヤーのエンティティを指定
IdempotencyId = idempotencyId,
};
var addInventoryItemsAsyncResponse = await economyApi.AddInventoryItemsAsync(addInventoryItemsAsyncRequest);
if (addInventoryItemsAsyncResponse.Error != null)
{
throw new Exception($"AddInventoryItemsAsync failed. Error={addInventoryItemsAsyncResponse.Error.GenerateErrorReport()}");
}
return new OkObjectResult("Success");
}
}
}