Azure Functions を使って CloudScript を実行するとき、実行したユーザーの細かい情報が詰め込まれた変数を使うことができます。
それが「context」というもので、この中を参照することでいろんな値を取得することができるんです。
「じゃあどうやって使うのか?」というのがなかなか難しいので、この記事では使い方をわかりやすく解説しています。
Azure Functions を実行する準備ができていない人は、CloudScriptでAzure Functionsを実行する方法を参照してみてください。
では、さっそく見ていきましょう。
Azure Functionsを使用したcontextモデル
クライアントから「1つのアイテムを消費する」という処理を呼び出す例について解説していきます。
まずは全体像を見ていきましょう。
サーバー側のメソッド名は「ConsumeItem」とします。
[FunctionName("ConsumeItem")]
public static async Task<dynamic> ConsumeItem(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)] HttpRequest req,
ILogger log)
{
string body = await req.ReadAsStringAsync();
var context = JsonConvert.DeserializeObject<FunctionExecutionContext<dynamic>>(body);
var args = context.FunctionArgument;
// 引数の取り出し
dynamic itemId = null;
if (args != null && args["ItemId"] != null)
itemId = args["ItemId"];
// アイテム消費
var result = await ConsumeItemAsync(context, itemId);
// 結果の返却
return new { resultValue = result };
}
実際にアイテム消費をするメソッド名は「ConsumeItemAsync」とします。
private static async Task<string> ConsumeItemAsync(FunctionExecutionContext<dynamic> context, dynamic itemId)
{
var apiSettings = new PlayFabApiSettings
{
TitleId = context.TitleAuthenticationContext.Id,
DeveloperSecretKey = Environment.GetEnvironmentVariable("PLAYFAB_DEV_SECRET_KEY", EnvironmentVariableTarget.Process),
};
var serverApi = new PlayFabServerInstanceAPI(apiSettings);
var result = await serverApi.ConsumeItemAsync(new PlayFab.ServerModels.ConsumeItemRequest()
{
PlayFabId = context.CallerEntityProfile.Lineage.MasterPlayerAccountId,
ItemInstanceId = itemId,
ConsumeCount = 1
});
return result.Result.ItemInstanceId;
}
ここから順を追って説明してきますね。
contextの取得
string body = await req.ReadAsStringAsync();
var context = JsonConvert.DeserializeObject<FunctionExecutionContext<dynamic>>(body);
この記事の主役となる変数ですね。
このあたりはお作法的な部分もあるので、「こういう書き方をするんだな」とおぼえておけばよいです。
引数の取得
引数は「context.FunctionArgument」で一覧を取得できます。
var args = context.FunctionArgument;
取得した引数の一覧は、KeyValue の形でさらに個別に取り出すことができます。
dynamic itemId = null;
if (args != null && args["ItemId"] != null)
itemId = args["ItemId"];
個人的には「null チェックはいらないのでは?」と思うのですが、公式のサンプルを見るとやっているので、念のためつけておきます。
タイトルIDとシークレットキーの設定
サーバーで API を実行するには、タイトルIDとシークレットキーの設定が必要になります。
それぞれを定数で管理しても良いのですが、誤って public なリポジトリにコミットしてしまうと、セキュリティ的に問題があります。
そこで、Azure Functions の環境変数を参照するようにします。
まだ環境変数を設定していない人は、Azure関数で環境変数を設定する方法を参考に設定しておきましょう。
ちなみに、環境変数が設定してあれば、「context.ApiSettings」で取得することができます。
サーバー API インスタンスの取得
これはお作法ですが、サーバー API インスタンスを生成してからサーバー処理を呼び出すようにします。
var apiSettings = new PlayFabApiSettings
{
TitleId = context.TitleAuthenticationContext.Id,
DeveloperSecretKey = Environment.GetEnvironmentVariable("PLAYFAB_DEV_SECRET_KEY", EnvironmentVariableTarget.Process),
};
var serverApi = new PlayFabServerInstanceAPI(apiSettings);
var result = await serverApi.ConsumeItemAsync(--以下略--
先ほど設定した環境変数でインスタンスを作るので、設定された環境でサーバー API が動作するようになる、という仕組みですね。
ちなみに、インスタンス化せず以下のように静的なものとして呼び出すことも可能です。
PlayFabSettings.staticSettings.TitleId = context.ApiSettings.TitleId;
PlayFabSettings.staticSettings.DeveloperSecretKey = context.ApiSettings.DeveloperSecretKey;
var result = await PlayFabServerAPI.ConsumeItemAsync(--以下略--
ですが、この方法は推奨されていません。
理由としては、他の処理と競合して予期しない動作をする可能性があるから、とのことです。
さらに詳しく知りたい人は以下のツイートの画像をご覧ください。
#PlayFab の CloudScript を C# で実装する人向けに重要な情報です💡
サーバー側で API を使用するときは、静的なものよりも「インスタンスAPI」が推奨されています。公式 GitHub にさらっと書いてあったので、見逃すところでした。そしてなぜか、エンティティオブジェクトの API は違うので注意。 pic.twitter.com/2HikrsjiXt
— ねこじょーかー@Unity勉強中 (@nekojoker1234) March 25, 2020
PlayFabIdの取得
次に、サーバーAPIを呼び出すために必要な PlayFabId を設定する必要があります。
少しわかりにくいですが、「context.CallerEntityProfile.Lineage.MasterPlayerAccountId」が PlayFabId にあたるものなので、注意してください。
ちなみに、JavaScript で実行する CloudScript とは PlayFabId の取得方法が異なります。
currentPlayerId で取得
context.CallerEntityProfile.Lineage.MasterPlayerAccountId で取得
その他もろもろ
今回の処理に必要なのは以上ですが、「context.CallerEntityProfile」の中身を見るといろいろと情報が詰まっています。
公式ドキュメントの EntityProfileBody のページに中身の一覧が載っているので、興味があれば合わせて見てみてください。
Objects でエンティティデータの取得ができるので、このあたりは使いやすいかもしれませんね。
プレイヤーの PlayStream イベントの受け取り
ここまでは、クライアントからの呼び出しでしたが、PlayStream 経由での実行となるとアプローチが変わります。
たとえば、アクションとルールを使うことで、特定のタイミングで Azure Function を自動実行することが可能です。
上記を実行するための処理を見ていきましょう。
Context クラスの定義
通常は FunctionExecutionContext クラスを使えばいいのですが、PlayStream 経由の場合は以下のように自分で用意する必要があります。
public class PlayerPlayStreamFunctionExecutionContext<T>
{
public PlayFab.CloudScriptModels.PlayerProfileModel PlayerProfile { get; set; }
public bool PlayerProfileTruncated { get; set; }
public PlayFab.CloudScriptModels.PlayStreamEventEnvelopeModel PlayStreamEventEnvelope { get; set; }
public TitleAuthenticationContext TitleAuthenticationContext { get; set; }
public bool? GeneratePlayStreamEvent { get; set; }
public T FunctionArgument { get; set; }
}
public class PlayerPlayStreamFunctionExecutionContext : PlayerPlayStreamFunctionExecutionContext<dynamic>
{
}
この定義をしておくことで、渡ってくる文字列を各プロパティにうまいこと変換してくれるようになります。
CS2AFHelperClasses.cs というサンプルが公式で用意されているので、これをコピーしてきても良いです。
サーバー処理全体
サーバーの処理全体はこんな感じになります。
[FunctionName("SendPushNotificationTemplate")]
public static async Task<dynamic> SendPushNotificationTemplate(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)] HttpRequest req, ILogger log)
{
// context の取り出し
string body = await req.ReadAsStringAsync();
var context = JsonConvert.DeserializeObject<PlayerPlayStreamFunctionExecutionContext<dynamic>>(body);
var args = context.FunctionArgument;
string playFabId = context.PlayerProfile.PlayerId;
// サーバーAPIの生成
var apiSettings = new PlayFabApiSettings
{
TitleId = context.TitleAuthenticationContext.Id,
DeveloperSecretKey = Environment.GetEnvironmentVariable("PLAYFAB_DEV_SECRET_KEY", EnvironmentVariableTarget.Process),
};
var serverApi = new PlayFabServerInstanceAPI(apiSettings);
await serverApi.SendPushNotificationFromTemplateAsync(new SendPushNotificationFromTemplateRequest
{
Recipient = playFabId,
PushNotificationTemplateId = args["TemplateId"].ToString()
});
return new { result = $"Push Template Success!! PlayFabId:{playFabId} TemplateId:{args["TemplateId"]}" };
}
ポイントを抜き出して解説していきますね。
まずは context を取り出すところです。
string body = await req.ReadAsStringAsync();
var context = JsonConvert.DeserializeObject<PlayerPlayStreamFunctionExecutionContext<dynamic>>(body);
引数の HttpRequest から情報を抜き出し、さらに自分で定義したクラスにデシリアライズしています。
デシリアライズする型を dynamic ではなく object にすれば、引数を object[] で受け取ることも可能です。
続いて、サーバーAPIの生成についてです。
var apiSettings = new PlayFabApiSettings
{
TitleId = context.TitleAuthenticationContext.Id,
DeveloperSecretKey = Environment.GetEnvironmentVariable("PLAYFAB_DEV_SECRET_KEY", EnvironmentVariableTarget.Process),
};
var serverApi = new PlayFabServerInstanceAPI(apiSettings);
最初に用意したクラスには PlayFabApiSettings が入っていないので、別で用意してあげる必要があります。
環境変数の設定方法については、Azure関数で環境変数を設定する方法を参照してください。
あとはいつもどおりなので、問題ないですね。
最後に
Azure Functions を使用した context モデルについて解説してきました。
さらに詳しく知りたい人は、公式ドキュメントのCloudScriptコンテキストモデルの使用をチェックしてみてください。
C# を使った PlayFab と Azure Functions との連携についてはまだまだ情報が少ないですが、この記事を参考に進められる人が増えれば嬉しいです。
PlayFab のことをもっと皆さんに知ってもらいたくて、合計500ページ以上にもなる書籍を5冊に分けて執筆しました。
私の知識をすべて詰め込んでいるので、ゲーム開発をさらに加速させたい方はぜひご覧ください。