TECH PLAY

電通総研

電通総研 の技術ブログ

836

こんにちは!金融ソリューション事業部の孫です。 今回の記事では、GameLiftを用いたUnrealEngineゲームセッションのマッチング基盤の構築をご紹介します! 実施事項が多い為、Part1~3の3記事に分けて連載します。 また、GameLiftを用いたゲームセッションの作成、接続については 前回の記事 でご紹介しておりますので、そちらもご覧いただければ幸いです。 Part1である今回は、 AWS GameLiftにおいてFlexMatchに関する コンポーネント の構築を行います。 Part2の記事は こちら です! Part3の記事は こちら です! はじめに 冒頭でGameLiftを用いてマッチング基盤を構築することを述べましたが、具体的にはGameLiftが提供するFlexMatch機能を利用します。 FlexMatch機能 とは、カスタマイズ可能なマッチメイキングサービスです。 特徴として、下記が挙げられます。 簡単にゲームにあったカスタムルールを複数作れる サーバーとの間の レイテンシー を基準にマッチング可能 時間と共にルールの条件を緩和することも可能 ユーザーによるマッチング結果の承諾機能 キューを使用してゲームセッションを効率的に配置 FlexMatchを使用することで、フレキシブルなマッチング処理を簡単に実現でき、 マルチプレイヤー ゲームをさらに楽しむことができます! 今回の検証では、GameLift以外他の AWS コンポーネント (Cognito、AmazonSNS、 Dynamo 、 API Gateway 、Lambda)も利用しています。説明をシンプルにする為、それらの コンポーネント の解説は割愛いたします。 マッチング基盤の構築手順 手順は、以下のとおりです。 【Part1】 1.ゲームセッション保管用キューの作成 2.マッチングイベント発行用 Amazon SNS の作成 3.マッチングイベント保管用 Amazon DynamoDB の作成 4.FlexMatchの構築 【Part2】 5.プレイヤー管理用 Amazon Cognitoの作成 6.マッチング処理用のバックエンド API の作成 【Part3】 7.UEクライアントへの API 組み込み 8.マッチング機能の検証 AWS の アーキテクチャ 構築全体像は、以下の アーキテクチャ 図をご参照ください。 今回はGameLiftが担当するゲーム用の クラウド サーバーの管理以外にも、以下の機能を実装します。 プレイヤーアカウントの作成、および認証認可管理(Cognito) マッチングをコン トロール するバックエンドサービス(Lambda関数、 SNS ) マッチング機能をクライアントから呼びだす API ( API Gateway ) マッチングイベントとプレイヤーデータの一時保管(DynamoDB) プレイヤーはまず Cognito 認証でアクセスキーを取得し、次にアクセスキーを用いてマッチング API にリク エス トし、最後にマッチング結果を取得してマッチングを完了します。 マッチングは現在オンライン中のプレイヤーを対象に、事前に設定したルールセットに従って処理を行います。 使用環境/ツール Unreal Engine 5.1.0 Windows10 21H2 x64 RAM 64GM, SSD 1TB NVIDIA GeForce GTX 3080Ti Gamelift SDK GameLift Cpp ServerSDK 3.4.2 GameLift Unreal plugin 3.4.0 Visual Studio 2019 version 16.11.21(以下VS2019) JavaSDK 1.8.0 ゲームセッション保管用キューの作成 FlexMatchを使ってプレイヤーマッチングを行うにあたり、一時的なゲームセッションを保管するキューを作成することで、Fleet IQサービスの最大活用およびマッチング遅延の改善を図ることができます。 GameLift ダッシュ ボードページで、「 ダッシュ ボード」⇒「キュー作成する」をクリックしてキュー作成ページを開きます キュー名を入れた上、「キューの作成」ボタンを押して、キューの作成を完了します ※今回は検証のため、その他設定はデフォルト値のままで十分です ※キューの作成にあたり、事前にFleetの作成が必要です。具体的な手順は 前回の記事 をご参照ください キュー名:「GameLift_PoC_Queue」としました マッチングイベント発行用 Amazon SNS の作成 AmazonSNS( Amazon Simple Notification Serviceの省略)の機能を利用することで、マッチング中にGameLiftが出力したイベントを非同期で拾ってくれます。 イベントによって現在マッチングのステータスを把握した上で、 ネクス トアクションを決めます。 イベントの種類については、以下が挙げられます。 MatchmakingSearching(マッチング中) PotentialMatchCreated(マッチング候補が作成済「プレイヤーの承諾前」) AcceptMatch(マッチング候補を承諾済) AcceptMatchCompleted(プレイヤーの承諾・却下または承諾の タイムアウト により、マッチングの承諾プロセス完了) MatchMakingSucceded(マッチングが正常に完了し、ゲームセッションが作成済) MatchMakingTimedOut( タイムアウト によってマッチング失敗) MatchMakingCancelled(マッチングがキャンセル済) MatchMakingFailed(マッチングでエラーが発生) ※Tips:マッチング処理をよりよくスムーズに実装するため、各イベントの返却文 参照資料 を覚えておくことをお勧めします 。 これから、AmazonSNSを作成します。 AWS マネジメントコンソールの検索窓口に「 SNS 」を入力して、 SNS のトップページに移動します 左側Menu「Topic」⇒「Topicの作成」をクリックして、Topicの作成画面が表示されます 以下の内容を入力して、「Topicの作成」ボタンを押して、 SNS を作成します 名前:「FlexMatchEventNofications」としました アクセスポリシー - オプション:アドバンストを選択し、以下の JSON 文を文末に入力します。 { " Sid ": " __console_pub_0 ", " Effect ": " Allow ", " Principal ": { " Service ": " gamelift.amazonaws.com " } , " Action ": " SNS:Publish ", " Resource ": " arn:aws:sns:your_region:your_account:your_topic_name " , } マッチングイベント保管用 Amazon DynamoDB の作成 今回の検証では、DynamoDBの役割として二つがあります AmazonSNSによって転送されたGameLiftのイベントを保管する オンライン中のプレイヤーIDやそのほかの必要な情報を一時的に預ける これから、DynamoDBを作成します。 AWS マネジメントコンソールの検索窓口に「DynamoDB」を入力して、DynamoDBのトップページに移動します 「テーブルの作成」をクリックして、イベント保管テーブルを作成します ※DynamoDBはNoSQLなので、データベースの作成がなくテーブルから作成します。 テーブル名:「MatchmakingTickets」としました パーティション キー:「Id」としました そのほかの設定:デフォルト値のまま 上記と同様な手順で、オンライン中のプレイヤーデータを保管するテーブルを作成します テーブル名:「Players」としました パーティション キー:「Id」としました そのほかの設定:デフォルト値のまま 作成したテーブルのOverviewページを開いて「Table details」⇒ 「Manage TTL 」をクリックして、 TTL Attributeを追加します。 ※DynamoDBに保存したデータは、基本は一時的なデータなので、 TTL を設定することによって自動的に消してもらうことを図ります TTL Attribute名:「 ttl 」としました ここまで、GameLiftのイベント保管テーブルを作成しました。 これから、AmazonSNSと処理連動するLambda関数を作成します。 ※Lambda関数の動作としては、AmazonSNSから受け取ったイベント情報をDynamoDBに保存します。 AWS マネジメントコンソールでLambdaのトップページに移動します 「関数の作成」ボタンをクリックして作成ページを開きます 以下の内容で関数を作成します 関数名:「TrackEvents」としました ランタイム:「NodeJs 18.x」としました 関数コードについて、以下の ソースコード を入力します 関数の役割としては、GameLiftが発行されたイベントを受け取って中身(イベント番号、イベント種類、ゲームセッション、プレイヤー情報)をDynamoDBに保存する TrackEventsコード const AWS = require(' aws - sdk '); const DynamoDb = new AWS .DynamoDB({region: 'ap-northeast-1'}); exports.handler = async (event) => { let message; let response; if (event.Records && event.Records.length > 0) { const record = event.Records[0]; if (record. Sns && record. Sns .Message) { console.log('message from gamelift: ' + record. Sns .Message); message = JSON .parse(record. Sns .Message); } } if (!message || message['detail-type'] != 'GameLift Matchmaking Event') { response = { statusCode: 400, body: JSON .stringify({ error: 'no message available or message is not about gamelift matchmaking' }) }; return response; } const messageDetail = message.detail; const dynamoDbRequestParams = { RequestItems: { MatchmakingTickets: [] } }; if (!messageDetail.tickets || messageDetail.tickets.length == 0) { response = { statusCode: 400, body: JSON .stringify({ error: 'no tickets found' }) }; return response; } if (messageDetail.type == 'MatchmakingSucceeded' || messageDetail.type == 'MatchmakingTimedOut' || messageDetail.type == 'MatchmakingCancelled' || messageDetail.type == 'MatchmakingFailed') { for (const ticket of messageDetail.tickets) { const ticketItem = {}; ticketItem.Id = {S: ticket.ticketId}; ticketItem.Type = {S: messageDetail.type}; ticketItem. ttl = {N: (Math.floor(Date.now() / 1000) + 3600).toString()}; if (messageDetail.type == 'MatchmakingSucceeded') { ticketItem.Players = {L: []}; const players = ticket.players; for (const player of players) { const playerItem = {M: {}}; playerItem.M.PlayerId = {S: player.playerId}; if (player.playerSessionId) { playerItem.M.PlayerSessionId = {S: player.playerSessionId}; } ticketItem.Players.L.push(playerItem); } ticketItem.GameSessionInfo = { M: { IpAddress: {S: messageDetail.gameSessionInfo.ipAddress}, Port: {N: messageDetail.gameSessionInfo.port.toString()} } }; } dynamoDbRequestParams.RequestItems.MatchmakingTickets.push({ PutRequest: { Item: ticketItem } }); } } await DynamoDb.batchWriteItem(dynamoDbRequestParams) .promise().then(data => { response = { statusCode: 200, body: JSON .stringify({ success: 'ticket data has been saved to dynamodb' }) }; }) .catch(err => { response = { statusCode: 400, body: JSON .stringify({ error: err }) }; }); return response; }; DynamoDBへのアクセスが必要なため、アクセス権限に追加します 「設定」⇒「アクセス権限」で、対象ロールをクリックしてアクセス権限編集ページにとばされます JSON タブの配下に、以下の JSON 文を文末に追記します { " Effect ": " Allow ", " Action ": " dynamodb : BatchWriteItem " " Resource ": " 「関数のARN入力」 " } ここまで、DynamoDBのテーブルとイベント登録用のLambdaを作成しました。 FlexMatchの構築 FlexMatch構築用のリソースを全部そろっているため、これから、FlexMatchルールセットの設定およびMatchmakingの設定を行います。 Matchmaking のルールセットを設定します GameLiftの ダッシュ ボードに移動し「マッチメーキングルールセットの作成」をクリックしてマッチングルール設定ページに移動します ルールセットの設定ページで以下の内容を入力します ルールセット名:「PocRuleSet」としました ルールセット: プレイヤーのチームを2つ作成します 各チームに1名のプレイヤーを含めます 各プレイヤーには所属グループ(groupid)という属性があります 最終的に両チームのプレイヤー数、かつgroupidは同じにする必要があります ルールセット内容 { "name": "poc_test", "ruleLanguageVersion": "1.0", "playerAttributes": [{ "name": "groupid", "type": "number", "default": 1 }], "teams": [{ "name": "play1", "maxPlayers": 1, "minPlayers": 1 }, { "name": "play2", "maxPlayers": 1, "minPlayers": 1 }], "rules": [{ "name": "EqualGroupId", "description": "Only launch a game when the group id of players in each team matches", "type": "comparison", "measurements": [ "teams[play1].attributes[groupid])" ], "referenceValue": "teams[play2].attributes[groupid])", "operation": "=" },{ "name": "EqualTeamSizes", "description": "Only launch a game when the number of players in each team matches", "type": "comparison", "measurements": [ "count(teams[play1].players)" ], "referenceValue": "count(teams[play2].players)", "operation": "=" }] } 「ルールセットの作成」をクリックして作成します Matchmaking Configurationを設定します GameLiftの ダッシュ ボードで「マッチメーキング設定の作成」をクリックしてマッチメーキングの設定ページに移動します 以下の内容でマッチメーキングの設定を行います 名前: 「GameLiftPOCMarchmaker」としました キュー: ap-northeast-1、GameLift_PoC_Queue(手順1で作成したキュー名) リク エス トの タイムアウト :60 ルールセット名: PocRuleSet(前手順で作成したルールセット) 通知先:手順2で作成したFlexMatchEventNoficationsのARN番号 そのほか:デフォルト値 Part1 FlexMatchに関する コンポーネント の構築は、以上となります! Part2の記事は こちら です! Part3の記事は こちら です! 現在ISIDは web3領域のグループ横断組織 を立ち上げ、Web3および メタバース 領域のR&Dを行っております(カテゴリー「3DCG」の記事は こちら )。 もし本領域にご興味のある方や、一緒にチャレンジしていきたい方は、ぜひお気軽にご連絡ください! 私たちと同じチームで働いてくれる仲間を、是非お待ちしております! ISID採用ページ(Web3/メタバース/AI) 執筆: @chen.sun 、レビュー: @wakamoto.ryosuke ( Shodo で執筆されました )
こんにちは!金融ソリューション事業部の孫です。 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へのアクセスが必要なため、アクセス権限を追加します 「設定」⇒「アクセス権限」で、対象ロールをクリックしてアクセス権限編集ページにとばされます JSON タブの配下に、以下の JSON 文を文末に追記します { " Sid ": " VisualEditor0 ", " Effect ": " Allow ", " Action ": " cognito-idp:InitiateAuth ", " Resource ": " * " } ここまでは、GameLiftUnreal-CognitoLogin関数を作成しました。 次に、 API Gateway において、関数を外から呼びだす API を作成します。 AWS マネジメントコンソールの検索窓口に「 API Gateway 」を入力して API Gateway のトップページに移動します RestAPIを選択し、「構築」ボタンを押して API の作成ページを開きます 以下の内容で API を作成します プロトコル を選択する:Rest 新しい API の作成 : New API 名前と説明 API 名 : 「GameLiftUnreal- API 」としました その他:デフォルト値 「 API の作成」ボタンを押して API を作成しました。 作成した API を開いて「アクション」⇒「リソースの作成」でloginリソースを作成します 「アクション」⇒「メソッドの作成」で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コード 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 リソースを作成します getplayerdata API リソースの作成 Resource Name:getplayerdata Resource Path : /getplayerdata その他:デフォルト値 startmatchmaking API リソースの作成 Resource Name:startmatchmaking Resource Path:/startmatchmaking pollmatchmaking API リソースの作成 Resource Name:pollmatchmaking Resource Path:/pollMatchmaking 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 API ごとに認証を設定します 図のように各 API メソッドごとに「メソッドリク エス ト」⇒「認可」にCognito認証を設定します ここまで、Cognitoおよびバックエンド API の作成は、以上となります! Part3 では、バックエンド API を用いてUEクライアントへの組込みおよびマッチングの検証を行います。 現在ISIDは web3領域のグループ横断組織 を立ち上げ、Web3および メタバース 領域のR&Dを行っております(カテゴリー「3DCG」の記事は こちら )。 もし本領域にご興味のある方や、一緒にチャレンジしていきたい方は、ぜひお気軽にご連絡ください! 私たちと同じチームで働いてくれる仲間を、是非お待ちしております! ISID採用ページ(Web3/メタバース/AI) 執筆: @chen.sun 、レビュー: @wakamoto.ryosuke ( Shodo で執筆されました )
こんにちは!金融ソリューション事業部の孫です。 Part1の記事 と Part2の記事 では、マッチングに関する AWS 側のリソースを全部構築しました。 Part3である今回は、構築したバックエンド API をUEクライアントに組み込んでマッチング検証を行います! Part1、Part2が未見の方は、ぜひ内容をご確認いただきたいです! Part1の記事は こちら です! Part2の記事は こちら です! UEクライアントへの API 組み込み 前回の記事 で実装したクライアントをベースに、上記に作成した API の呼び出し機能を実装します。 最後に、組込み完了後のクライアントを使ってマッチング処理をテストします。 CPPファイルの作成 プロジェクトフォルダに移動しUE5 Editorファイル(Gamelift_UE5.uproject)を開きます GameMode CPPを作成します C++ Classes 配下に、右クリックして「New C++ class」を選択します 「Game Mode Base」を選択して「OfflineGameMode」名前のCPPを作成します 下記図のように「world Setting」パネルを開いて、その中の「GameMode Override」項目内容は上記作った「OfflineGameMode」に設定します ログイン画面をコン トロール するCPPを作成します 上記と同様な手順で、「New C++ Class」を選択します 「UserWidget」を選択して「OfflineMainMenuWidget」名前のCPPを作成します VS2019でOfflineGameMode CPPファイルを編集します 「Gamelift_UE5.Build.cs」に「"Http", " Json ", "JsonUtilities"」モジュールを追加します 「OfflineGameMode.cpp」と「OfflineGameMode.h」を以下のように編集します ### <OfflineGameMode.cpp> #include "OfflineGameMode.h" #include "Gamelift_UE5Character.h" #include "UObject/ConstructorHelpers.h" AOfflineGameMode::AOfflineGameMode() { // set default pawn class to our Blueprinted character static ConstructorHelpers::FClassFinder<APawn> PlayerPawnBPClass(TEXT("/Game/ThirdPerson/Blueprints/BP_ThirdPersonCharacter")); if (PlayerPawnBPClass.Class != NULL) { DefaultPawnClass = PlayerPawnBPClass.Class; } } ### <OfflineGameMode.h> #pragma once #include "CoreMinimal.h" #include "GameFramework/GameModeBase.h" #include "OfflineGameMode.generated.h" UCLASS() class GAMELIFT_UE5_API AOfflineGameMode : public AGameModeBase { GENERATED_BODY() public: AOfflineGameMode(); }; VS2019でOfflineMainMenuWidget CPPファイルを編集します プレイヤーがログインボタンを押したら、マッチング処理が開始されます。 処理の流れとしては、「ログインリク エス ト(LoginRequest) ⇒ マッチング開始リク エス ト(StartMatchMakingRequest) ⇒ マッチング結果取得リク エス ト(PollMatchMakingRequest)」順番で API を呼び出し結果を取得します マッチング結果取得にあたり、「GetWorld()->GetTimerManager().SetTimer」タイマーを設定することによって1秒のポーリング間隔で結果を取得しています OfflineMainMenuWidget.hコード #pragma once #include "Http.h" #include "CoreMinimal.h" #include "Blueprint/UserWidget.h" #include "OfflineMainMenuWidget.generated.h" UCLASS() class GAMELIFT_UE5_ API UOfflineMainMenuWidget : public UUserWidget { GENERATED_BODY() public: UOfflineMainMenuWidget(const FObjectInitializer& ObjectInitializer); UFUNCTION(BlueprintCallable) void OnLoginClicked(); UPROPERTY(EditAnywhere) FString ApiGatewayEndpoint; UPROPERTY(EditAnywhere) FString LoginURI; UPROPERTY(EditAnywhere) FString StartMatchMakingURI; UPROPERTY(EditAnywhere) FString PollMatchMakingURI; UPROPERTY(BluePrintReadWrite) FString user; UPROPERTY(BluePrintReadWrite) FString pass; UPROPERTY() FTimerHandle PollMatchmakingHandle; private: FHttpModule* Http; FString IdToken; FString MatchmakingTicketId; void LoginRequest(FString usr, FString pwd ); void OnLoginResponse(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful); void StartMatchMakingRequest(FString idt ); void StartMatchMakingResponse(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful); void PollMatchMakingRequest(); void PollMatchMakingResponse(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful); }; OfflineMainMenuWidget.cppコード #include "OfflineMainMenuWidget.h" #include " Json .h" #include "JsonUtilities.h" #include "Kismet/GameplayStatics.h" UOfflineMainMenuWidget::UOfflineMainMenuWidget(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) { Http = &FHttpModule::Get(); ApiGatewayEndpoint = FString::Printf(TEXT("「 API URLで書き換え」")); LoginURI = FString::Printf(TEXT("/login")); StartMatchMakingURI = FString::Printf(TEXT("/startmatchmaking")); PollMatchMakingURI = FString::Printf(TEXT("/pollmatchmaking")); IdToken = ""; MatchmakingTicketId = ""; } void UOfflineMainMenuWidget::OnLoginClicked() { LoginRequest(user, pass); } void UOfflineMainMenuWidget::LoginRequest(FString usr, FString pwd ) { TSharedPtr JsonObject = MakeShareable(new FJsonObject()); JsonObject->SetStringField(TEXT("username"), *FString::Printf(TEXT("%s"), *usr)); JsonObject->SetStringField(TEXT("password"), *FString::Printf(TEXT("%s"), * pwd )); FString JsonBody; TSharedRef > JsonWriter = TJsonWriterFactory ::Create(&JsonBody); FJsonSerializer::Serialize(JsonObject.ToSharedRef(), JsonWriter); TSharedRef LoginHttpRequest = Http->CreateRequest(); LoginHttpRequest->SetVerb("POST"); LoginHttpRequest->SetURL(ApiGatewayEndpoint + LoginURI); LoginHttpRequest->SetHeader("Content-Type", "application/ json "); LoginHttpRequest->SetContentAsString(JsonBody); LoginHttpRequest->OnProcessRequestComplete().BindUObject(this, &UOfflineMainMenuWidget::OnLoginResponse); LoginHttpRequest->ProcessRequest(); } void UOfflineMainMenuWidget::OnLoginResponse(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bwasSuccessful) { if (bwasSuccessful) { TSharedPtr JsonObject; TSharedRef > Reader = TJsonReaderFactory ::Create(Response->GetContentAsString()); if (FJsonSerializer::Deserialize(Reader, JsonObject)) { IdToken = JsonObject->GetObjectField("tokens")->GetStringField("IdToken"); StartMatchMakingRequest(IdToken); } } } void UOfflineMainMenuWidget::StartMatchMakingRequest(FString idt ) { TSharedRef StartMatchMakingHttpRequest = Http->CreateRequest(); StartMatchMakingHttpRequest->SetVerb("GET"); StartMatchMakingHttpRequest->SetURL(ApiGatewayEndpoint + StartMatchMakingURI); StartMatchMakingHttpRequest->SetHeader("Content-type", "application/ json "); StartMatchMakingHttpRequest->SetHeader("Authorization", idt ); StartMatchMakingHttpRequest->OnProcessRequestComplete().BindUObject(this, &UOfflineMainMenuWidget::StartMatchMakingResponse); StartMatchMakingHttpRequest->ProcessRequest(); } void UOfflineMainMenuWidget::StartMatchMakingResponse(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bwasSuccessful) { if (bwasSuccessful) { TSharedPtr JsonObject; TSharedRef > Reader = TJsonReaderFactory ::Create(Response->GetContentAsString()); if (FJsonSerializer::Deserialize(Reader, JsonObject)) { if (JsonObject->HasField("ticketId")) { MatchmakingTicketId = JsonObject->GetStringField("ticketId"); GetWorld()->GetTimerManager().SetTimer(PollMatchmakingHandle, this, &UOfflineMainMenuWidget::PollMatchMakingRequest, 1.0f, true, 1.0f); } } } } void UOfflineMainMenuWidget::PollMatchMakingRequest() { TSharedPtr RequestObj = MakeShareable(new FJsonObject); RequestObj->SetStringField("ticketId", MatchmakingTicketId); FString RequestBody; TSharedRef > Writer = TJsonWriterFactory ::Create(&RequestBody); if (FJsonSerializer::Serialize(RequestObj.ToSharedRef(), Writer)) { TSharedRef PollMatchMakingHttpRequest = Http->CreateRequest(); PollMatchMakingHttpRequest->SetVerb("POST"); PollMatchMakingHttpRequest->SetURL(ApiGatewayEndpoint + PollMatchMakingURI); PollMatchMakingHttpRequest->SetHeader("Content-type", "application/ json "); PollMatchMakingHttpRequest->SetHeader("Authorization", IdToken); PollMatchMakingHttpRequest->OnProcessRequestComplete().BindUObject(this, &UOfflineMainMenuWidget::PollMatchMakingResponse); PollMatchMakingHttpRequest->SetContentAsString(RequestBody); PollMatchMakingHttpRequest->ProcessRequest(); } } void UOfflineMainMenuWidget::PollMatchMakingResponse(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bwasSuccessful) { if (bwasSuccessful) { TSharedPtr JsonObject; TSharedRef > Reader = TJsonReaderFactory ::Create(Response->GetContentAsString()); if (FJsonSerializer::Deserialize(Reader, JsonObject)) { FString IpAddress = JsonObject->GetObjectField("PlayerSession")->GetStringField("IpAddress"); FString Port = JsonObject->GetObjectField("PlayerSession")->GetStringField("Port"); TArray > Players = JsonObject->GetObjectField("Players")->GetArrayField("L"); TSharedPtr Player = Players[0]->AsObject()->GetObjectField("M"); FString PlayerSessionId = Player->GetObjectField("PlayerSessionId")->GetStringField("S"); FString PlayerId = Player->GetObjectField("PlayerId")->GetStringField("S"); FString LevelName = IpAddress + ":" + Port; const FString& Options = "?PlayerSessionId=" + PlayerSessionId + "?PlayerId=" + PlayerId; UGameplayStatics::OpenLevel(GetWorld(), FName(*LevelName), false, Options); } } } ユーザーログイン用BluePrintの作成 Blueprintsフォルダに移動し、右クリックして「 User Interface 」⇒「 Widget Blueprint」で「WBP_OfflineMainMenu」BluePrintを作成します 「WBP_OfflineMainMenu」BluePrintを選択し、「Open Level Blueprint」をクリックしてBlueprintエディターを開きます 以下の図のようにBluePrintを編集します ゲーム開始をトリガーとして、ユーザー名とパスワード入力のログイン画面を表示します 「WBP_OfflineMainMenu」BluePrintをダブルクリックし、以下のようにユーザー名とパスワード入力の画面をデザインします Loginボタンの「OnClicked Events」を追加します イベント処理では、プレイヤーが入力したユーザー名とパスワードを取得し、ログイン API をリク エス トします ここまでは、クライアント側の組込みが完了しました。 マッチング機能の検証 今回、ゲームセッションは3つ作成しました。 クライアントを6つ立ち上げて、ログイン後にそれぞれのゲームセッションにランダムにマッチングで振り分けられることを確認します。 ログイン前の状態 クライアント確認 (入力待ちの状態) GameLift側の確認 ゲームセッションが「0」であること ログイン後の状態 クライアント確認 2人ずつマッチングされて各クライアント画面でプレイヤー2人が確認されます GameLift側の確認 ゲームセッションが「3」であること 各ゲームセッション配下にプレイヤーセッションが「2」であること 終わりに 今回はFlexMatch機能を活用して、マッチング基盤を構築してみました。 この基盤は、単なるゲームで使われるものだけではなく、 メタバース 上でも活用可能だと考えられます。例えばイベント開催時にユーザー間のコミュニケーションを増やすことを目的として、同じ地域や性格のユーザーを同じルームに配置する、などの ユースケース が挙げられます。 ゲームで用いられる上記の技術は、将来 メタバース 上でますます活用されていくと思いますので、引き続きゲーム領域のサーバーサイド技術を学習していきたいです。 現在ISIDは web3領域のグループ横断組織 を立ち上げ、Web3および メタバース 領域のR&Dを行っております(カテゴリー「3DCG」の記事は こちら )。 もし本領域にご興味のある方や、一緒にチャレンジしていきたい方は、ぜひお気軽にご連絡ください! 私たちと同じチームで働いてくれる仲間を、是非お待ちしております! ISID採用ページ(Web3/メタバース/AI) 参考 https://docs.aws.amazon.com/ja_jp/gamelift/latest/flexmatchguide/match-events.html https://docs.unrealengine.com/5.0/en-US/setting-up-a-game-mode-in-unreal-engine/ https://docs.unrealengine.com/5.0/en-US/blueprints-visual-scripting-in-unreal-engine/ https://www.youtube.com/watch?v=lhABExDSpHE&list=PLuGWzrvNze7LEn4db8h3Jl325-asqqgP2&index=7 執筆: @chen.sun 、レビュー: @wakamoto.ryosuke ( Shodo で執筆されました )
こんにちは!金融ソリューション事業部の孫です。 Part1の記事 と Part2の記事 では、マッチングに関する AWS 側のリソースを全部構築しました。 Part3である今回は、構築したバックエンド API をUEクライアントに組み込んでマッチング検証を行います! Part1、Part2が未見の方は、ぜひ内容をご確認いただきたいです! Part1の記事は こちら です! Part2の記事は こちら です! UEクライアントへの API 組み込み 前回の記事 で実装したクライアントをベースに、上記に作成した API の呼び出し機能を実装します。 最後に、組込み完了後のクライアントを使ってマッチング処理をテストします。 CPPファイルの作成 プロジェクトフォルダに移動しUE5 Editorファイル(Gamelift_UE5.uproject)を開きます GameMode CPPを作成します C++ Classes 配下に、右クリックして「New C++ class」を選択します 「Game Mode Base」を選択して「OfflineGameMode」名前のCPPを作成します 下記図のように「world Setting」パネルを開いて、その中の「GameMode Override」項目内容は上記作った「OfflineGameMode」に設定します ログイン画面をコン トロール するCPPを作成します 上記と同様な手順で、「New C++ Class」を選択します 「UserWidget」を選択して「OfflineMainMenuWidget」名前のCPPを作成します VS2019でOfflineGameMode CPPファイルを編集します 「Gamelift_UE5.Build.cs」に「"Http", " Json ", "JsonUtilities"」モジュールを追加します 「OfflineGameMode.cpp」と「OfflineGameMode.h」を以下のように編集します ### <OfflineGameMode.cpp> #include "OfflineGameMode.h" #include "Gamelift_UE5Character.h" #include "UObject/ConstructorHelpers.h" AOfflineGameMode::AOfflineGameMode() { // set default pawn class to our Blueprinted character static ConstructorHelpers::FClassFinder<APawn> PlayerPawnBPClass(TEXT("/Game/ThirdPerson/Blueprints/BP_ThirdPersonCharacter")); if (PlayerPawnBPClass.Class != NULL) { DefaultPawnClass = PlayerPawnBPClass.Class; } } ### <OfflineGameMode.h> #pragma once #include "CoreMinimal.h" #include "GameFramework/GameModeBase.h" #include "OfflineGameMode.generated.h" UCLASS() class GAMELIFT_UE5_API AOfflineGameMode : public AGameModeBase { GENERATED_BODY() public: AOfflineGameMode(); }; VS2019でOfflineMainMenuWidget CPPファイルを編集します プレイヤーがログインボタンを押したら、マッチング処理が開始されます。 処理の流れとしては、「ログインリク エス ト(LoginRequest) ⇒ マッチング開始リク エス ト(StartMatchMakingRequest) ⇒ マッチング結果取得リク エス ト(PollMatchMakingRequest)」順番で API を呼び出し結果を取得します マッチング結果取得にあたり、「GetWorld()->GetTimerManager().SetTimer」タイマーを設定することによって1秒のポーリング間隔で結果を取得しています OfflineMainMenuWidget.hコード #pragma once #include "Http.h" #include "CoreMinimal.h" #include "Blueprint/UserWidget.h" #include "OfflineMainMenuWidget.generated.h" UCLASS() class GAMELIFT_UE5_ API UOfflineMainMenuWidget : public UUserWidget { GENERATED_BODY() public: UOfflineMainMenuWidget(const FObjectInitializer& ObjectInitializer); UFUNCTION(BlueprintCallable) void OnLoginClicked(); UPROPERTY(EditAnywhere) FString ApiGatewayEndpoint; UPROPERTY(EditAnywhere) FString LoginURI; UPROPERTY(EditAnywhere) FString StartMatchMakingURI; UPROPERTY(EditAnywhere) FString PollMatchMakingURI; UPROPERTY(BluePrintReadWrite) FString user; UPROPERTY(BluePrintReadWrite) FString pass; UPROPERTY() FTimerHandle PollMatchmakingHandle; private: FHttpModule* Http; FString IdToken; FString MatchmakingTicketId; void LoginRequest(FString usr, FString pwd ); void OnLoginResponse(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful); void StartMatchMakingRequest(FString idt ); void StartMatchMakingResponse(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful); void PollMatchMakingRequest(); void PollMatchMakingResponse(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful); }; OfflineMainMenuWidget.cppコード #include "OfflineMainMenuWidget.h" #include " Json .h" #include "JsonUtilities.h" #include "Kismet/GameplayStatics.h" UOfflineMainMenuWidget::UOfflineMainMenuWidget(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) { Http = &FHttpModule::Get(); ApiGatewayEndpoint = FString::Printf(TEXT("「 API URLで書き換え」")); LoginURI = FString::Printf(TEXT("/login")); StartMatchMakingURI = FString::Printf(TEXT("/startmatchmaking")); PollMatchMakingURI = FString::Printf(TEXT("/pollmatchmaking")); IdToken = ""; MatchmakingTicketId = ""; } void UOfflineMainMenuWidget::OnLoginClicked() { LoginRequest(user, pass); } void UOfflineMainMenuWidget::LoginRequest(FString usr, FString pwd ) { TSharedPtr JsonObject = MakeShareable(new FJsonObject()); JsonObject->SetStringField(TEXT("username"), *FString::Printf(TEXT("%s"), *usr)); JsonObject->SetStringField(TEXT("password"), *FString::Printf(TEXT("%s"), * pwd )); FString JsonBody; TSharedRef > JsonWriter = TJsonWriterFactory ::Create(&JsonBody); FJsonSerializer::Serialize(JsonObject.ToSharedRef(), JsonWriter); TSharedRef LoginHttpRequest = Http->CreateRequest(); LoginHttpRequest->SetVerb("POST"); LoginHttpRequest->SetURL(ApiGatewayEndpoint + LoginURI); LoginHttpRequest->SetHeader("Content-Type", "application/ json "); LoginHttpRequest->SetContentAsString(JsonBody); LoginHttpRequest->OnProcessRequestComplete().BindUObject(this, &UOfflineMainMenuWidget::OnLoginResponse); LoginHttpRequest->ProcessRequest(); } void UOfflineMainMenuWidget::OnLoginResponse(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bwasSuccessful) { if (bwasSuccessful) { TSharedPtr JsonObject; TSharedRef > Reader = TJsonReaderFactory ::Create(Response->GetContentAsString()); if (FJsonSerializer::Deserialize(Reader, JsonObject)) { IdToken = JsonObject->GetObjectField("tokens")->GetStringField("IdToken"); StartMatchMakingRequest(IdToken); } } } void UOfflineMainMenuWidget::StartMatchMakingRequest(FString idt ) { TSharedRef StartMatchMakingHttpRequest = Http->CreateRequest(); StartMatchMakingHttpRequest->SetVerb("GET"); StartMatchMakingHttpRequest->SetURL(ApiGatewayEndpoint + StartMatchMakingURI); StartMatchMakingHttpRequest->SetHeader("Content-type", "application/ json "); StartMatchMakingHttpRequest->SetHeader("Authorization", idt ); StartMatchMakingHttpRequest->OnProcessRequestComplete().BindUObject(this, &UOfflineMainMenuWidget::StartMatchMakingResponse); StartMatchMakingHttpRequest->ProcessRequest(); } void UOfflineMainMenuWidget::StartMatchMakingResponse(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bwasSuccessful) { if (bwasSuccessful) { TSharedPtr JsonObject; TSharedRef > Reader = TJsonReaderFactory ::Create(Response->GetContentAsString()); if (FJsonSerializer::Deserialize(Reader, JsonObject)) { if (JsonObject->HasField("ticketId")) { MatchmakingTicketId = JsonObject->GetStringField("ticketId"); GetWorld()->GetTimerManager().SetTimer(PollMatchmakingHandle, this, &UOfflineMainMenuWidget::PollMatchMakingRequest, 1.0f, true, 1.0f); } } } } void UOfflineMainMenuWidget::PollMatchMakingRequest() { TSharedPtr RequestObj = MakeShareable(new FJsonObject); RequestObj->SetStringField("ticketId", MatchmakingTicketId); FString RequestBody; TSharedRef > Writer = TJsonWriterFactory ::Create(&RequestBody); if (FJsonSerializer::Serialize(RequestObj.ToSharedRef(), Writer)) { TSharedRef PollMatchMakingHttpRequest = Http->CreateRequest(); PollMatchMakingHttpRequest->SetVerb("POST"); PollMatchMakingHttpRequest->SetURL(ApiGatewayEndpoint + PollMatchMakingURI); PollMatchMakingHttpRequest->SetHeader("Content-type", "application/ json "); PollMatchMakingHttpRequest->SetHeader("Authorization", IdToken); PollMatchMakingHttpRequest->OnProcessRequestComplete().BindUObject(this, &UOfflineMainMenuWidget::PollMatchMakingResponse); PollMatchMakingHttpRequest->SetContentAsString(RequestBody); PollMatchMakingHttpRequest->ProcessRequest(); } } void UOfflineMainMenuWidget::PollMatchMakingResponse(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bwasSuccessful) { if (bwasSuccessful) { TSharedPtr JsonObject; TSharedRef > Reader = TJsonReaderFactory ::Create(Response->GetContentAsString()); if (FJsonSerializer::Deserialize(Reader, JsonObject)) { FString IpAddress = JsonObject->GetObjectField("PlayerSession")->GetStringField("IpAddress"); FString Port = JsonObject->GetObjectField("PlayerSession")->GetStringField("Port"); TArray > Players = JsonObject->GetObjectField("Players")->GetArrayField("L"); TSharedPtr Player = Players[0]->AsObject()->GetObjectField("M"); FString PlayerSessionId = Player->GetObjectField("PlayerSessionId")->GetStringField("S"); FString PlayerId = Player->GetObjectField("PlayerId")->GetStringField("S"); FString LevelName = IpAddress + ":" + Port; const FString& Options = "?PlayerSessionId=" + PlayerSessionId + "?PlayerId=" + PlayerId; UGameplayStatics::OpenLevel(GetWorld(), FName(*LevelName), false, Options); } } } ユーザーログイン用BluePrintの作成 Blueprintsフォルダに移動し、右クリックして「 User Interface 」⇒「 Widget Blueprint」で「WBP_OfflineMainMenu」BluePrintを作成します 「WBP_OfflineMainMenu」BluePrintを選択し、「Open Level Blueprint」をクリックしてBlueprintエディターを開きます 以下の図のようにBluePrintを編集します ゲーム開始をトリガーとして、ユーザー名とパスワード入力のログイン画面を表示します 「WBP_OfflineMainMenu」BluePrintをダブルクリックし、以下のようにユーザー名とパスワード入力の画面をデザインします Loginボタンの「OnClicked Events」を追加します イベント処理では、プレイヤーが入力したユーザー名とパスワードを取得し、ログイン API をリク エス トします ここまでは、クライアント側の組込みが完了しました。 マッチング機能の検証 今回、ゲームセッションは3つ作成しました。 クライアントを6つ立ち上げて、ログイン後にそれぞれのゲームセッションにランダムにマッチングで振り分けられることを確認します。 ログイン前の状態 クライアント確認 (入力待ちの状態) GameLift側の確認 ゲームセッションが「0」であること ログイン後の状態 クライアント確認 2人ずつマッチングされて各クライアント画面でプレイヤー2人が確認されます GameLift側の確認 ゲームセッションが「3」であること 各ゲームセッション配下にプレイヤーセッションが「2」であること 終わりに 今回はFlexMatch機能を活用して、マッチング基盤を構築してみました。 この基盤は、単なるゲームで使われるものだけではなく、 メタバース 上でも活用可能だと考えられます。例えばイベント開催時にユーザー間のコミュニケーションを増やすことを目的として、同じ地域や性格のユーザーを同じルームに配置する、などの ユースケース が挙げられます。 ゲームで用いられる上記の技術は、将来 メタバース 上でますます活用されていくと思いますので、引き続きゲーム領域のサーバーサイド技術を学習していきたいです。 現在ISIDは web3領域のグループ横断組織 を立ち上げ、Web3および メタバース 領域のR&Dを行っております(カテゴリー「3DCG」の記事は こちら )。 もし本領域にご興味のある方や、一緒にチャレンジしていきたい方は、ぜひお気軽にご連絡ください! 私たちと同じチームで働いてくれる仲間を、是非お待ちしております! ISID採用ページ(Web3/メタバース/AI) 参考 https://docs.aws.amazon.com/ja_jp/gamelift/latest/flexmatchguide/match-events.html https://docs.unrealengine.com/5.0/en-US/setting-up-a-game-mode-in-unreal-engine/ https://docs.unrealengine.com/5.0/en-US/blueprints-visual-scripting-in-unreal-engine/ https://www.youtube.com/watch?v=lhABExDSpHE&list=PLuGWzrvNze7LEn4db8h3Jl325-asqqgP2&index=7 執筆: @chen.sun 、レビュー: @wakamoto.ryosuke ( Shodo で執筆されました )
こんにちは!金融ソリューション事業部の孫です。 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へのアクセスが必要なため、アクセス権限を追加します 「設定」⇒「アクセス権限」で、対象ロールをクリックしてアクセス権限編集ページにとばされます JSON タブの配下に、以下の JSON 文を文末に追記します { " Sid ": " VisualEditor0 ", " Effect ": " Allow ", " Action ": " cognito-idp:InitiateAuth ", " Resource ": " * " } ここまでは、GameLiftUnreal-CognitoLogin関数を作成しました。 次に、 API Gateway において、関数を外から呼びだす API を作成します。 AWS マネジメントコンソールの検索窓口に「 API Gateway 」を入力して API Gateway のトップページに移動します RestAPIを選択し、「構築」ボタンを押して API の作成ページを開きます 以下の内容で API を作成します プロトコル を選択する:Rest 新しい API の作成 : New API 名前と説明 API 名 : 「GameLiftUnreal- API 」としました その他:デフォルト値 「 API の作成」ボタンを押して API を作成しました。 作成した API を開いて「アクション」⇒「リソースの作成」でloginリソースを作成します 「アクション」⇒「メソッドの作成」で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コード 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 リソースを作成します getplayerdata API リソースの作成 Resource Name:getplayerdata Resource Path : /getplayerdata その他:デフォルト値 startmatchmaking API リソースの作成 Resource Name:startmatchmaking Resource Path:/startmatchmaking pollmatchmaking API リソースの作成 Resource Name:pollmatchmaking Resource Path:/pollMatchmaking 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 API ごとに認証を設定します 図のように各 API メソッドごとに「メソッドリク エス ト」⇒「認可」にCognito認証を設定します ここまで、Cognitoおよびバックエンド API の作成は、以上となります! Part3 では、バックエンド API を用いてUEクライアントへの組込みおよびマッチングの検証を行います。 現在ISIDは web3領域のグループ横断組織 を立ち上げ、Web3および メタバース 領域のR&Dを行っております(カテゴリー「3DCG」の記事は こちら )。 もし本領域にご興味のある方や、一緒にチャレンジしていきたい方は、ぜひお気軽にご連絡ください! 私たちと同じチームで働いてくれる仲間を、是非お待ちしております! ISID採用ページ(Web3/メタバース/AI) 執筆: @chen.sun 、レビュー: @wakamoto.ryosuke ( Shodo で執筆されました )
こんにちは!金融ソリューション事業部の孫です。 今回の記事では、GameLiftを用いたUnrealEngineゲームセッションのマッチング基盤の構築をご紹介します! 実施事項が多い為、Part1~3の3記事に分けて連載します。 また、GameLiftを用いたゲームセッションの作成、接続については 前回の記事 でご紹介しておりますので、そちらもご覧いただければ幸いです。 Part1である今回は、 AWS GameLiftにおいてFlexMatchに関する コンポーネント の構築を行います。 Part2の記事は こちら です! Part3の記事は こちら です! はじめに 冒頭でGameLiftを用いてマッチング基盤を構築することを述べましたが、具体的にはGameLiftが提供するFlexMatch機能を利用します。 FlexMatch機能 とは、カスタマイズ可能なマッチメイキングサービスです。 特徴として、下記が挙げられます。 簡単にゲームにあったカスタムルールを複数作れる サーバーとの間の レイテンシー を基準にマッチング可能 時間と共にルールの条件を緩和することも可能 ユーザーによるマッチング結果の承諾機能 キューを使用してゲームセッションを効率的に配置 FlexMatchを使用することで、フレキシブルなマッチング処理を簡単に実現でき、 マルチプレイヤー ゲームをさらに楽しむことができます! 今回の検証では、GameLift以外他の AWS コンポーネント (Cognito、AmazonSNS、 Dynamo 、 API Gateway 、Lambda)も利用しています。説明をシンプルにする為、それらの コンポーネント の解説は割愛いたします。 マッチング基盤の構築手順 手順は、以下のとおりです。 【Part1】 1.ゲームセッション保管用キューの作成 2.マッチングイベント発行用 Amazon SNS の作成 3.マッチングイベント保管用 Amazon DynamoDB の作成 4.FlexMatchの構築 【Part2】 5.プレイヤー管理用 Amazon Cognitoの作成 6.マッチング処理用のバックエンド API の作成 【Part3】 7.UEクライアントへの API 組み込み 8.マッチング機能の検証 AWS の アーキテクチャ 構築全体像は、以下の アーキテクチャ 図をご参照ください。 今回はGameLiftが担当するゲーム用の クラウド サーバーの管理以外にも、以下の機能を実装します。 プレイヤーアカウントの作成、および認証認可管理(Cognito) マッチングをコン トロール するバックエンドサービス(Lambda関数、 SNS ) マッチング機能をクライアントから呼びだす API ( API Gateway ) マッチングイベントとプレイヤーデータの一時保管(DynamoDB) プレイヤーはまず Cognito 認証でアクセスキーを取得し、次にアクセスキーを用いてマッチング API にリク エス トし、最後にマッチング結果を取得してマッチングを完了します。 マッチングは現在オンライン中のプレイヤーを対象に、事前に設定したルールセットに従って処理を行います。 使用環境/ツール Unreal Engine 5.1.0 Windows10 21H2 x64 RAM 64GM, SSD 1TB NVIDIA GeForce GTX 3080Ti Gamelift SDK GameLift Cpp ServerSDK 3.4.2 GameLift Unreal plugin 3.4.0 Visual Studio 2019 version 16.11.21(以下VS2019) JavaSDK 1.8.0 ゲームセッション保管用キューの作成 FlexMatchを使ってプレイヤーマッチングを行うにあたり、一時的なゲームセッションを保管するキューを作成することで、Fleet IQサービスの最大活用およびマッチング遅延の改善を図ることができます。 GameLift ダッシュ ボードページで、「 ダッシュ ボード」⇒「キュー作成する」をクリックしてキュー作成ページを開きます キュー名を入れた上、「キューの作成」ボタンを押して、キューの作成を完了します ※今回は検証のため、その他設定はデフォルト値のままで十分です ※キューの作成にあたり、事前にFleetの作成が必要です。具体的な手順は 前回の記事 をご参照ください キュー名:「GameLift_PoC_Queue」としました マッチングイベント発行用 Amazon SNS の作成 AmazonSNS( Amazon Simple Notification Serviceの省略)の機能を利用することで、マッチング中にGameLiftが出力したイベントを非同期で拾ってくれます。 イベントによって現在マッチングのステータスを把握した上で、 ネクス トアクションを決めます。 イベントの種類については、以下が挙げられます。 MatchmakingSearching(マッチング中) PotentialMatchCreated(マッチング候補が作成済「プレイヤーの承諾前」) AcceptMatch(マッチング候補を承諾済) AcceptMatchCompleted(プレイヤーの承諾・却下または承諾の タイムアウト により、マッチングの承諾プロセス完了) MatchMakingSucceded(マッチングが正常に完了し、ゲームセッションが作成済) MatchMakingTimedOut( タイムアウト によってマッチング失敗) MatchMakingCancelled(マッチングがキャンセル済) MatchMakingFailed(マッチングでエラーが発生) ※Tips:マッチング処理をよりよくスムーズに実装するため、各イベントの返却文 参照資料 を覚えておくことをお勧めします 。 これから、AmazonSNSを作成します。 AWS マネジメントコンソールの検索窓口に「 SNS 」を入力して、 SNS のトップページに移動します 左側Menu「Topic」⇒「Topicの作成」をクリックして、Topicの作成画面が表示されます 以下の内容を入力して、「Topicの作成」ボタンを押して、 SNS を作成します 名前:「FlexMatchEventNofications」としました アクセスポリシー - オプション:アドバンストを選択し、以下の JSON 文を文末に入力します。 { " Sid ": " __console_pub_0 ", " Effect ": " Allow ", " Principal ": { " Service ": " gamelift.amazonaws.com " } , " Action ": " SNS:Publish ", " Resource ": " arn:aws:sns:your_region:your_account:your_topic_name " , } マッチングイベント保管用 Amazon DynamoDB の作成 今回の検証では、DynamoDBの役割として二つがあります AmazonSNSによって転送されたGameLiftのイベントを保管する オンライン中のプレイヤーIDやそのほかの必要な情報を一時的に預ける これから、DynamoDBを作成します。 AWS マネジメントコンソールの検索窓口に「DynamoDB」を入力して、DynamoDBのトップページに移動します 「テーブルの作成」をクリックして、イベント保管テーブルを作成します ※DynamoDBはNoSQLなので、データベースの作成がなくテーブルから作成します。 テーブル名:「MatchmakingTickets」としました パーティション キー:「Id」としました そのほかの設定:デフォルト値のまま 上記と同様な手順で、オンライン中のプレイヤーデータを保管するテーブルを作成します テーブル名:「Players」としました パーティション キー:「Id」としました そのほかの設定:デフォルト値のまま 作成したテーブルのOverviewページを開いて「Table details」⇒ 「Manage TTL 」をクリックして、 TTL Attributeを追加します。 ※DynamoDBに保存したデータは、基本は一時的なデータなので、 TTL を設定することによって自動的に消してもらうことを図ります TTL Attribute名:「 ttl 」としました ここまで、GameLiftのイベント保管テーブルを作成しました。 これから、AmazonSNSと処理連動するLambda関数を作成します。 ※Lambda関数の動作としては、AmazonSNSから受け取ったイベント情報をDynamoDBに保存します。 AWS マネジメントコンソールでLambdaのトップページに移動します 「関数の作成」ボタンをクリックして作成ページを開きます 以下の内容で関数を作成します 関数名:「TrackEvents」としました ランタイム:「NodeJs 18.x」としました 関数コードについて、以下の ソースコード を入力します 関数の役割としては、GameLiftが発行されたイベントを受け取って中身(イベント番号、イベント種類、ゲームセッション、プレイヤー情報)をDynamoDBに保存する TrackEventsコード const AWS = require(' aws - sdk '); const DynamoDb = new AWS .DynamoDB({region: 'ap-northeast-1'}); exports.handler = async (event) => { let message; let response; if (event.Records && event.Records.length > 0) { const record = event.Records[0]; if (record. Sns && record. Sns .Message) { console.log('message from gamelift: ' + record. Sns .Message); message = JSON .parse(record. Sns .Message); } } if (!message || message['detail-type'] != 'GameLift Matchmaking Event') { response = { statusCode: 400, body: JSON .stringify({ error: 'no message available or message is not about gamelift matchmaking' }) }; return response; } const messageDetail = message.detail; const dynamoDbRequestParams = { RequestItems: { MatchmakingTickets: [] } }; if (!messageDetail.tickets || messageDetail.tickets.length == 0) { response = { statusCode: 400, body: JSON .stringify({ error: 'no tickets found' }) }; return response; } if (messageDetail.type == 'MatchmakingSucceeded' || messageDetail.type == 'MatchmakingTimedOut' || messageDetail.type == 'MatchmakingCancelled' || messageDetail.type == 'MatchmakingFailed') { for (const ticket of messageDetail.tickets) { const ticketItem = {}; ticketItem.Id = {S: ticket.ticketId}; ticketItem.Type = {S: messageDetail.type}; ticketItem. ttl = {N: (Math.floor(Date.now() / 1000) + 3600).toString()}; if (messageDetail.type == 'MatchmakingSucceeded') { ticketItem.Players = {L: []}; const players = ticket.players; for (const player of players) { const playerItem = {M: {}}; playerItem.M.PlayerId = {S: player.playerId}; if (player.playerSessionId) { playerItem.M.PlayerSessionId = {S: player.playerSessionId}; } ticketItem.Players.L.push(playerItem); } ticketItem.GameSessionInfo = { M: { IpAddress: {S: messageDetail.gameSessionInfo.ipAddress}, Port: {N: messageDetail.gameSessionInfo.port.toString()} } }; } dynamoDbRequestParams.RequestItems.MatchmakingTickets.push({ PutRequest: { Item: ticketItem } }); } } await DynamoDb.batchWriteItem(dynamoDbRequestParams) .promise().then(data => { response = { statusCode: 200, body: JSON .stringify({ success: 'ticket data has been saved to dynamodb' }) }; }) .catch(err => { response = { statusCode: 400, body: JSON .stringify({ error: err }) }; }); return response; }; DynamoDBへのアクセスが必要なため、アクセス権限に追加します 「設定」⇒「アクセス権限」で、対象ロールをクリックしてアクセス権限編集ページにとばされます JSON タブの配下に、以下の JSON 文を文末に追記します { " Effect ": " Allow ", " Action ": " dynamodb : BatchWriteItem " " Resource ": " 「関数のARN入力」 " } ここまで、DynamoDBのテーブルとイベント登録用のLambdaを作成しました。 FlexMatchの構築 FlexMatch構築用のリソースを全部そろっているため、これから、FlexMatchルールセットの設定およびMatchmakingの設定を行います。 Matchmaking のルールセットを設定します GameLiftの ダッシュ ボードに移動し「マッチメーキングルールセットの作成」をクリックしてマッチングルール設定ページに移動します ルールセットの設定ページで以下の内容を入力します ルールセット名:「PocRuleSet」としました ルールセット: プレイヤーのチームを2つ作成します 各チームに1名のプレイヤーを含めます 各プレイヤーには所属グループ(groupid)という属性があります 最終的に両チームのプレイヤー数、かつgroupidは同じにする必要があります ルールセット内容 { "name": "poc_test", "ruleLanguageVersion": "1.0", "playerAttributes": [{ "name": "groupid", "type": "number", "default": 1 }], "teams": [{ "name": "play1", "maxPlayers": 1, "minPlayers": 1 }, { "name": "play2", "maxPlayers": 1, "minPlayers": 1 }], "rules": [{ "name": "EqualGroupId", "description": "Only launch a game when the group id of players in each team matches", "type": "comparison", "measurements": [ "teams[play1].attributes[groupid])" ], "referenceValue": "teams[play2].attributes[groupid])", "operation": "=" },{ "name": "EqualTeamSizes", "description": "Only launch a game when the number of players in each team matches", "type": "comparison", "measurements": [ "count(teams[play1].players)" ], "referenceValue": "count(teams[play2].players)", "operation": "=" }] } 「ルールセットの作成」をクリックして作成します Matchmaking Configurationを設定します GameLiftの ダッシュ ボードで「マッチメーキング設定の作成」をクリックしてマッチメーキングの設定ページに移動します 以下の内容でマッチメーキングの設定を行います 名前: 「GameLiftPOCMarchmaker」としました キュー: ap-northeast-1、GameLift_PoC_Queue(手順1で作成したキュー名) リク エス トの タイムアウト :60 ルールセット名: PocRuleSet(前手順で作成したルールセット) 通知先:手順2で作成したFlexMatchEventNoficationsのARN番号 そのほか:デフォルト値 Part1 FlexMatchに関する コンポーネント の構築は、以上となります! Part2の記事は こちら です! Part3の記事は こちら です! 現在ISIDは web3領域のグループ横断組織 を立ち上げ、Web3および メタバース 領域のR&Dを行っております(カテゴリー「3DCG」の記事は こちら )。 もし本領域にご興味のある方や、一緒にチャレンジしていきたい方は、ぜひお気軽にご連絡ください! 私たちと同じチームで働いてくれる仲間を、是非お待ちしております! ISID採用ページ(Web3/メタバース/AI) 執筆: @chen.sun 、レビュー: @wakamoto.ryosuke ( Shodo で執筆されました )
こんにちは!金融ソリューション事業部の山下です。 本記事では Houdini で作成したトゥーン調エフェクトを、VAT(Vertex Animation Texture)という手法を用いて Unreal Engine で利用する方法を紹介します。 Houdiniはプロシージャルにノードベースで3DCG制作が可能なDCCツールです。特にシミュレーションの機能が充実しており、特に映画/ゲーム業界等の VFX 領域で活用されております。 VAT(Vertex Animation Texture)とは 実施環境/ツール 実施手順 1. Houdiniでベースとなるジオメトリを作成 2. FLIP Solverでシミュレーションを実施 3. シミュレーション結果をメッシュに加工してVATとしてExport 4. Unreal EngineでVATを読み込み 5. Niagara Systemでエフェクトを作成 6. BluePrintでエフェクト発生ロジックを組み込み 完成イメージ 所感 参考 VAT(Vertex Animation Texture)とは VATとは文字通り、メッシュの各頂点(Vertex)に対応するアニメーション情報を、テクスチャ画像として マッピング したデータを用いて ゲームエンジン などで利用する手法です。 以下画像のように、横軸に頂点情報、縦軸を時系列(フレーム)情報として マッピング しています。これを用いて、アニメーションに必要な位置情報や回転情報を伝えることができます。 VATを用いるメリットは、クオリティとパフォーマンスです。 今回のようにHoudiniで作成したアニメーションを、 Unreal Engine やUnityなどで比較的低いパフォーマンス負荷で利用可能になります。その為、VATは主にゲーム業界などで活用されています。 こちらのような素晴らしい作例 もあるので、ぜひご覧いただけると幸いです。 VATの詳細については こちらのSidefx公式チュートリアル が詳しいので、ご興味ある方はご覧ください。 実施環境/ツール OS: Windows 11 pro GPU : NVIDIA GeForce RTX 3070Ti Laptop DCC:Houdini Indie version 19.5.534 Game Engine: Unreal Engine 5.1.1 実施手順 Houdiniで元となるジオメトリを作成 FLIP Solverでシミュレーションを実施 シミュレーション結果をメッシュに加工してVATとしてExport Unreal Engine でVATを読み込み Niagara Systemでエフェクトを作成 BluePrintでエフェクト発生ロジックを組み込み 1. Houdiniでベースとなるジオメトリを作成 シミュレーションを適用する為のソースを作成します。 ネットワークの全体像は以下のとおりです。主要な処理に絞って解説します。 最初に Sphere を作成します。 後続工程でノイズをかける為、Primitive Typeを「Polygon」、Frequencyを6に設定します。 Mountain(Attribute Noise)ノードでノイズを適用します。 好みの形になるようにNoise Value やNoise Patternを調整します。 Nullノードとして「SOURCE」と 命名 します。 ここまでのジオメトリデータを用いて、シミュレーションのソースとして扱います。 法線ベクトルを用いて、シミュレーションの初期速度値を定義していきます。 Normalノードで法線ベクトルを作成します。 Add Normals toは「Points」に設定します。 fluidsourceノードで、法線ベクトルからVolumeを作成します。 法線ベクトルデータをvelとして変換する為に、Velocity VolumesタブのSource Attributreを「N」と入力します。 速度が追加されました。 Node Infoを見ると、Voxelデータが確認できます。 Voxel数は9,660、Sizeは0.1。density以外に、速度データとしてvelが追加されていることを確認できます。 「VEL」という名称でNullノードを作成して、ソースジオメトリの作成は完了です。 2. FLIP Solverでシミュレーションを実施 1.で作成したVELノードを選択した状態で、Particle Fluidsタブの「FLIP Fluid from Object」を選択し、Enterを押下します。 AutoDopNetworkが作成されます。 DOPネットワーク内で、以下のシンプルなネットワークを作成します。 FLIP Objectノードで、ソースとなるParticleの情報を設定します。 Particleを細かくする為に、Particle Separationを0.05に設定します。 Initial DataタブのSOP Pathに、1.で生成したSOURCEを設定します。 Source Volumeノードを設定します。 Volume Pathに、1.で作成したVELノードのパスを設定します。 Volume OperationのVelocityプルダウン項目に「Add」を設定します。 SOP To DOP BindingsタブのVelocity Volumeに「vel」を設定します。 FLIP Solverノードに対して、FLIP ObjectノードとSource Volumeノードを接続します。こちらはデフォルトのままで構いません。 デフォルトで配置されているGravityノードなどは削除します。 以下のシミュレーションが完成しました。 3. シミュレーション結果をメッシュに加工してVATとしてExport 新たにGeometoryを作成して、以下のネットワークを構築します。名称はPrepare_Meshとします。 最初にDOP Import FieldsノードでDOPのシミュレーション結果をimportします。 Particle Fluid Surface ノードでParticleからメッシュを作成します。 メッシュを自然に消滅させる為に、FilteringタブのErodeにキーフレームを打ちます。 今回は25フレームを2、50フレームで4に設定することで、自然な消滅を表現します。 33フレーム時点: 40フレーム時点: 50フレーム時点: 初期のアニメーションは不要なので、Time Shiftノードを使用します。 Frameに「$F+10」とすることで初期10フレームを省略します。 ConvertノードでMeshからPolygonに変換します。 PolyReduceノードでPolygon数を削減します。 Number To Keepに2500を指定します。 Polygon数は58955から2599、15.55%に削減されました。 Attribute Blur ノードで各ポリゴンの位置を滑らかにします。 ポリゴン数の削減によるクオリティ低下を補う効果があります。 Blur 前: Blur 後: Normalノードで、Pointsに対して法線ベクトルを追加します。 レンダリング 時により滑らかな表面になります。 Labs Delete Small Partsノードで、微細なポリゴンを削除します。 UV UnwrapノードでUVを設定します。 Nullノードに「VAT_OUT」と 命名 して、Houdiniのジオメトリは完成です。 最後にoutコンテキストに移動して、Labs Vertex Animation Texturesノードを作成します。 Modeを「Dynamic Remeshing(Fluid)」、Target Engineを「 Unreal Engine 」に設定します。 Input Geometryに先ほど作成した「VAT_OUT」を設定します。 RenderPassを「First Pass(Geometry, Lookup Table, Data/Material)」と「Second Pass(Animation Textures)」それぞれに設定して、「Render All」ボタン押下でマテリアルを生成します。 「data」「geo」「 tex 」フォルダにアウトプットが格納されていたらVATの生成は完了です。 4. Unreal Engine でVATを読み込み 今回は、 Unreal Engine のFirst Personテンプレートを用います。 3.にてHoudiniで作成したLabs Vertex Animation Texturesノードの以下リンクから取得したPluginフォルダを、UEプロジェクトフォルダに「Plugin」フォルダを作成した上でコピー&ペーストします。 UEプロジェクトを再起動して、正しくPluginが読み込まれていることを確認します。 3.で生成したファイルを Unreal Engine のContent Drawerにimportします。 HDR (.exr)のデータを選択して、Scripted Asset ActionsからSide FX Set VAT HDR Texturesを実行します。 同様にNon- HDR (.jpg)のデータに対して ide FX Set VAT Non HDR Texturesを実行します。 新規マテリアルを作成します。 ResultノードのNum Customized UVsに4を入力します。 任意のColorとMF_VAT_DynamicRemeshingノードを作成して、Resultノードに接続します。 上記マテリアルから、マテリアル インスタンス を生成します。 画像の通り、Houdini VATタブで、 FPS 、importしたテクスチャそれぞれ適用します。 また、importしたDataTableの値を参考に、Houdini VAT - InstancingタブでBound値を設定します。 設定元: 設定先: これでVATによるマテリアルは完成しました。 5. Niagara Systemでエフェクトを作成 新規 Niagara Effectを作成します。 今回はSimple Sprite Burstをベースにエフェクトを作成します。 Renderタブでデフォルトで入っているSprite Renderereを削除して、Mesh Rendererを追加します。 Meshesにimportしたfbx、Materialに4.で作成したマテリアル インスタンス を設定します。 Particle Spawnタブで、Lifetimeを1にします。 作成した Niagara SystemをSceneViewに ドラッグ&ドロップ して、エフェクトが再生されたらエフェクトは完了です。 6. BluePrintでエフェクト発生ロジックを組み込み 本記事ではVAT解説記事にフォーカスするためBPの詳細説明は割愛します。 FirstPersonプロジェクトでは、クリック押下で銃弾(Projectile)を発射するBluePrintが組み込まれているので、こちらの既存BPに対してエフェクトを追加します。 作成したネットワークはこちらです。 ProjectileのEvent Hit後、Projectileの位置に対してエフェクトを発生させています。 また、そのままだとProjectileが跳ね返ってしまうのでStop Movement Immediatelyノードで動きを止めつつ、Destroy Actorノードで削除しています。 また、Projectileが当たった壁に対して水しぶきを表現する為に、Destroy Actorの後に以下BPも追加しています。 別途Houdiniでシミュレーションして作成した以下マテリアル画像を用いて、Decalを発生させています。Houdiniからの画像Exportには、Labs Maps Bakerノードを用いました。 シミュレーション結果: ExportしたNormal: ExportしたOpacity: 完成イメージ 以下、 Unreal Engine での動作イメージです。 所感 今回はVAT(Vertex Animation Texture)という手法を用いて、Houdiniのシミュレーションを Unreal Engine に導入しました。 この手法は流体だけでなく、剛体の破壊シミュレーションや布などのソフトボディ、煙や爆発などあらゆるアニメーションに使用できます。また、UnrealEngineだけでなくUnityなど他のツールにも使用できる点も利便性が高いです。 Houdiniは非常に強力かつ精緻なシミュレーションが可能になる為、VATを利用することで ゲームエンジン などに組み込まれたツールよりも表現の幅の広がりが期待できます。 現在ISIDは web3領域のグループ横断組織 を立ち上げ、Web3および メタバース 領域のR&Dを行っております(カテゴリー「3DCG」の記事は こちら )。 もし本領域にご興味のある方や、一緒にチャレンジしていきたい方は、ぜひお気軽にご連絡ください! 私たちと同じチームで働いてくれる仲間を、是非お待ちしております! ISID採用ページ(Web3/メタバース/AI) 参考 https://vimeo.com/266717949 https://www.youtube.com/watch?v=BaKNjIC66_8 https://historia.co.jp/archives/21974/ 執筆: @yamashita.yuki 、レビュー: @wakamoto.ryosuke ( Shodo で執筆されました )
こんにちは!金融ソリューション事業部の山下です。 本記事では Houdini で作成したトゥーン調エフェクトを、VAT(Vertex Animation Texture)という手法を用いて Unreal Engine で利用する方法を紹介します。 Houdiniはプロシージャルにノードベースで3DCG制作が可能なDCCツールです。特にシミュレーションの機能が充実しており、特に映画/ゲーム業界等の VFX 領域で活用されております。 VAT(Vertex Animation Texture)とは 実施環境/ツール 実施手順 1. Houdiniでベースとなるジオメトリを作成 2. FLIP Solverでシミュレーションを実施 3. シミュレーション結果をメッシュに加工してVATとしてExport 4. Unreal EngineでVATを読み込み 5. Niagara Systemでエフェクトを作成 6. BluePrintでエフェクト発生ロジックを組み込み 完成イメージ 所感 参考 VAT(Vertex Animation Texture)とは VATとは文字通り、メッシュの各頂点(Vertex)に対応するアニメーション情報を、テクスチャ画像として マッピング したデータを用いて ゲームエンジン などで利用する手法です。 以下画像のように、横軸に頂点情報、縦軸を時系列(フレーム)情報として マッピング しています。これを用いて、アニメーションに必要な位置情報や回転情報を伝えることができます。 VATを用いるメリットは、クオリティとパフォーマンスです。 今回のようにHoudiniで作成したアニメーションを、 Unreal Engine やUnityなどで比較的低いパフォーマンス負荷で利用可能になります。その為、VATは主にゲーム業界などで活用されています。 こちらのような素晴らしい作例 もあるので、ぜひご覧いただけると幸いです。 VATの詳細については こちらのSidefx公式チュートリアル が詳しいので、ご興味ある方はご覧ください。 実施環境/ツール OS: Windows 11 pro GPU : NVIDIA GeForce RTX 3070Ti Laptop DCC:Houdini Indie version 19.5.534 Game Engine: Unreal Engine 5.1.1 実施手順 Houdiniで元となるジオメトリを作成 FLIP Solverでシミュレーションを実施 シミュレーション結果をメッシュに加工してVATとしてExport Unreal Engine でVATを読み込み Niagara Systemでエフェクトを作成 BluePrintでエフェクト発生ロジックを組み込み 1. Houdiniでベースとなるジオメトリを作成 シミュレーションを適用する為のソースを作成します。 ネットワークの全体像は以下のとおりです。主要な処理に絞って解説します。 最初に Sphere を作成します。 後続工程でノイズをかける為、Primitive Typeを「Polygon」、Frequencyを6に設定します。 Mountain(Attribute Noise)ノードでノイズを適用します。 好みの形になるようにNoise Value やNoise Patternを調整します。 Nullノードとして「SOURCE」と 命名 します。 ここまでのジオメトリデータを用いて、シミュレーションのソースとして扱います。 法線ベクトルを用いて、シミュレーションの初期速度値を定義していきます。 Normalノードで法線ベクトルを作成します。 Add Normals toは「Points」に設定します。 fluidsourceノードで、法線ベクトルからVolumeを作成します。 法線ベクトルデータをvelとして変換する為に、Velocity VolumesタブのSource Attributreを「N」と入力します。 速度が追加されました。 Node Infoを見ると、Voxelデータが確認できます。 Voxel数は9,660、Sizeは0.1。density以外に、速度データとしてvelが追加されていることを確認できます。 「VEL」という名称でNullノードを作成して、ソースジオメトリの作成は完了です。 2. FLIP Solverでシミュレーションを実施 1.で作成したVELノードを選択した状態で、Particle Fluidsタブの「FLIP Fluid from Object」を選択し、Enterを押下します。 AutoDopNetworkが作成されます。 DOPネットワーク内で、以下のシンプルなネットワークを作成します。 FLIP Objectノードで、ソースとなるParticleの情報を設定します。 Particleを細かくする為に、Particle Separationを0.05に設定します。 Initial DataタブのSOP Pathに、1.で生成したSOURCEを設定します。 Source Volumeノードを設定します。 Volume Pathに、1.で作成したVELノードのパスを設定します。 Volume OperationのVelocityプルダウン項目に「Add」を設定します。 SOP To DOP BindingsタブのVelocity Volumeに「vel」を設定します。 FLIP Solverノードに対して、FLIP ObjectノードとSource Volumeノードを接続します。こちらはデフォルトのままで構いません。 デフォルトで配置されているGravityノードなどは削除します。 以下のシミュレーションが完成しました。 3. シミュレーション結果をメッシュに加工してVATとしてExport 新たにGeometoryを作成して、以下のネットワークを構築します。名称はPrepare_Meshとします。 最初にDOP Import FieldsノードでDOPのシミュレーション結果をimportします。 Particle Fluid Surface ノードでParticleからメッシュを作成します。 メッシュを自然に消滅させる為に、FilteringタブのErodeにキーフレームを打ちます。 今回は25フレームを2、50フレームで4に設定することで、自然な消滅を表現します。 33フレーム時点: 40フレーム時点: 50フレーム時点: 初期のアニメーションは不要なので、Time Shiftノードを使用します。 Frameに「$F+10」とすることで初期10フレームを省略します。 ConvertノードでMeshからPolygonに変換します。 PolyReduceノードでPolygon数を削減します。 Number To Keepに2500を指定します。 Polygon数は58955から2599、15.55%に削減されました。 Attribute Blur ノードで各ポリゴンの位置を滑らかにします。 ポリゴン数の削減によるクオリティ低下を補う効果があります。 Blur 前: Blur 後: Normalノードで、Pointsに対して法線ベクトルを追加します。 レンダリング 時により滑らかな表面になります。 Labs Delete Small Partsノードで、微細なポリゴンを削除します。 UV UnwrapノードでUVを設定します。 Nullノードに「VAT_OUT」と 命名 して、Houdiniのジオメトリは完成です。 最後にoutコンテキストに移動して、Labs Vertex Animation Texturesノードを作成します。 Modeを「Dynamic Remeshing(Fluid)」、Target Engineを「 Unreal Engine 」に設定します。 Input Geometryに先ほど作成した「VAT_OUT」を設定します。 RenderPassを「First Pass(Geometry, Lookup Table, Data/Material)」と「Second Pass(Animation Textures)」それぞれに設定して、「Render All」ボタン押下でマテリアルを生成します。 「data」「geo」「 tex 」フォルダにアウトプットが格納されていたらVATの生成は完了です。 4. Unreal Engine でVATを読み込み 今回は、 Unreal Engine のFirst Personテンプレートを用います。 3.にてHoudiniで作成したLabs Vertex Animation Texturesノードの以下リンクから取得したPluginフォルダを、UEプロジェクトフォルダに「Plugin」フォルダを作成した上でコピー&ペーストします。 UEプロジェクトを再起動して、正しくPluginが読み込まれていることを確認します。 3.で生成したファイルを Unreal Engine のContent Drawerにimportします。 HDR (.exr)のデータを選択して、Scripted Asset ActionsからSide FX Set VAT HDR Texturesを実行します。 同様にNon- HDR (.jpg)のデータに対して ide FX Set VAT Non HDR Texturesを実行します。 新規マテリアルを作成します。 ResultノードのNum Customized UVsに4を入力します。 任意のColorとMF_VAT_DynamicRemeshingノードを作成して、Resultノードに接続します。 上記マテリアルから、マテリアル インスタンス を生成します。 画像の通り、Houdini VATタブで、 FPS 、importしたテクスチャそれぞれ適用します。 また、importしたDataTableの値を参考に、Houdini VAT - InstancingタブでBound値を設定します。 設定元: 設定先: これでVATによるマテリアルは完成しました。 5. Niagara Systemでエフェクトを作成 新規 Niagara Effectを作成します。 今回はSimple Sprite Burstをベースにエフェクトを作成します。 Renderタブでデフォルトで入っているSprite Renderereを削除して、Mesh Rendererを追加します。 Meshesにimportしたfbx、Materialに4.で作成したマテリアル インスタンス を設定します。 Particle Spawnタブで、Lifetimeを1にします。 作成した Niagara SystemをSceneViewに ドラッグ&ドロップ して、エフェクトが再生されたらエフェクトは完了です。 6. BluePrintでエフェクト発生ロジックを組み込み 本記事ではVAT解説記事にフォーカスするためBPの詳細説明は割愛します。 FirstPersonプロジェクトでは、クリック押下で銃弾(Projectile)を発射するBluePrintが組み込まれているので、こちらの既存BPに対してエフェクトを追加します。 作成したネットワークはこちらです。 ProjectileのEvent Hit後、Projectileの位置に対してエフェクトを発生させています。 また、そのままだとProjectileが跳ね返ってしまうのでStop Movement Immediatelyノードで動きを止めつつ、Destroy Actorノードで削除しています。 また、Projectileが当たった壁に対して水しぶきを表現する為に、Destroy Actorの後に以下BPも追加しています。 別途Houdiniでシミュレーションして作成した以下マテリアル画像を用いて、Decalを発生させています。Houdiniからの画像Exportには、Labs Maps Bakerノードを用いました。 シミュレーション結果: ExportしたNormal: ExportしたOpacity: 完成イメージ 以下、 Unreal Engine での動作イメージです。 所感 今回はVAT(Vertex Animation Texture)という手法を用いて、Houdiniのシミュレーションを Unreal Engine に導入しました。 この手法は流体だけでなく、剛体の破壊シミュレーションや布などのソフトボディ、煙や爆発などあらゆるアニメーションに使用できます。また、UnrealEngineだけでなくUnityなど他のツールにも使用できる点も利便性が高いです。 Houdiniは非常に強力かつ精緻なシミュレーションが可能になる為、VATを利用することで ゲームエンジン などに組み込まれたツールよりも表現の幅の広がりが期待できます。 現在ISIDは web3領域のグループ横断組織 を立ち上げ、Web3および メタバース 領域のR&Dを行っております(カテゴリー「3DCG」の記事は こちら )。 もし本領域にご興味のある方や、一緒にチャレンジしていきたい方は、ぜひお気軽にご連絡ください! 私たちと同じチームで働いてくれる仲間を、是非お待ちしております! ISID採用ページ(Web3/メタバース/AI) 参考 https://vimeo.com/266717949 https://www.youtube.com/watch?v=BaKNjIC66_8 https://historia.co.jp/archives/21974/ 執筆: @yamashita.yuki 、レビュー: @wakamoto.ryosuke ( Shodo で執筆されました )
XI本部 オープン イノベーション ラボの飯田です。 最近、ChatGPTをはじめとした生成AIが注目されています。 その中で、 Gigazine のニュースで以下の話を目にしました。 gigazine.net 上記の記事に感化され、心理学でChatGPTのようなものが、どのように扱われているのかを調べています(私は大学で心理学専攻だったこともあり)。その中で、心理学系論文の プレプリント が掲載されている PsyArXiv で、GPT-3の振る舞いを 認知心理学 の観点で分析した論文を見つけました。 生成系の振る舞いを理解する上でも参考になりそうだし、ISIDでも取り組んでいるロボット技術やLabratory Automationとの接合点を見つけた気がしましたので、ISIDでの取り組みを紹介しつつ、妄想したいと思います。 Binz, M., & Schulz, E. (2022, June 21). Using cognitive psychology to understand GPT-3. https://doi.org/10.31234/osf.io/6dfgk ※ PNASに査読付き論文も公開されている ようですが、有料なので、今回は無料のPsyArXivをよんでいます。 ※念押しになりますが、以下、引用するBinz&Schulz(2022)は「ChatGPT」ではなく、「GPT-3」にて実験を行っています。 認知心理学の観点からみたGPT-3 Binz&Schulz(2022)の概要 Binz&Schulz(2022)の実験イメージ 意思決定:ヒューリスティックとバイアス(例:リンダ問題) 熟慮:認知反射テスト 因果推論:ブリケット探知器 情報の探索:多腕バンディット課題 大規模言語モデルとロボット技術・Labratory Automationとの接合の可能性 最後に 認知心理学 の観点からみたGPT-3 Binz&Schulz(2022)の概要 GPT-3に 認知心理学 で使われる課題を与え、その解答からGPT-3の特性を考察 4カテゴリ(1.意思決定 / 2.情報探索 / 3.熟慮 / 4.因果推論) についての実験を行った GPT-3は多くの課題に正解し、バンディット課題でも良い成績を残し、モデルベース 強化学習 を行っている可能性を示した。これらの結果は、GPT-3が単なる確率的なオウム返しではないことを示している。 一方で、人間の認知機能で重要な"統制的探査”や"因果推論”の能力を持っていないことも推察された これは人間とGPT-3の間で、世界についての知識習得・学習方法が違うことによって説明できる 人間は、他の人とつながり、質問をし、積極的に環境と関わることで学習する GPT-3などの大規模 言語モデル は、受動的に多くのテキストを与えられ、次に来る単語を予測することで学習する 人間が持つ認知機能の複雑さにより近づくためには、テキストを受動的に学習するだけではなく、 積極的に世界とインタ ラク ションすることが重要 と指摘している Binz&Schulz(2022)の実験イメージ 概要だけでは、イメージをつかみにくいと思いますので、簡単にBinz&Schulz(2022)がどのような実験をしたのか触れたいと思います。 意思決定: ヒューリスティック とバイアス(例:リンダ問題) 以下は、リンダのプロフィールです。 現在のリンダについて推測する場合、(1)と(2)のどちらの可能性が高いと思いますか? リンダは31歳、独身で、積極的に発言する非常に聡明です。 大学では哲学を専攻し、学生時代には差別や社会正義の問題に関心を持っていました。また、反核デモに参加していました。 1. リンダは銀行の出納係である。 2. リンダは銀行の出納係であり、フェミニスト運動の活動家である。  上記のような質問をされると、2は1の部分集合なので、確率的には1の方が間違いなく高いにもかかわらず、多くの人は2を選択する傾向にあります。 そのような「一般的な状況よりも、特殊な状況の方が起こりやすい」と誤判断することは"合接の誤謬"や"連言錯誤"と呼ばれます。 ステレオタイプ に合致した方を過大に評価しやすい意思決定プロセス(代表性 ヒューリスティクス )とされます。 →GPT-3は人と同じように、2番目の選択肢を選び、合接の誤謬に陥りました ※神谷先生がChatGPTでも試していらっしゃいます リンダ問題の答えを聞いて怒り出さないのが人間らしくない pic.twitter.com/HxxdPUbFOT — Yuki Kamitani (@ykamit) 2022年12月5日 熟慮:認知反射テスト 誤った解答がパッと浮かびやすい問題を課して、直観型か熟慮型かを見分けるようなテストです。 例)5台の機械を5分間動かすと、製品が5つできる。100台の機械で100個の製品を作るには、何分かかるか。 - パッと浮かびやすい解答:100分 - 正解:5分 →GPT-3は、多くの人間が選んでしまう、パッと浮かびやすい解答を行い不正解となりました 因果推論:ブリケット探知器 ブリケット探知器とは、以下のような装置であり、子供の因果推論能力を推定するテストです ある箱の上に、いろいろな色や形をした積み木のようなものを置く。 特定の積み木を特定の置き方で置いたとき、箱が光る。 →GPT-3は、人間と同じように、因果を見出しました 情報の探索:多腕バンディット課題 利益を最大化するための「活用」と「探索」の2種類の情報探索方法を適切に使い分けることができるか?という課題 活用:利益がどれぐらい得られるか過去に経験した手段の活用 探索:利益をさらに得られるかもしれない未知の手段の探索 このような質問が提示されて回答をしていくイメージです。 →GPT-3は人間と同等、それ以上のスコアを収めることができた。 しかし、探索方法の戦略を見ると、ランダム探索を主に行っており、統制的探査は見られなかった。 Binz&Schulz(2022)では、他にも詳しく行っていますので、気になる方は論文をお読みください。 大規模 言語モデル とロボット技術・Labratory Automationとの接合の可能性 Binz&Schulz(2022)では、「大規模 言語モデル はテキストを受動的に学習しているだけであるため、因果推論等は弱い」という指摘がありました。 その世界に介入(試行錯誤)して結果を得る取り組みとして、ロボット技術やLabratory Automation等があるのではないかと思います。 ISIDイノラボでは下記のように、ロボット技術を身近なものに活かす取り組みを色々と行っております。 ロボット技術を上手く使うことにより、ソフトウェアだけでは実現できない世界とのインタ ラク ション・フィードバックを基にした学習が可能となり、大規模 言語モデル の一層の進化が期待できるのではないでしょうか? www.isid.co.jp www.isid.co.jp また、Labratory Automationとは、下の動画のようなロボットによる科学実験の自動化技術です。 下記のNatureの動画のように、人間を介在せず、実験・試行錯誤をできます。 ※ISIDイノラボでもLabratory Automationの研究開発に取り組んでおり、その内容もいずれお伝えできればと思います。 大規模 言語モデル とロボット技術やLabratory Automationを上手く組み合わせることができたら、この論文でGPT-3の弱点と言われる因果推論もできるようになってくるのかもしれないと感じました。 認知心理学 では、身体化認知(embodied cognition)という概念があります。身体化認知では、人間の認知機能や概念知識を構築するにおいて、身体(感覚や身体的経験)が必須であるという立場です。 psychmuseum.jp ※上記P18にある通り、実験の追試に失敗したりなど、再現性問題が指摘されていたりしますが・・・ また、静的なデー タセット によるト レーニン グから学んだ表現よりも、世界との相互作用を通じて学習した表現の方が強力であるとして Embodied AIが提唱されており 、CVPRでは Embodied AIのワークショップ も開催されています。 大規模 言語モデル においても、より進化させていくにあたっては、身体化認知のような 認知科学 の知見も活かされてくるのだろうなと感じました。 最後に ChatGPTでも簡単にできるテストを試してみましたので、結果を載せておきます。 リンダ問題 最初やってみたら、実験の決まり文句だとバレました 少し修正したら、誤魔化すことができて、連言錯誤に陥りました(改変が妥当でない可能性もありますが) 認知反射テスト キレイに間違えてくれました 情報探索(適切な質問を選ぶことができるのか?) 正解しました 最後に、私たちは一緒に働いてくれる仲間を募集しています! デジタル技術を社会課題解決につなげるようなプロジェクトを推進していきたいプロジェクトマネージャーやエンジニアを募集しています。 ぜひご応募ください! ソリューションアーキテクト スマートシティ導入コンサルタント/スマートシティ戦略コンサルタント 執筆: @iida.michitaka 、レビュー: @yamada.y ( Shodo で執筆されました )
XI本部 オープン イノベーション ラボの飯田です。 最近、ChatGPTをはじめとした生成AIが注目されています。 その中で、 Gigazine のニュースで以下の話を目にしました。 gigazine.net 上記の記事に感化され、心理学でChatGPTのようなものが、どのように扱われているのかを調べています(私は大学で心理学専攻だったこともあり)。その中で、心理学系論文の プレプリント が掲載されている PsyArXiv で、GPT-3の振る舞いを 認知心理学 の観点で分析した論文を見つけました。 生成系の振る舞いを理解する上でも参考になりそうだし、ISIDでも取り組んでいるロボット技術やLabratory Automationとの接合点を見つけた気がしましたので、ISIDでの取り組みを紹介しつつ、妄想したいと思います。 Binz, M., & Schulz, E. (2022, June 21). Using cognitive psychology to understand GPT-3. https://doi.org/10.31234/osf.io/6dfgk ※ PNASに査読付き論文も公開されている ようですが、有料なので、今回は無料のPsyArXivをよんでいます。 ※念押しになりますが、以下、引用するBinz&Schulz(2022)は「ChatGPT」ではなく、「GPT-3」にて実験を行っています。 認知心理学の観点からみたGPT-3 Binz&Schulz(2022)の概要 Binz&Schulz(2022)の実験イメージ 意思決定:ヒューリスティックとバイアス(例:リンダ問題) 熟慮:認知反射テスト 因果推論:ブリケット探知器 情報の探索:多腕バンディット課題 大規模言語モデルとロボット技術・Labratory Automationとの接合の可能性 最後に 認知心理学 の観点からみたGPT-3 Binz&Schulz(2022)の概要 GPT-3に 認知心理学 で使われる課題を与え、その解答からGPT-3の特性を考察 4カテゴリ(1.意思決定 / 2.情報探索 / 3.熟慮 / 4.因果推論) についての実験を行った GPT-3は多くの課題に正解し、バンディット課題でも良い成績を残し、モデルベース 強化学習 を行っている可能性を示した。これらの結果は、GPT-3が単なる確率的なオウム返しではないことを示している。 一方で、人間の認知機能で重要な"統制的探査”や"因果推論”の能力を持っていないことも推察された これは人間とGPT-3の間で、世界についての知識習得・学習方法が違うことによって説明できる 人間は、他の人とつながり、質問をし、積極的に環境と関わることで学習する GPT-3などの大規模 言語モデル は、受動的に多くのテキストを与えられ、次に来る単語を予測することで学習する 人間が持つ認知機能の複雑さにより近づくためには、テキストを受動的に学習するだけではなく、 積極的に世界とインタ ラク ションすることが重要 と指摘している Binz&Schulz(2022)の実験イメージ 概要だけでは、イメージをつかみにくいと思いますので、簡単にBinz&Schulz(2022)がどのような実験をしたのか触れたいと思います。 意思決定: ヒューリスティック とバイアス(例:リンダ問題) 以下は、リンダのプロフィールです。 現在のリンダについて推測する場合、(1)と(2)のどちらの可能性が高いと思いますか? リンダは31歳、独身で、積極的に発言する非常に聡明です。 大学では哲学を専攻し、学生時代には差別や社会正義の問題に関心を持っていました。また、反核デモに参加していました。 1. リンダは銀行の出納係である。 2. リンダは銀行の出納係であり、フェミニスト運動の活動家である。  上記のような質問をされると、2は1の部分集合なので、確率的には1の方が間違いなく高いにもかかわらず、多くの人は2を選択する傾向にあります。 そのような「一般的な状況よりも、特殊な状況の方が起こりやすい」と誤判断することは"合接の誤謬"や"連言錯誤"と呼ばれます。 ステレオタイプ に合致した方を過大に評価しやすい意思決定プロセス(代表性 ヒューリスティクス )とされます。 →GPT-3は人と同じように、2番目の選択肢を選び、合接の誤謬に陥りました ※神谷先生がChatGPTでも試していらっしゃいます リンダ問題の答えを聞いて怒り出さないのが人間らしくない pic.twitter.com/HxxdPUbFOT — Yuki Kamitani (@ykamit) 2022年12月5日 熟慮:認知反射テスト 誤った解答がパッと浮かびやすい問題を課して、直観型か熟慮型かを見分けるようなテストです。 例)5台の機械を5分間動かすと、製品が5つできる。100台の機械で100個の製品を作るには、何分かかるか。 - パッと浮かびやすい解答:100分 - 正解:5分 →GPT-3は、多くの人間が選んでしまう、パッと浮かびやすい解答を行い不正解となりました 因果推論:ブリケット探知器 ブリケット探知器とは、以下のような装置であり、子供の因果推論能力を推定するテストです ある箱の上に、いろいろな色や形をした積み木のようなものを置く。 特定の積み木を特定の置き方で置いたとき、箱が光る。 →GPT-3は、人間と同じように、因果を見出しました 情報の探索:多腕バンディット課題 利益を最大化するための「活用」と「探索」の2種類の情報探索方法を適切に使い分けることができるか?という課題 活用:利益がどれぐらい得られるか過去に経験した手段の活用 探索:利益をさらに得られるかもしれない未知の手段の探索 このような質問が提示されて回答をしていくイメージです。 →GPT-3は人間と同等、それ以上のスコアを収めることができた。 しかし、探索方法の戦略を見ると、ランダム探索を主に行っており、統制的探査は見られなかった。 Binz&Schulz(2022)では、他にも詳しく行っていますので、気になる方は論文をお読みください。 大規模 言語モデル とロボット技術・Labratory Automationとの接合の可能性 Binz&Schulz(2022)では、「大規模 言語モデル はテキストを受動的に学習しているだけであるため、因果推論等は弱い」という指摘がありました。 その世界に介入(試行錯誤)して結果を得る取り組みとして、ロボット技術やLabratory Automation等があるのではないかと思います。 ISIDイノラボでは下記のように、ロボット技術を身近なものに活かす取り組みを色々と行っております。 ロボット技術を上手く使うことにより、ソフトウェアだけでは実現できない世界とのインタ ラク ション・フィードバックを基にした学習が可能となり、大規模 言語モデル の一層の進化が期待できるのではないでしょうか? www.isid.co.jp www.isid.co.jp また、Labratory Automationとは、下の動画のようなロボットによる科学実験の自動化技術です。 下記のNatureの動画のように、人間を介在せず、実験・試行錯誤をできます。 ※ISIDイノラボでもLabratory Automationの研究開発に取り組んでおり、その内容もいずれお伝えできればと思います。 大規模 言語モデル とロボット技術やLabratory Automationを上手く組み合わせることができたら、この論文でGPT-3の弱点と言われる因果推論もできるようになってくるのかもしれないと感じました。 認知心理学 では、身体化認知(embodied cognition)という概念があります。身体化認知では、人間の認知機能や概念知識を構築するにおいて、身体(感覚や身体的経験)が必須であるという立場です。 psychmuseum.jp ※上記P18にある通り、実験の追試に失敗したりなど、再現性問題が指摘されていたりしますが・・・ また、静的なデー タセット によるト レーニン グから学んだ表現よりも、世界との相互作用を通じて学習した表現の方が強力であるとして Embodied AIが提唱されており 、CVPRでは Embodied AIのワークショップ も開催されています。 大規模 言語モデル においても、より進化させていくにあたっては、身体化認知のような 認知科学 の知見も活かされてくるのだろうなと感じました。 最後に ChatGPTでも簡単にできるテストを試してみましたので、結果を載せておきます。 リンダ問題 最初やってみたら、実験の決まり文句だとバレました 少し修正したら、誤魔化すことができて、連言錯誤に陥りました(改変が妥当でない可能性もありますが) 認知反射テスト キレイに間違えてくれました 情報探索(適切な質問を選ぶことができるのか?) 正解しました 最後に、私たちは一緒に働いてくれる仲間を募集しています! デジタル技術を社会課題解決につなげるようなプロジェクトを推進していきたいプロジェクトマネージャーやエンジニアを募集しています。 ぜひご応募ください! ソリューションアーキテクト スマートシティ導入コンサルタント/スマートシティ戦略コンサルタント 執筆: @iida.michitaka 、レビュー: @yamada.y ( Shodo で執筆されました )
こんにちは。X(クロス) イノベーション 本部 ソフトウェアデザインセンター の山下です。 今回はユーザーに合わせてオートスケールする GitHub ActionsのRunnerについて紹介しようと思います。 課題と目的 公式の推奨している方法について 構築の手順 事前準備 terraformの実行 terraformファイルの作成 terraformの実行 GitHub Appにhookの設定を追加 実際に利用する場合 まとめ 課題と目的 GitHub Actionsを使ってCIを実施するのは一般的になってきています。 ISIDでも GitHub Actionsを活用してCIを実施しています。 しかし、 GitHub 社が提供しているrunners( GitHub -hosted runners)では困る場合があります。「 GitHub Actionsでオンプレミス環境のCI/CDを実行する方法 」の記事では、オンプレミス環境のような外部ネットワークに接続できないような環境で GitHub Actionsを利用したCIを実施する場合に、self-hosted runnersを紹介しました。 オンプレ環境で実行できないこと以外にも、 GitHub Actionsの標準環境にはいくつか不満があります。 CIの待ち時間が長い CIに使うマシンの性能を調整したい CIの費用が高い といった問題です。 GitHub -hosted runnersでは、 GitHub 社が提供しているインフラに依存しています。このため、 GitHub 側の調子が悪いときにCI開始されるまでの待ち時間が長くなったりします。 また、CIの実行に使うマシンも GitHub 社が決めたものの範囲で選ぶことになってしまいます。 以前は、選択の余地がなかったのですが、Larger Runnersがbeta版ですが公開されて状況は改善しつつあります。Larger Runnersに関する公式の記事は こちら です。 また、 GitHub Actionsの料金は Linux の場合、1分当たり$0.008 となっていて安くはないです。 単純計算すると1時間あたり $0.48 になってしまい、 AWS だとt3.2xlargeを借りることが出来てしまいます。 以下のドキュメントによると、 GitHub 標準のrunnerの性能は、2コア、7GBメモリのマシンのようなので若干コスト高になってしまいます。 https://docs.github.com/ja/actions/using-github-hosted-runners/about-github-hosted-runners#supported-runners-and-hardware-resources self-hosted runnerを導入することで、自前の環境を構築すれば待ち時間や性能の問題は解決します。しかし、高性能なマシンをずっと起動しているとコストが高くなってしまいます。 必要な時、必要な分だけEC2を起動し GitHub Actionsのself-hosted runnersを動作できれば、コストパフォーマンスに優れるself-hosted runners環境を構築できます。 今回はその環境(Autoscale self-hosted runners)について解説します。 公式の推奨している方法について GitHub の 公式の資料 にオートスケールするself-hosted runnersについての記載があります。 基本的に、この手順に従って構築すれば問題ないです。公式では以下の2種類の方法が提供されています。 actions-runner-controller/actions-runner-controller - GitHub Actions のセルフホステッド ランナー用の Kubernetes コントローラー。 philips-labs/terraform-aws-github-runner - AWS 上のスケーラブルな GitHub Actions ランナー用の Terraform モジュール。 actions-runner-controller/actions-runner-controller は kubernetes 上にself-hosted runnersを構築する物になっています。既に kubernetes を運用している場合にはこちらを導入するのが良いですが、 GitHub Actionsのためだけに kubernetes を運用するのは運用負荷を考えると難しいので今回は採用しませんでした。 philips-labs/terraform-aws-github-runner は、 AWS のAutoScaling Groupを利用して self-hosted runners環境を構築するものとなっています。構築はterraformを利用することになります。今回はこちらを使って環境構築を行います。 構築の手順 それでは早速構築の手順について解説します。 GitHub Appの設定と terraform の実行と両方を行っていく必要があるので順番に気を付けて実施してください。 事前準備 環境構築に際していくつか事前に準備しておく物があります。 terraformのインストール GitHub Appの作成 GitHub AppのClientID GitHub App用の 秘密鍵 terraformは各自インストールする必要があります。 公式サイト から適宜インストールしておいてください。 terraform-aws-github-runner のマニュアルに記載がありますが、 GitHub 上で GitHub Appを作成する必要があります。いくつか注意が必要です。 まず、最初に作成する際は、WebhookをActiveにしないで作成します。 今回は特定のプロジェクトで利用するような事を想定し、 GitHub Appに与える権限をRepositoryレベルで設定します。以下のように、Actions、Checks、MetadataをRead-onlyにします。それに加えて、AdministrationをRead & writeとしました。 terraformの実行 terraformファイルの作成 今回は以下のような2つのtfファイルを作成して実行しました。片方が VPC を作成するもの、片方が今回のself-hosted runner用の物です。 // vpc.tf module "vpc" { source = "terraform-aws-modules/vpc/aws" version = "3.11.2" name = "vpc-${local.environment}" cidr = "10.0.0.0/16" azs = ["${local.aws_region}a", "${local.aws_region}c", "${local.aws_region}d"] private_subnets = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"] public_subnets = ["10.0.101.0/24", "10.0.102.0/24", "10.0.103.0/24"] enable_dns_hostnames = true enable_nat_gateway = true map_public_ip_on_launch = false single_nat_gateway = true tags = { Environment = local.environment } // main.tf locals { version = "v1.8.0" environment = "default" aws_region = "ap-northeast-1" } provider "aws" { region = "ap-northeast-1" } variable "github_app_key_base64" {} variable "github_app_id" {} resource "random_id" "random" { byte_length = 20 } data "aws_caller_identity" "current" {} module "runners" { source = "philips-labs/github-runner/aws" version = "1.8.0" create_service_linked_role_spot = false aws_region = local.aws_region vpc_id = module.vpc.vpc_id subnet_ids = module.vpc.private_subnets prefix = local.environment tags = { costcenter = "p7-ci-template" Project = "AutoScaleTest" } github_app = { key_base64 = var.github_app_key_base64 id = var.github_app_id webhook_secret = random_id.random.hex } webhook_lambda_zip = "webhook.zip" runner_binaries_syncer_lambda_zip = "runner-binaries-syncer.zip" runners_lambda_zip = "runners.zip" enable_organization_runners = false # ランナーのラベル # github actionsの設定ファイルで指定するラベルとなる runner_extra_labels = "default,<CIで指定するラベル>" # enable access to the runners via SSM enable_ssm_on_runners = true instance_types = ["m5.large", "c5.large"] # override delay of events in seconds delay_webhook_event = 5 runners_maximum_count = 1 # set up a fifo queue to remain order fifo_build_queue = true # override scaling down scale_down_schedule_expression = "cron(* * * * ? *)" } また、変数の設定ファイル(terraform.tfvars)も作成します。 以下のような内容です。 秘密鍵 は base64 に変換することに注意してください。 github_app_id = "<<github appのid>>" github_app_key_base64 = <<EOF <<取得したgithub appの秘密鍵をbase64エンコードしたもの>> EOF terraformの実行 ここまで準備すれば、あとは terraform を実行するだけです terraform init terraform apply すると、 AWS 上に環境が構築されます。 GitHub Appにhookの設定を追加 先ほど、 terraform apply をした出力に GitHub appで利用するwebhookのURLが含まれています。 terraform output -json を実行しても取得できます。 以下のような出力が得られます。 { "runners": { "sensitive": false, "type": [ "object", { "lambda_syncer_name": "string" } ], "value": { "lambda_syncer_name": "default-syncer" } }, "webhook_endpoint": { "sensitive": false, "type": "string", "value": "<webhookのURL>" }, "webhook_secret": { "sensitive": true, "type": "string", "value": "<secret>" } } これらの <webhookのURL> 、 <secret> の内容が必要です。 この値を、 GitHub Appのwebhookのendpointとsecretとして登録します。画面では以下のようになります。 そして、eventのサブスクライブが必要なので、Permission & eventsから workflow job にチェックを入れてください。これで設定完了です。 実際に利用する場合 GitHub Actionsを設定する yaml ファイルで runs-on : [ self-hosted, default, <設定したラベル> ] と記載しておけば、実行時に自動的に インスタンス の起動などが行われてその インスタンス 上で実行されます。 まとめ 今回は、オートスケールする GitHub self-hosted runnersの構築手順について紹介しました。 これを使ってどんどんCIを回していきたいですね。 私たちは同じチームで働いてくれる仲間を探しています。今回のエントリで紹介したような仕事に興味のある方、ご応募お待ちしています。 - ソリューションアーキテクト 執筆: @yamashita.tsuyoshi 、レビュー: @sato.taichi ( Shodo で執筆されました )
こんにちは。X(クロス) イノベーション 本部 ソフトウェアデザインセンター の山下です。 今回はユーザーに合わせてオートスケールする GitHub ActionsのRunnerについて紹介しようと思います。 課題と目的 公式の推奨している方法について 構築の手順 事前準備 terraformの実行 terraformファイルの作成 terraformの実行 GitHub Appにhookの設定を追加 実際に利用する場合 まとめ 課題と目的 GitHub Actionsを使ってCIを実施するのは一般的になってきています。 ISIDでも GitHub Actionsを活用してCIを実施しています。 しかし、 GitHub 社が提供しているrunners( GitHub -hosted runners)では困る場合があります。「 GitHub Actionsでオンプレミス環境のCI/CDを実行する方法 」の記事では、オンプレミス環境のような外部ネットワークに接続できないような環境で GitHub Actionsを利用したCIを実施する場合に、self-hosted runnersを紹介しました。 オンプレ環境で実行できないこと以外にも、 GitHub Actionsの標準環境にはいくつか不満があります。 CIの待ち時間が長い CIに使うマシンの性能を調整したい CIの費用が高い といった問題です。 GitHub -hosted runnersでは、 GitHub 社が提供しているインフラに依存しています。このため、 GitHub 側の調子が悪いときにCI開始されるまでの待ち時間が長くなったりします。 また、CIの実行に使うマシンも GitHub 社が決めたものの範囲で選ぶことになってしまいます。 以前は、選択の余地がなかったのですが、Larger Runnersがbeta版ですが公開されて状況は改善しつつあります。Larger Runnersに関する公式の記事は こちら です。 また、 GitHub Actionsの料金は Linux の場合、1分当たり$0.008 となっていて安くはないです。 単純計算すると1時間あたり $0.48 になってしまい、 AWS だとt3.2xlargeを借りることが出来てしまいます。 以下のドキュメントによると、 GitHub 標準のrunnerの性能は、2コア、7GBメモリのマシンのようなので若干コスト高になってしまいます。 https://docs.github.com/ja/actions/using-github-hosted-runners/about-github-hosted-runners#supported-runners-and-hardware-resources self-hosted runnerを導入することで、自前の環境を構築すれば待ち時間や性能の問題は解決します。しかし、高性能なマシンをずっと起動しているとコストが高くなってしまいます。 必要な時、必要な分だけEC2を起動し GitHub Actionsのself-hosted runnersを動作できれば、コストパフォーマンスに優れるself-hosted runners環境を構築できます。 今回はその環境(Autoscale self-hosted runners)について解説します。 公式の推奨している方法について GitHub の 公式の資料 にオートスケールするself-hosted runnersについての記載があります。 基本的に、この手順に従って構築すれば問題ないです。公式では以下の2種類の方法が提供されています。 actions-runner-controller/actions-runner-controller - GitHub Actions のセルフホステッド ランナー用の Kubernetes コントローラー。 philips-labs/terraform-aws-github-runner - AWS 上のスケーラブルな GitHub Actions ランナー用の Terraform モジュール。 actions-runner-controller/actions-runner-controller は kubernetes 上にself-hosted runnersを構築する物になっています。既に kubernetes を運用している場合にはこちらを導入するのが良いですが、 GitHub Actionsのためだけに kubernetes を運用するのは運用負荷を考えると難しいので今回は採用しませんでした。 philips-labs/terraform-aws-github-runner は、 AWS のAutoScaling Groupを利用して self-hosted runners環境を構築するものとなっています。構築はterraformを利用することになります。今回はこちらを使って環境構築を行います。 構築の手順 それでは早速構築の手順について解説します。 GitHub Appの設定と terraform の実行と両方を行っていく必要があるので順番に気を付けて実施してください。 事前準備 環境構築に際していくつか事前に準備しておく物があります。 terraformのインストール GitHub Appの作成 GitHub AppのClientID GitHub App用の 秘密鍵 terraformは各自インストールする必要があります。 公式サイト から適宜インストールしておいてください。 terraform-aws-github-runner のマニュアルに記載がありますが、 GitHub 上で GitHub Appを作成する必要があります。いくつか注意が必要です。 まず、最初に作成する際は、WebhookをActiveにしないで作成します。 今回は特定のプロジェクトで利用するような事を想定し、 GitHub Appに与える権限をRepositoryレベルで設定します。以下のように、Actions、Checks、MetadataをRead-onlyにします。それに加えて、AdministrationをRead & writeとしました。 terraformの実行 terraformファイルの作成 今回は以下のような2つのtfファイルを作成して実行しました。片方が VPC を作成するもの、片方が今回のself-hosted runner用の物です。 // vpc.tf module "vpc" { source = "terraform-aws-modules/vpc/aws" version = "3.11.2" name = "vpc-${local.environment}" cidr = "10.0.0.0/16" azs = ["${local.aws_region}a", "${local.aws_region}c", "${local.aws_region}d"] private_subnets = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"] public_subnets = ["10.0.101.0/24", "10.0.102.0/24", "10.0.103.0/24"] enable_dns_hostnames = true enable_nat_gateway = true map_public_ip_on_launch = false single_nat_gateway = true tags = { Environment = local.environment } // main.tf locals { version = "v1.8.0" environment = "default" aws_region = "ap-northeast-1" } provider "aws" { region = "ap-northeast-1" } variable "github_app_key_base64" {} variable "github_app_id" {} resource "random_id" "random" { byte_length = 20 } data "aws_caller_identity" "current" {} module "runners" { source = "philips-labs/github-runner/aws" version = "1.8.0" create_service_linked_role_spot = false aws_region = local.aws_region vpc_id = module.vpc.vpc_id subnet_ids = module.vpc.private_subnets prefix = local.environment tags = { costcenter = "p7-ci-template" Project = "AutoScaleTest" } github_app = { key_base64 = var.github_app_key_base64 id = var.github_app_id webhook_secret = random_id.random.hex } webhook_lambda_zip = "webhook.zip" runner_binaries_syncer_lambda_zip = "runner-binaries-syncer.zip" runners_lambda_zip = "runners.zip" enable_organization_runners = false # ランナーのラベル # github actionsの設定ファイルで指定するラベルとなる runner_extra_labels = "default,<CIで指定するラベル>" # enable access to the runners via SSM enable_ssm_on_runners = true instance_types = ["m5.large", "c5.large"] # override delay of events in seconds delay_webhook_event = 5 runners_maximum_count = 1 # set up a fifo queue to remain order fifo_build_queue = true # override scaling down scale_down_schedule_expression = "cron(* * * * ? *)" } また、変数の設定ファイル(terraform.tfvars)も作成します。 以下のような内容です。 秘密鍵 は base64 に変換することに注意してください。 github_app_id = "<<github appのid>>" github_app_key_base64 = <<EOF <<取得したgithub appの秘密鍵をbase64エンコードしたもの>> EOF terraformの実行 ここまで準備すれば、あとは terraform を実行するだけです terraform init terraform apply すると、 AWS 上に環境が構築されます。 GitHub Appにhookの設定を追加 先ほど、 terraform apply をした出力に GitHub appで利用するwebhookのURLが含まれています。 terraform output -json を実行しても取得できます。 以下のような出力が得られます。 { "runners": { "sensitive": false, "type": [ "object", { "lambda_syncer_name": "string" } ], "value": { "lambda_syncer_name": "default-syncer" } }, "webhook_endpoint": { "sensitive": false, "type": "string", "value": "<webhookのURL>" }, "webhook_secret": { "sensitive": true, "type": "string", "value": "<secret>" } } これらの <webhookのURL> 、 <secret> の内容が必要です。 この値を、 GitHub Appのwebhookのendpointとsecretとして登録します。画面では以下のようになります。 そして、eventのサブスクライブが必要なので、Permission & eventsから workflow job にチェックを入れてください。これで設定完了です。 実際に利用する場合 GitHub Actionsを設定する yaml ファイルで runs-on : [ self-hosted, default, <設定したラベル> ] と記載しておけば、実行時に自動的に インスタンス の起動などが行われてその インスタンス 上で実行されます。 まとめ 今回は、オートスケールする GitHub self-hosted runnersの構築手順について紹介しました。 これを使ってどんどんCIを回していきたいですね。 私たちは同じチームで働いてくれる仲間を探しています。今回のエントリで紹介したような仕事に興味のある方、ご応募お待ちしています。 - ソリューションアーキテクト 執筆: @yamashita.tsuyoshi 、レビュー: @sato.taichi ( Shodo で執筆されました )
こんにちは、 GMS 事業部グループ経営 コンサルティング 第1ユニットの安田しおりです。 この記事では、ISIDに興味を持ってくださっている就活生の皆さんに向けて3年目(2021年入社)の私の働き方を紹介したいと思います。 仕事内容について まずは、私がどんな仕事をしているのか仕事内容を簡単に紹介します。 私が所属するグループ経営 コンサルティング 第1ユニット(GMC1ユニット)では STRAVIS という 連結会計 ソリューションを扱っています。私はSTRAVISの コンサルタント として様々な業務に関わっています。ここでは、主な3つの業務についてご紹介します。 1. 導入プロジェクト 導入プロジェクトでは、STRAVISをお客様にご利用いただくためのシステム導入作業として、要件定義や設定、検証、トライアル支援等を実施します。要件定義のフェーズではお客様から業務内容を ヒアリ ングし、必要な機能や設定内容を検討します。設定フェーズでは、要件定義フェーズで整理した内容に基づいて実際にSTRAVISを設定します。基本的にSTRAVISは導入時にコーディングの必要がなく、画面操作によって基本情報や処理の設定を行うことができます。検証フェーズでは、設定内容を確認するためのテストケースを作成し、テストを実施します。トライアルフェーズではシステムの試験稼働をサポートします。 2. セールス支援の案件 新しく 連結会計 システムの導入を検討しているお客様への提案や、既存のSTRAVISユーザーへの追加提案活動を行います。 セールス支援での コンサルタント の役割は、お客様にSTRAVISの特徴やSTRAVISを導入するメリットを理解していただくことです。そのために、お客様の前でSTRAVISを操作しながら機能紹介をしたり、実際にお客様にシステムを触ってもらいながら操作をレクチャーしたりします。また、具体的な提案内容を提案書にまとめ、プレゼンします。お客様へのプレゼンや機能紹介は「上手く伝えられるか」と緊張する瞬間ではありますが、提案後にお客様からSTRAVISに対して良い評価のコメントをいただけるととてもやりがいを感じます。 3. BPO ( アウトソーシング サービス) ISIDでは、STRAVIS導入後のお客様に対してSTRAVIS関連の連結決算業務の一部を支援する アウトソーシング サービスを提供しています。私は BPO 案件の中で、STRAVISの設定のメンテナンスを行ったり、データ確認や仕訳作成の一部作業を担当したりしています。実際の決算業務の中でSTRAVISを使うことができるため、自分の成長に大きくつながったプロジェクトだと考えています。 上記の3つの案件以外にも、STRAVISオンライン セミ ナーの講師を務めるなど幅広く活動しています。 1週間の流れ  仕事内容の紹介で複数案件に参加していると書きましたが、どのような1週間を過ごしているかスケジュールを紹介します。月によって案件の内容は変わりますが、1週間の中でセールス支援がメインの日もあれば導入作業や BPO の作業がメインの日もあります。お客様とのミーティングの予定も多く、常に複数のお客様と関わりがあります。他にも部内の活動として、グループ会や1on1が週に1回程度あります。グループ会では案件状況の報告や、メンバ間でのノウハウ共有を行います。 また、部長やグルプマネージャーと1対1で会話する機会として週に1回15分程度1on1を実施しています。1on1を定期的に実施していることで、業務の中での相談などもより気軽にできていると思います。 終業後は、同期に会ったり、美容院に行ったりとプライベートの時間を確保して気持ちをリフレッシュしています。 GMC1ユニットでの働き方の特長 次に、私が考える「この部署での働き方の特長」についてです。私がGMC1ユニットで働く中で、自分に合っていると感じたポイント2つについて説明します。 私が考える特長の1つめは「幅広さ」です。 私は入社3年目ですが、様々な案件に関わってきました。複数の案件に同時に関わることが多いため、次々と新しいことに挑戦できますし、 スキルアップ できる機会も豊富にあります。そして単に新しいことに挑戦する機会が多いだけでなく、性質の異なる案件を経験することで様々な角度から知識やスキルを習得できる点もメリットに感じています。例えば、導入プロジェクトを経験していたことでセールス支援の時に具体的な導入事例を紹介できた、というように幅広く経験して良かったなと思うことが多いです。このように得た知識を発揮できる場が多いと自分の成長を実感でき、モチベーションの維持にも繋がっています。 また私の場合、この部署での幅広い業務経験が「特にどのスキルを伸ばしていきたいか」「どういう事に特化した人材を目指すのか」という選択肢を拡げることに繋がりました。入社直後はやりたい事のイメージが掴めていませんでしたが、今では選択肢の中から将来のキャリアをイメージできています。 このような理由から、GMC1ユニットの「幅広さ」が私にとってマッチしていたと考えています。 2つ目の特長は、若手のうちからお客様との関わりが多いという点です。 「新規提案中のお客様」「一緒にSTRAVISの導入を行っているお客様」「 BPO で関わるSTRAVISユーザー」など様々な立場のお客様とプロジェクトを通して接することができます。お客様の反応を直接確認できるので、良い反応を貰えると嬉しいですし、それがモチベーションにもつながっています。逆に、お客様の反応から自分の改善するべき点に気づく場合もあります。例えばお客様からの指摘や質問に一人では対応できず、どの部分の理解が不足しているのか気付くこともあります。お客様からの評価と気づきをあらゆる角度から得ることが出来るという点が顧客との関わりが多いメリットだと思っています。 また若手のうちから、セールス支援でも導入プロジェクトでもメインスピーカーとして参加できるので「プレゼン力を身につけたい」「社外でも通用するビジネススキルを身につけたい」といった人にも向いていると思います。学生時代、私は人前で話すのがとても緊張するタイプでしたが、今ではスピーカーとして会議に参加することも徐々に楽しめるようになってきました。 入る前のイメージと入った後感じたこと 私は学生時代、ITとも会計とも無縁の専攻でした。そのため、配属前は自分に会計やシステムの事が理解できるのか、チームのメンバの一員として役に立つことが出来るのか不安を感じていました。また、「若手のうちから挑戦できる」=「放置される」なのではないかという不安もありました。 こんな風に不安を感じながら配属された私ですが、GMC1ユニットでは一人一人のレベルにあった新人教育がされ、先輩からのサポートも手厚いため不安なく働くことができました。具体的に、私が配属後一番初めに行ったのは「STRAVISを知る」事でした。GMC1ユニットではユーザー向けのeラーニングや セミ ナーを多数用意しているので、それらを受講して勉強することができます。さらに導入案件やセールス案件に参加して、実際のプロジェクトの流れや会計の知識、STRAVISの機能や操作について学ぶこともしました。 私のように、入社前・配属前の段階で会計やITの学習経験がない場合でも不安に感じる必要はないです。GMC1ユニットでは自分のスキルに合わせて学んでいくことができます。もし、少しでも興味を持ってくださった方はまずはISIDやSTRAVISを知ることから始めていただけると嬉しいです。 最後に 就活生の皆さんの中には私のようにIT系の専攻ではないけれどISIDに興味を持ってくださっている方もいるかと思います。 GMS 事業部GMC1ユニットでは既にITや会計に詳しいという方はもちろんですが、今はまだ興味を持ち始めた段階だという方も大歓迎です。私の働き方紹介が皆さんの将来を考える材料になればうれしいなと思います。 以下採用ページへのリンクです。興味を持ってくださった方は、ぜひご応募ください。 www.isid.co.jp 執筆: @yasuda.shiori 、レビュー: Ishizawa Kento (@kent) ( Shodo で執筆されました )
こんにちは、 GMS 事業部グループ経営 コンサルティング 第1ユニットの安田しおりです。 この記事では、ISIDに興味を持ってくださっている就活生の皆さんに向けて3年目(2021年入社)の私の働き方を紹介したいと思います。 仕事内容について まずは、私がどんな仕事をしているのか仕事内容を簡単に紹介します。 私が所属するグループ経営 コンサルティング 第1ユニット(GMC1ユニット)では STRAVIS という 連結会計 ソリューションを扱っています。私はSTRAVISの コンサルタント として様々な業務に関わっています。ここでは、主な3つの業務についてご紹介します。 1. 導入プロジェクト 導入プロジェクトでは、STRAVISをお客様にご利用いただくためのシステム導入作業として、要件定義や設定、検証、トライアル支援等を実施します。要件定義のフェーズではお客様から業務内容を ヒアリ ングし、必要な機能や設定内容を検討します。設定フェーズでは、要件定義フェーズで整理した内容に基づいて実際にSTRAVISを設定します。基本的にSTRAVISは導入時にコーディングの必要がなく、画面操作によって基本情報や処理の設定を行うことができます。検証フェーズでは、設定内容を確認するためのテストケースを作成し、テストを実施します。トライアルフェーズではシステムの試験稼働をサポートします。 2. セールス支援の案件 新しく 連結会計 システムの導入を検討しているお客様への提案や、既存のSTRAVISユーザーへの追加提案活動を行います。 セールス支援での コンサルタント の役割は、お客様にSTRAVISの特徴やSTRAVISを導入するメリットを理解していただくことです。そのために、お客様の前でSTRAVISを操作しながら機能紹介をしたり、実際にお客様にシステムを触ってもらいながら操作をレクチャーしたりします。また、具体的な提案内容を提案書にまとめ、プレゼンします。お客様へのプレゼンや機能紹介は「上手く伝えられるか」と緊張する瞬間ではありますが、提案後にお客様からSTRAVISに対して良い評価のコメントをいただけるととてもやりがいを感じます。 3. BPO ( アウトソーシング サービス) ISIDでは、STRAVIS導入後のお客様に対してSTRAVIS関連の連結決算業務の一部を支援する アウトソーシング サービスを提供しています。私は BPO 案件の中で、STRAVISの設定のメンテナンスを行ったり、データ確認や仕訳作成の一部作業を担当したりしています。実際の決算業務の中でSTRAVISを使うことができるため、自分の成長に大きくつながったプロジェクトだと考えています。 上記の3つの案件以外にも、STRAVISオンライン セミ ナーの講師を務めるなど幅広く活動しています。 1週間の流れ  仕事内容の紹介で複数案件に参加していると書きましたが、どのような1週間を過ごしているかスケジュールを紹介します。月によって案件の内容は変わりますが、1週間の中でセールス支援がメインの日もあれば導入作業や BPO の作業がメインの日もあります。お客様とのミーティングの予定も多く、常に複数のお客様と関わりがあります。他にも部内の活動として、グループ会や1on1が週に1回程度あります。グループ会では案件状況の報告や、メンバ間でのノウハウ共有を行います。 また、部長やグルプマネージャーと1対1で会話する機会として週に1回15分程度1on1を実施しています。1on1を定期的に実施していることで、業務の中での相談などもより気軽にできていると思います。 終業後は、同期に会ったり、美容院に行ったりとプライベートの時間を確保して気持ちをリフレッシュしています。 GMC1ユニットでの働き方の特長 次に、私が考える「この部署での働き方の特長」についてです。私がGMC1ユニットで働く中で、自分に合っていると感じたポイント2つについて説明します。 私が考える特長の1つめは「幅広さ」です。 私は入社3年目ですが、様々な案件に関わってきました。複数の案件に同時に関わることが多いため、次々と新しいことに挑戦できますし、 スキルアップ できる機会も豊富にあります。そして単に新しいことに挑戦する機会が多いだけでなく、性質の異なる案件を経験することで様々な角度から知識やスキルを習得できる点もメリットに感じています。例えば、導入プロジェクトを経験していたことでセールス支援の時に具体的な導入事例を紹介できた、というように幅広く経験して良かったなと思うことが多いです。このように得た知識を発揮できる場が多いと自分の成長を実感でき、モチベーションの維持にも繋がっています。 また私の場合、この部署での幅広い業務経験が「特にどのスキルを伸ばしていきたいか」「どういう事に特化した人材を目指すのか」という選択肢を拡げることに繋がりました。入社直後はやりたい事のイメージが掴めていませんでしたが、今では選択肢の中から将来のキャリアをイメージできています。 このような理由から、GMC1ユニットの「幅広さ」が私にとってマッチしていたと考えています。 2つ目の特長は、若手のうちからお客様との関わりが多いという点です。 「新規提案中のお客様」「一緒にSTRAVISの導入を行っているお客様」「 BPO で関わるSTRAVISユーザー」など様々な立場のお客様とプロジェクトを通して接することができます。お客様の反応を直接確認できるので、良い反応を貰えると嬉しいですし、それがモチベーションにもつながっています。逆に、お客様の反応から自分の改善するべき点に気づく場合もあります。例えばお客様からの指摘や質問に一人では対応できず、どの部分の理解が不足しているのか気付くこともあります。お客様からの評価と気づきをあらゆる角度から得ることが出来るという点が顧客との関わりが多いメリットだと思っています。 また若手のうちから、セールス支援でも導入プロジェクトでもメインスピーカーとして参加できるので「プレゼン力を身につけたい」「社外でも通用するビジネススキルを身につけたい」といった人にも向いていると思います。学生時代、私は人前で話すのがとても緊張するタイプでしたが、今ではスピーカーとして会議に参加することも徐々に楽しめるようになってきました。 入る前のイメージと入った後感じたこと 私は学生時代、ITとも会計とも無縁の専攻でした。そのため、配属前は自分に会計やシステムの事が理解できるのか、チームのメンバの一員として役に立つことが出来るのか不安を感じていました。また、「若手のうちから挑戦できる」=「放置される」なのではないかという不安もありました。 こんな風に不安を感じながら配属された私ですが、GMC1ユニットでは一人一人のレベルにあった新人教育がされ、先輩からのサポートも手厚いため不安なく働くことができました。具体的に、私が配属後一番初めに行ったのは「STRAVISを知る」事でした。GMC1ユニットではユーザー向けのeラーニングや セミ ナーを多数用意しているので、それらを受講して勉強することができます。さらに導入案件やセールス案件に参加して、実際のプロジェクトの流れや会計の知識、STRAVISの機能や操作について学ぶこともしました。 私のように、入社前・配属前の段階で会計やITの学習経験がない場合でも不安に感じる必要はないです。GMC1ユニットでは自分のスキルに合わせて学んでいくことができます。もし、少しでも興味を持ってくださった方はまずはISIDやSTRAVISを知ることから始めていただけると嬉しいです。 最後に 就活生の皆さんの中には私のようにIT系の専攻ではないけれどISIDに興味を持ってくださっている方もいるかと思います。 GMS 事業部GMC1ユニットでは既にITや会計に詳しいという方はもちろんですが、今はまだ興味を持ち始めた段階だという方も大歓迎です。私の働き方紹介が皆さんの将来を考える材料になればうれしいなと思います。 以下採用ページへのリンクです。興味を持ってくださった方は、ぜひご応募ください。 www.isid.co.jp 執筆: @yasuda.shiori 、レビュー: Ishizawa Kento (@kent) ( Shodo で執筆されました )
こんにちは、ISID 金融ソリューション事業部の岡崎です。今回はEpicGames社が提供する ゲームエンジン 、 UnrealEngine5 のPlugin「PixelStreaming」を使用し、WebUIからサーバー上のUE5の操作を行いました。 はじめに PixelStreamingを利用してWebUIからUEを操作するためには、UEアプリ上のBluePrintにURLクエリや JavaScript などを紐づける必要があります。 今回は、WebUIとUEアプリの連携をする為の方法について解説します。 BluePrintとは、UnrealEngineの スクリプティング システムです。 BluePrintではキーボードの入力やマウスの動きを感知し、UEアプリ上でのイベントを追加していくことができます。 全体の処理の流れとしては、以下の方法でご説明します。 WebUI上のボタンクリックで、任意の JSON をWebサーバー側に送信 UEのBluePrint側で value のキーに入った文字列を受け取る 受け取った文字列をUEアプリ上に文字列として表示させる 検証環境/ツール Unreal Engine5.1 AWS EC2 AMI Windows _Server-2022-English-Full-Base-2023.01.19 インスタンス タイプ g4dn.12xlarge EBS 250GB Chrome ver.110.0.5481.177 実施手順 1. AWS 上にEC2 インスタンス を立ち上げ、UnrealEngineを導入 2. UEプロジェクトを作成 3. UE BluePrintを作成 4. プレイヤーページのHTMLファイルを更新 5. UEアプリのパッケージング実行 1. AWS 上にEC2 インスタンス を立ち上げ、UnrealEngineを導入 金融ソリューション事業部の山下さん の記事を参考に、 AWS 上にEC2 インスタンス を立ち上げます。 ※上記記事では、AMIは「 Ubuntu 20.04 LTS」ですが、UE5.1のBluePrintエディター上の挙動がうまくいかなかったため、 「 Windows _Server-2022-English-Full-Base-2023.01.19」に変更して作業を行いました。 また、UEの挙動がとても重かったので、 インスタンス タイプも「g4dn.xlarge」から「g4dn.12xlarge」へ変更しております。 次に、UnrealEngineを導入します。 作成した Windows ServerにRDPを利用し接続してUnrealEngineを導入します。 今回は UnrealEngineの公式サイト の手順に従ってダウンロードを行いました。 2. UEプロジェクトを作成 今回の検証では「ThirdPersonTemplate」を使用してプロジェクトを作成します。 金融ソリューション事業部の山下さん の記事と同様に「PixelStreaming」の プラグイン を有効にし、 [ Always Show Touch Interface (タッチ インターフェースを常に表示) ] 設定も有効にします。 Additional Launch Parametersの設定も行います。 AudioMixer -PixelStreamingIP=localhost -PixelStreamingPort=8888 -AllowPixelStreamingCommands ※上記記事のパラメータに -AllowPixelStreamingCommands の記述を追加しています。 3. UE BluePrintを作成 UEのPixelStreaming プラグイン では、BluePrintにアクセスするための API が公開されているので、 これを利用してHTMLページで送信されるイベントをUEで受け取り、処理を走らせることができます。 まずは、 UnrealEngine公式のリファレンス を参考に、先ほど作成したプロジェクトで、BluePrintの設定をします。 ViewPort下部のContent DrawerからContentを選択し、右のスペースで右クリックを行い、BluePrintClassを作成します。 ここでParent Classは「Actor」を選択します。 作成されたBluePrintClassを選択することでBluePrintエディタに移動できます。 まずは、UEのBluePrint側でWebUIのボタンがクリックされた際、送られてくる任意の JSON から、 value のキーに入った文字列を受け取る処理を作成します。 (WebUIからボタンクリックで JSON を送る処理は後述します) PixelStreaming用のBluePrintAPIにアクセスするためには、PixelStreamingInputComponentを追加しないといけません。そのために「Add」ボタンより「Pixel Streaming Input」を選択します。 左側のComponents欄に追加された「Pixel Streaming Input」を右側のイベントグラフにドラッグしてきます。 作成された「Pixel Streaming Input」のノードの矢印から、「Bind Event to On Input Event」を選択し、ノードの作成を行います。 新規BluePrintを作成した際に自動生成される「Event BeginPlay」イベントと、先ほど作成した「Bind Event to On Input Event」をつなぎます。これにより、PixelStreamingを行っているWebUI上でのイベントをBluePrintAPIを通じて受け取ることができます。 ここでは受け取った文字列をUEアプリ上に文字列として表示させる処理を作成します。 先ほど作成した「Bind Event to On Input Event」から「Custom Event」ノードを作成します。今回は「UI Interaction」と 命名 します。 その「UI Interaction」にDescriptorにBluePrintAPIを通じてデータが送られてくるので、「Get Json String Value 」関数を使用し、 keyが「 Value 」の値を文字列として返す設定を行います。さらにそこから「Print String」関数を使用し、「UI Interaction」と連結させます。 この際「Print String」関数の設定で「Print to Screen」にチェックを入れるのを忘れないようにしてください。 これでWebUI上でボタンがクリックされた際、任意の Json を受け取り、 value のキーに入った文字列をBluePrintを通して、UEアプリ上に文字列として表示させる処理のBluePrintが完成しました。 最後にBluePrintの コンパイル と保存を忘れずに行い、BluePrintエディターを閉じます。最後にContent Drawerより、 作成したBluePrintをViewPortにドラッグして配置して、UE側の設定が完了します。 4. プレイヤーページのHTMLファイルを更新 WebUIのボタンクリックで任意の JSON をWebサーバー側に送信するために、 プレイヤーページのHTMLでは、 JavaScript を使用してBluePrintAPIを実行する必要があるので、その処理を追加していきます。 金融ソリューション事業部の山下さん の記事を参考にしてプロジェクトを作成している場合、 SignallingServerを起動するために使用したフォルダ Samples/PixelStreaming/WebServers/SignallingWebServer 配下に Publicフォルダやscriptsフォルダを見つけることができます。 ここにPixelStreamingを行う際のWebUIに使用するHTMLファイルやJSファイルが格納されています。 Public配下のplayer.htmlがPixelStreamingで表示される静的ファイルになります。 まずはBluePrintAPIを発火させるためのボタンを作成します。 HTMLファイル内の <div id="player"></div> を下記に変更します。 <div id="player"> <button type="button" style="position:absolute; z-index:1;" onclick="execPrintString('Hello PixelStreaming');">Hello</button> </div> 次に JavaScript を追加します。 bodyタグの末尾に下記のscriptタグを追加します。 <script type="text/javascript"> function execPrintString(text) { var descriptor = { Value: text } emitUIInteraction(descriptor); } </script> この「emitUIInteraction」の引数の Json や文字列がBluePrintAPIに渡り、先ほど作成したBluePrintの「UI Interaction」のDescriptorに格納されます。 これでHTMLファイルの更新の完了です。 5. UEアプリのパッケージング実行 次にUnrealEngineで作成したプロジェクトをパッケージ化します。 コンピュータ上のフォルダを参照して、パッケージ化されたアプリを配置する場所を決めます。 パッケージ化が完了すると、指定した場所に「 windows 」フォルダが配置されます。 その「 windows 」フォルダの中の作成したexeファイルにパラメータを付与するためにショートカットを作成します。 ショートカットを作成したら、そのショートカットのプロパティを選択し、ターゲットの欄に -AudioMixer -PixelStreamingIP=localhost -PixelStreamingPort=8888 -AllowPixelStreamingCommands の記述を追加します。 これでパッケージ化の完了です。 SignallingServerを起動し、上記で作成したexeファイルのショートカットからパッケージ化したアプリを開くことができます。 実際にEC2で設定した グローバルIP をブラウザで入力することでPixelStreamingしたWebサイトに接続できます。 WebUIの左上部に作成した「Hello」のボタンが設置されており、押下することで「Hello PixelStreaming」という文字列が表示されていれば成功です! 終わりに 今回はPixelStreamingでWebUIのカスタマイズに挑戦してみました。 まだボタンを追加してみただけですが、今後WebUIに欲しい要素をWeb側とUE側どちらにも追加していくことができるようになりました。 BluePrintAPIにはWebUIからの情報をUEに反映させるだけではなく、UE側の変更をWebUIに反映させることもできるようなので、今後はそちらの調査を行ってみようと思います。 現在ISIDは web3領域のグループ横断組織 を立ち上げ、Web3および メタバース 領域のR&Dを行っております(カテゴリー「3DCG」の記事は こちら )。 もし本領域にご興味のある方や、一緒にチャレンジしていきたい方は、ぜひお気軽にご連絡ください! 私たちと同じチームで働いてくれる仲間を、是非お待ちしております! ISID採用ページ(Web3/メタバース/AI) 参考 ・ https://docs.unrealengine.com/5.1/ja/customizing-the-player-web-page-in-unreal-engine/ ・ https://docs.unrealengine.com/5.1/ja/getting-started-with-pixel-streaming-in-unreal-engine/ ・ https://zenn.dev/pate_techmemo/articles/07721136756dcc 執筆: @okazaki.wataru 、レビュー: @wakamoto.ryosuke ( Shodo で執筆されました )
こんにちは、ISID 金融ソリューション事業部の岡崎です。今回はEpicGames社が提供する ゲームエンジン 、 UnrealEngine5 のPlugin「PixelStreaming」を使用し、WebUIからサーバー上のUE5の操作を行いました。 はじめに PixelStreamingを利用してWebUIからUEを操作するためには、UEアプリ上のBluePrintにURLクエリや JavaScript などを紐づける必要があります。 今回は、WebUIとUEアプリの連携をする為の方法について解説します。 BluePrintとは、UnrealEngineの スクリプティング システムです。 BluePrintではキーボードの入力やマウスの動きを感知し、UEアプリ上でのイベントを追加していくことができます。 全体の処理の流れとしては、以下の方法でご説明します。 WebUI上のボタンクリックで、任意の JSON をWebサーバー側に送信 UEのBluePrint側で value のキーに入った文字列を受け取る 受け取った文字列をUEアプリ上に文字列として表示させる 検証環境/ツール Unreal Engine5.1 AWS EC2 AMI Windows _Server-2022-English-Full-Base-2023.01.19 インスタンス タイプ g4dn.12xlarge EBS 250GB Chrome ver.110.0.5481.177 実施手順 1. AWS 上にEC2 インスタンス を立ち上げ、UnrealEngineを導入 2. UEプロジェクトを作成 3. UE BluePrintを作成 4. プレイヤーページのHTMLファイルを更新 5. UEアプリのパッケージング実行 1. AWS 上にEC2 インスタンス を立ち上げ、UnrealEngineを導入 金融ソリューション事業部の山下さん の記事を参考に、 AWS 上にEC2 インスタンス を立ち上げます。 ※上記記事では、AMIは「 Ubuntu 20.04 LTS」ですが、UE5.1のBluePrintエディター上の挙動がうまくいかなかったため、 「 Windows _Server-2022-English-Full-Base-2023.01.19」に変更して作業を行いました。 また、UEの挙動がとても重かったので、 インスタンス タイプも「g4dn.xlarge」から「g4dn.12xlarge」へ変更しております。 次に、UnrealEngineを導入します。 作成した Windows ServerにRDPを利用し接続してUnrealEngineを導入します。 今回は UnrealEngineの公式サイト の手順に従ってダウンロードを行いました。 2. UEプロジェクトを作成 今回の検証では「ThirdPersonTemplate」を使用してプロジェクトを作成します。 金融ソリューション事業部の山下さん の記事と同様に「PixelStreaming」の プラグイン を有効にし、 [ Always Show Touch Interface (タッチ インターフェースを常に表示) ] 設定も有効にします。 Additional Launch Parametersの設定も行います。 AudioMixer -PixelStreamingIP=localhost -PixelStreamingPort=8888 -AllowPixelStreamingCommands ※上記記事のパラメータに -AllowPixelStreamingCommands の記述を追加しています。 3. UE BluePrintを作成 UEのPixelStreaming プラグイン では、BluePrintにアクセスするための API が公開されているので、 これを利用してHTMLページで送信されるイベントをUEで受け取り、処理を走らせることができます。 まずは、 UnrealEngine公式のリファレンス を参考に、先ほど作成したプロジェクトで、BluePrintの設定をします。 ViewPort下部のContent DrawerからContentを選択し、右のスペースで右クリックを行い、BluePrintClassを作成します。 ここでParent Classは「Actor」を選択します。 作成されたBluePrintClassを選択することでBluePrintエディタに移動できます。 まずは、UEのBluePrint側でWebUIのボタンがクリックされた際、送られてくる任意の JSON から、 value のキーに入った文字列を受け取る処理を作成します。 (WebUIからボタンクリックで JSON を送る処理は後述します) PixelStreaming用のBluePrintAPIにアクセスするためには、PixelStreamingInputComponentを追加しないといけません。そのために「Add」ボタンより「Pixel Streaming Input」を選択します。 左側のComponents欄に追加された「Pixel Streaming Input」を右側のイベントグラフにドラッグしてきます。 作成された「Pixel Streaming Input」のノードの矢印から、「Bind Event to On Input Event」を選択し、ノードの作成を行います。 新規BluePrintを作成した際に自動生成される「Event BeginPlay」イベントと、先ほど作成した「Bind Event to On Input Event」をつなぎます。これにより、PixelStreamingを行っているWebUI上でのイベントをBluePrintAPIを通じて受け取ることができます。 ここでは受け取った文字列をUEアプリ上に文字列として表示させる処理を作成します。 先ほど作成した「Bind Event to On Input Event」から「Custom Event」ノードを作成します。今回は「UI Interaction」と 命名 します。 その「UI Interaction」にDescriptorにBluePrintAPIを通じてデータが送られてくるので、「Get Json String Value 」関数を使用し、 keyが「 Value 」の値を文字列として返す設定を行います。さらにそこから「Print String」関数を使用し、「UI Interaction」と連結させます。 この際「Print String」関数の設定で「Print to Screen」にチェックを入れるのを忘れないようにしてください。 これでWebUI上でボタンがクリックされた際、任意の Json を受け取り、 value のキーに入った文字列をBluePrintを通して、UEアプリ上に文字列として表示させる処理のBluePrintが完成しました。 最後にBluePrintの コンパイル と保存を忘れずに行い、BluePrintエディターを閉じます。最後にContent Drawerより、 作成したBluePrintをViewPortにドラッグして配置して、UE側の設定が完了します。 4. プレイヤーページのHTMLファイルを更新 WebUIのボタンクリックで任意の JSON をWebサーバー側に送信するために、 プレイヤーページのHTMLでは、 JavaScript を使用してBluePrintAPIを実行する必要があるので、その処理を追加していきます。 金融ソリューション事業部の山下さん の記事を参考にしてプロジェクトを作成している場合、 SignallingServerを起動するために使用したフォルダ Samples/PixelStreaming/WebServers/SignallingWebServer 配下に Publicフォルダやscriptsフォルダを見つけることができます。 ここにPixelStreamingを行う際のWebUIに使用するHTMLファイルやJSファイルが格納されています。 Public配下のplayer.htmlがPixelStreamingで表示される静的ファイルになります。 まずはBluePrintAPIを発火させるためのボタンを作成します。 HTMLファイル内の <div id="player"></div> を下記に変更します。 <div id="player"> <button type="button" style="position:absolute; z-index:1;" onclick="execPrintString('Hello PixelStreaming');">Hello</button> </div> 次に JavaScript を追加します。 bodyタグの末尾に下記のscriptタグを追加します。 <script type="text/javascript"> function execPrintString(text) { var descriptor = { Value: text } emitUIInteraction(descriptor); } </script> この「emitUIInteraction」の引数の Json や文字列がBluePrintAPIに渡り、先ほど作成したBluePrintの「UI Interaction」のDescriptorに格納されます。 これでHTMLファイルの更新の完了です。 5. UEアプリのパッケージング実行 次にUnrealEngineで作成したプロジェクトをパッケージ化します。 コンピュータ上のフォルダを参照して、パッケージ化されたアプリを配置する場所を決めます。 パッケージ化が完了すると、指定した場所に「 windows 」フォルダが配置されます。 その「 windows 」フォルダの中の作成したexeファイルにパラメータを付与するためにショートカットを作成します。 ショートカットを作成したら、そのショートカットのプロパティを選択し、ターゲットの欄に -AudioMixer -PixelStreamingIP=localhost -PixelStreamingPort=8888 -AllowPixelStreamingCommands の記述を追加します。 これでパッケージ化の完了です。 SignallingServerを起動し、上記で作成したexeファイルのショートカットからパッケージ化したアプリを開くことができます。 実際にEC2で設定した グローバルIP をブラウザで入力することでPixelStreamingしたWebサイトに接続できます。 WebUIの左上部に作成した「Hello」のボタンが設置されており、押下することで「Hello PixelStreaming」という文字列が表示されていれば成功です! 終わりに 今回はPixelStreamingでWebUIのカスタマイズに挑戦してみました。 まだボタンを追加してみただけですが、今後WebUIに欲しい要素をWeb側とUE側どちらにも追加していくことができるようになりました。 BluePrintAPIにはWebUIからの情報をUEに反映させるだけではなく、UE側の変更をWebUIに反映させることもできるようなので、今後はそちらの調査を行ってみようと思います。 現在ISIDは web3領域のグループ横断組織 を立ち上げ、Web3および メタバース 領域のR&Dを行っております(カテゴリー「3DCG」の記事は こちら )。 もし本領域にご興味のある方や、一緒にチャレンジしていきたい方は、ぜひお気軽にご連絡ください! 私たちと同じチームで働いてくれる仲間を、是非お待ちしております! ISID採用ページ(Web3/メタバース/AI) 参考 ・ https://docs.unrealengine.com/5.1/ja/customizing-the-player-web-page-in-unreal-engine/ ・ https://docs.unrealengine.com/5.1/ja/getting-started-with-pixel-streaming-in-unreal-engine/ ・ https://zenn.dev/pate_techmemo/articles/07721136756dcc 執筆: @okazaki.wataru 、レビュー: @wakamoto.ryosuke ( Shodo で執筆されました )
こんにちは!金融ソリューション事業部の山下です。 本記事では、 前回の記事 に続き、StableDiffusion生成画像からUnrealEngineで使用可能なマテリアルを生成します。 今回はノードベー スプログ ラミングなど不要ですので、さくっと終わります。 実施環境/ツール 実施手順 1. Stable Diffusionでパターン画像の生成 2. Substance 3D Samplerでマテリアル化 3. Unreal Engineでマテリアル適用、レンダリング 所感 参考 実施環境/ツール OS: Windows 11 pro GPU : NVIDIA GeForce RTX 3070Ti Laptop DCC: Adobe Substance 3D Sampler 3.4.1 Game Engine: Unreal Engine 5.1.0 AI Model:Stable Diffusion 2.1 実施手順 Stable Diffusionでパターン画像の生成 Substance 3D Samplerでマテリアル化 Unreal Engine でマテリアル適用、 レンダリング 1. Stable Diffusionでパターン画像の生成 ChatGPTで生成した以下プロンプトで、パターン画像を生成しました。 beautiful pattern of traditional japanese paper, Exquisite, intricate, delicate, elegant, sophisticated, subtle, timeless, stunning, lustrous, and graceful, 8k, seamless, no dof, use of gold --- (DeepL日本語訳) 伝統的な和紙の美しい模様、精巧、複雑、繊細、エレガント、洗練、繊細、時代を超えた、見事、光沢、優美、8K、シームレス、ノードフ、金使用 Sampling Stepsは100、Resolutionは768x768です。 バッチ処理 で300枚くらい生成したものから、良さそうなものを4パターンほど選定しました。 2. Substance 3D Samplerでマテリアル化 プロジェクト新規作成後、生成画像をレイヤータブに ドラッグ&ドロップ します。 「画像からマテリアル」を選択して「読み込み」を押下します。 マテリアルが作成されます。 上記はプレビュー形式を「布」で表示しております。 ちなみに、「服」形式などのプレビューも可能です。 このままでも十分マテリアルとして使えますが、よりリアルな質感にする為、刺繍フィルターを適用します。 「アセット」>「フィルター」>「Embroidery」を追加します。 質感に刺繍フィルターが加わり、よりリアルになりました。 マテリアル画像として書き出します。 「共有」>「書き出し」>「書き出し形式...」を押下します。 ディレクト リや必要なチャンネルを指定して、「書き出し」を押下します。 無事、アウトプット ディレクト リに画像が出力されていれば、マテリアルは完成です。 3. Unreal Engine でマテリアル適用、 レンダリング Content Drawerに、マテリアル画像をimportします。 マテリアルを新規作成して、ノードを接続します。 任意のメッシュにマテリアルを適用すれば完成です。 4パターンのうち、2パターン分のマテリアルを作成してメッシュに適用しました。 以下、MovieRenderQueueで レンダリング を行い、 After Effects で動画変換した結果になります。 所感 今回、StableDiffusion生成画像からフォトリアルなマテリアルを生成しました。これまでのマテリアル制作ワークフローにおけるプロセスを飛躍的に改善できる可能性があると感じました。 一方で、現状はAIに関する一定の リテラシー が求められることも否めません(プロンプトの調整やAIモデルの選定、必要に応じたファインチューニングなど)。 まだ誰もが使える形ではツール等に組み込まれていない以上、このままの形で一般普及するイメージはつきませんが、AI x 3DCGの領域は文字通り日進月歩で進化している為、想像以上に早期にブレークスルーが起きると思われます。引き続き本領域、注目していきたいと思います! 現在ISIDは web3領域のグループ横断組織 を立ち上げ、Web3および メタバース 領域のR&Dを行っております(カテゴリー「3DCG」の記事は こちら )。 もし本領域にご興味のある方や、一緒にチャレンジしていきたい方は、ぜひお気軽にご連絡ください! 私たちと同じチームで働いてくれる仲間を、是非お待ちしております! ISID採用ページ(Web3/メタバース/AI) 参考 https://www.youtube.com/watch?v=cL_ZYdkIqBU https://shop.cgworld.jp/shopdetail/000000001040/ https://www.adobe.com/jp/products/substance3d-sampler.html 執筆: @yamashita.yuki 、レビュー: @wakamoto.ryosuke ( Shodo で執筆されました )
こんにちは!金融ソリューション事業部の山下です。 本記事では、 前回の記事 に続き、StableDiffusion生成画像からUnrealEngineで使用可能なマテリアルを生成します。 今回はノードベー スプログ ラミングなど不要ですので、さくっと終わります。 実施環境/ツール 実施手順 1. Stable Diffusionでパターン画像の生成 2. Substance 3D Samplerでマテリアル化 3. Unreal Engineでマテリアル適用、レンダリング 所感 参考 実施環境/ツール OS: Windows 11 pro GPU : NVIDIA GeForce RTX 3070Ti Laptop DCC: Adobe Substance 3D Sampler 3.4.1 Game Engine: Unreal Engine 5.1.0 AI Model:Stable Diffusion 2.1 実施手順 Stable Diffusionでパターン画像の生成 Substance 3D Samplerでマテリアル化 Unreal Engine でマテリアル適用、 レンダリング 1. Stable Diffusionでパターン画像の生成 ChatGPTで生成した以下プロンプトで、パターン画像を生成しました。 beautiful pattern of traditional japanese paper, Exquisite, intricate, delicate, elegant, sophisticated, subtle, timeless, stunning, lustrous, and graceful, 8k, seamless, no dof, use of gold --- (DeepL日本語訳) 伝統的な和紙の美しい模様、精巧、複雑、繊細、エレガント、洗練、繊細、時代を超えた、見事、光沢、優美、8K、シームレス、ノードフ、金使用 Sampling Stepsは100、Resolutionは768x768です。 バッチ処理 で300枚くらい生成したものから、良さそうなものを4パターンほど選定しました。 2. Substance 3D Samplerでマテリアル化 プロジェクト新規作成後、生成画像をレイヤータブに ドラッグ&ドロップ します。 「画像からマテリアル」を選択して「読み込み」を押下します。 マテリアルが作成されます。 上記はプレビュー形式を「布」で表示しております。 ちなみに、「服」形式などのプレビューも可能です。 このままでも十分マテリアルとして使えますが、よりリアルな質感にする為、刺繍フィルターを適用します。 「アセット」>「フィルター」>「Embroidery」を追加します。 質感に刺繍フィルターが加わり、よりリアルになりました。 マテリアル画像として書き出します。 「共有」>「書き出し」>「書き出し形式...」を押下します。 ディレクト リや必要なチャンネルを指定して、「書き出し」を押下します。 無事、アウトプット ディレクト リに画像が出力されていれば、マテリアルは完成です。 3. Unreal Engine でマテリアル適用、 レンダリング Content Drawerに、マテリアル画像をimportします。 マテリアルを新規作成して、ノードを接続します。 任意のメッシュにマテリアルを適用すれば完成です。 4パターンのうち、2パターン分のマテリアルを作成してメッシュに適用しました。 以下、MovieRenderQueueで レンダリング を行い、 After Effects で動画変換した結果になります。 所感 今回、StableDiffusion生成画像からフォトリアルなマテリアルを生成しました。これまでのマテリアル制作ワークフローにおけるプロセスを飛躍的に改善できる可能性があると感じました。 一方で、現状はAIに関する一定の リテラシー が求められることも否めません(プロンプトの調整やAIモデルの選定、必要に応じたファインチューニングなど)。 まだ誰もが使える形ではツール等に組み込まれていない以上、このままの形で一般普及するイメージはつきませんが、AI x 3DCGの領域は文字通り日進月歩で進化している為、想像以上に早期にブレークスルーが起きると思われます。引き続き本領域、注目していきたいと思います! 現在ISIDは web3領域のグループ横断組織 を立ち上げ、Web3および メタバース 領域のR&Dを行っております(カテゴリー「3DCG」の記事は こちら )。 もし本領域にご興味のある方や、一緒にチャレンジしていきたい方は、ぜひお気軽にご連絡ください! 私たちと同じチームで働いてくれる仲間を、是非お待ちしております! ISID採用ページ(Web3/メタバース/AI) 参考 https://www.youtube.com/watch?v=cL_ZYdkIqBU https://shop.cgworld.jp/shopdetail/000000001040/ https://www.adobe.com/jp/products/substance3d-sampler.html 執筆: @yamashita.yuki 、レビュー: @wakamoto.ryosuke ( Shodo で執筆されました )
こんにちは。HCM事業部5年目の尾形です。 今回は「若手による仕事の紹介」として、 理系院卒・HCM事業部 所属という立場から私の仕事についてお話しします。記事タイトルはお気に入りの小説から。風景描写が好きです。 簡単な自己紹介 ISIDではかなりレアなのですが、 博士後期 課程を修了して入社し、HCM事業部に配属されました。 入社のきっかけは、同じ研究室の先輩が先に入社しており、働きやすそうだなと思ったことでした。 最近はインクを塗りあう某 シューティングゲーム にハマっています。 一応X帯です。 「人財」管理の システム開発 真面目な話に移ります。 この記事を読んでいる人の多くは就職活動中の方かと思いますが、 HCM事業部 と聞いて何をしている部署か分かるでしょうか? 入社するまで私もよく分かっていませんでした。 HCMとは Human Capital Management の略で、日本語では人財管理 1 と言われます。どの組織にどんなスキルを持った人がいるのか、企業規模が大きくなるほど管理は難しくなります。 そんな課題を解決するため、ISIDでは自社パッケージ 2 商品である統合HCMシステム「 POSITIVE 」 3 を提供しています。HCM事業部はPOSITIVEの開発、販売、導入を行っている部署で、私はPOSITIVEの開発に携わっています。 パッケージ開発という仕事 ISIDの業態は何かと聞かれれば、おそらく SIer ということになると思います。 SIer であればお客様の要望を伺い、ニーズを満たすシステムを開発していくのが本筋ですが、HCM事業部の仕事は大きく異なります。 POSITIVEというパッケージを開発している以上、特定のお客様の要望に沿って開発するのではなく、 利用される全てのお客様にとって価値のある改善 を重ねていく必要があります。 POSITIVE製品サイトの導入事例 4 に掲載されている通り、 ANAホールディングス 株式会社様や 明治安田生命保険 相互会社様など、皆さんもよく知るであろう多くの企業様でご利用いただいています。 業種・業態にとらわれない多種多様な企業がお客様となっており、私たちの業務にも非常に大きな責任が伴います。 さらなる人財活用に向けた改善 最近ではISO30414 5 という人的資本情報開示に関する標準化も行われ、社内のみならず投資家からも人財情報活用が注目を集めています。 そんな背景の中、 どんな機能・サービスを提供すればお客様に喜んで使ってもらえるか をよく考えて今後の対応を企画することが開発者に求められています。これは既存機能の改善だけに留まらず、全く新しいア イデア を創出する必要もあるため、自由度が高いがゆえに非常に難しい課題です。しかし同時に、これはとてもやりがいのある課題でもあり、私もチームの一員として日々議論を重ねています。 これだけだと意識だけ高いふわっとした内容になってしまうので、具体的に何をやっているのか付け加えておきます。 今後対応すべき内容を決定するための顧客訪問・ ヒアリ ング(オンライン・オフラインどちらも) 人財管理をめぐる市場動向調査(各種資料を基にした調査や、チーム内でのディスカッション) お客様に影響する法改正への対応 先に挙げたような大きな課題もありますが、他にも直近で対応すべき課題もあります。 POSITIVEは人財情報の管理だけでなく、 給与計算、就業管理 の機能も備えています 6 。 特に給与計算においては、 社会保険 や税制など、毎年のように法制度が改正されるため、その対応が必要になります。 これは特に緊急性が高く、お客様が円滑に業務を遂行するために迅速かつ確実な対応が求められます。 この対応に際し、具体的には以下のような業務にあたっています。 各種開発作業(システム設計、実装、テスト。全てやることもあれば一部担当することもあります。) 開発作業のプロジェクト管理(人員・スケジュール調整、開発体制の運用見直し) 一日のスケジュール 一日のスケジュールとしてはこんな感じです。 9:30始業、主にテレワークでの業務のため、やや夜型な生活になっています。 あくまで一例ですので、日によっては私用のため早く切り上げたり、もう少し遅い時間まで仕事を進めることもあります。 研究も仕事に活かせる 私の大学院時代の研究テーマは無線通信でした。 お察しの通り、当時の研究内容と今の業務内容は全く関係ありません。 しかしながら、研究活動を通して得たスキルは今の業務に大きく活きていると実感しています。 相手に伝えるスキル 研究成果は学会発表や論文という形で外部に公開しますが、自分の研究内容を他者に伝えるのはとても難しいことです。 それでもエッセンスを理解してもらうために、皆さんも伝え方には特に気を配り、苦心していると思います。 伝える内容が異なるだけで、 伝え方を考えるプロセスは業務でも日常的に発生します 。 うまく伝わらないことに起因する ディスコミュニケーション は業務に大きな支障をきたしますので、このスキルは一見地味ですが非常に重要です。 論理的思考力 ビジネス書でも良く見る単語ですが、研究活動では必須スキルです。 会社に入ってから研修をわざわざ受けて身に付けることも多いスキルを、 研究活動では当たり前のように使っている わけです。 問題は何か、原因は何か、改善すべきポイントはどこか。 こういったことを考え続ける点も、研究と仕事に共通する点の一つと言えます。 学生のうちに勉強しておいた方がいいこと よく「大学生のうちに勉強しておいた方がいいことは?」と聞かれますが、ビジネス系の知識は入社後からでも十分身に付けられます。 大学に通う期間は(院進学等もろもろ込みでも)10年もないと思います。 一方、会社に入ってから過ごす期間は会社が変わったとしても40年以上あるでしょう。 勉強に励むのはもちろんですが、友人と楽しく過ごす・趣味に没頭する・新しいことに挑戦するなど、今しかできないことに注力してもらえればと個人的には思います。 趣味でやっていたことが仕事の思わぬところで役に立つ 、なんてこともあります。 某ゲームでは、味方に気を配り自分のブキができる最大限のサポートをすることが非常に重要です。仕事でも同様に、周囲の状況を見つつ、自分の長所を活かしたサポートをすることがチームの KO勝ち プロジェクト成功につながります。 最後に ISIDは新卒入社者への教育・サポート体制が手厚く、働き始めるうえで不自由のないよう会社が尽力してくれています。 少しでも興味が湧いたら、是非新卒採用サイトも覗いてみてください。 www.isid.co.jp 執筆: @ogata 、レビュー: Ishizawa Kento (@kent) ( Shodo で執筆されました ) 近年ではヒトを資源(Resource)から資本(Capital)として捉える潮流があり、「人材」よりも「人財」という表現を使うことも多いです。 ↩ パッケージ商品とは、お店で売っているような既製品のことです。とはいえPOSITIVEは家電量販店等で販売しているわけではなく、お客様から問い合わせをいただいて個別に販売・導入を行っています。 ↩ POSITIVE紹介サイト(ISID): https://www.isid.co.jp/positive/ ↩ POSITIVE紹介サイト(ISID)-導入企業一覧: https://www.isid.co.jp/positive/case/ ↩ ISO30414(ISO): https://www.iso.org/standard/69338.html ↩ グループ展開されている大企業様向けに人事・給与・就業を一括で管理できるシステムを提供していることは、POSITIVEの強みの一つです。 ↩
こんにちは。HCM事業部5年目の尾形です。 今回は「若手による仕事の紹介」として、 理系院卒・HCM事業部 所属という立場から私の仕事についてお話しします。記事タイトルはお気に入りの小説から。風景描写が好きです。 簡単な自己紹介 ISIDではかなりレアなのですが、 博士後期 課程を修了して入社し、HCM事業部に配属されました。 入社のきっかけは、同じ研究室の先輩が先に入社しており、働きやすそうだなと思ったことでした。 最近はインクを塗りあう某 シューティングゲーム にハマっています。 一応X帯です。 「人財」管理の システム開発 真面目な話に移ります。 この記事を読んでいる人の多くは就職活動中の方かと思いますが、 HCM事業部 と聞いて何をしている部署か分かるでしょうか? 入社するまで私もよく分かっていませんでした。 HCMとは Human Capital Management の略で、日本語では人財管理 1 と言われます。どの組織にどんなスキルを持った人がいるのか、企業規模が大きくなるほど管理は難しくなります。 そんな課題を解決するため、ISIDでは自社パッケージ 2 商品である統合HCMシステム「 POSITIVE 」 3 を提供しています。HCM事業部はPOSITIVEの開発、販売、導入を行っている部署で、私はPOSITIVEの開発に携わっています。 パッケージ開発という仕事 ISIDの業態は何かと聞かれれば、おそらく SIer ということになると思います。 SIer であればお客様の要望を伺い、ニーズを満たすシステムを開発していくのが本筋ですが、HCM事業部の仕事は大きく異なります。 POSITIVEというパッケージを開発している以上、特定のお客様の要望に沿って開発するのではなく、 利用される全てのお客様にとって価値のある改善 を重ねていく必要があります。 POSITIVE製品サイトの導入事例 4 に掲載されている通り、 ANAホールディングス 株式会社様や 明治安田生命保険 相互会社様など、皆さんもよく知るであろう多くの企業様でご利用いただいています。 業種・業態にとらわれない多種多様な企業がお客様となっており、私たちの業務にも非常に大きな責任が伴います。 さらなる人財活用に向けた改善 最近ではISO30414 5 という人的資本情報開示に関する標準化も行われ、社内のみならず投資家からも人財情報活用が注目を集めています。 そんな背景の中、 どんな機能・サービスを提供すればお客様に喜んで使ってもらえるか をよく考えて今後の対応を企画することが開発者に求められています。これは既存機能の改善だけに留まらず、全く新しいア イデア を創出する必要もあるため、自由度が高いがゆえに非常に難しい課題です。しかし同時に、これはとてもやりがいのある課題でもあり、私もチームの一員として日々議論を重ねています。 これだけだと意識だけ高いふわっとした内容になってしまうので、具体的に何をやっているのか付け加えておきます。 今後対応すべき内容を決定するための顧客訪問・ ヒアリ ング(オンライン・オフラインどちらも) 人財管理をめぐる市場動向調査(各種資料を基にした調査や、チーム内でのディスカッション) お客様に影響する法改正への対応 先に挙げたような大きな課題もありますが、他にも直近で対応すべき課題もあります。 POSITIVEは人財情報の管理だけでなく、 給与計算、就業管理 の機能も備えています 6 。 特に給与計算においては、 社会保険 や税制など、毎年のように法制度が改正されるため、その対応が必要になります。 これは特に緊急性が高く、お客様が円滑に業務を遂行するために迅速かつ確実な対応が求められます。 この対応に際し、具体的には以下のような業務にあたっています。 各種開発作業(システム設計、実装、テスト。全てやることもあれば一部担当することもあります。) 開発作業のプロジェクト管理(人員・スケジュール調整、開発体制の運用見直し) 一日のスケジュール 一日のスケジュールとしてはこんな感じです。 9:30始業、主にテレワークでの業務のため、やや夜型な生活になっています。 あくまで一例ですので、日によっては私用のため早く切り上げたり、もう少し遅い時間まで仕事を進めることもあります。 研究も仕事に活かせる 私の大学院時代の研究テーマは無線通信でした。 お察しの通り、当時の研究内容と今の業務内容は全く関係ありません。 しかしながら、研究活動を通して得たスキルは今の業務に大きく活きていると実感しています。 相手に伝えるスキル 研究成果は学会発表や論文という形で外部に公開しますが、自分の研究内容を他者に伝えるのはとても難しいことです。 それでもエッセンスを理解してもらうために、皆さんも伝え方には特に気を配り、苦心していると思います。 伝える内容が異なるだけで、 伝え方を考えるプロセスは業務でも日常的に発生します 。 うまく伝わらないことに起因する ディスコミュニケーション は業務に大きな支障をきたしますので、このスキルは一見地味ですが非常に重要です。 論理的思考力 ビジネス書でも良く見る単語ですが、研究活動では必須スキルです。 会社に入ってから研修をわざわざ受けて身に付けることも多いスキルを、 研究活動では当たり前のように使っている わけです。 問題は何か、原因は何か、改善すべきポイントはどこか。 こういったことを考え続ける点も、研究と仕事に共通する点の一つと言えます。 学生のうちに勉強しておいた方がいいこと よく「大学生のうちに勉強しておいた方がいいことは?」と聞かれますが、ビジネス系の知識は入社後からでも十分身に付けられます。 大学に通う期間は(院進学等もろもろ込みでも)10年もないと思います。 一方、会社に入ってから過ごす期間は会社が変わったとしても40年以上あるでしょう。 勉強に励むのはもちろんですが、友人と楽しく過ごす・趣味に没頭する・新しいことに挑戦するなど、今しかできないことに注力してもらえればと個人的には思います。 趣味でやっていたことが仕事の思わぬところで役に立つ 、なんてこともあります。 某ゲームでは、味方に気を配り自分のブキができる最大限のサポートをすることが非常に重要です。仕事でも同様に、周囲の状況を見つつ、自分の長所を活かしたサポートをすることがチームの KO勝ち プロジェクト成功につながります。 最後に ISIDは新卒入社者への教育・サポート体制が手厚く、働き始めるうえで不自由のないよう会社が尽力してくれています。 少しでも興味が湧いたら、是非新卒採用サイトも覗いてみてください。 www.isid.co.jp 執筆: @ogata 、レビュー: Ishizawa Kento (@kent) ( Shodo で執筆されました ) 近年ではヒトを資源(Resource)から資本(Capital)として捉える潮流があり、「人材」よりも「人財」という表現を使うことも多いです。 ↩ パッケージ商品とは、お店で売っているような既製品のことです。とはいえPOSITIVEは家電量販店等で販売しているわけではなく、お客様から問い合わせをいただいて個別に販売・導入を行っています。 ↩ POSITIVE紹介サイト(ISID): https://www.isid.co.jp/positive/ ↩ POSITIVE紹介サイト(ISID)-導入企業一覧: https://www.isid.co.jp/positive/case/ ↩ ISO30414(ISO): https://www.iso.org/standard/69338.html ↩ グループ展開されている大企業様向けに人事・給与・就業を一括で管理できるシステムを提供していることは、POSITIVEの強みの一つです。 ↩