こんにちは!金融ソリューション事業部の孫です。
Part1の記事では、FlexMatchに関するコンポーネントの構築をご紹介しました!
さて、Part2である今回は、
- プレイヤーの認証・管理用Cognitoの作成
- UEクライアントの組込みに関するバックエンドAPIの作成
をご紹介します!
Part2の続きとして、Part3の記事はこちらです!
プレイヤー管理用Amazon Cognitoの作成
マッチング検証にあたり、複数プレイヤーの登録が必要になります。
その為、プレイヤーの認証、承認および管理をシンプルにする為、Amazon Cognitoを採用します。
- AWS マネジメントコンソールでCognitoのトップページに移動します
- 左側Menu「ユーザープール」⇒「ユーザープールの作成」ボタンをクリックして作成ページを開きます
- Cognitoの設定は、以下の内容を入力します
- Pool Name: 「GameLiftUnreal-UserPool」とします
- 「Review Defauls」をクリックし、設定を確認した上で、プールを作成します
※Tips: ここまでの設定はプール作成後、変更できないため、ちゃんと確認してから前に進めてください - プールの作成完了後、左Menuに「App Clients」⇒ 「Add an app client」をクリックして以下の内容でAPPクライアントを作成します
- App client name:「GameLiftUnreal-LambdaLoginClient」としました
- 「Generate client secret」のチェックを外します
- 「Auth Flows Configuration」: 「Enable username password based authentication」と「Enable refresh token based authentication」のみチェックします
- そのほか:デフォルト値
- 左menu「App Integration」⇒「App Client Settings」をクリックし設定を行います
- Cognito User Pool: チェック入れます
- Sign in and sign out URLs -> CallBack URL(s): https://aws.amazon.com
- Sign in and sign out URLs -> Sign out URL(s): https://aws.amazon.com
- 左menu「App Integration」⇒「Domain name」のDomain Prefix: 今回は「gameliftunreal-cog-20230222」 としました
ここまで、Cognitoの構築は完了しました。
次に、UEクライアントとCognito連携用のAPIを構築します。
AWS マネジメントコンソールでLambdaのトップページに移動します
「関数の作成」ボタンをクリックして、関数作成ページを開きます
- 以下の内容で関数を作成します。
- 関数名:「GameLiftUnreal-CognitoLogin」としました
- ランタイム:「Python3.9」としました
- 関数コードは、以下の内容を入力します
- 関数の役割としては、プレイヤーが入力したユーザー名とパスワードをCognitoに送信し、その認証結果をプレイヤーに返却します
GameLiftUnreal-CognitoLoginコード
import boto3 import os import sys USER_POOL_APP_CLIENT_ID = 'Cognitoで生成したクライアントアプリへのアクセスキー' client = boto3.client('cognito-idp') def lambda_handler(event, context): if 'username' not in event or 'password' not in event: return { 'status': 'fail', 'msg': 'Username and password are required' } resp, msg = initiate_auth(event['username'], event['password']) if msg != None: return { 'status': 'fail', 'msg': msg } return { 'status': 'success', 'tokens': resp['AuthenticationResult'] } def initiate_auth(username, password): try: resp = client.initiate_auth( ClientId=USER_POOL_APP_CLIENT_ID, AuthFlow='USER_PASSWORD_AUTH', AuthParameters={ 'USERNAME': username, 'PASSWORD': password }) except client.exceptions.InvalidParameterException as e: return None, "Username and password must not be empty" except (client.exceptions.NotAuthorizedException, client.exceptions.UserNotFoundException) as e: return None, "Username or password is incorrect" except Exception as e: print("Uncaught exception:", e, file=sys.stderr) return None, "Unknown error" return resp, None
- Cognitoへのアクセスが必要なため、アクセス権限を追加します
{ "Sid": "VisualEditor0", "Effect": "Allow", "Action": "cognito-idp:InitiateAuth", "Resource": "*" }
ここまでは、GameLiftUnreal-CognitoLogin関数を作成しました。
次に、API Gatewayにおいて、関数を外から呼びだすAPIを作成します。
- RestAPIを選択し、「構築」ボタンを押してAPIの作成ページを開きます
- 「アクション」⇒「メソッドの作成」でPOSTメソッドを作成します
- 統合タイプ:lambda Functionチェック
- Lambdaリージョン:ap-northeast-1
- Lambda 関数:前手順で作成したGameLiftUnreal-CognitoLoginを選択
プレイヤーのIDとパスワードを入力した上で、ログインAPIにリクエストしたら、Cognitoからアクセスキーを発行してくれます。
そして、発行されたアクセスキーを用いて、他のAPIにも正常にリクエストができます。
マッチング処理用のバックエンドAPIの作成
上記AmazonSNS作成時に説明した通り、各マッチングイベントごとに後続処理の実装が必要なのですが、今回は検証をシンプルにする目的で「StartMatchMaking」イベントのみ処理します。
また、その他の補助処理として「①GetPlayerData:プレイヤーデータ取得」と「②PollMatchMaking:マッチング結果取得」も実装します。
- バックエンドのLambda関数を作成します
- 上記のLambda作成と同様な手順でLambda作成ページに移動し、以下の内容で関数本体を作成します
- StartMatchmaking
- 関数の役割としては、マッチングで必要なデータをインプットしてマッチングを開始します
- 関数名:「StartMatchmaking」としました
- ランタイム:「NodeJs 18.x」としました
- GetPlayerData
- 関数の役割としては、現在ログイン中のプレイヤー情報を取得します
- 関数名:「GetPlayerData」としました
- ランタイム:「NodeJs 18.x」としました
- PollMatchMakingのLambda関数定義
- 関数の役割としては、マッチング結果を取得します(ポーリング方式)
- 関数名:「PollMatchMaking」としました
- ランタイム:「NodeJs 18.x」としました
- StartMatchmaking
- 上記作成した関数本体を開いて、対象のソースコードを入力します
- 上記のLambda作成と同様な手順でLambda作成ページに移動し、以下の内容で関数本体を作成します
StartMatchmakingコード
const AWS = require('aws-sdk'); const Lambda = new AWS.Lambda({region: 'ap-northeast-1'}); const GameLift = new AWS.GameLift({region: 'ap-northeast-1'}); exports.handler = async (event) => { let response; let raisedError; let latencyMap; if (event.body) { const body = JSON.parse(event.body); if (body.latencyMap) { latencyMap = body.latencyMap; } } if (!latencyMap) { response = { statusCode: 400, body: JSON.stringify({ error: 'incoming request did not have a latency map' }) }; return response; } const lambdaRequestParams = { FunctionName: 'GetPlayerData', Payload: JSON.stringify(event) }; let playerData; await Lambda.invoke(lambdaRequestParams) .promise().then(data => { if (data && data.Payload) { const payload = JSON.parse(data.Payload); if (payload.body) { const payloadBody = JSON.parse(payload.body); playerData = payloadBody.playerData; } } }) .catch(err => { raisedError = err; }); if (raisedError) { response = { statusCode: 400, body: JSON.stringify({ error: raisedError }) }; return response; } else if (!playerData) { response = { statusCode: 400, body: JSON.stringify({ error: 'unable to retrieve player data' }) }; return response; } const playerId = playerData.Id.S; const groupId = parseInt(playerData.groupId.N, 10); const gameLiftRequestParams = { ConfigurationName: 'GameLiftTutorialMatchmaker', Players: [{ LatencyInMs: latencyMap, PlayerId: playerId, PlayerAttributes: { groupid: { N: groupId } } }] }; console.log('matchmaking request: ' + JSON.stringify(gameLiftRequestParams)); let ticketId; await GameLift.startMatchmaking(gameLiftRequestParams) .promise().then(data => { if (data && data.MatchmakingTicket) { ticketId = data.MatchmakingTicket.TicketId; } response = { statusCode: 200, body: JSON.stringify({ 'ticketId': ticketId }) }; }) .catch(err => { response = { statusCode: 400, body: JSON.stringify({ error: err }) }; }); return response; };
GetPlayerDataコード
const AWS = require('aws-sdk'); const Cognito = new AWS.CognitoIdentityServiceProvider({region: 'ap-northeast-1'}); const DynamoDb = new AWS.DynamoDB({region: 'ap-northeast-11'}); exports.handler = async (event) => { let response; let raisedError; let accessToken; if (event.headers) { if (event.headers['Authorization']) { accessToken = event.headers['Authorization']; } } const cognitoRequestParams = { AccessToken: accessToken }; let sub; await Cognito.getUser(cognitoRequestParams) .promise().then(data => { if (data && data.UserAttributes) { for (const attribute of data.UserAttributes) { if (attribute.Name == 'sub') { sub = attribute.Value; break; } } } }) .catch(err => { raisedError = err; }); if (raisedError) { response = { statusCode: 400, body: JSON.stringify({ error: raisedError }) }; return response; } const dynamoDbRequestParams = { TableName: 'Players', Key: { Id: {S: sub} } }; let playerData; await DynamoDb.getItem(dynamoDbRequestParams) .promise().then(data => { if (data && data.Item) { playerData = data.Item; } response = { statusCode: 200, body: JSON.stringify({ 'playerData': playerData }) }; }) .catch(err => { response = { statusCode: 400, body: JSON.stringify({ error: err }) }; }); return response; };
PollMatchMakingコード
const AWS = require('aws-sdk'); const DynamoDb = new AWS.DynamoDB({region: 'ap-northeast-1'}); exports.handler = async (event) => { let response; let ticketId; if (event.body) { const body = JSON.parse(event.body); if (body.ticketId) { ticketId = body.ticketId; } } if (!ticketId) { response = { statusCode: 400, body: JSON.stringify({ error: 'incoming request did not have a ticket id' }) }; return response; } const dynamoDbRequestParams = { TableName: 'MatchmakingTickets', Key: { Id: {S: ticketId} } }; let ticket; await DynamoDb.getItem(dynamoDbRequestParams) .promise().then(data => { if (data && data.Item) { ticket = data.Item; } response = { statusCode: 200, body: JSON.stringify({ 'ticket': ticket }) }; }) .catch(err => { response = { statusCode: 400, body: JSON.stringify({ error: err }) }; }); return response; };
- 各関数のアクセス権限を編集します
- 1.StartMatchmakingの アクセスポリシーに以下の内容を文末に追加します
{ "Effect": "Allow", "Action": "lambda:InvokeFunction", "Resource": "arn:aws:lambda:your_region:your_account:function:GetPlayerData", }, { "Effect": "Allow", "Action": "gamelift:StartMatchmaking", "Resource": "*", }
- 2.GetPlayerDataの アクセスポリシーに以下の内容を文末に追加します
{ "Effect": "Allow", "Action": "Dynamodb:GetItem", "Resource": "arn:aws:dynamodb:your_region:your_account:table/Players", }
- 3.PollMatchMakingの アクセスポリシーに以下の内容を文末に追加します
{ "Effect": "Allow", "Action": "Dynamodb:GetItem", "Resource": "arn:aws:dynamodb:your_region:your_account:table/MatchmakingTickets", }
- API GatewayでのAPIの作成
- 上記ログインAPIの作成と同様な手順でAPIの作成ページに移動し、以下の内容でAPIリソースを作成します
- APIごとにメソッドを作成します
- getplayerdata APIのGETメソッド作成
- 統合タイプ:lambda Functionチェック
- Lambdaリージョン:ap-northeast-1
- Lambda 関数:GetPlayerData
- startmatchmaking APIのPOSTメソッド作成
- 統合タイプ:lambda Functionチェック
- Lambdaリージョン:ap-northeast-1
- Lambda 関数:StartMatchMaking
- pollmatchmaking APIのPOSTメソッド作成
- 統合タイプ:lambda Functionチェック
- Lambdaリージョン:ap-northeast-1
- Lambda 関数:PollMatchMaking
- getplayerdata APIのGETメソッド作成
ここまで、CognitoおよびバックエンドAPIの作成は、以上となります!
Part3では、バックエンドAPIを用いてUEクライアントへの組込みおよびマッチングの検証を行います。
現在ISIDはweb3領域のグループ横断組織を立ち上げ、Web3およびメタバース領域のR&Dを行っております(カテゴリー「3DCG」の記事はこちら)。
もし本領域にご興味のある方や、一緒にチャレンジしていきたい方は、ぜひお気軽にご連絡ください!
私たちと同じチームで働いてくれる仲間を、是非お待ちしております!
ISID採用ページ(Web3/メタバース/AI)
執筆:@chen.sun、レビュー:@wakamoto.ryosuke
(Shodoで執筆されました)