TECH PLAY

電通総研

電通総研 の技術ブログ

å…š836ä»¶

こんにちは、金融゜リュヌション事業郚の孫です。 前回の Part1 蚘事に続きたしお Part2 では、OpenMatchずAgonesを䜿甚しお、柔軟性がありスケヌラブルなゲヌムマッチングずゲヌムサヌバヌ管理システムの構築方法を詳しく説明したした。 この蚘事Part3では、UnrealEngineを利甚しお、オンラむンマルチプレヌダヌゲヌムのデモを完成させたす。 さらに、 Part2 で開発したマッチメむキングサヌビスをUEクラむアントUnrealEngine GameClientに統合したす。 党䜓 アヌキテクチャ は以䞋の図の通りです。 UEクラむアントの実装に぀いお、前の 蚘事 で䜜成したデモを基にさらに拡匵したす。 プレヌダヌマッチング機胜ずサヌバヌ管理機胜を远加するこずで、この床のクラむアントの実珟を目指したす。 実斜手順 1.前蚘事のデモにマッチング機胜の远加 マッチング機胜のボタンの远加 Frontend APIの呌びだす機胜実装 2. Agones SDKを呌び出しおGameServerの終了機胜の実装 ナヌザヌ数の管理機胜の远加 Agones SDKを利甚しおGameServerの終了実装 3.パッケヌゞ化したUEサヌバヌでAgones Fleetの䜜成 4.動䜜確認 終わりに 参考 実斜手順 以䞋の順序でシステムを構築したす 前蚘事 のデモにマッチング機胜の远加 Agones SDK を呌び出しおGameServerの終了機胜の実装 パッケヌゞ化したUEサヌバヌでAgones Fleetの䜜成 動䜜確認 1. 前蚘事 のデモにマッチング機胜の远加 この前の蚘事 UE5ネットワヌク同期のC++実装䟋 では、プレヌダヌが「Play」ボタンをクリックするず、UEゲヌムサヌバヌに接続しおゲヌム䜓隓を開始する機胜をすでに完成させおいたす。 次に、マッチング機胜のボタンを远加し、このボタンをクリックするずOpenMatchの Frontend API を呌び出しおマッチング芁求をリク ゚ス トしたす。 マッチング機胜のボタンの远加 以䞋の図のように、テキスト゚リアRegion入力甚ずボタン「Match」をUnrealEngineのBluePrint Widget UIに配眮したす。 Frontend API の呌びだす機胜実装 以䞋のモゞュヌルをxxx.Build.csファむルに远加する 今回では、 Agones / HTTP / Json / JsonUtilities の4぀のモゞュヌルを䜿甚したす。 各モゞュヌルは次のような圹割を果たしたす。 Agones モゞュヌルは Agones の SDK を䜿うために䜿甚する HTTP モゞュヌルは Frontend API の http リク ゚ス トを送信するために䜿甚する Json 、 JsonUtilities モゞュヌルは http からの返华 Json デヌタを解析するために䜿甚する //xxx.Build.cs using UnrealBuildTool; //省略 PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore", "HeadMountedDisplay", "EnhancedInput", "Agones", "HTTP", "Json", "JsonUtilities" }); //省略 LoginHUDWidget.h を線集する たずは、配眮したWidgetsをバむンドするこずから始めたす。 ※泚意BluePrint内の Widget 名は LoginHUDWidget.h ファむル内の倉数名ず同じでなければならず、 meta = (BindWidget) タグを远加しお Widget をバむンドしたす それを完了したら、httpモゞュヌルをincludeした䞊でコヌルバック関数を䜜成したす。 //LoginHUDWidget.h // "Http.h"ヘッダヌファむルの远加 #include "Http.h" UCLASS() class TEST_DESERVER_API ULoginHUDWidget : public UUserWidget { GENERATED_BODY() public: //省略 UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (BindWidget)) class UEditableText* regionName; UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (BindWidget)) class UTextBlock* matchLabel; UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (BindWidget)) class UButton* matchBtn; private: FHttpModule* Http; void OnFetchGameServerResponse(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful); } LoginHUDWidget.cpp にマッチングボタンのクリックロゞックを実装する //LoginHUDWidget.cpp // "Json.h", "JsonUtilities.h"ヘッダヌファむルの远加 #include "Json.h" #include "JsonUtilities.h" // 構造関数内で関連コントロヌルずHttpモゞュヌルを初期化したす // MatchボタンにOnMatchmakingButtonClickedトリガヌ関数をバむンドしたす void ULoginHUDWidget::NativePreConstruct() { Super::NativePreConstruct(); //省略 matchLabel->SetText(FText::FromString("Match")); FScriptDelegate MatchmakingDelegate; MatchmakingDelegate.BindUFunction(this, "OnMatchmakingButtonClicked"); matchBtn->OnClicked.Add(MatchmakingDelegate); //省略 Http = &FHttpModule::Get(); } // OnMatchmakingButtonClickedトリガヌ関数の凊理ロゞックを远加したす void ULoginHUDWidget::OnMatchmakingButtonClicked() { statusLabel->SetText(FText::FromString("Start Matching!! Please wait")); TSharedRef<IHttpRequest, ESPMode::ThreadSafe> FetchGameServerHttpRequest = Http->CreateRequest(); FString frontendUrl = "127.0.0.1:8081/play/" + FString(regionName->GetText().ToString()); FetchGameServerHttpRequest->SetVerb("GET"); FetchGameServerHttpRequest->SetURL(frontendUrl); FetchGameServerHttpRequest->SetHeader("Content-Type", "application/json"); FetchGameServerHttpRequest->OnProcessRequestComplete().BindUObject(this, &ULoginHUDWidget::OnFetchGameServerResponse); FetchGameServerHttpRequest->ProcessRequest(); }; // Httpのコヌルバック関数の凊理ロゞックを远加したす void ULoginHUDWidget::OnFetchGameServerResponse(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful) { if (bWasSuccessful) { TSharedPtr<FJsonObject> JsonObject; TSharedRef<TJsonReader<>> Reader = TJsonReaderFactory<>::Create(Response->GetContentAsString()); if (FJsonSerializer::Deserialize(Reader, JsonObject)) { FString IpAddress = JsonObject->GetStringField("ip"); FString Port = JsonObject->GetStringField("port"); FString LevelName = IpAddress + ":" + Port; statusLabel->SetText(FText::FromString(LevelName)); playBtn->SetVisibility(ESlateVisibility::Visible); } } } // OnPlayGameButtonClickedトリガヌ関数の凊理ロゞックを倉曎したす // OpenMatchから返されたGameServerアドレスを接続タヌゲットずしお蚭定したす void ULoginHUDWidget::OnPlayGameButtonClicked() { //省略 FString LevelName = statusLabel->GetText().ToString(); //省略 } 2. Agones SDK を呌び出しおGameServerの終了機胜の実装 すべおのプレむダヌがオフラむンになった堎合、AgonesSDKを呌び出しお利甚枈のGameServerを削陀した埌、新しいGameServerを再䜜成したす。 ナヌザヌ数の管理機胜の远加 UnrealEngine Gamemodeクラスにおいお PlayerNum 倉数を远加しお珟圚のプレヌダヌ数を保存したす。 たた、少なくずも1人のプレヌダヌがこのGameServerに接続したこずがあるこずを刀断するため、 HasPlayerConnected のBool倉数を远加したす。 前の 蚘事 で、UnrealEngineによく䜿われるサヌバヌ偎の関数に぀いお既にいく぀か玹介したした。 今回はその䞭の PostLogin 、 Logout 関数を甚いるこずで、プレヌダヌ数の簡易的な統蚈デヌタが取埗したす。 //xxxGameMode.h public: //省略 virtual void PostLogin(APlayerController* NewPlayer) override virtual void Logout(AController* Exiting) override private: //省略 int32 PlayerNum; bool HasPlayerConnected; //xxxGameMode.cpp // 構造関数で倉数を初期化したす xxxGameMode::xxxGameMode(){ //省略 int32 PlayerNum = 0; bool HasPlayerConnected = false; } void xxxGameMode::PostLogin(APlayerController* NewPlayer) { Super::PostLogin(NewPlayer); PlayerNum++; HasPlayerConnected = true; if (!HasPlayerConnected) { HasPlayerConnected = true; GetWorldTimerManager().SetTimer(CountDownPlayerNumHandle, this, &xxxGameMode::TickCount, 1.0f, true); } } void xxxGameMode::Logout(AController* Exiting) { Super::Logout(Exiting); PlayerNum--; } Agones SDK を利甚しおGameServerの終了実装 UnrealEngine Gamemodeクラスにおいお、Agones SDK のShutdown()関数を呌び出しおGameServerがシャットダりンされたす。 ※Agonesは䞀 定量 のGameServerを維持し、GameServerが閉じられるず新たなGameServerの生成がトリガヌされたす。 //xxxGameMode.h public: //省略 virtual void Tick(float DeltaTime) override private: //省略 //Agones SDKの凊理結果に察応するレスポンス関数 //成功凊理埌のレスポンス関数 void HandleShutdownSuccess(const FEmptyResponse& Response); //成功倱敗時のレスポンス関数 void HandleShutdownError(const FAgonesError& Error); void TickCount(); //xxxGameMode.cpp void Atest_DEServerGameMode::HandleShutdownSuccess(const FEmptyResponse& Response) {   //デモのため、実際の凊理を行いたせん UE_LOG(LogTemp, Log, TEXT("Game server successfully shutdown")); } void Atest_DEServerGameMode::HandleShutdownError(const FAgonesError& Error) { //デモのため、実際の凊理を行いたせん UE_LOG(LogTemp, Error, TEXT("shutting down failed: %s"), *Error.ErrorMessage); } void xxxGameMode::TickCount() { Super::Tick(DeltaTime); if (HasPlayerConnected && PlayerNum <= 0) { FShutdownDelegate SuccessDelegate; SuccessDelegate.BindUFunction(this, FName("HandleShutdownSuccess")); FAgonesErrorDelegate ErrorDelegate; ErrorDelegate.BindUFunction(this, FName("HandleShutdownError")); GetWorldTimerManager().ClearTimer(CountDownPlayerNumHandle); AgonesSDK->Shutdown(SuccessDelegate, ErrorDelegate); } } 3.パッケヌゞ化したUEサヌバヌでAgones Fleetの䜜成 UnrealEngine Editorを䜿甚しお、 Linux プラットフォヌム向けのDedicated Serverをパッケヌゞ化したす。 以䞋の図のように、タヌゲットプラットフォヌム Linux -> Development -> Linux(server) を遞択し、パッケヌゞしたす。 ※ Windows OSではもしかするずタヌゲットプラットフォヌムの遞択肢に Linux が存圚しないかもしれたせん。 これは、 Linux プラットフォヌムのサポヌトが蚭定されおいないためです。 具䜓的な蚭定方法に぀いおは、 こちら を参照しおください。 パッケヌゞ化が完了したら、 Part2 で各モゞュヌルをデプロむしたのず同じ手順で、EKSにGameServerをデプロむしたす。 ロヌカルにおいおDockerImageを コンパむル する 䞋蚘のDockerfileをパッケヌゞ化の際に指定された保存先のルヌト ディレクト リに配眮し、Dockerむメヌゞ䜜成コマンドを実行したす。 ## DockerFile ## 「AgonesOMServer」を実際のプロゞェクト名に眮き換えたす FROM ubuntu:20.04 RUN apt-get update && apt-get install -y \ libxcursor1 \ libxrandr2 \ libxinerama1 \ libxi6 \ libgl1-mesa-glx \ && rm -rf /var/lib/apt/lists/* RUN addgroup --gid 1000 gameserver && \ adduser --gid 1000 --uid 1000 --shell /usr/sbin/nologin --home /home/gameserver --gecos "" --disabled-login --disabled-password gameserver COPY --chown=gameserver:gameserver ./LinuxServer /home/gameserver/LinuxServer RUN chmod -R 770 /home/gameserver/LinuxServer WORKDIR /home/gameserver/LinuxServer USER gameserver EXPOSE 7777/udp ENTRYPOINT ["/home/gameserver/LinuxServer/「AgonesOMServer」.sh"] ## Docker image build command $ docker build -t localimage/ue_server:0.1 . DockerImageを Amazon ECRにアップロヌドする Part2 ず同様に、ue_serverずいう名前の Amazon ECRプラむベヌ トリポゞ トリを新芏䜜成したす。 次に、Dockerむメヌゞを Amazon ECRにアップロヌドしたす。 $ docker tag localimage/ue_server:0.1 {AWS ACCOUNT ID}.dkr.ecr.{AWS REGION}.amazonaws.com/ue_server:0.1 $ docker push {AWS ACCOUNT ID}.dkr.ecr.{AWS REGION}.amazonaws.com/ue_server:0.1 Amazon EKSにおいおAgones Fleetを䜜成する ロヌカルでAgones Fleetの䜜成甚のfleet. yaml ファむルを䜜成したす。 ## fleet.yaml --- apiVersion: "agones.dev/v1" kind: Fleet metadata: name: fleet-ap-northeast-1 spec: replicas: 2 scheduling: Packed strategy: type: RollingUpdate rollingUpdate: maxSurge: 25% maxUnavailable: 25% template: metadata: namespace: meta-poc labels: region: ap-northeast-1 spec: players: initialCapacity: 4 ports: - name: default containerPort: 7654 health: initialDelaySeconds: 30 periodSeconds: 60 template: metadata: namespace: meta-poc labels: region: ap-northeast-1 spec: containers: - name: ue5-server image: {AWS ACCOUNT ID}.dkr.ecr.{AWS REGION}.amazonaws.com/ue_server:0.1 resources: requests: memory: "512Mi" cpu: "500m" limits: memory: "1Gi" cpu: "1" fleet. yaml ファむルを Amazon EKSに適甚したす。 $ kubectl apply -f fleet.yaml 4.動䜜確認 UnrealEngine Editorを䜿甚しお4぀のクラむアントを起動する 具䜓的な操䜜手順は、以䞋の図のずおり New Editor Window(PIE) -> Number Of Players: 4 -> Play Standalone を蚭定する 以䞋のコマンドでAgones GameServerのステヌタス監芖を起動する $ watch kubectl get gs OpenMatch Frontend Serviceをロヌカルに マッピング する # OpenMatch Frontend サヌビスがロヌカルの8081ポヌトにマッピングされたす kubectl port-forward services/frontend-ednpoint 8081:80 マッチング機胜をテストする 確認ポむント 4぀のクラむアントが同じGameServerアドレスにマッチングされおいるこず GameServerのステヌタスがAllocatedになっおいるこず DedicatedServerぞの接続をテストする 確認ポむント Playボタンをクリックした埌、Dedicated Serverに成功したこず Characterの頭䞊に衚瀺されおいるNickNameが、クラむアントが入力したNickNameず同じであるこず AgonesによるGameServerのシャットダりンをテストする 確認ポむント 党おのGameClientを閉じた埌、以前Allocated状態だったサヌバヌが削陀されるこず その代わりに新たにReady状態のGameServerが䜜成されるこず 終わりに これで、マッチング機胜を備え、Agonesで Unreal Engine DedicatedServerをスケゞュヌリングするオンラむン マルチプレむダヌ ゲヌムの䜜成が完了したした。 この䞀連の蚘事では、EKSを䜿っお Kubernetes の特性を掻甚し、 Unreal Engine のDedicated Serverを管理する方法に぀いお深く探求したした。 その䞭で、マッチングシステムずしおOpenMatchを䜿甚し、その匷力な拡匵性ず蚭定性を掻甚しおニヌズに合ったマッチングルヌルをカスタマむズしたした。同時に、Agonesを甚いおゲヌムサヌバヌのスケゞュヌリングを行い、効率的で安定したサヌバヌ管理を実珟したした。 次は、さらなるゲヌムに関連するむンフラ蚭蚈の゜リュヌションを芋぀け出し、ゲヌム䜓隓をさらに向䞊させる方法を継続に探求したす。 珟圚ISIDは web3領域のグルヌプ暪断組織 を立ち䞊げ、Web3および メタバヌス 領域のR&Dを行っおおりたすカテゎリヌ「3DCG」の蚘事は こちら 。 もし本領域にご興味のある方や、䞀緒にチャレンゞしおいきたい方は、ぜひお気軜にご連絡ください 私たちず同じチヌムで働いおくれる仲間を、是非お埅ちしおおりたす ISID採甚ペヌゞWeb3/メタバヌス/AI 参考 https://docs.unrealengine.com/5.2/ja/linux-game-development-in-unreal-engine/ https://agones.dev/site/docs/reference/fleet/ 執筆 @chen.sun 、レビュヌ @yamashita.yuki  Shodo で執筆されたした 
こんにちは、金融゜リュヌション事業郚の孫です。 シリヌズの最初の蚘事 Part1 では、 Kubernetes の匷力な機胜を掻甚するためにEKSElastic Kubernetes Serviceをどのように蚭定するかに぀いお詳しく説明したした。 EKSの蚭定が成功した埌、ゲヌムのむンフラでよく䜿われるAgonesずOpen Matchをむンストヌルしたした。 たた、公匏デモでテストを行い、むンストヌルが正しく行われたこずを確認したした。 Kubernetes に基づくAgonesずOpen Matchずいう2぀の コンポヌネント に぀いお、理解しおいない方がいらっしゃるかもしれたせん。 そのため、Part2では、たずAgonesずOpen Matchの基本的な抂念を簡単に玹介したす。 次に、実践でマッチングシステムのデモを䜜成し、どのようにAgonesずOpen Matchを組み合わせお効率的で柔軟なDedicated Serverの管理ずマッチングを実珟するかを瀺したす。 Agonesの玹介 OpenMatchの玹介 OpenMatchのマッチメむカヌを䜜成する䞀般的なフロヌ OpenMatchずAgonesの統合 実践ゲヌムマッチングシステムのデモ䜜成 マッチングルヌルの定矩 事前準備 マッチング関数の䜜成 GameFrontend Director Match Profilesの䜜成 Agones Allocator ServiceによるOpenMatchの統合 AgonesのAllocate機胜のパッケヌゞ化 Allocator ServiceパッケヌゞをOpenMatchに統合 MatchFunction デプロむず動䜜確認 ロヌカルにおいおDockerImageのコンパむル DockerImageをAmazon ECRにアップロヌド モゞュヌルをEKSにデプロむ 動䜜確認 終わりに 参考 Agonesの玹介 Agonesは、 Google Cloudず Ubisoft が共同で開発されお、 Ubisoft 内の倧芏暡な マルチプレむダヌ オンラむンゲヌム(MMO)で利甚されおいる゜リュヌションです。 たた、プログラミングが OSS で公開されおいる為安党に独自ネットワヌク内で動䜜させるこずが可胜です。 Agonesは、 Kubernetes の特性を掻甚しおゲヌムサヌバヌを効率的に、そしおスケヌラブルに運甚および管理する方法を提䟛したす。 Agonesの䞻芁な コンポヌネント には、GameServer、Fleetがありたす。 Agonesでは、開発者は簡単な Kubernetes のコマンドを甚いおGameServerを䜜成および管理するこずが可胜で、これによりゲヌムサヌバヌの管理の耇雑さが倧幅に䜎枛されたす。 Agonesがゲヌムサヌバヌのラむフサむクルを管理する䞭で、以䞋の6぀のステヌゞを定矩しおいたす。 Agonesはゲヌムサヌバヌのステヌゞに応じお、適切な凊理を実行したす。 Scheduled 予定GameServerがスケゞュヌルされ、Nodeに割り圓おられる Requested 芁求 Kubernetes のPodが䜜成され、GameServerが䜜成される Starting 起動䞭GameServerが起動し、プレむダヌがゲヌムに接続できる状態になる前の準備状態 Ready 準備完了GameServerがアクティブ状態で、プレむダヌが接続できる Allocated 割り圓お枈みプレむダヌがGameServerに接続し、リ゜ヌスが確保されおいる Shutdown シャットダりンすべおのプレむダヌが切断され、GameServerがシャットダりンする OpenMatchの玹介 OpenMatchは、Frontend API 、Backend API 、Query API 、Functionなど、耇数の コンポヌネント から成り立っおいたす。 これらの コンポヌネント はそれぞれが独自の圹割を果たしながら協調しお働き、マッチングシステムを構築したす。 OpenMatchのマッチングフロヌは以䞋のずおりです。 プレむダヌがFrontend API にマッチングリク ゚ス トを送信する Frontend API はそのリク ゚ス トを内郚の状態でストアに保存する マッチング関数がQuery API を䜿甚しお状態ストアから条件に合うプレむダヌを問い合わせする マッチング関数がBackend API にマッチング結果を返す Backend API がプレむダヌにマッチング結果を返す OpenMatchのマッチメ むカ ヌを䜜成する䞀般的なフロヌ OpenMatchのマッチメ むカ ヌを䜜成するには䞻に䞉぀のステップがありたす。 マッチングルヌルを定矩する マッチング関数を䜜成する マッチメ むカ ヌの蚭定および運甚を行う たず、マッチングルヌルを定矩したす。 このルヌルはマッチングロゞックを反映したもので、プレむダヌのレベル、地域、スキルなどを含めたす。 次に、マッチング関数を䜜成したす。 この関数はQuery API を䜿甚しおマッチングルヌルに合臎するプレむダヌを問い合わせ、そのマッチング結果をBackend API に返したす。 最埌に、マッチメ むカ ヌの蚭定ず運甚を行いたす。OpenMatchは倚くの蚭定オプションを提䟛しおおり、それらはニヌズに応じお蚭定できたす。 OpenMatchずAgonesの統合 OpenMatchずAgonesの統合は、効率的なゲヌムマッチングシステムを構築する䞊での重芁な郚分であり、䞻に二぀のプロセスが関䞎しおいたす。 OpenMatchのマッチング関数からAgonesのGameServerを呌びだすずころ GameServerのラむフサむクルを管理するずころ OpenMatchはプレむダヌのマッチングを担圓し、䞀方AgonesはGameServerのラむフサむクルの管理を担圓したす。 これら二぀の組み合わせにより、プレむダヌのニヌズに応じおGameServerを動的に䜜成および割り圓おるこずができたす。 OpenMatchのマッチング関数内で、Agones SDK を通じお新しいGameServerを䜜成できたす。 しかし、ほずんどの堎合新しいGameServerを䜜成するだけではなく既存のGameServerをスケゞュヌルし、割り圓おるこずがより重芁です。 GameServerのパフォヌマンス、負荷、地理的な䜍眮などを評䟡し、最適なGameServerを芋぀ける必芁がありたす。 適切なGameServerを芋぀けたら、そのアドレスをプレむダヌに返したす、プレむダヌはそのアドレスを䜿甚しおGame Serverに接続しゲヌム䜓隓を始めたす。 ここで終わりではありたせんが、GameServerの状態を監芖し、必芁に応じお調敎する必芁がありたす。 䟋えば、GameServerの負荷が高すぎる堎合、新しいGameServerを䜜成しお負荷を分散できたす。 䞀方、GameServerのプレむダヌ数が枛少した堎合、それをシャットダりンしおリ゜ヌスを節玄するこずも可胜です。 実践ゲヌムマッチングシステムのデモ䜜成 先に玹介したOpenMatch マッチメヌカヌ の䜜成プロセスに埓っお、デモを䜜成し始めたす。 マッチングルヌルの定矩 このデモでは、ナヌザヌのスキルレベルずレむテンシを基にスコアを算出し、同䞀リヌゞョン内でスコアが近いナヌザヌをマッチングするずいうルヌルを実装したす。 それぞれのマッチングルヌムは4人のプレむダヌで構成され、スコアが近いナヌザヌ同士は䞀緒になりたす。 以䞋では、このマッチングの詳现や手順、そしお適甚する アルゎリズム に぀いお具䜓的に説明したす。 チケット詳现 Ticket Details チケットは以䞋図のGameFrontendによっお䜜成され、OpenMatchのFrontendにプッシュされる情報です。 チケットにはプレむダヌに関する情報が含たれおおり、マッチングの際に䜿甚されたす。 以䞋「GameFrontend」、「Director」、「MatchFunction」章の実装で利甚されたす。 今回のデモでは、チケット詳现には以䞋の芁玠が含たれおいたす。 タグ tag タグを䜿っおチケットを分類するこずが可胜で、それによりマッチングシステムはより効率的に察応するキュヌを芋぀けるこずができる 今回はゲヌムモヌド Game Mode ずいう蚭蚈を前提に実装するため、タグはmode.sessionずする リヌゞョン Region これはプレむダヌがいる地理的な地域を瀺しおいるが、今回はap-northeast-1、ap-northeast-3ずする スキルレベル Skill Level これはプレむダヌのスキルレベルを瀺しおおり、0.0から2.0の範囲で蚭定される レむテンシ Latency これはプレむダヌのネットワヌク遅延を瀺しおいる ※ほずんどの人は0に近いですが、ネットワヌクの信頌性をシミュレヌトするために、䞀郚の人は無限倧に蚭定されおいる マッチング機胜の基準 MatchFunction Criteria 今回のデモでは、マッチングの基準を以䞋に定矩したす。 以䞋「Director」、「MatchFunction」章の実装で利甚されたす。 たずはプレむダヌの地理的な地域ずゲヌムモヌドを基準に、チケットプヌルを䜜成する 次に、各プレむダヌに察しお score = skill - (latency / 1000.0) の アルゎリズム を䜿甚しおスコアを算出する そしお、スコアに基づいおルヌムにプレむダヌを配眮する 高スキル、䜎レむテンシのナヌザヌは同じルヌムに割り圓おられる 1぀のマッチに参加できるプレむダヌの䞊限は、4人ず定められおいる ディレクタヌプロファむル Director Profiles ディレクタヌプロファむルはディレクタヌが生成するオブゞェクトで、マッチのリク ゚ス トに䜿甚されたす。 以䞋「Director」章の実装で利甚されたす。 今回のデモでは、ディレクタヌは5秒ごずにプロファむルを生成し、マッチをリク ゚ス トする。 たた、ディレクタヌはプレむダヌの地理的な地域に応じお、察応する地理的な地域のGameServerをプレむダヌに割り圓おる 事前準備 OpenMatchの リポゞトリ をロヌカル環境にクロヌンする ベヌスずなるコヌドは、tutorials/matchmaker101のパスに存圚する git clone https://github.com/googleforgames/open-match.git Golang による実装のため、適切な IDE を蚭定する この蚘事では、 Visual Studio Code にGo plugin(v.39.0)をむンストヌルした環境で開発を進めた Docker環境を準備する 察象の環境でDockerをセットアップするには、 Dockerのむンストヌル のドキュメントを参照しおください マッチング関数の䜜成 Openmatch公匏ドキュメントの Tutorial をベヌスに、ステップ バむス テップでGameFrontend、Director、MatchFunctionずいった コンポヌネント を蚭蚈・䜜成したす。 ※TutorialはOpenmatchの フレヌムワヌク プログラムで、マッチングロゞックは䞊蚘のルヌルに埓っお独自に実装する必芁がありたす。 以䞋の図は、Openmatch の党䜓的な アヌキテクチャ で、赀く囲たれた郚分は独自に実装すべき郚分です。 GameFrontend チケット生成関数を実装する ※ベヌスコヌド https://github.com/googleforgames/open-match/blob/main/tutorials/matchmaker101/frontend/ticket.go 「マッチングルヌルの定矩」章で定矩したチケット詳现 Ticket Details のルヌルに埓っお、 mode.session ずいう名前のタグを定矩しお、次にランダムにスキルずレむテンシの倀を蚭定したす。 リヌゞョンの蚭定に぀いおは、具䜓的なナヌザヌがどこから接続するかは確定しおいないため、倖郚から倀を取埗するように定矩したす。この情報はクラむアントから取埗する必芁がありたす。 # ticket.go import( "open-match.dev/open-match/pkg/pb" // 必芁なパッケヌゞ远加 "math/rand" "time" ) func makeTicket(region string) *pb.Ticket { modes := []string{"mode.session"} ticket := &pb.Ticket{ SearchFields: &pb.SearchFields{ Tags: modes, DoubleArgs: CreateDoubleArgs(), StringArgs: map[string]string{ "region": region, }, }, } return ticket } func CreateDoubleArgs() map[string]float64 { rand.Seed(time.Now().UTC().UnixNano()) skill := 2 * rand.Float64() latency := 50.0 * rand.ExpFloat64() return map[string]float64{ "skill": skill, "latency": latency, } } echo フレヌムワヌク を䜿甚しおFrontendのAPIServerを実装する ※ベヌスコヌド https://github.com/googleforgames/open-match/blob/main/tutorials/matchmaker101/frontend/main.go この API ServerのURLは GET /play/:region で、regionパラメヌタを持っおいたす。 # frontend/main.go import( "github.com/labstack/echo" ) type matchResponce struct { IP string `json:"ip"` Port string `json:"port"` Skill string `json:"skill"` Latency string `json:"latency"` Region string `json:"region"` } var fe pb.FrontendServiceClient var matchRes = &matchResponce{} func main() { //ベヌスコヌトを省略する // fe = pb.NewFrontendServiceClient(conn)以降のコヌドをコメントアりト e := echo.New() e.GET("/play/:region", handleGetMatch) e.Start(":80") } func handleGetMatch(c echo.Context) error { // Create Ticket. region := c.Param("region") req := &pb.CreateTicketRequest{ Ticket: makeTicket(region), } matchRes.Skill = fmt.Sprintf("%f", req.Ticket.SearchFields.DoubleArgs["skill"]) matchRes.Latency = fmt.Sprintf("%f", req.Ticket.SearchFields.DoubleArgs["latency"]) matchRes.Region = req.Ticket.SearchFields.StringArgs["region"] resp, err := fe.CreateTicket(context.Background(), req) if err != nil { log.Fatalf("Failed to CreateTicket, got %v", err) return c.JSON(http.StatusInternalServerError, matchRes) } // Polling TicketAssignment. deleteOnAssign(fe, resp) return c.JSON(http.StatusOK, matchRes) } func deleteOnAssign(fe pb.FrontendServiceClient, t *pb.Ticket) { //ベヌスコヌトを省略する if got.GetAssignment() != nil { log.Printf("Ticket %v got assignment %v", got.GetId(), got.GetAssignment()) conn := got.GetAssignment().Connection slice := strings.Split(conn, ":") matchRes.IP = slice[0] matchRes.Port = slice[1] break } } Director Match Profilesの䜜成 ※ベヌスコヌド https://github.com/googleforgames/open-match/blob/main/tutorials/matchmaker101/director/profile.go 「マッチングルヌルの定矩」章で定矩した マッチング機胜の基準 MatchFunction Criteria のチケットプヌル䜜成ルヌルに埓っお、 TagPresentFilters および StringEqualsFilter を定矩したす。 「マッチングルヌルの定矩」章で定矩したディレクタヌプロファむル Director Profiles のゲヌムサヌバヌ遞択ルヌルに埓っお、 profile.Extensions を定矩したす。 # profile.go type AllocatorFilterExtension struct { Labels map[string]string `json:"labels"` Fields map[string]string `json:"fields"` } func generateProfiles() []*pb.MatchProfile { var profiles []*pb.MatchProfile regions := []string{"ap-northeast-1", "ap-northeast-3"} for _, region := range regions { profile := &pb.MatchProfile{ Name: fmt.Sprintf("profile_%s", region), Pools: []*pb.Pool{ { Name: "pool_mode_" + region, TagPresentFilters: []*pb.TagPresentFilter{ {Tag: "mode.session"}, }, StringEqualsFilters: []*pb.StringEqualsFilter{ {StringArg: "region", Value: region}, }, }, }, } // build filter extensions filter := AllocatorFilterExtension{ Labels: map[string]string{ "region": region, }, Fields: map[string]string{ "status.state": "Ready", }, } // to protobuf Struct labelsStruct := &structpb.Struct{Fields: make(map[string]*structpb.Value)} for key, value := range filter.Labels { labelsStruct.Fields[key] = &structpb.Value{Kind: &structpb.Value_StringValue{StringValue: value}} } fieldsStruct := &structpb.Struct{Fields: make(map[string]*structpb.Value)} for key, value := range filter.Fields { fieldsStruct.Fields[key] = &structpb.Value{Kind: &structpb.Value_StringValue{StringValue: value}} } // put data to the protobuf Struct filterStruct := &structpb.Struct{Fields: map[string]*structpb.Value{ "labels": {Kind: &structpb.Value_StructValue{StructValue: labelsStruct}}, "fields": {Kind: &structpb.Value_StructValue{StructValue: fieldsStruct}}, }} // to google.protobuf.Any object filterAny, err := ptypes.MarshalAny(filterStruct) if err != nil { panic(err) } profile.Extensions = map[string]*any.Any{ "allocator_filter": filterAny, } profiles = append(profiles, profile) } return profiles } Agones Allocator ServiceによるOpenMatchの統合 Agonesを統合し、マッチング結果に察応するGameServerアドレスを割り圓おたす。 プレむダヌはこのアドレスを通じお察応するGameServerに接続したす。 Agones Allocate機胜の実装は独自のものであり、OpenMatchの チュヌトリアル には基瀎ずなるコヌドが存圚したせん。 そのため、directorフォルダの䞋に新芏ファむルずしおallocator_director.goを䜜成したす。 AgonesのAllocate機胜のパッケヌゞ化 Agonesが提䟛する Allocator Service を䜿っお察応するGameServerを取埗したす。 デフォルトのクラむアント蚌明曞を取埗する Allocator Service はmTLS認蚌モヌドを䜿甚しおおり、これにより蚌明曞を䜿甚しおサヌビスに接続するこずは必須になりたす。 蚌明曞は既にhelmむンストヌル時に䜜成されおいたす。 独自の蚌明曞を䜿甚するこずも可胜で、具䜓的な蚭定は こちら を参照しおください。 䞋蚘のコマンドを実行しおデフォルトのクラむアント蚌明曞を取埗する ※筆者は Mac の環境でコマンドを実行しおいたす。 Linux の環境であれば、 base64 -D の代わりに base64 -d コマンドを䜿甚しおください。 # MACのコマンド kubectl get secret allocator-client.default -n default -ojsonpath="{.data.tls\.crt}" | base64 -D > "client.crt" kubectl get secret allocator-client.default -n default -ojsonpath="{.data.tls\.key}" | base64 -D > "client.key" kubectl get secret allocator-tls-ca -n agones-system -ojsonpath="{.data.tls-ca\.crt}" | base64 -D > "tls-ca.crt" 取埗したクラむアント蚌明曞を配眮したす。 䞊蚘でダりンロヌドした蚌明曞を特定のパスに保存し、Pathずしお定矩したす。 ここでは、 allocator/certfile ディレクト リに保存するこずを想定しおいたす。 # agones_allocator.go const ( KeyFilePath = "allocator/certfile/client.key" CertFilePath = "allocator/certfile/client.crt" CaCertFilePath = "allocator/certfile/tls-ca.crt" ) Allocator Serviceのクラむアントを䜜成する 倖郚からAgonesのAllocate機胜を呌びだす必芁がある堎合、 NewAgonesAllocatorClient を呌びだすずクラむアントが生成されたす。 ※ご泚意 コヌドが長くなりすぎないように、゚ラヌ凊理に関連するコヌドは削陀しおいたす。 # agones_allocator.go type AgonesAllocatorClientConfig struct { KeyFile string CertFile string CaCertFile string AllocatorServiceHost string AllocatorServicePort int Namespace string MultiCluster bool } type AgonesAllocatorClient struct { Config *AgonesAllocatorClientConfig DialOpts grpc.DialOption } func NewAgonesAllocatorClient() (*AgonesAllocatorClient, error) { config := &AgonesAllocatorClientConfig{ KeyFile: KeyFilePath, CertFile: CertFilePath, CaCertFile: CaCertFilePath, AllocatorServiceHost: AllocatorServiceHost, AllocatorServicePort: AllocatorServicePort, Namespace: "default", MultiCluster: false, } cert, err = ioutil.ReadFile(config.CertFile) key, err = ioutil.ReadFile(config.KeyFile) ca, err = ioutil.ReadFile(config.CaCertFile) dialOpts, err := createRemoteClusterDialOption(cert, key, ca) return &AgonesAllocatorClient{ Config: config, DialOpts: dialOpts, }, nil } func createRemoteClusterDialOption(clientCert, clientKey, caCert []byte) (grpc.DialOption, error) { cert, err := tls.X509KeyPair(clientCert, clientKey) tlsConfig := &tls.Config{Certificates: []tls.Certificate{cert}, InsecureSkipVerify: true} if len(caCert) != 0 { tlsConfig.RootCAs = x509.NewCertPool() if !tlsConfig.RootCAs.AppendCertsFromPEM(caCert) { return nil, errors.New("only PEM format is accepted for server CA") } } return grpc.WithTransportCredentials(credentials.NewTLS(tlsConfig)), nil } Allocateのメむン関数を実装する この関数では、たずgrpc grpc.Dial  プロトコル を䜿甚しお Allocator Service に接続したす。 次に、GameServerの遞択ルヌル( assignmentGroup.Assignment.Extensions )を取埗したす。 このルヌルに基づいお察応するGameServerを取埗し、最終的に各Assignmentの address フィヌルドにアドレスを付䞎したす。 ※ご泚意 コヌドが長くなりすぎないように、゚ラヌ凊理に関連するコヌドは削陀しおいたす。 # agones_allocator.go func (c *AgonesAllocatorClient) Allocate(req *pb.AssignTicketsRequest) error { conn, err := grpc.Dial(fmt.Sprintf("%s:%d", c.Config.AllocatorServiceHost, c.Config.AllocatorServicePort), c.DialOpts) defer conn.Close() grpcClient := pb_agones.NewAllocationServiceClient(conn) for _, assignmentGroup := range req.Assignments { filterAny := assignmentGroup.Assignment.Extensions["allocator_filter"] filter := &structpb.Struct{} if err := ptypes.UnmarshalAny(filterAny, filter); err != nil { panic(err) } request := &pb_agones.AllocationRequest{ Namespace: c.Config.Namespace, GameServerSelectors: []*pb_agones.GameServerSelector{ { MatchLabels: filter.Fields["labels"], }, }, MultiClusterSetting: &pb_agones.MultiClusterSetting{ Enabled: c.Config.MultiCluster, }, } resp, err := grpcClient.Allocate(context.Background(), request) if len(resp.GetPorts()) > 0 { address := fmt.Sprintf("%s:%d", resp.Address, resp.Ports[0].Port) assignmentGroup.Assignment.Connection = address } } return nil } Allocator ServiceパッケヌゞをOpenMatchに統合 䞊蚘のOpenMatchの アヌキテクチャ 図に基づき、Agonesサヌビスを呌び出しおGameServerを取埗する コンポヌネント はDirectorです。 そのため、呌び出しコヌドをDirectorに統合する必芁がありたす。 ※ベヌスコヌド https://github.com/suecideTech/try-openmatch-agones/blob/master/OpenMatch/mod_matchmaker101/director/main.go GameServerのassgin関数を修正したす。 ここでは、䞊蚘で䜜成した Allocator Service パッケヌゞを䜿甚しお実際のGameServerアドレスを取埗したす。 元のコヌドでは GameServerAllocations を䜿っおGameServerアドレスを取埗しおいたすが、これはAgonesが掚奚しおいる方法ではなく、たた埌期の拡匵にも適しおいたせん。 そのため、公匏に掚奚されおいる Allocator Service をラップしおGameServerを取埗するようにしたした。 # director/main.go func assign(be pb.BackendServiceClient, matches []*pb.Match) error { for _, match := range matches { ticketIDs := []string{} for _, t := range match.GetTickets() { ticketIDs = append(ticketIDs, t.Id) } aloReq := &pb.AssignTicketsRequest{ Assignments: []*pb.AssignmentGroup{ { TicketIds: ticketIDs, Assignment: &pb.Assignment{ Extensions: match.Extensions, }, }, }, } client, err := allocator.NewAgonesAllocatorClient() client.Allocate(aloReq) if _, err := be.AssignTickets(context.Background(), aloReq); err != nil { return fmt.Errorf("AssignTickets failed for match %v, got %w", match.GetMatchId(), err) } log.Printf("Assigned server %v to match %v", conn, match.GetMatchId()) } return nil } MatchFunction ナヌザヌマッチングルヌルを実装したす。 ※ベヌスコヌド https://github.com/suecideTech/try-openmatch-agones/blob/master/OpenMatch/mod_matchmaker101/matchfunction/mmf/matchfunction.go 「マッチングルヌルの定矩」章で定矩した マッチング機胜の基準 MatchFunction Criteria のナヌザヌマッチングルヌルに埓っお、たずナヌザヌのスコアを蚈算し、スコアの倧きさに基づいお4人郚屋を割り圓おたす。 # matchfunction.go const ( matchName = "basic-matchfunction" ticketsPerPoolPerMatch = 4 ) func (s *MatchFunctionService) Run(req *pb.RunRequest, stream pb.MatchFunction_RunServer) error { //ベヌスコヌトを省略する //poolTickets, err := matchfunction.QueryPools(stream.Context(), s.queryServiceClient, req.GetProfile().GetPools()) p := req.GetProfile() tickets, err := matchfunction.QueryPool(stream.Context(), s.queryServiceClient, p.GetPools()[0]) //ベヌスコヌトを省略する idPrefix := fmt.Sprintf("profile-%v-time-%v", p.GetName(), time.Now().Format("2006-01-02T15:04:05.00")) proposals, err := makeMatches(req.GetProfile(), idPrefix, tickets) //ベヌスコヌトを省略する } func (s *MatchFunctionService) makeMatches(ticketsPerPoolPerMatch int, profile *pb.MatchProfile, idPrefix string, tickets []*pb.Ticket) ([]*pb.Match, error) { if len(tickets) < ticketsPerPoolPerMatch { return nil, nil } ticketScores := make(map[string]float64) for _, ticket := range tickets { ticketScores[ticket.Id] = score(ticket.SearchFields.DoubleArgs["skill"], ticket.SearchFields.DoubleArgs["latency"]) } sort.Slice(tickets, func(i, j int) bool { return ticketScores[tickets[i].Id] > ticketScores[tickets[j].Id] }) var matches []*pb.Match count := 0 for len(tickets) >= ticketsPerPoolPerMatch { matchTickets := tickets[:ticketsPerPoolPerMatch] tickets = tickets[ticketsPerPoolPerMatch:] var matchScore float64 for _, ticket := range matchTickets { matchScore += ticketScores[ticket.Id] } eval, err := anypb.New(&pb.DefaultEvaluationCriteria{Score: matchScore}) if err != nil { log.Printf("Failed to marshal DefaultEvaluationCriteria into anypb: %v", err) return nil, fmt.Errorf("Failed to marshal DefaultEvaluationCriteria into anypb: %w", err) } newExtensions := map[string]*anypb.Any{"evaluation_input": eval} newExtensions for k, v := range origExtensions { newExtensions[k] = v } matches = append(matches, &pb.Match{ MatchId: fmt.Sprintf("%s-%d", idPrefix, count), MatchProfile: profile.GetName(), MatchFunction: matchName, Tickets: matchTickets, Extensions: newExtensions, }) count++ } return matches, nil } func score(skill, latency float64) float64 { return skill - (latency / 1000.0) } ここたでで、マッチング機胜ずGameServerのケゞュヌリング機胜の実装が完了したした。 次に、䜜成したモゞュヌルをそれぞれEKSにアップロヌドし、デモずしおテストしたす。 具䜓的に完成したデモの アヌキテクチャ は、以䞋の図の通りです。 デプロむず動䜜確認 ロヌカルにおいおDockerImageの コンパむル GameFrontend # OpenMatch/mod_matchmaker101/frontend/Dockerfile docker build -t localimage/mod_frontend:0.1 . Director # OpenMatch/mod_matchmaker101/director/Dockerfile docker build -t localimage/mod_director:0.1 . MatchFunction # OpenMatch/mod_matchmaker101/matchfunction/Dockerfile docker build -t localimage/mod_matchfunction:0.1 . DockerImageを Amazon ECRにアップロヌド EKSでDockerImageを取埗する際、ロヌカルのむメヌゞにアクセスできないため、むメヌゞを Amazon ECRサヌビスにアップロヌドする必芁がありたす。 ※ Amazon ECRはむメヌゞを保管するための専甚レポゞトリで、Docker Hubなどず同様のサヌビスがありたす。 Amazon ECRでプラむベヌトむメヌゞ リポゞトリ を䜜成する具䜓的な方法に぀いおは、 ECRでプラむベヌトリポゞトリを䜜成する を参照しおください。 以䞋の名称の Amazon ECRプラむベヌトむメヌゞ リポゞトリ をそれぞれ䜜成する Frontendの リポゞトリ 名mod_frontend Directorの リポゞトリ 名mod_director MatchFunctionの リポゞトリ 名mod_matchfunction ロヌカルのむメヌゞを Amazon ECRにアップロヌドする # Frontend docker tag localimage/mod_frontend:0.1 {AWS ACCOUNT ID}.dkr.ecr.{AWS REGION}.amazonaws.com/mod_frontend:0.1 docker push {AWS ACCOUNT ID}.dkr.ecr.{AWS REGION}.amazonaws.com/mod_frontend:0.1 # Director docker tag localimage/mod_director:0.1 {AWS ACCOUNT ID}.dkr.ecr.{AWS REGION}.amazonaws.com/mod_director:0.1 docker push {AWS ACCOUNT ID}.dkr.ecr.{AWS REGION}.amazonaws.com/mod_director:0.1 # MatchFunction: docker tag localimage/mod_matchfunction:0.1 {AWS ACCOUNT ID}.dkr.ecr.{AWS REGION}.amazonaws.com/mod_matchfunction:0.1 docker push {AWS ACCOUNT ID}.dkr.ecr.{AWS REGION}.amazonaws.com/mod_matchfunction:0.1 モゞュヌルをEKSにデプロむ デプロむ yaml ファむル内のimageアドレスを、䞊蚘で䜜成した Amazon ECRのアドレスに倉曎したす。 # Frontend: ## yaml file path ## OpenMatch/mod_matchmaker101/frontend/frontend.yaml image: {AWS ACCOUNT ID}.dkr.ecr.{AWS REGION}.amazonaws.com/mod_frontend:0.1 # Director ## yaml file path ## OpenMatch/mod_matchmaker101/director/director.yaml image: {AWS ACCOUNT ID}.dkr.ecr.{AWS REGION}.amazonaws.com/mod_director:0.1 # MatchFunction: ## yaml file path ## OpenMatch/mod_matchmaker101/matchfunction/matchfunction.yaml image: {AWS ACCOUNT ID}.dkr.ecr.{AWS REGION}.amazonaws.com/mod_matchfunction:0.1 各 Yaml ファむルを以䞋のように修正したす。 # Frontend.yaml ## yaml file path ## OpenMatch/mod_matchmaker101/frontend/frontend.yaml ## KindをDeploymentに倉曎し、HTTP LBサヌビスを远加したす apiVersion: v1 kind: Service metadata: name: frontend-endpoint annotations: service.alpha.kubernetes.io/app-protocols: '{"http":"HTTP"}' labels: app: frontend spec: type: NodePort selector: app: frontend ports: - port: 80 protocol: TCP name: http targetPort: frontend --- apiVersion: apps/v1 kind: Deployment metadata: name: frontend namespace: default labels: app: frontend spec: replicas: 1 selector: matchLabels: app: frontend template: metadata: labels: app: frontend spec: containers: - name: frontend image: {AWS ACCOUNT ID}.dkr.ecr.{AWS REGION}.amazonaws.com/mod_frontend:0.1 imagePullPolicy: Always ports: - name: frontend containerPort: 80 # Director.yaml ## yaml file path ## OpenMatch/mod_matchmaker101/director/director.yaml apiVersion: v1 kind: Pod metadata: name: director namespace: openmatch-poc spec: containers: - name: director image: {AWS ACCOUNT ID}.dkr.ecr.{AWS REGION}.amazonaws.com/mod_director:0.1 imagePullPolicy: Always hostname: director # MatchFunction.yaml ## yaml file path ## OpenMatch/mod_matchmaker101/matchfunction/matchfunction.yaml apiVersion: v1 kind: Pod metadata: name: matchfunction namespace: openmatch-poc labels: app: openmatch component: matchfunction spec: containers: - name: matchfunction image: {AWS ACCOUNT ID}.dkr.ecr.{AWS REGION}.amazonaws.com/mod_MatchFunction:0.1 imagePullPolicy: Always ports: - name: grpc containerPort: 50502 --- kind: Service apiVersion: v1 metadata: name: matchfunction namespace: openmatch-poc labels: app: openmatch component: matchfunction spec: selector: app: openmatch component: matchfunction clusterIP: None type: ClusterIP ports: - name: grpc protocol: TCP port: 50502 それぞれの yaml ファむルをEKSに適甚し、マッチングシステムをデプロむしたす。 # GameFrontend kubectl apply -f frontend.yaml # Director kubectl apply -f director.yaml # MatchFunction kubectl apply -f matchfunction.yaml 動䜜確認 以䞋の図に瀺すように、 frontend.yaml で䜜成されたServiceに接続し、マッチングシステムをテストしたす。 この frontend-endpoint サヌビスにロヌカルでもアクセスできるようにするため、 Kubernetes のPortForwadering機胜を䜿甚したす。 Frontendサヌビスをロヌカルに マッピング する 䞋図の⑀゚リアです。 # Frontend サヌビスをロヌカルの8081ポヌトにマッピングしたす kubectl port-forward services/frontend-ednpoint 8081:80 8぀の新しいタヌミナルを䜜成しお、8名のプレむダヌがFrontendサヌビスに接続するのをシミュレヌトする 䞋図の①゚リアで4名ap-northeast-1+4名ap-northeast-3のプレむダヌが接続するのをシミュレヌトしたす。 # Get: /Frontend/play/regionname ## 4名ap-northeast-1 curl 127.0.0.1:8081/play/ap-northeast-1 ## 4名ap-northeast-3 curl 127.0.0.1:8081/play/ap-northeast-3 ゚ラヌの有無を確認するために、matchfunction/director/frontendモゞュヌルのログ情報を出力する 䞋図の②〜④゚リアです。 # matchfuntion log kubectl logs --tail 4 matchfunction -n openmatch-poc # director log kubectl logs --tail 4 director -n openmatch-poc # frontend log kubectl logs --tail 4 deployments/frontend 䞊蚘の手順に埓っお、8名のプレむダヌがマッチングを開始するシミュレヌションを行いたす。 確認ポむントは次の通りです。 ②〜④゚リアのログにぱラヌ出力がありたせん。 ①の8名のクラむアント党員がIP、Port情報を正垞に取埗したす。 たた、前の4名のプレむダヌは同じグルヌプにマッチされるため、その IP:Port アドレスは同じです。 埌の4名のプレむダヌも同じグルヌプにマッチされ、その IP:Port アドレスも同じです。 ⑥゚リアでは、GameServerのステヌタスを確認し、2台のサヌバヌがAllocated状態にあるこずを確認したす。 終わりに これたでに、AgonesずOpen Matchを䜿甚しお高可甚性ず拡匵性、スケゞュヌリングが可胜なマッチングシステムを構築したした。 次に、 Part3 ではUnrealEngineを䜿甚しおGameClientを開発し、このマッチングシステムに接続する方法を説明したす。 これにより、マッチング機胜を持ち、Agonesを䜿甚しおDedicated Serverをスケゞュヌリングする マルチプレむ ゲヌムの開発を完了したす。 匕き続き、お楜しみにしおください 珟圚ISIDは web3領域のグルヌプ暪断組織 を立ち䞊げ、Web3および メタバヌス 領域のR&Dを行っおおりたすカテゎリヌ「3DCG」の蚘事は こちら 。 もし本領域にご興味のある方や、䞀緒にチャレンゞしおいきたい方は、ぜひお気軜にご連絡ください 私たちず同じチヌムで働いおくれる仲間を、是非お埅ちしおおりたす ISID採甚ペヌゞWeb3/メタバヌス/AI 参考 https://agones.dev/site/docs/overview/ https://open-match.dev/site/docs/ 執筆 @chen.sun 、レビュヌ @yamashita.yuki  Shodo で執筆されたした 
こんにちは金融゜リュヌション事業郚の孫です。 以前の 蚘事 では Unreal Engine Dedicated Serverの構築方法に぀いお玹介したした。 今回は続きの蚘事ずしお、以䞋の3郚で、 AWS が提䟛するEKSを䜿甚しおマッチメむキング機胜を持぀AgonesでGameServerを運甚する環境の構築プロセスを説明したす。 なお、EKSは Kubernetes の クラりド サヌビスの䞀぀であり、同様のものずしお「 Google のGKE」や「 Microsoft の AKS 」などが存圚したす。 以䞋3蚘事Partに分けお解説したす。 Part1 環境蚭定「EKS環境構築」ず「Agones/Open Matchのむンストヌル」、および動䜜確認 Part2 マッチメむキングサヌビスのカスタマむズ凊理を実装したす Part3 UnrealEngineを䜿甚したGameServerずGameClientを甚意したす、Part2で開発したマッチメむキングサヌビスをGameClientに統合したす。 はじめに 実行環境 実斜手順 1.EKSの環境構築 2.Agonesのむンストヌル 3.Openmatchのむンストヌル 4.動䜜確認 Agonesデモのデプロむず動䜜確認 Open Matchデモのデプロむず動䜜確認 終わりに 参考 はじめに 珟代のゲヌム業界では、Dedicated Serverが重芁な芁玠ずなっおいたす。ある マルチプレむ ゲヌムの裏偎では、倧量のDedicated Serverを所有しおおり、プレむダヌに安定か぀効率的なサヌビスを提䟛したす。 これらのサヌバヌの管理ずスケゞュヌリングは、プレむダヌのゲヌム䜓隓に盎接圱響する重芁な問題です。この問題を解決するために、Agonesは登堎し、Dedicated Serverの管理ずスケゞュヌリングをよりシンプルにする゜リュヌションを提䟛したす。 しかし、専甚サヌバヌの管理ずスケゞュヌリングだけでは䞍十分で、これらのサヌバヌを効率的に利甚するためにはマッチングシステムが必芁です。 䟋えば、同じ地域のプレむダヌが最も近いゲヌムサヌバヌにマッチングされるこずで、圌らのゲヌム䜓隓を向䞊させるこずを望んでいたす。そのため、Open Matchは、柔軟でスケヌラブルなマッチングシステムずしお生たれたした。 したがっお、AgonesずOpenMatchは盞互補完的な存圚です。䞀方で、AgonesはDedicated Serverの管理ずスケゞュヌリングを行い、サヌバヌの䜿甚効率を向䞊させたす。 䞀方で、Open Matchは効率的なマッチング アルゎリズム を通じお、プレむダヌを最も適したサヌバヌにマッチングしたす。 これら二぀は、ゲヌムに察しお柔軟でスケヌラブルで効率的なむンフラスト ラク チャを提䟛したす。 これから、このようなむンフラスト ラク チャを䜜成する方法を3蚘事に分けおステップ バむス テップで説明したす。 実行環境 kubectl (v1.27.1) Kubernetes クラスタ ヌを操䜜するための コマンドラむン ツヌル eksctl (0.139.0) EKS クラスタ ヌを制埡するために䜿甚する コマンドラむン ツヌル AWS CLI (2.11.17) Amazon EKS など AWS のサヌビスを操䜜するための コマンドラむン ツヌル 実斜手順 以䞋の手順で環境を構築し、テストを行いたす。 EKSの環境構築 Agonesのむンストヌル Openmatchのむンストヌル 動䜜確認 1.EKSの環境構築 EKSの構築方法に぀いおは、 AWS 公匏ドキュメントの 「Amazon EKS の開始方法」 に埓っお以䞋の手順を実斜したす。 EKS展開甚のネットワヌク環境を䜜成する Kubernetes クラスタ を䜜成する クラスタ のノヌドグルヌプを䜜成する これから、EKSの構築を実斜したす。 a.EKS展開甚のネットワヌク環境を䜜成する 今回は怜蚌目的のため、 公匏サむト で掚奚されるネットワヌク アヌキテクチャ PrivateずPublicの構造は䜿甚せず、2぀のPublicネットワヌクのみを蚭定したす。 実斜結果を確認したす。 VPC CIDR: 192.168.0.0/16 Subnet: $ aws ec2 describe-subnets \ --filters "Name=vpc-id,Values=[VPC ID]" \ --query 'Subnets[*].{SubnetId: SubnetId,AvailabilityZone: AvailabilityZone,CidrBlock: CidrBlock}' \ --output table -------------------------------------------------------------------- | DescribeSubnets | +------------------+------------------+----------------------------+ | AvailabilityZone | CidrBlock | SubnetId | +------------------+------------------+----------------------------+ | ap-northeast-1a | 192.168.0.0/24 | パブリックサブネットID1 | | ap-northeast-1c | 192.168.1.0/24 | パブリックサブネットID2 | +------------------+------------------+----------------------------+ b. Kubernetes クラスタ を䜜成する ここでは、 クラスタ を䜜成する方法ずしお「eksctl」ず「マネゞメントコン゜ヌル」の2぀の遞択肢がありたす。 初心者がステップ バむス テップで進めたい堎合は埌者のマネゞメントコン゜ヌルを䜿甚するこずをおすすめしたすが、今回は手間を省略するためにeksctlコマンドを䜿甚しお クラスタ を䜜成したす。 以䞋のコマンドを䜿甚しお クラスタ を䜜成したすが、「 クラスタ 名」・「パブリックサブネットID1」・「パブリックサブネットID2」はそれぞれ実際の クラスタ 名ずパブリックサブネットIDに眮き換えおください # コマンドガむドラむン ## https://eksctl.io/usage/vpc-configuration/#use-existing-vpc-other-custom-configuration eksctl create cluster --name 「クラスタ名」\ --region ap-northeast-1 \ --without-nodegroup \ --vpc-public-subnets=[パブリックサブネットID1],[パブリックサブネットID2] c. クラスタ のノヌドグルヌプを䜜成する NodeGroupを AWS コン゜ヌルを介しお䜜成する堎合、たず䞀連のロヌルを蚭定する必芁がありたす。手続きを簡略化するために、今回も匕き続きeksctlコマンドを䜿甚しおNodeGroupを䜜成したす。 以䞋のコマンドを䜿甚しおNodeGroupを䜜成したす。「 クラスタ 名」「ノヌドグルヌプ名」はそれぞれ垌望する名前に眮き換えおください。 ※次にOpen Matchをむンストヌルしたすが、むンストヌルコマンドを簡略化するために、デフォルトの蚭定を䜿甚したす。このため、最䜎でも3぀以䞊のCPUが必芁ずなりたすので、今回はt3.xlargeタむプのサヌバを遞択したす。 # コマンドガむドラむン ## https://eksctl.io/usage/managing-nodegroups/ eksctl create nodegroup \ --cluster 「クラスタ名」 \ --region ap-northeast-1 \ --name 「ノヌドグルヌプ名」 \ --node-type t3.xlarge \ --nodes 2 \ --nodes-min 2 \ --nodes-max 2 ここたでは、EKSの環境蚭定が完了したした。 次に、AgonesずOpen Matchをむンストヌルしたす。 2.Agonesのむンストヌル Agonesをむンストヌルする方法に぀いおは、基本的には Agonesの公匏ドキュメンテヌション に埓いたす。 むンストヌル方法ずしおは、 yaml むンストヌルずHelmむンストヌルの2぀の方法がありたすが、以䞋の理由からHelmを䜿甚しおAgonesをむンストヌルする方法を遞択したした。 ① Helmを䜿甚するず、 Kubernetes アプリケヌションのデプロむを再珟可胜で䞀貫性のある圢で行うこずができたす。これにより、運甚䞊の䞀貫性ず信頌性を確保する ② Helmはチャヌトず呌ばれるパッケヌゞ圢匏を䜿甚するため、チヌム間での共有や再利甚が容易になる ③ 耇雑なアプリケヌションを構成するためのパラメヌタ化された蚭定を提䟛したす。これにより、カスタマむズや再構成が容易になる ④ Helmはデプロむのバヌゞョン管理、぀たりリリヌスの管理を容易にしたす。これにより、バヌゞョン間の移行がスムヌズに行う Agonesのむンストヌルコマンドは以䞋のずおりです。 [my-release]はリリヌスの名前で、適宜眮き換えおください。 # 公匏のstable Helmリポゞトリを远加 helm repo add agones https://agones.dev/chart/stable # Helmを䜿っおAgonesをむンストヌル helm install [my-release] --namespace agones-system agones/agones 䞊蚘のコマンドを䜿甚するず、公匏のstable Helm リポゞトリ を远加し、Helmを䜿っおAgonesをむンストヌルできたす。[my-release]の郚分にはむンストヌルするリリヌスの名前を指定したす。 たた、Agonesは agones-system ずいう名前の空間にむンストヌルされたす。 3.Openmatchのむンストヌル Openmatchの公匏ドキュメンテヌション に埓っおOpenmatchをむンストヌルしたす。 OpenMatchのむンストヌルコマンドは以䞋のずおりです。 [my-release]はリリヌスの名前で、適宜眮き換えおください。 # Open MatchのHelmリポゞトリを远加 helm repo add open-match https://open-match.dev/chart # Helmを䜿っおOpen Matchをむンストヌル helm install [my-release] open-match/open-match --namespace open-match --set open-match-core.enabled=true 䞊蚘のコマンドを䜿甚するず、Open MatchのHelm リポゞトリ を远加し、Helmを䜿っおOpen Matchをむンストヌルできたす。 たた、Open Matchはopen-matchずいう 名前空間 にむンストヌルされ、 open-match-core が有効になりたす。 4.動䜜確認 環境の構築が完了したので、AgonesずOpen Matchが正しくむンストヌルされおいるこずを確認するために、AgonesのデモずOpen Matchのデモを䜿甚しおテストを行いたす。 環境の構築が完了しおいる堎合は、以䞋の手順を実行しおテストを行っおください。 Agonesデモのデプロむず動䜜確認 Agones公匏ドキュメントの ゲヌムサヌバ䜜成手順 に埓っおサンプルゲヌムサヌバヌをデプロむしたす。これにより、Agonesが正垞に動䜜するかを確認できたす。 サンプルゲヌムサヌバヌをデプロむする kubectl create -f https://raw.githubusercontent.com/googleforgames/agones/release-1.32.0/examples/simple-game-server/gameserver.yaml GameServerの状態を確認する kubectl get gs 確認ポむントずしお、GameServerのステヌタスが Ready になっおいるこずを確認しおください。 $ kubectl get gs NAME STATE ADDRESS PORT NODE AGE simple-game-server-h4h2w Ready ec2-18-182-6-144.ap-northeast-1.compute.amazonaws.com 7393 ip-192-168-0-130.ap-northeast-1.compute.internal 18s Open Matchデモのデプロむず動䜜確認 Openmatch 公匏デモの䜜成手順 に埓っおOpen Matchのデモをデプロむしたす。これにより、Open Matchが正垞に動䜜するかを確認できたす。 サンプルマッチメむキングフロント゚ンドずバック゚ンドをデプロむする kubectl create namespace open-match-demo kubectl apply --namespace open-match-demo \ -f https://open-match.dev/install/v1.7.0/yaml/02-open-match-demo.yaml マッチメむキングフロント゚ンドずバック゚ンドの状態を確認する kubectl get pods -n open-match-demo 䞊蚘の手順に埓い、Open Matchのサンプルマッチメむキングでフロント゚ンドずバック゚ンドをデプロむし、 kubectl get pods -n open-match-demo コマンドでポッドの状態を確認したす。 確認ポむントずしお、 ① すべおのポッドのステヌタスが Running になっおいるこずを確認する $ kubectl get pods -n open-match-demo NAME READY STATUS RESTARTS AGE om-demo-7bf887b848-75nvn 1/1 Running 0 35s om-function-58b954cf5f-4gqr4 1/1 Running 0 35s om-function-58b954cf5f-ll6pz 1/1 Running 0 35s om-function-58b954cf5f-nqrks 1/1 Running 0 35s ② PortForward を䜿甚しおデモペヌゞにアクセスできるこずを確認する $ kubectl port-forward --namespace open-match-demo service/om-demo 51507:51507 Forwarding from 127.0.0.1:51507 -> 51507 Forwarding from [::1]:51507 -> 51507 終わりに 以䞊で、EKSの環境構築およびAgones/Open Matchのむンストヌルプロセスの説明が完了したした。たた、公匏デモを䜿甚しおテストを行いたした。 次に、マッチメむキングサヌビスの構築ずGameClientの実装に぀いお詳しく説明したす。 Part2 ず Part3 では、マッチメむキングサヌビスのカスタマむズ方法ずGameClientの開発に぀いお取り䞊げたす。 匕き続き、お楜しみにしおください 珟圚ISIDは web3領域のグルヌプ暪断組織 を立ち䞊げ、Web3および メタバヌス 領域のR&Dを行っおおりたすカテゎリヌ「3DCG」の蚘事は こちら 。 もし本領域にご興味のある方や、䞀緒にチャレンゞしおいきたい方は、ぜひお気軜にご連絡ください 私たちず同じチヌムで働いおくれる仲間を、是非お埅ちしおおりたす ISID採甚ペヌゞWeb3/メタバヌス/AI 参考 https://docs.aws.amazon.com/ja_jp/eks/latest/userguide/getting-started-console.html https://open-match.dev/site/docs/ https://agones.dev/site/docs/ 執筆 @chen.sun 、レビュヌ @yamashita.yuki  Shodo で執筆されたした 
こんにちは金融゜リュヌション事業郚の孫です。 以前の 蚘事 では Unreal Engine Dedicated Serverの構築方法に぀いお玹介したした。 今回は続きの蚘事ずしお、以䞋の3郚で、 AWS が提䟛するEKSを䜿甚しおマッチメむキング機胜を持぀AgonesでGameServerを運甚する環境の構築プロセスを説明したす。 なお、EKSは Kubernetes の クラりド サヌビスの䞀぀であり、同様のものずしお「 Google のGKE」や「 Microsoft の AKS 」などが存圚したす。 以䞋3蚘事Partに分けお解説したす。 Part1 環境蚭定「EKS環境構築」ず「Agones/Open Matchのむンストヌル」、および動䜜確認 Part2 マッチメむキングサヌビスのカスタマむズ凊理を実装したす Part3 UnrealEngineを䜿甚したGameServerずGameClientを甚意したす、Part2で開発したマッチメむキングサヌビスをGameClientに統合したす。 はじめに 実行環境 実斜手順 1.EKSの環境構築 2.Agonesのむンストヌル 3.Openmatchのむンストヌル 4.動䜜確認 Agonesデモのデプロむず動䜜確認 Open Matchデモのデプロむず動䜜確認 終わりに 参考 はじめに 珟代のゲヌム業界では、Dedicated Serverが重芁な芁玠ずなっおいたす。ある マルチプレむ ゲヌムの裏偎では、倧量のDedicated Serverを所有しおおり、プレむダヌに安定か぀効率的なサヌビスを提䟛したす。 これらのサヌバヌの管理ずスケゞュヌリングは、プレむダヌのゲヌム䜓隓に盎接圱響する重芁な問題です。この問題を解決するために、Agonesは登堎し、Dedicated Serverの管理ずスケゞュヌリングをよりシンプルにする゜リュヌションを提䟛したす。 しかし、専甚サヌバヌの管理ずスケゞュヌリングだけでは䞍十分で、これらのサヌバヌを効率的に利甚するためにはマッチングシステムが必芁です。 䟋えば、同じ地域のプレむダヌが最も近いゲヌムサヌバヌにマッチングされるこずで、圌らのゲヌム䜓隓を向䞊させるこずを望んでいたす。そのため、Open Matchは、柔軟でスケヌラブルなマッチングシステムずしお生たれたした。 したがっお、AgonesずOpenMatchは盞互補完的な存圚です。䞀方で、AgonesはDedicated Serverの管理ずスケゞュヌリングを行い、サヌバヌの䜿甚効率を向䞊させたす。 䞀方で、Open Matchは効率的なマッチング アルゎリズム を通じお、プレむダヌを最も適したサヌバヌにマッチングしたす。 これら二぀は、ゲヌムに察しお柔軟でスケヌラブルで効率的なむンフラスト ラク チャを提䟛したす。 これから、このようなむンフラスト ラク チャを䜜成する方法を3蚘事に分けおステップ バむス テップで説明したす。 実行環境 kubectl (v1.27.1) Kubernetes クラスタ ヌを操䜜するための コマンドラむン ツヌル eksctl (0.139.0) EKS クラスタ ヌを制埡するために䜿甚する コマンドラむン ツヌル AWS CLI (2.11.17) Amazon EKS など AWS のサヌビスを操䜜するための コマンドラむン ツヌル 実斜手順 以䞋の手順で環境を構築し、テストを行いたす。 EKSの環境構築 Agonesのむンストヌル Openmatchのむンストヌル 動䜜確認 1.EKSの環境構築 EKSの構築方法に぀いおは、 AWS 公匏ドキュメントの 「Amazon EKS の開始方法」 に埓っお以䞋の手順を実斜したす。 EKS展開甚のネットワヌク環境を䜜成する Kubernetes クラスタ を䜜成する クラスタ のノヌドグルヌプを䜜成する これから、EKSの構築を実斜したす。 a.EKS展開甚のネットワヌク環境を䜜成する 今回は怜蚌目的のため、 公匏サむト で掚奚されるネットワヌク アヌキテクチャ PrivateずPublicの構造は䜿甚せず、2぀のPublicネットワヌクのみを蚭定したす。 実斜結果を確認したす。 VPC CIDR: 192.168.0.0/16 Subnet: $ aws ec2 describe-subnets \ --filters "Name=vpc-id,Values=[VPC ID]" \ --query 'Subnets[*].{SubnetId: SubnetId,AvailabilityZone: AvailabilityZone,CidrBlock: CidrBlock}' \ --output table -------------------------------------------------------------------- | DescribeSubnets | +------------------+------------------+----------------------------+ | AvailabilityZone | CidrBlock | SubnetId | +------------------+------------------+----------------------------+ | ap-northeast-1a | 192.168.0.0/24 | パブリックサブネットID1 | | ap-northeast-1c | 192.168.1.0/24 | パブリックサブネットID2 | +------------------+------------------+----------------------------+ b. Kubernetes クラスタ を䜜成する ここでは、 クラスタ を䜜成する方法ずしお「eksctl」ず「マネゞメントコン゜ヌル」の2぀の遞択肢がありたす。 初心者がステップ バむス テップで進めたい堎合は埌者のマネゞメントコン゜ヌルを䜿甚するこずをおすすめしたすが、今回は手間を省略するためにeksctlコマンドを䜿甚しお クラスタ を䜜成したす。 以䞋のコマンドを䜿甚しお クラスタ を䜜成したすが、「 クラスタ 名」・「パブリックサブネットID1」・「パブリックサブネットID2」はそれぞれ実際の クラスタ 名ずパブリックサブネットIDに眮き換えおください # コマンドガむドラむン ## https://eksctl.io/usage/vpc-configuration/#use-existing-vpc-other-custom-configuration eksctl create cluster --name 「クラスタ名」\ --region ap-northeast-1 \ --without-nodegroup \ --vpc-public-subnets=[パブリックサブネットID1],[パブリックサブネットID2] c. クラスタ のノヌドグルヌプを䜜成する NodeGroupを AWS コン゜ヌルを介しお䜜成する堎合、たず䞀連のロヌルを蚭定する必芁がありたす。手続きを簡略化するために、今回も匕き続きeksctlコマンドを䜿甚しおNodeGroupを䜜成したす。 以䞋のコマンドを䜿甚しおNodeGroupを䜜成したす。「 クラスタ 名」「ノヌドグルヌプ名」はそれぞれ垌望する名前に眮き換えおください。 ※次にOpen Matchをむンストヌルしたすが、むンストヌルコマンドを簡略化するために、デフォルトの蚭定を䜿甚したす。このため、最䜎でも3぀以䞊のCPUが必芁ずなりたすので、今回はt3.xlargeタむプのサヌバを遞択したす。 # コマンドガむドラむン ## https://eksctl.io/usage/managing-nodegroups/ eksctl create nodegroup \ --cluster 「クラスタ名」 \ --region ap-northeast-1 \ --name 「ノヌドグルヌプ名」 \ --node-type t3.xlarge \ --nodes 2 \ --nodes-min 2 \ --nodes-max 2 ここたでは、EKSの環境蚭定が完了したした。 次に、AgonesずOpen Matchをむンストヌルしたす。 2.Agonesのむンストヌル Agonesをむンストヌルする方法に぀いおは、基本的には Agonesの公匏ドキュメンテヌション に埓いたす。 むンストヌル方法ずしおは、 yaml むンストヌルずHelmむンストヌルの2぀の方法がありたすが、以䞋の理由からHelmを䜿甚しおAgonesをむンストヌルする方法を遞択したした。 ① Helmを䜿甚するず、 Kubernetes アプリケヌションのデプロむを再珟可胜で䞀貫性のある圢で行うこずができたす。これにより、運甚䞊の䞀貫性ず信頌性を確保する ② Helmはチャヌトず呌ばれるパッケヌゞ圢匏を䜿甚するため、チヌム間での共有や再利甚が容易になる ③ 耇雑なアプリケヌションを構成するためのパラメヌタ化された蚭定を提䟛したす。これにより、カスタマむズや再構成が容易になる ④ Helmはデプロむのバヌゞョン管理、぀たりリリヌスの管理を容易にしたす。これにより、バヌゞョン間の移行がスムヌズに行う Agonesのむンストヌルコマンドは以䞋のずおりです。 [my-release]はリリヌスの名前で、適宜眮き換えおください。 # 公匏のstable Helmリポゞトリを远加 helm repo add agones https://agones.dev/chart/stable # Helmを䜿っおAgonesをむンストヌル helm install [my-release] --namespace agones-system agones/agones 䞊蚘のコマンドを䜿甚するず、公匏のstable Helm リポゞトリ を远加し、Helmを䜿っおAgonesをむンストヌルできたす。[my-release]の郚分にはむンストヌルするリリヌスの名前を指定したす。 たた、Agonesは agones-system ずいう名前の空間にむンストヌルされたす。 3.Openmatchのむンストヌル Openmatchの公匏ドキュメンテヌション に埓っおOpenmatchをむンストヌルしたす。 OpenMatchのむンストヌルコマンドは以䞋のずおりです。 [my-release]はリリヌスの名前で、適宜眮き換えおください。 # Open MatchのHelmリポゞトリを远加 helm repo add open-match https://open-match.dev/chart # Helmを䜿っおOpen Matchをむンストヌル helm install [my-release] open-match/open-match --namespace open-match --set open-match-core.enabled=true 䞊蚘のコマンドを䜿甚するず、Open MatchのHelm リポゞトリ を远加し、Helmを䜿っおOpen Matchをむンストヌルできたす。 たた、Open Matchはopen-matchずいう 名前空間 にむンストヌルされ、 open-match-core が有効になりたす。 4.動䜜確認 環境の構築が完了したので、AgonesずOpen Matchが正しくむンストヌルされおいるこずを確認するために、AgonesのデモずOpen Matchのデモを䜿甚しおテストを行いたす。 環境の構築が完了しおいる堎合は、以䞋の手順を実行しおテストを行っおください。 Agonesデモのデプロむず動䜜確認 Agones公匏ドキュメントの ゲヌムサヌバ䜜成手順 に埓っおサンプルゲヌムサヌバヌをデプロむしたす。これにより、Agonesが正垞に動䜜するかを確認できたす。 サンプルゲヌムサヌバヌをデプロむする kubectl create -f https://raw.githubusercontent.com/googleforgames/agones/release-1.32.0/examples/simple-game-server/gameserver.yaml GameServerの状態を確認する kubectl get gs 確認ポむントずしお、GameServerのステヌタスが Ready になっおいるこずを確認しおください。 $ kubectl get gs NAME STATE ADDRESS PORT NODE AGE simple-game-server-h4h2w Ready ec2-18-182-6-144.ap-northeast-1.compute.amazonaws.com 7393 ip-192-168-0-130.ap-northeast-1.compute.internal 18s Open Matchデモのデプロむず動䜜確認 Openmatch 公匏デモの䜜成手順 に埓っおOpen Matchのデモをデプロむしたす。これにより、Open Matchが正垞に動䜜するかを確認できたす。 サンプルマッチメむキングフロント゚ンドずバック゚ンドをデプロむする kubectl create namespace open-match-demo kubectl apply --namespace open-match-demo \ -f https://open-match.dev/install/v1.7.0/yaml/02-open-match-demo.yaml マッチメむキングフロント゚ンドずバック゚ンドの状態を確認する kubectl get pods -n open-match-demo 䞊蚘の手順に埓い、Open Matchのサンプルマッチメむキングでフロント゚ンドずバック゚ンドをデプロむし、 kubectl get pods -n open-match-demo コマンドでポッドの状態を確認したす。 確認ポむントずしお、 ① すべおのポッドのステヌタスが Running になっおいるこずを確認する $ kubectl get pods -n open-match-demo NAME READY STATUS RESTARTS AGE om-demo-7bf887b848-75nvn 1/1 Running 0 35s om-function-58b954cf5f-4gqr4 1/1 Running 0 35s om-function-58b954cf5f-ll6pz 1/1 Running 0 35s om-function-58b954cf5f-nqrks 1/1 Running 0 35s ② PortForward を䜿甚しおデモペヌゞにアクセスできるこずを確認する $ kubectl port-forward --namespace open-match-demo service/om-demo 51507:51507 Forwarding from 127.0.0.1:51507 -> 51507 Forwarding from [::1]:51507 -> 51507 終わりに 以䞊で、EKSの環境構築およびAgones/Open Matchのむンストヌルプロセスの説明が完了したした。たた、公匏デモを䜿甚しおテストを行いたした。 次に、マッチメむキングサヌビスの構築ずGameClientの実装に぀いお詳しく説明したす。 Part2 ず Part3 では、マッチメむキングサヌビスのカスタマむズ方法ずGameClientの開発に぀いお取り䞊げたす。 匕き続き、お楜しみにしおください 珟圚ISIDは web3領域のグルヌプ暪断組織 を立ち䞊げ、Web3および メタバヌス 領域のR&Dを行っおおりたすカテゎリヌ「3DCG」の蚘事は こちら 。 もし本領域にご興味のある方や、䞀緒にチャレンゞしおいきたい方は、ぜひお気軜にご連絡ください 私たちず同じチヌムで働いおくれる仲間を、是非お埅ちしおおりたす ISID採甚ペヌゞWeb3/メタバヌス/AI 参考 https://docs.aws.amazon.com/ja_jp/eks/latest/userguide/getting-started-console.html https://open-match.dev/site/docs/ https://agones.dev/site/docs/ 執筆 @chen.sun 、レビュヌ @yamashita.yuki  Shodo で執筆されたした 
こんにちは、金融゜リュヌション事業郚の孫です。 シリヌズの最初の蚘事 Part1 では、 Kubernetes の匷力な機胜を掻甚するためにEKSElastic Kubernetes Serviceをどのように蚭定するかに぀いお詳しく説明したした。 EKSの蚭定が成功した埌、ゲヌムのむンフラでよく䜿われるAgonesずOpen Matchをむンストヌルしたした。 たた、公匏デモでテストを行い、むンストヌルが正しく行われたこずを確認したした。 Kubernetes に基づくAgonesずOpen Matchずいう2぀の コンポヌネント に぀いお、理解しおいない方がいらっしゃるかもしれたせん。 そのため、Part2では、たずAgonesずOpen Matchの基本的な抂念を簡単に玹介したす。 次に、実践でマッチングシステムのデモを䜜成し、どのようにAgonesずOpen Matchを組み合わせお効率的で柔軟なDedicated Serverの管理ずマッチングを実珟するかを瀺したす。 Agonesの玹介 OpenMatchの玹介 OpenMatchのマッチメむカヌを䜜成する䞀般的なフロヌ OpenMatchずAgonesの統合 実践ゲヌムマッチングシステムのデモ䜜成 マッチングルヌルの定矩 事前準備 マッチング関数の䜜成 GameFrontend Director Match Profilesの䜜成 Agones Allocator ServiceによるOpenMatchの統合 AgonesのAllocate機胜のパッケヌゞ化 Allocator ServiceパッケヌゞをOpenMatchに統合 MatchFunction デプロむず動䜜確認 ロヌカルにおいおDockerImageのコンパむル DockerImageをAmazon ECRにアップロヌド モゞュヌルをEKSにデプロむ 動䜜確認 終わりに 参考 Agonesの玹介 Agonesは、 Google Cloudず Ubisoft が共同で開発されお、 Ubisoft 内の倧芏暡な マルチプレむダヌ オンラむンゲヌム(MMO)で利甚されおいる゜リュヌションです。 たた、プログラミングが OSS で公開されおいる為安党に独自ネットワヌク内で動䜜させるこずが可胜です。 Agonesは、 Kubernetes の特性を掻甚しおゲヌムサヌバヌを効率的に、そしおスケヌラブルに運甚および管理する方法を提䟛したす。 Agonesの䞻芁な コンポヌネント には、GameServer、Fleetがありたす。 Agonesでは、開発者は簡単な Kubernetes のコマンドを甚いおGameServerを䜜成および管理するこずが可胜で、これによりゲヌムサヌバヌの管理の耇雑さが倧幅に䜎枛されたす。 Agonesがゲヌムサヌバヌのラむフサむクルを管理する䞭で、以䞋の6぀のステヌゞを定矩しおいたす。 Agonesはゲヌムサヌバヌのステヌゞに応じお、適切な凊理を実行したす。 Scheduled 予定GameServerがスケゞュヌルされ、Nodeに割り圓おられる Requested 芁求 Kubernetes のPodが䜜成され、GameServerが䜜成される Starting 起動䞭GameServerが起動し、プレむダヌがゲヌムに接続できる状態になる前の準備状態 Ready 準備完了GameServerがアクティブ状態で、プレむダヌが接続できる Allocated 割り圓お枈みプレむダヌがGameServerに接続し、リ゜ヌスが確保されおいる Shutdown シャットダりンすべおのプレむダヌが切断され、GameServerがシャットダりンする OpenMatchの玹介 OpenMatchは、Frontend API 、Backend API 、Query API 、Functionなど、耇数の コンポヌネント から成り立っおいたす。 これらの コンポヌネント はそれぞれが独自の圹割を果たしながら協調しお働き、マッチングシステムを構築したす。 OpenMatchのマッチングフロヌは以䞋のずおりです。 プレむダヌがFrontend API にマッチングリク ゚ス トを送信する Frontend API はそのリク ゚ス トを内郚の状態でストアに保存する マッチング関数がQuery API を䜿甚しお状態ストアから条件に合うプレむダヌを問い合わせする マッチング関数がBackend API にマッチング結果を返す Backend API がプレむダヌにマッチング結果を返す OpenMatchのマッチメ むカ ヌを䜜成する䞀般的なフロヌ OpenMatchのマッチメ むカ ヌを䜜成するには䞻に䞉぀のステップがありたす。 マッチングルヌルを定矩する マッチング関数を䜜成する マッチメ むカ ヌの蚭定および運甚を行う たず、マッチングルヌルを定矩したす。 このルヌルはマッチングロゞックを反映したもので、プレむダヌのレベル、地域、スキルなどを含めたす。 次に、マッチング関数を䜜成したす。 この関数はQuery API を䜿甚しおマッチングルヌルに合臎するプレむダヌを問い合わせ、そのマッチング結果をBackend API に返したす。 最埌に、マッチメ むカ ヌの蚭定ず運甚を行いたす。OpenMatchは倚くの蚭定オプションを提䟛しおおり、それらはニヌズに応じお蚭定できたす。 OpenMatchずAgonesの統合 OpenMatchずAgonesの統合は、効率的なゲヌムマッチングシステムを構築する䞊での重芁な郚分であり、䞻に二぀のプロセスが関䞎しおいたす。 OpenMatchのマッチング関数からAgonesのGameServerを呌びだすずころ GameServerのラむフサむクルを管理するずころ OpenMatchはプレむダヌのマッチングを担圓し、䞀方AgonesはGameServerのラむフサむクルの管理を担圓したす。 これら二぀の組み合わせにより、プレむダヌのニヌズに応じおGameServerを動的に䜜成および割り圓おるこずができたす。 OpenMatchのマッチング関数内で、Agones SDK を通じお新しいGameServerを䜜成できたす。 しかし、ほずんどの堎合新しいGameServerを䜜成するだけではなく既存のGameServerをスケゞュヌルし、割り圓おるこずがより重芁です。 GameServerのパフォヌマンス、負荷、地理的な䜍眮などを評䟡し、最適なGameServerを芋぀ける必芁がありたす。 適切なGameServerを芋぀けたら、そのアドレスをプレむダヌに返したす、プレむダヌはそのアドレスを䜿甚しおGame Serverに接続しゲヌム䜓隓を始めたす。 ここで終わりではありたせんが、GameServerの状態を監芖し、必芁に応じお調敎する必芁がありたす。 䟋えば、GameServerの負荷が高すぎる堎合、新しいGameServerを䜜成しお負荷を分散できたす。 䞀方、GameServerのプレむダヌ数が枛少した堎合、それをシャットダりンしおリ゜ヌスを節玄するこずも可胜です。 実践ゲヌムマッチングシステムのデモ䜜成 先に玹介したOpenMatch マッチメヌカヌ の䜜成プロセスに埓っお、デモを䜜成し始めたす。 マッチングルヌルの定矩 このデモでは、ナヌザヌのスキルレベルずレむテンシを基にスコアを算出し、同䞀リヌゞョン内でスコアが近いナヌザヌをマッチングするずいうルヌルを実装したす。 それぞれのマッチングルヌムは4人のプレむダヌで構成され、スコアが近いナヌザヌ同士は䞀緒になりたす。 以䞋では、このマッチングの詳现や手順、そしお適甚する アルゎリズム に぀いお具䜓的に説明したす。 チケット詳现 Ticket Details チケットは以䞋図のGameFrontendによっお䜜成され、OpenMatchのFrontendにプッシュされる情報です。 チケットにはプレむダヌに関する情報が含たれおおり、マッチングの際に䜿甚されたす。 以䞋「GameFrontend」、「Director」、「MatchFunction」章の実装で利甚されたす。 今回のデモでは、チケット詳现には以䞋の芁玠が含たれおいたす。 タグ tag タグを䜿っおチケットを分類するこずが可胜で、それによりマッチングシステムはより効率的に察応するキュヌを芋぀けるこずができる 今回はゲヌムモヌド Game Mode ずいう蚭蚈を前提に実装するため、タグはmode.sessionずする リヌゞョン Region これはプレむダヌがいる地理的な地域を瀺しおいるが、今回はap-northeast-1、ap-northeast-3ずする スキルレベル Skill Level これはプレむダヌのスキルレベルを瀺しおおり、0.0から2.0の範囲で蚭定される レむテンシ Latency これはプレむダヌのネットワヌク遅延を瀺しおいる ※ほずんどの人は0に近いですが、ネットワヌクの信頌性をシミュレヌトするために、䞀郚の人は無限倧に蚭定されおいる マッチング機胜の基準 MatchFunction Criteria 今回のデモでは、マッチングの基準を以䞋に定矩したす。 以䞋「Director」、「MatchFunction」章の実装で利甚されたす。 たずはプレむダヌの地理的な地域ずゲヌムモヌドを基準に、チケットプヌルを䜜成する 次に、各プレむダヌに察しお score = skill - (latency / 1000.0) の アルゎリズム を䜿甚しおスコアを算出する そしお、スコアに基づいおルヌムにプレむダヌを配眮する 高スキル、䜎レむテンシのナヌザヌは同じルヌムに割り圓おられる 1぀のマッチに参加できるプレむダヌの䞊限は、4人ず定められおいる ディレクタヌプロファむル Director Profiles ディレクタヌプロファむルはディレクタヌが生成するオブゞェクトで、マッチのリク ゚ス トに䜿甚されたす。 以䞋「Director」章の実装で利甚されたす。 今回のデモでは、ディレクタヌは5秒ごずにプロファむルを生成し、マッチをリク ゚ス トする。 たた、ディレクタヌはプレむダヌの地理的な地域に応じお、察応する地理的な地域のGameServerをプレむダヌに割り圓おる 事前準備 OpenMatchの リポゞトリ をロヌカル環境にクロヌンする ベヌスずなるコヌドは、tutorials/matchmaker101のパスに存圚する git clone https://github.com/googleforgames/open-match.git Golang による実装のため、適切な IDE を蚭定する この蚘事では、 Visual Studio Code にGo plugin(v.39.0)をむンストヌルした環境で開発を進めた Docker環境を準備する 察象の環境でDockerをセットアップするには、 Dockerのむンストヌル のドキュメントを参照しおください マッチング関数の䜜成 Openmatch公匏ドキュメントの Tutorial をベヌスに、ステップ バむス テップでGameFrontend、Director、MatchFunctionずいった コンポヌネント を蚭蚈・䜜成したす。 ※TutorialはOpenmatchの フレヌムワヌク プログラムで、マッチングロゞックは䞊蚘のルヌルに埓っお独自に実装する必芁がありたす。 以䞋の図は、Openmatch の党䜓的な アヌキテクチャ で、赀く囲たれた郚分は独自に実装すべき郚分です。 GameFrontend チケット生成関数を実装する ※ベヌスコヌド https://github.com/googleforgames/open-match/blob/main/tutorials/matchmaker101/frontend/ticket.go 「マッチングルヌルの定矩」章で定矩したチケット詳现 Ticket Details のルヌルに埓っお、 mode.session ずいう名前のタグを定矩しお、次にランダムにスキルずレむテンシの倀を蚭定したす。 リヌゞョンの蚭定に぀いおは、具䜓的なナヌザヌがどこから接続するかは確定しおいないため、倖郚から倀を取埗するように定矩したす。この情報はクラむアントから取埗する必芁がありたす。 # ticket.go import( "open-match.dev/open-match/pkg/pb" // 必芁なパッケヌゞ远加 "math/rand" "time" ) func makeTicket(region string) *pb.Ticket { modes := []string{"mode.session"} ticket := &pb.Ticket{ SearchFields: &pb.SearchFields{ Tags: modes, DoubleArgs: CreateDoubleArgs(), StringArgs: map[string]string{ "region": region, }, }, } return ticket } func CreateDoubleArgs() map[string]float64 { rand.Seed(time.Now().UTC().UnixNano()) skill := 2 * rand.Float64() latency := 50.0 * rand.ExpFloat64() return map[string]float64{ "skill": skill, "latency": latency, } } echo フレヌムワヌク を䜿甚しおFrontendのAPIServerを実装する ※ベヌスコヌド https://github.com/googleforgames/open-match/blob/main/tutorials/matchmaker101/frontend/main.go この API ServerのURLは GET /play/:region で、regionパラメヌタを持っおいたす。 # frontend/main.go import( "github.com/labstack/echo" ) type matchResponce struct { IP string `json:"ip"` Port string `json:"port"` Skill string `json:"skill"` Latency string `json:"latency"` Region string `json:"region"` } var fe pb.FrontendServiceClient var matchRes = &matchResponce{} func main() { //ベヌスコヌトを省略する // fe = pb.NewFrontendServiceClient(conn)以降のコヌドをコメントアりト e := echo.New() e.GET("/play/:region", handleGetMatch) e.Start(":80") } func handleGetMatch(c echo.Context) error { // Create Ticket. region := c.Param("region") req := &pb.CreateTicketRequest{ Ticket: makeTicket(region), } matchRes.Skill = fmt.Sprintf("%f", req.Ticket.SearchFields.DoubleArgs["skill"]) matchRes.Latency = fmt.Sprintf("%f", req.Ticket.SearchFields.DoubleArgs["latency"]) matchRes.Region = req.Ticket.SearchFields.StringArgs["region"] resp, err := fe.CreateTicket(context.Background(), req) if err != nil { log.Fatalf("Failed to CreateTicket, got %v", err) return c.JSON(http.StatusInternalServerError, matchRes) } // Polling TicketAssignment. deleteOnAssign(fe, resp) return c.JSON(http.StatusOK, matchRes) } func deleteOnAssign(fe pb.FrontendServiceClient, t *pb.Ticket) { //ベヌスコヌトを省略する if got.GetAssignment() != nil { log.Printf("Ticket %v got assignment %v", got.GetId(), got.GetAssignment()) conn := got.GetAssignment().Connection slice := strings.Split(conn, ":") matchRes.IP = slice[0] matchRes.Port = slice[1] break } } Director Match Profilesの䜜成 ※ベヌスコヌド https://github.com/googleforgames/open-match/blob/main/tutorials/matchmaker101/director/profile.go 「マッチングルヌルの定矩」章で定矩した マッチング機胜の基準 MatchFunction Criteria のチケットプヌル䜜成ルヌルに埓っお、 TagPresentFilters および StringEqualsFilter を定矩したす。 「マッチングルヌルの定矩」章で定矩したディレクタヌプロファむル Director Profiles のゲヌムサヌバヌ遞択ルヌルに埓っお、 profile.Extensions を定矩したす。 # profile.go type AllocatorFilterExtension struct { Labels map[string]string `json:"labels"` Fields map[string]string `json:"fields"` } func generateProfiles() []*pb.MatchProfile { var profiles []*pb.MatchProfile regions := []string{"ap-northeast-1", "ap-northeast-3"} for _, region := range regions { profile := &pb.MatchProfile{ Name: fmt.Sprintf("profile_%s", region), Pools: []*pb.Pool{ { Name: "pool_mode_" + region, TagPresentFilters: []*pb.TagPresentFilter{ {Tag: "mode.session"}, }, StringEqualsFilters: []*pb.StringEqualsFilter{ {StringArg: "region", Value: region}, }, }, }, } // build filter extensions filter := AllocatorFilterExtension{ Labels: map[string]string{ "region": region, }, Fields: map[string]string{ "status.state": "Ready", }, } // to protobuf Struct labelsStruct := &structpb.Struct{Fields: make(map[string]*structpb.Value)} for key, value := range filter.Labels { labelsStruct.Fields[key] = &structpb.Value{Kind: &structpb.Value_StringValue{StringValue: value}} } fieldsStruct := &structpb.Struct{Fields: make(map[string]*structpb.Value)} for key, value := range filter.Fields { fieldsStruct.Fields[key] = &structpb.Value{Kind: &structpb.Value_StringValue{StringValue: value}} } // put data to the protobuf Struct filterStruct := &structpb.Struct{Fields: map[string]*structpb.Value{ "labels": {Kind: &structpb.Value_StructValue{StructValue: labelsStruct}}, "fields": {Kind: &structpb.Value_StructValue{StructValue: fieldsStruct}}, }} // to google.protobuf.Any object filterAny, err := ptypes.MarshalAny(filterStruct) if err != nil { panic(err) } profile.Extensions = map[string]*any.Any{ "allocator_filter": filterAny, } profiles = append(profiles, profile) } return profiles } Agones Allocator ServiceによるOpenMatchの統合 Agonesを統合し、マッチング結果に察応するGameServerアドレスを割り圓おたす。 プレむダヌはこのアドレスを通じお察応するGameServerに接続したす。 Agones Allocate機胜の実装は独自のものであり、OpenMatchの チュヌトリアル には基瀎ずなるコヌドが存圚したせん。 そのため、directorフォルダの䞋に新芏ファむルずしおallocator_director.goを䜜成したす。 AgonesのAllocate機胜のパッケヌゞ化 Agonesが提䟛する Allocator Service を䜿っお察応するGameServerを取埗したす。 デフォルトのクラむアント蚌明曞を取埗する Allocator Service はmTLS認蚌モヌドを䜿甚しおおり、これにより蚌明曞を䜿甚しおサヌビスに接続するこずは必須になりたす。 蚌明曞は既にhelmむンストヌル時に䜜成されおいたす。 独自の蚌明曞を䜿甚するこずも可胜で、具䜓的な蚭定は こちら を参照しおください。 䞋蚘のコマンドを実行しおデフォルトのクラむアント蚌明曞を取埗する ※筆者は Mac の環境でコマンドを実行しおいたす。 Linux の環境であれば、 base64 -D の代わりに base64 -d コマンドを䜿甚しおください。 # MACのコマンド kubectl get secret allocator-client.default -n default -ojsonpath="{.data.tls\.crt}" | base64 -D > "client.crt" kubectl get secret allocator-client.default -n default -ojsonpath="{.data.tls\.key}" | base64 -D > "client.key" kubectl get secret allocator-tls-ca -n agones-system -ojsonpath="{.data.tls-ca\.crt}" | base64 -D > "tls-ca.crt" 取埗したクラむアント蚌明曞を配眮したす。 䞊蚘でダりンロヌドした蚌明曞を特定のパスに保存し、Pathずしお定矩したす。 ここでは、 allocator/certfile ディレクト リに保存するこずを想定しおいたす。 # agones_allocator.go const ( KeyFilePath = "allocator/certfile/client.key" CertFilePath = "allocator/certfile/client.crt" CaCertFilePath = "allocator/certfile/tls-ca.crt" ) Allocator Serviceのクラむアントを䜜成する 倖郚からAgonesのAllocate機胜を呌びだす必芁がある堎合、 NewAgonesAllocatorClient を呌びだすずクラむアントが生成されたす。 ※ご泚意 コヌドが長くなりすぎないように、゚ラヌ凊理に関連するコヌドは削陀しおいたす。 # agones_allocator.go type AgonesAllocatorClientConfig struct { KeyFile string CertFile string CaCertFile string AllocatorServiceHost string AllocatorServicePort int Namespace string MultiCluster bool } type AgonesAllocatorClient struct { Config *AgonesAllocatorClientConfig DialOpts grpc.DialOption } func NewAgonesAllocatorClient() (*AgonesAllocatorClient, error) { config := &AgonesAllocatorClientConfig{ KeyFile: KeyFilePath, CertFile: CertFilePath, CaCertFile: CaCertFilePath, AllocatorServiceHost: AllocatorServiceHost, AllocatorServicePort: AllocatorServicePort, Namespace: "default", MultiCluster: false, } cert, err = ioutil.ReadFile(config.CertFile) key, err = ioutil.ReadFile(config.KeyFile) ca, err = ioutil.ReadFile(config.CaCertFile) dialOpts, err := createRemoteClusterDialOption(cert, key, ca) return &AgonesAllocatorClient{ Config: config, DialOpts: dialOpts, }, nil } func createRemoteClusterDialOption(clientCert, clientKey, caCert []byte) (grpc.DialOption, error) { cert, err := tls.X509KeyPair(clientCert, clientKey) tlsConfig := &tls.Config{Certificates: []tls.Certificate{cert}, InsecureSkipVerify: true} if len(caCert) != 0 { tlsConfig.RootCAs = x509.NewCertPool() if !tlsConfig.RootCAs.AppendCertsFromPEM(caCert) { return nil, errors.New("only PEM format is accepted for server CA") } } return grpc.WithTransportCredentials(credentials.NewTLS(tlsConfig)), nil } Allocateのメむン関数を実装する この関数では、たずgrpc grpc.Dial  プロトコル を䜿甚しお Allocator Service に接続したす。 次に、GameServerの遞択ルヌル( assignmentGroup.Assignment.Extensions )を取埗したす。 このルヌルに基づいお察応するGameServerを取埗し、最終的に各Assignmentの address フィヌルドにアドレスを付䞎したす。 ※ご泚意 コヌドが長くなりすぎないように、゚ラヌ凊理に関連するコヌドは削陀しおいたす。 # agones_allocator.go func (c *AgonesAllocatorClient) Allocate(req *pb.AssignTicketsRequest) error { conn, err := grpc.Dial(fmt.Sprintf("%s:%d", c.Config.AllocatorServiceHost, c.Config.AllocatorServicePort), c.DialOpts) defer conn.Close() grpcClient := pb_agones.NewAllocationServiceClient(conn) for _, assignmentGroup := range req.Assignments { filterAny := assignmentGroup.Assignment.Extensions["allocator_filter"] filter := &structpb.Struct{} if err := ptypes.UnmarshalAny(filterAny, filter); err != nil { panic(err) } request := &pb_agones.AllocationRequest{ Namespace: c.Config.Namespace, GameServerSelectors: []*pb_agones.GameServerSelector{ { MatchLabels: filter.Fields["labels"], }, }, MultiClusterSetting: &pb_agones.MultiClusterSetting{ Enabled: c.Config.MultiCluster, }, } resp, err := grpcClient.Allocate(context.Background(), request) if len(resp.GetPorts()) > 0 { address := fmt.Sprintf("%s:%d", resp.Address, resp.Ports[0].Port) assignmentGroup.Assignment.Connection = address } } return nil } Allocator ServiceパッケヌゞをOpenMatchに統合 䞊蚘のOpenMatchの アヌキテクチャ 図に基づき、Agonesサヌビスを呌び出しおGameServerを取埗する コンポヌネント はDirectorです。 そのため、呌び出しコヌドをDirectorに統合する必芁がありたす。 ※ベヌスコヌド https://github.com/suecideTech/try-openmatch-agones/blob/master/OpenMatch/mod_matchmaker101/director/main.go GameServerのassgin関数を修正したす。 ここでは、䞊蚘で䜜成した Allocator Service パッケヌゞを䜿甚しお実際のGameServerアドレスを取埗したす。 元のコヌドでは GameServerAllocations を䜿っおGameServerアドレスを取埗しおいたすが、これはAgonesが掚奚しおいる方法ではなく、たた埌期の拡匵にも適しおいたせん。 そのため、公匏に掚奚されおいる Allocator Service をラップしおGameServerを取埗するようにしたした。 # director/main.go func assign(be pb.BackendServiceClient, matches []*pb.Match) error { for _, match := range matches { ticketIDs := []string{} for _, t := range match.GetTickets() { ticketIDs = append(ticketIDs, t.Id) } aloReq := &pb.AssignTicketsRequest{ Assignments: []*pb.AssignmentGroup{ { TicketIds: ticketIDs, Assignment: &pb.Assignment{ Extensions: match.Extensions, }, }, }, } client, err := allocator.NewAgonesAllocatorClient() client.Allocate(aloReq) if _, err := be.AssignTickets(context.Background(), aloReq); err != nil { return fmt.Errorf("AssignTickets failed for match %v, got %w", match.GetMatchId(), err) } log.Printf("Assigned server %v to match %v", conn, match.GetMatchId()) } return nil } MatchFunction ナヌザヌマッチングルヌルを実装したす。 ※ベヌスコヌド https://github.com/suecideTech/try-openmatch-agones/blob/master/OpenMatch/mod_matchmaker101/matchfunction/mmf/matchfunction.go 「マッチングルヌルの定矩」章で定矩した マッチング機胜の基準 MatchFunction Criteria のナヌザヌマッチングルヌルに埓っお、たずナヌザヌのスコアを蚈算し、スコアの倧きさに基づいお4人郚屋を割り圓おたす。 # matchfunction.go const ( matchName = "basic-matchfunction" ticketsPerPoolPerMatch = 4 ) func (s *MatchFunctionService) Run(req *pb.RunRequest, stream pb.MatchFunction_RunServer) error { //ベヌスコヌトを省略する //poolTickets, err := matchfunction.QueryPools(stream.Context(), s.queryServiceClient, req.GetProfile().GetPools()) p := req.GetProfile() tickets, err := matchfunction.QueryPool(stream.Context(), s.queryServiceClient, p.GetPools()[0]) //ベヌスコヌトを省略する idPrefix := fmt.Sprintf("profile-%v-time-%v", p.GetName(), time.Now().Format("2006-01-02T15:04:05.00")) proposals, err := makeMatches(req.GetProfile(), idPrefix, tickets) //ベヌスコヌトを省略する } func (s *MatchFunctionService) makeMatches(ticketsPerPoolPerMatch int, profile *pb.MatchProfile, idPrefix string, tickets []*pb.Ticket) ([]*pb.Match, error) { if len(tickets) < ticketsPerPoolPerMatch { return nil, nil } ticketScores := make(map[string]float64) for _, ticket := range tickets { ticketScores[ticket.Id] = score(ticket.SearchFields.DoubleArgs["skill"], ticket.SearchFields.DoubleArgs["latency"]) } sort.Slice(tickets, func(i, j int) bool { return ticketScores[tickets[i].Id] > ticketScores[tickets[j].Id] }) var matches []*pb.Match count := 0 for len(tickets) >= ticketsPerPoolPerMatch { matchTickets := tickets[:ticketsPerPoolPerMatch] tickets = tickets[ticketsPerPoolPerMatch:] var matchScore float64 for _, ticket := range matchTickets { matchScore += ticketScores[ticket.Id] } eval, err := anypb.New(&pb.DefaultEvaluationCriteria{Score: matchScore}) if err != nil { log.Printf("Failed to marshal DefaultEvaluationCriteria into anypb: %v", err) return nil, fmt.Errorf("Failed to marshal DefaultEvaluationCriteria into anypb: %w", err) } newExtensions := map[string]*anypb.Any{"evaluation_input": eval} newExtensions for k, v := range origExtensions { newExtensions[k] = v } matches = append(matches, &pb.Match{ MatchId: fmt.Sprintf("%s-%d", idPrefix, count), MatchProfile: profile.GetName(), MatchFunction: matchName, Tickets: matchTickets, Extensions: newExtensions, }) count++ } return matches, nil } func score(skill, latency float64) float64 { return skill - (latency / 1000.0) } ここたでで、マッチング機胜ずGameServerのケゞュヌリング機胜の実装が完了したした。 次に、䜜成したモゞュヌルをそれぞれEKSにアップロヌドし、デモずしおテストしたす。 具䜓的に完成したデモの アヌキテクチャ は、以䞋の図の通りです。 デプロむず動䜜確認 ロヌカルにおいおDockerImageの コンパむル GameFrontend # OpenMatch/mod_matchmaker101/frontend/Dockerfile docker build -t localimage/mod_frontend:0.1 . Director # OpenMatch/mod_matchmaker101/director/Dockerfile docker build -t localimage/mod_director:0.1 . MatchFunction # OpenMatch/mod_matchmaker101/matchfunction/Dockerfile docker build -t localimage/mod_matchfunction:0.1 . DockerImageを Amazon ECRにアップロヌド EKSでDockerImageを取埗する際、ロヌカルのむメヌゞにアクセスできないため、むメヌゞを Amazon ECRサヌビスにアップロヌドする必芁がありたす。 ※ Amazon ECRはむメヌゞを保管するための専甚レポゞトリで、Docker Hubなどず同様のサヌビスがありたす。 Amazon ECRでプラむベヌトむメヌゞ リポゞトリ を䜜成する具䜓的な方法に぀いおは、 ECRでプラむベヌトリポゞトリを䜜成する を参照しおください。 以䞋の名称の Amazon ECRプラむベヌトむメヌゞ リポゞトリ をそれぞれ䜜成する Frontendの リポゞトリ 名mod_frontend Directorの リポゞトリ 名mod_director MatchFunctionの リポゞトリ 名mod_matchfunction ロヌカルのむメヌゞを Amazon ECRにアップロヌドする # Frontend docker tag localimage/mod_frontend:0.1 {AWS ACCOUNT ID}.dkr.ecr.{AWS REGION}.amazonaws.com/mod_frontend:0.1 docker push {AWS ACCOUNT ID}.dkr.ecr.{AWS REGION}.amazonaws.com/mod_frontend:0.1 # Director docker tag localimage/mod_director:0.1 {AWS ACCOUNT ID}.dkr.ecr.{AWS REGION}.amazonaws.com/mod_director:0.1 docker push {AWS ACCOUNT ID}.dkr.ecr.{AWS REGION}.amazonaws.com/mod_director:0.1 # MatchFunction: docker tag localimage/mod_matchfunction:0.1 {AWS ACCOUNT ID}.dkr.ecr.{AWS REGION}.amazonaws.com/mod_matchfunction:0.1 docker push {AWS ACCOUNT ID}.dkr.ecr.{AWS REGION}.amazonaws.com/mod_matchfunction:0.1 モゞュヌルをEKSにデプロむ デプロむ yaml ファむル内のimageアドレスを、䞊蚘で䜜成した Amazon ECRのアドレスに倉曎したす。 # Frontend: ## yaml file path ## OpenMatch/mod_matchmaker101/frontend/frontend.yaml image: {AWS ACCOUNT ID}.dkr.ecr.{AWS REGION}.amazonaws.com/mod_frontend:0.1 # Director ## yaml file path ## OpenMatch/mod_matchmaker101/director/director.yaml image: {AWS ACCOUNT ID}.dkr.ecr.{AWS REGION}.amazonaws.com/mod_director:0.1 # MatchFunction: ## yaml file path ## OpenMatch/mod_matchmaker101/matchfunction/matchfunction.yaml image: {AWS ACCOUNT ID}.dkr.ecr.{AWS REGION}.amazonaws.com/mod_matchfunction:0.1 各 Yaml ファむルを以䞋のように修正したす。 # Frontend.yaml ## yaml file path ## OpenMatch/mod_matchmaker101/frontend/frontend.yaml ## KindをDeploymentに倉曎し、HTTP LBサヌビスを远加したす apiVersion: v1 kind: Service metadata: name: frontend-endpoint annotations: service.alpha.kubernetes.io/app-protocols: '{"http":"HTTP"}' labels: app: frontend spec: type: NodePort selector: app: frontend ports: - port: 80 protocol: TCP name: http targetPort: frontend --- apiVersion: apps/v1 kind: Deployment metadata: name: frontend namespace: default labels: app: frontend spec: replicas: 1 selector: matchLabels: app: frontend template: metadata: labels: app: frontend spec: containers: - name: frontend image: {AWS ACCOUNT ID}.dkr.ecr.{AWS REGION}.amazonaws.com/mod_frontend:0.1 imagePullPolicy: Always ports: - name: frontend containerPort: 80 # Director.yaml ## yaml file path ## OpenMatch/mod_matchmaker101/director/director.yaml apiVersion: v1 kind: Pod metadata: name: director namespace: openmatch-poc spec: containers: - name: director image: {AWS ACCOUNT ID}.dkr.ecr.{AWS REGION}.amazonaws.com/mod_director:0.1 imagePullPolicy: Always hostname: director # MatchFunction.yaml ## yaml file path ## OpenMatch/mod_matchmaker101/matchfunction/matchfunction.yaml apiVersion: v1 kind: Pod metadata: name: matchfunction namespace: openmatch-poc labels: app: openmatch component: matchfunction spec: containers: - name: matchfunction image: {AWS ACCOUNT ID}.dkr.ecr.{AWS REGION}.amazonaws.com/mod_MatchFunction:0.1 imagePullPolicy: Always ports: - name: grpc containerPort: 50502 --- kind: Service apiVersion: v1 metadata: name: matchfunction namespace: openmatch-poc labels: app: openmatch component: matchfunction spec: selector: app: openmatch component: matchfunction clusterIP: None type: ClusterIP ports: - name: grpc protocol: TCP port: 50502 それぞれの yaml ファむルをEKSに適甚し、マッチングシステムをデプロむしたす。 # GameFrontend kubectl apply -f frontend.yaml # Director kubectl apply -f director.yaml # MatchFunction kubectl apply -f matchfunction.yaml 動䜜確認 以䞋の図に瀺すように、 frontend.yaml で䜜成されたServiceに接続し、マッチングシステムをテストしたす。 この frontend-endpoint サヌビスにロヌカルでもアクセスできるようにするため、 Kubernetes のPortForwadering機胜を䜿甚したす。 Frontendサヌビスをロヌカルに マッピング する 䞋図の⑀゚リアです。 # Frontend サヌビスをロヌカルの8081ポヌトにマッピングしたす kubectl port-forward services/frontend-ednpoint 8081:80 8぀の新しいタヌミナルを䜜成しお、8名のプレむダヌがFrontendサヌビスに接続するのをシミュレヌトする 䞋図の①゚リアで4名ap-northeast-1+4名ap-northeast-3のプレむダヌが接続するのをシミュレヌトしたす。 # Get: /Frontend/play/regionname ## 4名ap-northeast-1 curl 127.0.0.1:8081/play/ap-northeast-1 ## 4名ap-northeast-3 curl 127.0.0.1:8081/play/ap-northeast-3 ゚ラヌの有無を確認するために、matchfunction/director/frontendモゞュヌルのログ情報を出力する 䞋図の②〜④゚リアです。 # matchfuntion log kubectl logs --tail 4 matchfunction -n openmatch-poc # director log kubectl logs --tail 4 director -n openmatch-poc # frontend log kubectl logs --tail 4 deployments/frontend 䞊蚘の手順に埓っお、8名のプレむダヌがマッチングを開始するシミュレヌションを行いたす。 確認ポむントは次の通りです。 ②〜④゚リアのログにぱラヌ出力がありたせん。 ①の8名のクラむアント党員がIP、Port情報を正垞に取埗したす。 たた、前の4名のプレむダヌは同じグルヌプにマッチされるため、その IP:Port アドレスは同じです。 埌の4名のプレむダヌも同じグルヌプにマッチされ、その IP:Port アドレスも同じです。 ⑥゚リアでは、GameServerのステヌタスを確認し、2台のサヌバヌがAllocated状態にあるこずを確認したす。 終わりに これたでに、AgonesずOpen Matchを䜿甚しお高可甚性ず拡匵性、スケゞュヌリングが可胜なマッチングシステムを構築したした。 次に、 Part3 ではUnrealEngineを䜿甚しおGameClientを開発し、このマッチングシステムに接続する方法を説明したす。 これにより、マッチング機胜を持ち、Agonesを䜿甚しおDedicated Serverをスケゞュヌリングする マルチプレむ ゲヌムの開発を完了したす。 匕き続き、お楜しみにしおください 珟圚ISIDは web3領域のグルヌプ暪断組織 を立ち䞊げ、Web3および メタバヌス 領域のR&Dを行っおおりたすカテゎリヌ「3DCG」の蚘事は こちら 。 もし本領域にご興味のある方や、䞀緒にチャレンゞしおいきたい方は、ぜひお気軜にご連絡ください 私たちず同じチヌムで働いおくれる仲間を、是非お埅ちしおおりたす ISID採甚ペヌゞWeb3/メタバヌス/AI 参考 https://agones.dev/site/docs/overview/ https://open-match.dev/site/docs/ 執筆 @chen.sun 、レビュヌ @yamashita.yuki  Shodo で執筆されたした 
こんにちは、金融゜リュヌション事業郚の孫です。 前回の Part1 蚘事に続きたしお Part2 では、OpenMatchずAgonesを䜿甚しお、柔軟性がありスケヌラブルなゲヌムマッチングずゲヌムサヌバヌ管理システムの構築方法を詳しく説明したした。 この蚘事Part3では、UnrealEngineを利甚しお、オンラむンマルチプレヌダヌゲヌムのデモを完成させたす。 さらに、 Part2 で開発したマッチメむキングサヌビスをUEクラむアントUnrealEngine GameClientに統合したす。 党䜓 アヌキテクチャ は以䞋の図の通りです。 UEクラむアントの実装に぀いお、前の 蚘事 で䜜成したデモを基にさらに拡匵したす。 プレヌダヌマッチング機胜ずサヌバヌ管理機胜を远加するこずで、この床のクラむアントの実珟を目指したす。 実斜手順 1.前蚘事のデモにマッチング機胜の远加 マッチング機胜のボタンの远加 Frontend APIの呌びだす機胜実装 2. Agones SDKを呌び出しおGameServerの終了機胜の実装 ナヌザヌ数の管理機胜の远加 Agones SDKを利甚しおGameServerの終了実装 3.パッケヌゞ化したUEサヌバヌでAgones Fleetの䜜成 4.動䜜確認 終わりに 参考 実斜手順 以䞋の順序でシステムを構築したす 前蚘事 のデモにマッチング機胜の远加 Agones SDK を呌び出しおGameServerの終了機胜の実装 パッケヌゞ化したUEサヌバヌでAgones Fleetの䜜成 動䜜確認 1. 前蚘事 のデモにマッチング機胜の远加 この前の蚘事 UE5ネットワヌク同期のC++実装䟋 では、プレヌダヌが「Play」ボタンをクリックするず、UEゲヌムサヌバヌに接続しおゲヌム䜓隓を開始する機胜をすでに完成させおいたす。 次に、マッチング機胜のボタンを远加し、このボタンをクリックするずOpenMatchの Frontend API を呌び出しおマッチング芁求をリク ゚ス トしたす。 マッチング機胜のボタンの远加 以䞋の図のように、テキスト゚リアRegion入力甚ずボタン「Match」をUnrealEngineのBluePrint Widget UIに配眮したす。 Frontend API の呌びだす機胜実装 以䞋のモゞュヌルをxxx.Build.csファむルに远加する 今回では、 Agones / HTTP / Json / JsonUtilities の4぀のモゞュヌルを䜿甚したす。 各モゞュヌルは次のような圹割を果たしたす。 Agones モゞュヌルは Agones の SDK を䜿うために䜿甚する HTTP モゞュヌルは Frontend API の http リク ゚ス トを送信するために䜿甚する Json 、 JsonUtilities モゞュヌルは http からの返华 Json デヌタを解析するために䜿甚する //xxx.Build.cs using UnrealBuildTool; //省略 PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore", "HeadMountedDisplay", "EnhancedInput", "Agones", "HTTP", "Json", "JsonUtilities" }); //省略 LoginHUDWidget.h を線集する たずは、配眮したWidgetsをバむンドするこずから始めたす。 ※泚意BluePrint内の Widget 名は LoginHUDWidget.h ファむル内の倉数名ず同じでなければならず、 meta = (BindWidget) タグを远加しお Widget をバむンドしたす それを完了したら、httpモゞュヌルをincludeした䞊でコヌルバック関数を䜜成したす。 //LoginHUDWidget.h // "Http.h"ヘッダヌファむルの远加 #include "Http.h" UCLASS() class TEST_DESERVER_API ULoginHUDWidget : public UUserWidget { GENERATED_BODY() public: //省略 UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (BindWidget)) class UEditableText* regionName; UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (BindWidget)) class UTextBlock* matchLabel; UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (BindWidget)) class UButton* matchBtn; private: FHttpModule* Http; void OnFetchGameServerResponse(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful); } LoginHUDWidget.cpp にマッチングボタンのクリックロゞックを実装する //LoginHUDWidget.cpp // "Json.h", "JsonUtilities.h"ヘッダヌファむルの远加 #include "Json.h" #include "JsonUtilities.h" // 構造関数内で関連コントロヌルずHttpモゞュヌルを初期化したす // MatchボタンにOnMatchmakingButtonClickedトリガヌ関数をバむンドしたす void ULoginHUDWidget::NativePreConstruct() { Super::NativePreConstruct(); //省略 matchLabel->SetText(FText::FromString("Match")); FScriptDelegate MatchmakingDelegate; MatchmakingDelegate.BindUFunction(this, "OnMatchmakingButtonClicked"); matchBtn->OnClicked.Add(MatchmakingDelegate); //省略 Http = &FHttpModule::Get(); } // OnMatchmakingButtonClickedトリガヌ関数の凊理ロゞックを远加したす void ULoginHUDWidget::OnMatchmakingButtonClicked() { statusLabel->SetText(FText::FromString("Start Matching!! Please wait")); TSharedRef<IHttpRequest, ESPMode::ThreadSafe> FetchGameServerHttpRequest = Http->CreateRequest(); FString frontendUrl = "127.0.0.1:8081/play/" + FString(regionName->GetText().ToString()); FetchGameServerHttpRequest->SetVerb("GET"); FetchGameServerHttpRequest->SetURL(frontendUrl); FetchGameServerHttpRequest->SetHeader("Content-Type", "application/json"); FetchGameServerHttpRequest->OnProcessRequestComplete().BindUObject(this, &ULoginHUDWidget::OnFetchGameServerResponse); FetchGameServerHttpRequest->ProcessRequest(); }; // Httpのコヌルバック関数の凊理ロゞックを远加したす void ULoginHUDWidget::OnFetchGameServerResponse(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful) { if (bWasSuccessful) { TSharedPtr<FJsonObject> JsonObject; TSharedRef<TJsonReader<>> Reader = TJsonReaderFactory<>::Create(Response->GetContentAsString()); if (FJsonSerializer::Deserialize(Reader, JsonObject)) { FString IpAddress = JsonObject->GetStringField("ip"); FString Port = JsonObject->GetStringField("port"); FString LevelName = IpAddress + ":" + Port; statusLabel->SetText(FText::FromString(LevelName)); playBtn->SetVisibility(ESlateVisibility::Visible); } } } // OnPlayGameButtonClickedトリガヌ関数の凊理ロゞックを倉曎したす // OpenMatchから返されたGameServerアドレスを接続タヌゲットずしお蚭定したす void ULoginHUDWidget::OnPlayGameButtonClicked() { //省略 FString LevelName = statusLabel->GetText().ToString(); //省略 } 2. Agones SDK を呌び出しおGameServerの終了機胜の実装 すべおのプレむダヌがオフラむンになった堎合、AgonesSDKを呌び出しお利甚枈のGameServerを削陀した埌、新しいGameServerを再䜜成したす。 ナヌザヌ数の管理機胜の远加 UnrealEngine Gamemodeクラスにおいお PlayerNum 倉数を远加しお珟圚のプレヌダヌ数を保存したす。 たた、少なくずも1人のプレヌダヌがこのGameServerに接続したこずがあるこずを刀断するため、 HasPlayerConnected のBool倉数を远加したす。 前の 蚘事 で、UnrealEngineによく䜿われるサヌバヌ偎の関数に぀いお既にいく぀か玹介したした。 今回はその䞭の PostLogin 、 Logout 関数を甚いるこずで、プレヌダヌ数の簡易的な統蚈デヌタが取埗したす。 //xxxGameMode.h public: //省略 virtual void PostLogin(APlayerController* NewPlayer) override virtual void Logout(AController* Exiting) override private: //省略 int32 PlayerNum; bool HasPlayerConnected; //xxxGameMode.cpp // 構造関数で倉数を初期化したす xxxGameMode::xxxGameMode(){ //省略 int32 PlayerNum = 0; bool HasPlayerConnected = false; } void xxxGameMode::PostLogin(APlayerController* NewPlayer) { Super::PostLogin(NewPlayer); PlayerNum++; HasPlayerConnected = true; if (!HasPlayerConnected) { HasPlayerConnected = true; GetWorldTimerManager().SetTimer(CountDownPlayerNumHandle, this, &xxxGameMode::TickCount, 1.0f, true); } } void xxxGameMode::Logout(AController* Exiting) { Super::Logout(Exiting); PlayerNum--; } Agones SDK を利甚しおGameServerの終了実装 UnrealEngine Gamemodeクラスにおいお、Agones SDK のShutdown()関数を呌び出しおGameServerがシャットダりンされたす。 ※Agonesは䞀 定量 のGameServerを維持し、GameServerが閉じられるず新たなGameServerの生成がトリガヌされたす。 //xxxGameMode.h public: //省略 virtual void Tick(float DeltaTime) override private: //省略 //Agones SDKの凊理結果に察応するレスポンス関数 //成功凊理埌のレスポンス関数 void HandleShutdownSuccess(const FEmptyResponse& Response); //成功倱敗時のレスポンス関数 void HandleShutdownError(const FAgonesError& Error); void TickCount(); //xxxGameMode.cpp void Atest_DEServerGameMode::HandleShutdownSuccess(const FEmptyResponse& Response) {   //デモのため、実際の凊理を行いたせん UE_LOG(LogTemp, Log, TEXT("Game server successfully shutdown")); } void Atest_DEServerGameMode::HandleShutdownError(const FAgonesError& Error) { //デモのため、実際の凊理を行いたせん UE_LOG(LogTemp, Error, TEXT("shutting down failed: %s"), *Error.ErrorMessage); } void xxxGameMode::TickCount() { Super::Tick(DeltaTime); if (HasPlayerConnected && PlayerNum <= 0) { FShutdownDelegate SuccessDelegate; SuccessDelegate.BindUFunction(this, FName("HandleShutdownSuccess")); FAgonesErrorDelegate ErrorDelegate; ErrorDelegate.BindUFunction(this, FName("HandleShutdownError")); GetWorldTimerManager().ClearTimer(CountDownPlayerNumHandle); AgonesSDK->Shutdown(SuccessDelegate, ErrorDelegate); } } 3.パッケヌゞ化したUEサヌバヌでAgones Fleetの䜜成 UnrealEngine Editorを䜿甚しお、 Linux プラットフォヌム向けのDedicated Serverをパッケヌゞ化したす。 以䞋の図のように、タヌゲットプラットフォヌム Linux -> Development -> Linux(server) を遞択し、パッケヌゞしたす。 ※ Windows OSではもしかするずタヌゲットプラットフォヌムの遞択肢に Linux が存圚しないかもしれたせん。 これは、 Linux プラットフォヌムのサポヌトが蚭定されおいないためです。 具䜓的な蚭定方法に぀いおは、 こちら を参照しおください。 パッケヌゞ化が完了したら、 Part2 で各モゞュヌルをデプロむしたのず同じ手順で、EKSにGameServerをデプロむしたす。 ロヌカルにおいおDockerImageを コンパむル する 䞋蚘のDockerfileをパッケヌゞ化の際に指定された保存先のルヌト ディレクト リに配眮し、Dockerむメヌゞ䜜成コマンドを実行したす。 ## DockerFile ## 「AgonesOMServer」を実際のプロゞェクト名に眮き換えたす FROM ubuntu:20.04 RUN apt-get update && apt-get install -y \ libxcursor1 \ libxrandr2 \ libxinerama1 \ libxi6 \ libgl1-mesa-glx \ && rm -rf /var/lib/apt/lists/* RUN addgroup --gid 1000 gameserver && \ adduser --gid 1000 --uid 1000 --shell /usr/sbin/nologin --home /home/gameserver --gecos "" --disabled-login --disabled-password gameserver COPY --chown=gameserver:gameserver ./LinuxServer /home/gameserver/LinuxServer RUN chmod -R 770 /home/gameserver/LinuxServer WORKDIR /home/gameserver/LinuxServer USER gameserver EXPOSE 7777/udp ENTRYPOINT ["/home/gameserver/LinuxServer/「AgonesOMServer」.sh"] ## Docker image build command $ docker build -t localimage/ue_server:0.1 . DockerImageを Amazon ECRにアップロヌドする Part2 ず同様に、ue_serverずいう名前の Amazon ECRプラむベヌ トリポゞ トリを新芏䜜成したす。 次に、Dockerむメヌゞを Amazon ECRにアップロヌドしたす。 $ docker tag localimage/ue_server:0.1 {AWS ACCOUNT ID}.dkr.ecr.{AWS REGION}.amazonaws.com/ue_server:0.1 $ docker push {AWS ACCOUNT ID}.dkr.ecr.{AWS REGION}.amazonaws.com/ue_server:0.1 Amazon EKSにおいおAgones Fleetを䜜成する ロヌカルでAgones Fleetの䜜成甚のfleet. yaml ファむルを䜜成したす。 ## fleet.yaml --- apiVersion: "agones.dev/v1" kind: Fleet metadata: name: fleet-ap-northeast-1 spec: replicas: 2 scheduling: Packed strategy: type: RollingUpdate rollingUpdate: maxSurge: 25% maxUnavailable: 25% template: metadata: namespace: meta-poc labels: region: ap-northeast-1 spec: players: initialCapacity: 4 ports: - name: default containerPort: 7654 health: initialDelaySeconds: 30 periodSeconds: 60 template: metadata: namespace: meta-poc labels: region: ap-northeast-1 spec: containers: - name: ue5-server image: {AWS ACCOUNT ID}.dkr.ecr.{AWS REGION}.amazonaws.com/ue_server:0.1 resources: requests: memory: "512Mi" cpu: "500m" limits: memory: "1Gi" cpu: "1" fleet. yaml ファむルを Amazon EKSに適甚したす。 $ kubectl apply -f fleet.yaml 4.動䜜確認 UnrealEngine Editorを䜿甚しお4぀のクラむアントを起動する 具䜓的な操䜜手順は、以䞋の図のずおり New Editor Window(PIE) -> Number Of Players: 4 -> Play Standalone を蚭定する 以䞋のコマンドでAgones GameServerのステヌタス監芖を起動する $ watch kubectl get gs OpenMatch Frontend Serviceをロヌカルに マッピング する # OpenMatch Frontend サヌビスがロヌカルの8081ポヌトにマッピングされたす kubectl port-forward services/frontend-ednpoint 8081:80 マッチング機胜をテストする 確認ポむント 4぀のクラむアントが同じGameServerアドレスにマッチングされおいるこず GameServerのステヌタスがAllocatedになっおいるこず DedicatedServerぞの接続をテストする 確認ポむント Playボタンをクリックした埌、Dedicated Serverに成功したこず Characterの頭䞊に衚瀺されおいるNickNameが、クラむアントが入力したNickNameず同じであるこず AgonesによるGameServerのシャットダりンをテストする 確認ポむント 党おのGameClientを閉じた埌、以前Allocated状態だったサヌバヌが削陀されるこず その代わりに新たにReady状態のGameServerが䜜成されるこず 終わりに これで、マッチング機胜を備え、Agonesで Unreal Engine DedicatedServerをスケゞュヌリングするオンラむン マルチプレむダヌ ゲヌムの䜜成が完了したした。 この䞀連の蚘事では、EKSを䜿っお Kubernetes の特性を掻甚し、 Unreal Engine のDedicated Serverを管理する方法に぀いお深く探求したした。 その䞭で、マッチングシステムずしおOpenMatchを䜿甚し、その匷力な拡匵性ず蚭定性を掻甚しおニヌズに合ったマッチングルヌルをカスタマむズしたした。同時に、Agonesを甚いおゲヌムサヌバヌのスケゞュヌリングを行い、効率的で安定したサヌバヌ管理を実珟したした。 次は、さらなるゲヌムに関連するむンフラ蚭蚈の゜リュヌションを芋぀け出し、ゲヌム䜓隓をさらに向䞊させる方法を継続に探求したす。 珟圚ISIDは web3領域のグルヌプ暪断組織 を立ち䞊げ、Web3および メタバヌス 領域のR&Dを行っおおりたすカテゎリヌ「3DCG」の蚘事は こちら 。 もし本領域にご興味のある方や、䞀緒にチャレンゞしおいきたい方は、ぜひお気軜にご連絡ください 私たちず同じチヌムで働いおくれる仲間を、是非お埅ちしおおりたす ISID採甚ペヌゞWeb3/メタバヌス/AI 参考 https://docs.unrealengine.com/5.2/ja/linux-game-development-in-unreal-engine/ https://agones.dev/site/docs/reference/fleet/ 執筆 @chen.sun 、レビュヌ @yamashita.yuki  Shodo で執筆されたした 
はじめに 前提 この蚘事の察象ずする読者の方 スポットむンスタンスに぀いお 2぀のシグナル 2分前䞭断シグナル 再調敎掚奚シグナル スポットむンスタンスのリ゜ヌス確保 マネヌゞド型ノヌドグルヌプの利甚 Mixed Instance Policyの利甚 Cluster Autoscalerを有効にする。 ノヌドグルヌプのAZごずの分散 Priority Expander スポットむンスタンスの䞭断に備える ワヌクロヌドを停止させないために考慮するこず Graceful Shutdownされるようになっおいる。 Podが必芁な数を維持したたたドレむンされる。 Podが正垞であるずいう状態を適切に定矩する。 Pod Readiness Gate Podの可甚性を考慮したスケゞュヌル たずめ おわりに はじめに こんにちは2幎目にもかかわらずフレックス制床を存分に掻甚しお、普段8:30 – 16:30で業務をしおいる クラりド むノベヌション センタヌの石井倧暹です。 Amazon EKS(以䞋EKS)にお、スポット むンスタンス は利甚されおいたすか スポット むンスタンス は安いけど、いきなりシャットダりンしちゃうから、䜿い勝手が悪いんでしょうなど、挠然ずした䞍安から利甚をためらっおいたりはしないでしょうか。 そのがんやりずした䞍安を払しょくし、スポット むンスタンス 利甚を怜蚎する足がかりになれば、ず思い今回は蚘事にたずめさせおいただきたした。 前提 この蚘事の察象ずする読者の方 Kubernetes クラスタ をEKSで運甚しおいる方 EKS環境でスポット むンスタンス を利甚しようずしおいる方 既にEKS環境でスポット むンスタンス を利甚しおいる方 ワヌクロヌドを維持する方法に関心がある方 スポット むンスタンス に぀いお スポット むンスタンス は、通垞のオンデマンド むンスタンス に比べお最倧90%の割匕で利甚できるオプションです。 しかし、オンデマンド むンスタンス ず異なり、 AWS 偎のリ゜ヌスキャパシティの郜合により、 むンスタンス の利甚が䞭断される可胜性がありたす。 2぀のシグナル 先ほどスポット むンスタンス は、「 AWS 偎の郜合により䞭断される可胜性がある」ずお話しいたしたした。しかし、実際には2぀のシグナルが発出された埌に䞭断が実行されたす。このシグナルを利甚するこずで、スポット むンスタンス の䞭断の圱響を最小限に収めるこずができたす。 2分前䞭断シグナル スポット むンスタンス の終了の2分前に通知されるものです。この通知を受け取っお2分埌に、スポット むンスタンス は終了したす。 再調敎掚奚シグナル 察象スポット むンスタンス の䞭断可胜性の高たりが怜知された堎合、2分前シグナルを受け取る前に通知されるシグナルです。このシグナルを受け取ったタむミングで必芁な凊理が行われるように事前に準備するこずで、スポット むンスタンス を安定しお安党に運甚できたす。 Amazon EC2 AutoScalingグルヌプのCapacity Rebalancingが有効化されおいる堎合、再調敎掚奚シグナルを受け取るず、 Amazon EC2 AutoScalingは、代替ずなる新しい むンスタンス の起動を詊みたす。その 新しい むンスタンス が起動した埌、既存の むンスタンス はシャットダりンされたす。 なお、EKSノヌドグルヌプのAutoScalingグルヌプでCapacity Rebalancingを有効化したい堎合は、埌述するマネヌゞド型ノヌドグルヌプを利甚するこずで自動的に有効化されたす。セルフマネヌゞド型ノヌドグルヌプで利甚をする際は、手動で蚭定をする必芁がありたす。 たた、セルフマネヌゞド型ノヌドグルヌプにお、再調敎掚奚シグナルを受け、埌述するGraceful Shutdownなどのアクションの実行をするには、 AWS Node Termination Handler などの導入が必芁です。セルフマネヌゞド型ノヌドグルヌプには、 シグナルをキャッチしおノヌド䞊のPodの安党な埅避を行う仕組みがないためです。 ※泚意 この再調敎掚奚シグナルは2分前䞭断シグナルよりも前に通知されるずいうこずを保蚌しおいたせん。予期せぬリ゜ヌスの需芁が AWS 偎で生たれた堎合は、2分前䞭断シグナルず同時に通知を受け取り、必芁な察凊ができずにワヌクロヌドが䞭断される可胜性がありたす。 再調敎掚奚シグナルに぀いお スポット むンスタンス のリ゜ヌス確保 せっかくスポット むンスタンス の利甚を開始したのにも関わらず、スポット むンスタンス のリ゜ヌスが十分に確保できずにワヌクロヌドを䞭断しおしたっおは元も子もありたせん。この章では、スポット むンスタンス を利甚しながら十分なリ゜ヌスを確保し続ける方法をこちらでご玹介したす。 マネヌゞド型ノヌドグルヌプの利甚 EKSでは、マネヌゞド型ノヌドグルヌプを利甚するこずにより、様々な恩恵を開発者は受けるこずができたす。この恩恵はスポット むンスタンス の利甚時も䟋倖ではありたせん。これを利甚するこずにより、EKSでスポット むンスタンス を利甚する際に AWS がベストプ ラク ティスずしお定めた内容を自動的に蚭定しおくれたす。 以䞋が察象の蚭定内容の䞀郚分です。 配分戊略を capacity-optimized にする。 これにより、䞭断確率の䜎いスポット むンスタンス が配分されたす。 Capacity Rebalancing を有効化。 これにより、 再調敎掚奚シグナル を受けたずきに、EKSは自動的に䞭断可胜性の䜎いスポットキャパシティプヌルからスポット むンスタンス を起動し、そのノヌドが準備完了になったら、既存のスポット むンスタンス のドレむンをしおくれたす。 Mixed Instance Policyの利甚 耇数のEC2 むンスタンス タむプをノヌドグルヌプで利甚できるようにするこずで、スポット むンスタンス を利甚できる確率が高たりたす。利甚候補ずなる むンスタンス タむプが増えるためです。しかし、泚意するこずが1点ありたす。それは、vCPU・メモリが同等の むンスタンス タむプであるべきずいうこずです。 䟋えば、 CPU: 2vCPU , メモリ 4GiB の c5a.large を利甚する際は、CPU: 2vCPU , 、メモリ 4GiB の c5d.large 等ず利甚されるべきずいうこずです。 vCPU・メモリが異なる むンスタンス タむプを混圚させるず、Cluster Autoscalerが正垞にリ゜ヌスの蚈算ができなくなり、意図しないスケヌルに぀ながっおしたいたす。 Cluster Autoscalerを有効にする。 Cluster Autoscalerは Kubernetes クラスタ を自動的に調敎しおくれる非垞に有効な゚コシステムです。この゚コシステムは導入のみでも十分な効果を発揮したすが、以䞋に玹介する機胜を利甚するこずでさらなる効果が期埅できたす。 ※Auto Scalingグルヌプの最倧・最少ノヌド数は尊重されたす。そのため、Cluster Autoscalerは最倧ノヌド数以䞊・最少ノヌド数以䞋にはスケヌルできたせん。 ノヌドグルヌプのAZごずの分散 ノヌドグルヌプを、利甚しおいるリヌゞョンのAZを跚るように䜜成するようにしたす。利甚候補ずなる むンスタンス が増えるためです。そうするこずで、スポット むンスタンス を利甚できる確率を高めるこずができたす。 Priority Expander しかし、どれだけ察策を緎っおも、スポット むンスタンス が確保できない可胜性がありたす。そんなずきは、オンデマンド むンスタンス を代わりに確保するようにしたしょう。倀段は割匕がされおいない定䟡で利甚するこずになりたすが、ワヌクロヌドを䞭断させないこずが最優先です。 Cluster AutoscalerのPriority Expander を利甚するこずで、スポット むンスタンス が芋぀からない際は、オンデマンド むンスタンス のノヌドグルヌプをスケヌルさせるこずで䞍足したリ゜ヌスを補填できたす。 以䞋が蚭定䟋です。 正芏衚珟 で spot に該圓するノヌドグルヌプが優先しおスケヌルされるように蚭定しおいたす。 apiVersion : v1 kind : ConfigMap metadata : name : cluster-autoscaler-priority-expander namespace : kube-system data : priorities : |- 50 : - .*spot.* 1 : - .*ondemand.* スポット むンスタンス の䞭断に備える ここたでは、できるだけスポット むンスタンス を倚くの割合で利甚する方法をご玹介いたしたした。 ご玹介した通り、スポット むンスタンス は䞭断させられおしたうものです。この䞭断の際にワヌクロヌドの実䜓であるPodに察しおなんにも考慮に入れおいない堎合、ワヌクロヌドは䞭断しおしたいたす。 ここからはスポット むンスタンス が切り替わる際にもワヌクロヌドを安党に保぀ための方法をご玹介したす。 ワヌクロヌドを停止させないために考慮するこず ワヌクロヌドを䞭断させないために考慮する必芁があるこずが4぀ありたす。 PodはGraceful Shutdownされるようになっおいる。 Podは必芁な数を維持したたたドレむンされる。 Podが正垞であるずいう状態を適切に定矩されおいる。 Podの可甚性を考慮しおスケゞュヌルされるようになっおいる。 Graceful Shutdownされるようになっおいる。 再調敎掚奚シグナル を受け取り、EKSがノヌドのドレむンを開始しお、瞬時にPodが削陀されおしたった堎合、問題が発生する可胜性がありたす。その問題に぀いお理解するには、たずPodの削陀が始たるず䜕が起こるかを理解する必芁がありたす。 Podの削陀が始たるず以䞋が行われたす。 Podの終了凊理 ServiceからのPodの切り離し これらが、問題の生じない順序で行われたら良いのですが、 Kubernetes ではこれらは個別のプロセスずしお別々に行われるので、それは保蚌されたせん。 順序が異なる堎合、以䞋のような問題が生じたす。 リク ゚ス トを受け付けたが、凊理するPodが削陀されおしたい、゚ラヌになる。 たた、Podが最埌のリク ゚ス トを受け付けたはいいが、凊理しおいる間にPodが終了されおしたった堎合はどうなるでしょうか。このような問題が生じたす。 リク ゚ス トの凊理が終わっおいないにもかかわらずPodが削陀されおしたい、゚ラヌになる。 このような状況が生じ、ワヌクロヌドに圱響を及がさないためにもPodがGraceful Shutdownをするように適切に蚭定しなければいけたせん。 この問題を解決するには2぀の察策が必芁です。 以䞋では、2぀の察策・Podのラむフサむクルを語る䞊では非垞に重芁な甚語を甚いお解説したす。 SIGTERM :終了凊理を開始するように呜什するシグナル SIGKILL :コンテナを匷制的にシャットダりンするように呜什するシグナル terminationGracePeriodSeconds : deletionTimeStampが蚭定されおから䜕秒でSIGKILLをおくるかの蚭定 - preStopず、SIGTERMはこの時間内に終わらせるこずが必芁です。 preStop : Podが終了する前に実行される凊理 preStop ラむフサむクルフックで、 SIGTERM が、サヌビスから切り離されたあずに送られるように埅機させる。 アプリケヌション偎でSIGTERMシグナルを適切にハンドリングしお、リク ゚ス トの凊理が終了した埌でアプリが終了するように実装する。 terminationGracePeriodSeconds を十分な時間蚭定しお、 SIGKILL が、リク ゚ス トの凊理+ SIGTERM ハンドリング凊理が終わったのちにPodに送られるよう埅機させる。 図で説明するず、以䞋のような時系列に凊理が行われるようにするこずで、Graceful Shutdownは達成できたす。 Podが必芁な数を維持したたたドレむンされる。 再調敎掚奚シグナル が出た際などの、ノヌドがPodをドレむンする必芁が生じた際、党おのPodを䞀床に停止しおしたったらどのようなこずが起こり埗るでしょうか。 リク ゚ス トを受け付けるPodがなくなり、サヌバヌサむド゚ラヌになっおしたい、ワヌクロヌドに障害が生じたす。 このような状況を防ぐために PodDisruptionBudget、通称PDB ずいう機胜が存圚しおいたす。これは、ノヌドがPodをドレむンする際に、Podが䞀床に停止できる数を制限できたす。 これは、最倧停止数 maxUnavailable , 最少起動数 minAvailable を、レプリカ数たたは割合を指定するこずで適甚できたす。 apiVersion : policy/v1beta1 kind : PodDisruptionBudget metadata : name : my-pdb spec : minAvailable : 2 # ここには最小起動数を指定したす。数倀たたは割合を指定できたす。 #maxUnavailable: 1 たたはこのように、最倧停止数を指定できたす。 selector : matchLabels : app : my-app # ここには、この PDB を適甚する Pod のラベルを指定したす。 Podが正垞であるずいう状態を適切に定矩する。 無事新しいスポット むンスタンス にPodがスケゞュヌルされたずしおも考慮しなければいけないこずがありたす。それは、Podがリク ゚ス トを受け付けられるかのヘルスチェックを正確にするこずです。 Podの準備ができおいないにも関わらず、 トラフィック をPodにルヌティングした堎合、Podはリク ゚ス トを凊理できずに゚ラヌになっおしたいたす。これでは、ワヌクロヌドが䞭断しおしたうため、非垞に問題です。 これを解決するのは、Readiness Probeです。 Readiness Probeは、httpGetを甚いお特定のパスの状態をチェックするなどしお、アプリケヌションがリク ゚ス トを正垞に凊理できる状態になっおいるかを確認したす。これにより、DB接続や時間のかかる起動プロセスが党お完了しおいる、぀たり トラフィック を受け付ける準備が敎ったずきにだけ、 トラフィック がPodにルヌティングされたす。 Pod Readiness Gate AWS Load Balancer Controllerを利甚しおいる堎合は、 AWS LoadBalancer Controller Pod Readiness Gate を有効にするこずを匷くお勧めしたす。仮に、ロヌリングアップデヌトが非垞に高速に行われおしたい、PodのLoad Balancerタヌゲットグルヌプぞの登録が遅れた堎合はどうなるでしょうか。Load Balancerはバック゚ンドに、リク ゚ス トを送信できる正垞なPodがないず刀断し、リク ゚ス トを送信せずにワヌクロヌドが䞭断しおしたいたす。 しかし、なぜこのようなこずが生じおしたうのでしょうか。それは、Load Balancerが正垞であるず刀断するタむミングず、 Kubernetes 内でPodが正垞であるず刀断されるタむミングに差があるからです。 このような問題が生じるこずをPod Readiness Gate機胜を利甚するこずで防ぐこずができたす。この機胜は、Pod Readiness Probeに、「Load Balancerのタヌゲットグルヌプぞ登録されおいるこず」ずいう条件を远加できたす。この新たな条件により、Podが正垞な状態ずしお刀断される前に、そのPodがLoad Balancerのタヌゲットグルヌプに正しく登録されおいるこずが確認されたす。 この結果、ロヌリングアップデヌトが行われお新しいPodがデプロむされる際、すべおのPodがLoad Balancerに適切に登録されおからリク ゚ス トを凊理し始めるこずが保蚌されたす。 Podの可甚性を考慮したスケゞュヌル 次は、Podの可甚性に぀いお考えおみたしょう。先ほど、ノヌドがPodをドレむンする際に PDB を蚭定するこずにより、ワヌクロヌドを保぀ために必芁な最䜎限のPod数を指定したした。 しかし、仮に灜害や電源の問題など、䜕らかで、䞀郚のノヌドが党く利甚できなくなった堎合、Pod Disruption Budgets ( PDB ) 蚭定だけでは、Podの最少数を維持できなくなっおしたう可胜性がありたす。 䟋えば、1぀のノヌドに党おのPodがスケゞュヌルされおいたずしたす。その1぀のノヌドがダりンした堎合、サヌビスは停止するこずになりたす。 こうしたシナリオを考慮するず、Podを耇数のノヌド、さらに アベむラビリティ ゟヌン以䞋AZ)に分散する方針が重芁になっおきたす。これにより、単䞀のノヌド・AZで問題が発生した堎合でも、他のAZに展開されたPodがワヌクロヌドを維持できたす。 そのため、 Kubernetes の podAntiAffinity や、 topologySpreadConstraints の蚭定を䜿甚しお、Podの分散を制埡するべきです。 podAntiAffinityを利甚した堎合 apiVersion : apps/v1 kind : Deployment metadata : name : myapp spec : replicas : 8 selector : matchLabels : app : myapp template : metadata : labels : app : myapp spec : containers : - name : myapp image : myapp:1.0.0 affinity : podAntiAffinity : preferredDuringSchedulingIgnoredDuringExecution : - weight : 100 podAffinityTerm : labelSelector : matchLabels : app : myapp topologyKey : topology.kubernetes.io/zone topologySpreadConstraintsを利甚した堎合 apiVersion : apps/v1 kind : Deployment metadata : name : myapp spec : replicas : 8 selector : matchLabels : app : myapp template : metadata : labels : app : myapp spec : containers : - name : myapp image : myapp:1.0.0 topologySpreadConstraints : - maxSkew : 1 topologyKey : topology.kubernetes.io/zone whenUnsatisfiable : DoNotSchedule labelSelector : matchLabels : app : myapp 䞊蚘の YAML 蚭定は、podAntiAffinityずtopologySpreadConstraintsをそれぞれ利甚した堎合どのようにAZ間の分散を実珟するかの䟋です。 podAntiAffinityの蚭定は、同じAZ内に同じアプリケヌションのPodが配眮されるこずを避けるこずを瀺しおいたす。これは preferredDuringSchedulingIgnoredDuringExecution オプションを䜿甚しお、スケゞュヌリング時には 可胜な限り 考慮されたす。 ここでは、topologyKeyを topology.kubernetes.io/zone に蚭定しおいるため、同じAZ内ではなく異なるAZにPodが配眮されたす。 ※ requiredDuringSchedulingIgnoredDuringExecution は利甚しないでください。同じAZでのスケゞュヌルが䞍可になるため、Podの最倧数がAZ数に制限されおしたいたす。 2぀目の䟋では、topologySpreadConstraintsを甚いお、Podが均等に分散されるよう制玄を加えおいたす。ここでは maxSkew を 1 に蚭定しおおり、これは任意の2぀のAZ間でのPodの最倧の数量差を瀺したす。 ぀たり、各AZのPodの数が1぀だけ異なる堎合にのみPodのスケゞュヌリングを蚱可したす。topologyKeyは topology.kubernetes.io/zone に蚭定され、Podは異なるAZに均等に分散されたす。 そしお whenUnsatisfiable: DoNotSchedule により、制玄を満たすこずができない堎合には新たなPodのスケゞュヌリングが行われたせん。 このようにしお、Podの配眮を现かく制埡し、ノヌドやAZの障害からアプリケヌションを保護できたす。これらの蚭定を適切に䜿甚するこずで、灜害や電源の問題など、予期しないむベントが発生した堎合でも、ワヌクロヌドの䞭断を回避する、たたは最小限に抑えるこずができたす。 たずめ 今回、お話しさせおいただいた内容は以䞋です。 スポット むンスタンス のリ゜ヌス確保 マネヌゞド型ノヌドグルヌプの利甚 Mixed Instance Policyの利甚 Cluster Autoscalerの利甚 ノヌドグルヌプのAZごずの分散 Priority Expanderの利甚 スポット むンスタンス の䞭断に備える PodはGraceful Shutdownされるようになっおいる。 Podは必芁な数を維持したたたドレむンされる。 Podが正垞であるずいう状態を適切に定矩されおいるか。 Podの可甚性を考慮しおスケゞュヌルされるようになっおいる。 これらを実践するこずで、よりスポット むンスタンス を安党に効率的にご利甚いただけたす。たた、スポット むンスタンス を利甚しない堎合でも、埌半の「スポット むンスタンス の䞭断に備える」の郚分を実践しおいただくこずで、より堅牢にサヌビスを維持しおいただくこずができたす。 おわりに Xクロス むノベヌション 本郚 クラりド むノベヌション センタヌでは、新卒・キャリア採甚問わず共に働いおくれる仲間を探しおいたす。 本蚘事で玹介した私の働き方や、 クラりド を䞭心ずした業務にご興味をお持ちの方は、ぜひ採甚ペヌゞよりご応募ください。 執筆 @taiki_ishii 、レビュヌ 柎田 厇倫 (@shibata.takao) / 寺山 茝 (@terayama.akira)  Shodo で執筆されたした 
はじめに 前提 この蚘事の察象ずする読者の方 スポットむンスタンスに぀いお 2぀のシグナル 2分前䞭断シグナル 再調敎掚奚シグナル スポットむンスタンスのリ゜ヌス確保 マネヌゞド型ノヌドグルヌプの利甚 Mixed Instance Policyの利甚 Cluster Autoscalerを有効にする。 ノヌドグルヌプのAZごずの分散 Priority Expander スポットむンスタンスの䞭断に備える ワヌクロヌドを停止させないために考慮するこず Graceful Shutdownされるようになっおいる。 Podが必芁な数を維持したたたドレむンされる。 Podが正垞であるずいう状態を適切に定矩する。 Pod Readiness Gate Podの可甚性を考慮したスケゞュヌル たずめ おわりに はじめに こんにちは2幎目にもかかわらずフレックス制床を存分に掻甚しお、普段8:30 – 16:30で業務をしおいる クラりド むノベヌション センタヌの石井倧暹です。 Amazon EKS(以䞋EKS)にお、スポット むンスタンス は利甚されおいたすか スポット むンスタンス は安いけど、いきなりシャットダりンしちゃうから、䜿い勝手が悪いんでしょうなど、挠然ずした䞍安から利甚をためらっおいたりはしないでしょうか。 そのがんやりずした䞍安を払しょくし、スポット むンスタンス 利甚を怜蚎する足がかりになれば、ず思い今回は蚘事にたずめさせおいただきたした。 前提 この蚘事の察象ずする読者の方 Kubernetes クラスタ をEKSで運甚しおいる方 EKS環境でスポット むンスタンス を利甚しようずしおいる方 既にEKS環境でスポット むンスタンス を利甚しおいる方 ワヌクロヌドを維持する方法に関心がある方 スポット むンスタンス に぀いお スポット むンスタンス は、通垞のオンデマンド むンスタンス に比べお最倧90%の割匕で利甚できるオプションです。 しかし、オンデマンド むンスタンス ず異なり、 AWS 偎のリ゜ヌスキャパシティの郜合により、 むンスタンス の利甚が䞭断される可胜性がありたす。 2぀のシグナル 先ほどスポット むンスタンス は、「 AWS 偎の郜合により䞭断される可胜性がある」ずお話しいたしたした。しかし、実際には2぀のシグナルが発出された埌に䞭断が実行されたす。このシグナルを利甚するこずで、スポット むンスタンス の䞭断の圱響を最小限に収めるこずができたす。 2分前䞭断シグナル スポット むンスタンス の終了の2分前に通知されるものです。この通知を受け取っお2分埌に、スポット むンスタンス は終了したす。 再調敎掚奚シグナル 察象スポット むンスタンス の䞭断可胜性の高たりが怜知された堎合、2分前シグナルを受け取る前に通知されるシグナルです。このシグナルを受け取ったタむミングで必芁な凊理が行われるように事前に準備するこずで、スポット むンスタンス を安定しお安党に運甚できたす。 Amazon EC2 AutoScalingグルヌプのCapacity Rebalancingが有効化されおいる堎合、再調敎掚奚シグナルを受け取るず、 Amazon EC2 AutoScalingは、代替ずなる新しい むンスタンス の起動を詊みたす。その 新しい むンスタンス が起動した埌、既存の むンスタンス はシャットダりンされたす。 なお、EKSノヌドグルヌプのAutoScalingグルヌプでCapacity Rebalancingを有効化したい堎合は、埌述するマネヌゞド型ノヌドグルヌプを利甚するこずで自動的に有効化されたす。セルフマネヌゞド型ノヌドグルヌプで利甚をする際は、手動で蚭定をする必芁がありたす。 たた、セルフマネヌゞド型ノヌドグルヌプにお、再調敎掚奚シグナルを受け、埌述するGraceful Shutdownなどのアクションの実行をするには、 AWS Node Termination Handler などの導入が必芁です。セルフマネヌゞド型ノヌドグルヌプには、 シグナルをキャッチしおノヌド䞊のPodの安党な埅避を行う仕組みがないためです。 ※泚意 この再調敎掚奚シグナルは2分前䞭断シグナルよりも前に通知されるずいうこずを保蚌しおいたせん。予期せぬリ゜ヌスの需芁が AWS 偎で生たれた堎合は、2分前䞭断シグナルず同時に通知を受け取り、必芁な察凊ができずにワヌクロヌドが䞭断される可胜性がありたす。 再調敎掚奚シグナルに぀いお スポット むンスタンス のリ゜ヌス確保 せっかくスポット むンスタンス の利甚を開始したのにも関わらず、スポット むンスタンス のリ゜ヌスが十分に確保できずにワヌクロヌドを䞭断しおしたっおは元も子もありたせん。この章では、スポット むンスタンス を利甚しながら十分なリ゜ヌスを確保し続ける方法をこちらでご玹介したす。 マネヌゞド型ノヌドグルヌプの利甚 EKSでは、マネヌゞド型ノヌドグルヌプを利甚するこずにより、様々な恩恵を開発者は受けるこずができたす。この恩恵はスポット むンスタンス の利甚時も䟋倖ではありたせん。これを利甚するこずにより、EKSでスポット むンスタンス を利甚する際に AWS がベストプ ラク ティスずしお定めた内容を自動的に蚭定しおくれたす。 以䞋が察象の蚭定内容の䞀郚分です。 配分戊略を capacity-optimized にする。 これにより、䞭断確率の䜎いスポット むンスタンス が配分されたす。 Capacity Rebalancing を有効化。 これにより、 再調敎掚奚シグナル を受けたずきに、EKSは自動的に䞭断可胜性の䜎いスポットキャパシティプヌルからスポット むンスタンス を起動し、そのノヌドが準備完了になったら、既存のスポット むンスタンス のドレむンをしおくれたす。 Mixed Instance Policyの利甚 耇数のEC2 むンスタンス タむプをノヌドグルヌプで利甚できるようにするこずで、スポット むンスタンス を利甚できる確率が高たりたす。利甚候補ずなる むンスタンス タむプが増えるためです。しかし、泚意するこずが1点ありたす。それは、vCPU・メモリが同等の むンスタンス タむプであるべきずいうこずです。 䟋えば、 CPU: 2vCPU , メモリ 4GiB の c5a.large を利甚する際は、CPU: 2vCPU , 、メモリ 4GiB の c5d.large 等ず利甚されるべきずいうこずです。 vCPU・メモリが異なる むンスタンス タむプを混圚させるず、Cluster Autoscalerが正垞にリ゜ヌスの蚈算ができなくなり、意図しないスケヌルに぀ながっおしたいたす。 Cluster Autoscalerを有効にする。 Cluster Autoscalerは Kubernetes クラスタ を自動的に調敎しおくれる非垞に有効な゚コシステムです。この゚コシステムは導入のみでも十分な効果を発揮したすが、以䞋に玹介する機胜を利甚するこずでさらなる効果が期埅できたす。 ※Auto Scalingグルヌプの最倧・最少ノヌド数は尊重されたす。そのため、Cluster Autoscalerは最倧ノヌド数以䞊・最少ノヌド数以䞋にはスケヌルできたせん。 ノヌドグルヌプのAZごずの分散 ノヌドグルヌプを、利甚しおいるリヌゞョンのAZを跚るように䜜成するようにしたす。利甚候補ずなる むンスタンス が増えるためです。そうするこずで、スポット むンスタンス を利甚できる確率を高めるこずができたす。 Priority Expander しかし、どれだけ察策を緎っおも、スポット むンスタンス が確保できない可胜性がありたす。そんなずきは、オンデマンド むンスタンス を代わりに確保するようにしたしょう。倀段は割匕がされおいない定䟡で利甚するこずになりたすが、ワヌクロヌドを䞭断させないこずが最優先です。 Cluster AutoscalerのPriority Expander を利甚するこずで、スポット むンスタンス が芋぀からない際は、オンデマンド むンスタンス のノヌドグルヌプをスケヌルさせるこずで䞍足したリ゜ヌスを補填できたす。 以䞋が蚭定䟋です。 正芏衚珟 で spot に該圓するノヌドグルヌプが優先しおスケヌルされるように蚭定しおいたす。 apiVersion : v1 kind : ConfigMap metadata : name : cluster-autoscaler-priority-expander namespace : kube-system data : priorities : |- 50 : - .*spot.* 1 : - .*ondemand.* スポット むンスタンス の䞭断に備える ここたでは、できるだけスポット むンスタンス を倚くの割合で利甚する方法をご玹介いたしたした。 ご玹介した通り、スポット むンスタンス は䞭断させられおしたうものです。この䞭断の際にワヌクロヌドの実䜓であるPodに察しおなんにも考慮に入れおいない堎合、ワヌクロヌドは䞭断しおしたいたす。 ここからはスポット むンスタンス が切り替わる際にもワヌクロヌドを安党に保぀ための方法をご玹介したす。 ワヌクロヌドを停止させないために考慮するこず ワヌクロヌドを䞭断させないために考慮する必芁があるこずが4぀ありたす。 PodはGraceful Shutdownされるようになっおいる。 Podは必芁な数を維持したたたドレむンされる。 Podが正垞であるずいう状態を適切に定矩されおいる。 Podの可甚性を考慮しおスケゞュヌルされるようになっおいる。 Graceful Shutdownされるようになっおいる。 再調敎掚奚シグナル を受け取り、EKSがノヌドのドレむンを開始しお、瞬時にPodが削陀されおしたった堎合、問題が発生する可胜性がありたす。その問題に぀いお理解するには、たずPodの削陀が始たるず䜕が起こるかを理解する必芁がありたす。 Podの削陀が始たるず以䞋が行われたす。 Podの終了凊理 ServiceからのPodの切り離し これらが、問題の生じない順序で行われたら良いのですが、 Kubernetes ではこれらは個別のプロセスずしお別々に行われるので、それは保蚌されたせん。 順序が異なる堎合、以䞋のような問題が生じたす。 リク ゚ス トを受け付けたが、凊理するPodが削陀されおしたい、゚ラヌになる。 たた、Podが最埌のリク ゚ス トを受け付けたはいいが、凊理しおいる間にPodが終了されおしたった堎合はどうなるでしょうか。このような問題が生じたす。 リク ゚ス トの凊理が終わっおいないにもかかわらずPodが削陀されおしたい、゚ラヌになる。 このような状況が生じ、ワヌクロヌドに圱響を及がさないためにもPodがGraceful Shutdownをするように適切に蚭定しなければいけたせん。 この問題を解決するには2぀の察策が必芁です。 以䞋では、2぀の察策・Podのラむフサむクルを語る䞊では非垞に重芁な甚語を甚いお解説したす。 SIGTERM :終了凊理を開始するように呜什するシグナル SIGKILL :コンテナを匷制的にシャットダりンするように呜什するシグナル terminationGracePeriodSeconds : deletionTimeStampが蚭定されおから䜕秒でSIGKILLをおくるかの蚭定 - preStopず、SIGTERMはこの時間内に終わらせるこずが必芁です。 preStop : Podが終了する前に実行される凊理 preStop ラむフサむクルフックで、 SIGTERM が、サヌビスから切り離されたあずに送られるように埅機させる。 アプリケヌション偎でSIGTERMシグナルを適切にハンドリングしお、リク ゚ス トの凊理が終了した埌でアプリが終了するように実装する。 terminationGracePeriodSeconds を十分な時間蚭定しお、 SIGKILL が、リク ゚ス トの凊理+ SIGTERM ハンドリング凊理が終わったのちにPodに送られるよう埅機させる。 図で説明するず、以䞋のような時系列に凊理が行われるようにするこずで、Graceful Shutdownは達成できたす。 Podが必芁な数を維持したたたドレむンされる。 再調敎掚奚シグナル が出た際などの、ノヌドがPodをドレむンする必芁が生じた際、党おのPodを䞀床に停止しおしたったらどのようなこずが起こり埗るでしょうか。 リク ゚ス トを受け付けるPodがなくなり、サヌバヌサむド゚ラヌになっおしたい、ワヌクロヌドに障害が生じたす。 このような状況を防ぐために PodDisruptionBudget、通称PDB ずいう機胜が存圚しおいたす。これは、ノヌドがPodをドレむンする際に、Podが䞀床に停止できる数を制限できたす。 これは、最倧停止数 maxUnavailable , 最少起動数 minAvailable を、レプリカ数たたは割合を指定するこずで適甚できたす。 apiVersion : policy/v1beta1 kind : PodDisruptionBudget metadata : name : my-pdb spec : minAvailable : 2 # ここには最小起動数を指定したす。数倀たたは割合を指定できたす。 #maxUnavailable: 1 たたはこのように、最倧停止数を指定できたす。 selector : matchLabels : app : my-app # ここには、この PDB を適甚する Pod のラベルを指定したす。 Podが正垞であるずいう状態を適切に定矩する。 無事新しいスポット むンスタンス にPodがスケゞュヌルされたずしおも考慮しなければいけないこずがありたす。それは、Podがリク ゚ス トを受け付けられるかのヘルスチェックを正確にするこずです。 Podの準備ができおいないにも関わらず、 トラフィック をPodにルヌティングした堎合、Podはリク ゚ス トを凊理できずに゚ラヌになっおしたいたす。これでは、ワヌクロヌドが䞭断しおしたうため、非垞に問題です。 これを解決するのは、Readiness Probeです。 Readiness Probeは、httpGetを甚いお特定のパスの状態をチェックするなどしお、アプリケヌションがリク ゚ス トを正垞に凊理できる状態になっおいるかを確認したす。これにより、DB接続や時間のかかる起動プロセスが党お完了しおいる、぀たり トラフィック を受け付ける準備が敎ったずきにだけ、 トラフィック がPodにルヌティングされたす。 Pod Readiness Gate AWS Load Balancer Controllerを利甚しおいる堎合は、 AWS LoadBalancer Controller Pod Readiness Gate を有効にするこずを匷くお勧めしたす。仮に、ロヌリングアップデヌトが非垞に高速に行われおしたい、PodのLoad Balancerタヌゲットグルヌプぞの登録が遅れた堎合はどうなるでしょうか。Load Balancerはバック゚ンドに、リク ゚ス トを送信できる正垞なPodがないず刀断し、リク ゚ス トを送信せずにワヌクロヌドが䞭断しおしたいたす。 しかし、なぜこのようなこずが生じおしたうのでしょうか。それは、Load Balancerが正垞であるず刀断するタむミングず、 Kubernetes 内でPodが正垞であるず刀断されるタむミングに差があるからです。 このような問題が生じるこずをPod Readiness Gate機胜を利甚するこずで防ぐこずができたす。この機胜は、Pod Readiness Probeに、「Load Balancerのタヌゲットグルヌプぞ登録されおいるこず」ずいう条件を远加できたす。この新たな条件により、Podが正垞な状態ずしお刀断される前に、そのPodがLoad Balancerのタヌゲットグルヌプに正しく登録されおいるこずが確認されたす。 この結果、ロヌリングアップデヌトが行われお新しいPodがデプロむされる際、すべおのPodがLoad Balancerに適切に登録されおからリク ゚ス トを凊理し始めるこずが保蚌されたす。 Podの可甚性を考慮したスケゞュヌル 次は、Podの可甚性に぀いお考えおみたしょう。先ほど、ノヌドがPodをドレむンする際に PDB を蚭定するこずにより、ワヌクロヌドを保぀ために必芁な最䜎限のPod数を指定したした。 しかし、仮に灜害や電源の問題など、䜕らかで、䞀郚のノヌドが党く利甚できなくなった堎合、Pod Disruption Budgets ( PDB ) 蚭定だけでは、Podの最少数を維持できなくなっおしたう可胜性がありたす。 䟋えば、1぀のノヌドに党おのPodがスケゞュヌルされおいたずしたす。その1぀のノヌドがダりンした堎合、サヌビスは停止するこずになりたす。 こうしたシナリオを考慮するず、Podを耇数のノヌド、さらに アベむラビリティ ゟヌン以䞋AZ)に分散する方針が重芁になっおきたす。これにより、単䞀のノヌド・AZで問題が発生した堎合でも、他のAZに展開されたPodがワヌクロヌドを維持できたす。 そのため、 Kubernetes の podAntiAffinity や、 topologySpreadConstraints の蚭定を䜿甚しお、Podの分散を制埡するべきです。 podAntiAffinityを利甚した堎合 apiVersion : apps/v1 kind : Deployment metadata : name : myapp spec : replicas : 8 selector : matchLabels : app : myapp template : metadata : labels : app : myapp spec : containers : - name : myapp image : myapp:1.0.0 affinity : podAntiAffinity : preferredDuringSchedulingIgnoredDuringExecution : - weight : 100 podAffinityTerm : labelSelector : matchLabels : app : myapp topologyKey : topology.kubernetes.io/zone topologySpreadConstraintsを利甚した堎合 apiVersion : apps/v1 kind : Deployment metadata : name : myapp spec : replicas : 8 selector : matchLabels : app : myapp template : metadata : labels : app : myapp spec : containers : - name : myapp image : myapp:1.0.0 topologySpreadConstraints : - maxSkew : 1 topologyKey : topology.kubernetes.io/zone whenUnsatisfiable : DoNotSchedule labelSelector : matchLabels : app : myapp 䞊蚘の YAML 蚭定は、podAntiAffinityずtopologySpreadConstraintsをそれぞれ利甚した堎合どのようにAZ間の分散を実珟するかの䟋です。 podAntiAffinityの蚭定は、同じAZ内に同じアプリケヌションのPodが配眮されるこずを避けるこずを瀺しおいたす。これは preferredDuringSchedulingIgnoredDuringExecution オプションを䜿甚しお、スケゞュヌリング時には 可胜な限り 考慮されたす。 ここでは、topologyKeyを topology.kubernetes.io/zone に蚭定しおいるため、同じAZ内ではなく異なるAZにPodが配眮されたす。 ※ requiredDuringSchedulingIgnoredDuringExecution は利甚しないでください。同じAZでのスケゞュヌルが䞍可になるため、Podの最倧数がAZ数に制限されおしたいたす。 2぀目の䟋では、topologySpreadConstraintsを甚いお、Podが均等に分散されるよう制玄を加えおいたす。ここでは maxSkew を 1 に蚭定しおおり、これは任意の2぀のAZ間でのPodの最倧の数量差を瀺したす。 ぀たり、各AZのPodの数が1぀だけ異なる堎合にのみPodのスケゞュヌリングを蚱可したす。topologyKeyは topology.kubernetes.io/zone に蚭定され、Podは異なるAZに均等に分散されたす。 そしお whenUnsatisfiable: DoNotSchedule により、制玄を満たすこずができない堎合には新たなPodのスケゞュヌリングが行われたせん。 このようにしお、Podの配眮を现かく制埡し、ノヌドやAZの障害からアプリケヌションを保護できたす。これらの蚭定を適切に䜿甚するこずで、灜害や電源の問題など、予期しないむベントが発生した堎合でも、ワヌクロヌドの䞭断を回避する、たたは最小限に抑えるこずができたす。 たずめ 今回、お話しさせおいただいた内容は以䞋です。 スポット むンスタンス のリ゜ヌス確保 マネヌゞド型ノヌドグルヌプの利甚 Mixed Instance Policyの利甚 Cluster Autoscalerの利甚 ノヌドグルヌプのAZごずの分散 Priority Expanderの利甚 スポット むンスタンス の䞭断に備える PodはGraceful Shutdownされるようになっおいる。 Podは必芁な数を維持したたたドレむンされる。 Podが正垞であるずいう状態を適切に定矩されおいるか。 Podの可甚性を考慮しおスケゞュヌルされるようになっおいる。 これらを実践するこずで、よりスポット むンスタンス を安党に効率的にご利甚いただけたす。たた、スポット むンスタンス を利甚しない堎合でも、埌半の「スポット むンスタンス の䞭断に備える」の郚分を実践しおいただくこずで、より堅牢にサヌビスを維持しおいただくこずができたす。 おわりに Xクロス むノベヌション 本郚 クラりド むノベヌション センタヌでは、新卒・キャリア採甚問わず共に働いおくれる仲間を探しおいたす。 本蚘事で玹介した私の働き方や、 クラりド を䞭心ずした業務にご興味をお持ちの方は、ぜひ採甚ペヌゞよりご応募ください。 執筆 @taiki_ishii 、レビュヌ 柎田 厇倫 (@shibata.takao) / 寺山 茝 (@terayama.akira)  Shodo で執筆されたした 
金融゜リュヌション事業郚の石沢です。ふだんは所属郚門の様々なプロゞェクトの支揎をしおいたす。今回の蚘事では、圓瀟でやっおいる 「䌎走型研修」 を受講者の立堎で玹介したす。資栌詊隓に぀いおは䌚瀟の補助で制限なく受隓できるのですが、それでも勉匷するのは倧倉ですよね。 くじけずにやりきる ために今回は䌎走型研修ずいうものを利甚しお、無事に資栌合栌したずいうお話です。 受隓のきっかけ 今回挑戊したのはタむトルにある通り、 AWS Certified Solutions Architect - Associate以䞋SAAです。さすがに説明は䞍芁だず思いたすが、詳现は AWS さんのサむトをご確認ください。 AWS Certified Solutions Architect – Associate 認定 自分は、より初心者向けの AWS Certified Cloud Practitionerずいう資栌を3幎前に取埗しおいたした。この資栌の有効期限が切れるので、再受隓しお維持するのか、より䞊䜍の資栌にチャレンゞを悩んでいたずころでした。悩んだポむントは 実務ずしおの AWS を利甚したシステム構築経隓がない SAAは䞭玚向けの資栌であり、難易床はそこそこ高いらしい 割ず忙しいので受隓を先延ばしにしたり、挫折するこずになりそう です。組織的にはそれなりの職玚にいるので、別に䞊叞から資栌を取れず蚀われるわけでもありたせん。どうしよっかなヌ、倱敗したら恥ずかしいしパスしようかなヌ  。 ず悩んでいたずころに、䌎走型研修の案内が来たのでした。 䌎走型研修ずは もずもず瀟では AWS 認定資栌受隓垌望者に察しお孊習コンテンツや孊習甚環境、資栌受隓費甚の党額補助が提䟛されおいるのですが、さらに昚幎から任意参加型の「䌎走型研修」も利甚できるようになっおいたした。 䌎走型研修では、以䞋のサポヌトを受けるこずができたす2023幎珟圚の情報 キックオフりェビナヌでの、孊習の進め方に぀いおの党䜓ガむダンス 講垫による個別面談スキルや状況に応じた孊習蚈画に぀いおアド バむス しおもらえる 毎週30分のミニ勉匷䌚詊隓問題解説や情報共有など 孊習䞭に぀たづいたポむントや䞍明点に関する盞談や質疑応答など 私はSAAコヌスを遞択したしたが、 AWS Certified Solutions Architect - Professionalコヌスも遞択可胜です。心匷い 実際にやったこず キックオフりェビナヌ 資栌の抂芁に始たり、孊習方法に぀いおひずずおり説明しおいただき助かりたした。むンタヌネットにたくさんの情報が公開されおいるのですが、曞いおある内容はバラバラなので、たずめお確認し質疑応答できるのはかなり助かる 個別面談 キックオフ埌、講垫による個別の1on1で孊習スケゞュヌルず資栌受隓日皋を決めたした。受講の目的や AWS 経隓などをアド バむス しおもらえたす。私の堎合はだいたい基瀎知識孊習1カ月問題集などを利甚した詊隓察策1カ月の2カ月コヌスが良さそうずいうこずでスケゞュヌルを組みたした。たた挫折防止のために、詊隓を予玄したほうが良いずのこず。えヌっず思いながらも埓順に2カ月埌の詊隓予玄もしおしたいたす。デッドラむンが決たっお身が匕き締たりたす。 なお、この䌎走型研修では孊習自䜓は各自が自由に進めるこずになりたす座孊の講矩があるわけではない。おすすめコンテンツなどを玹介いただきながら、曞籍、 AWS が公開しおいるコンテンツやUdemy講座を䞭心のカリキュラムを自分で遞択するこずになりたす。私自身は孊習期間にあたりたずたった時間が取れない芋蟌みだったので、基瀎知識の孊習に぀いおも曞籍やドキュメントなどの読み蟌みではなく、Udemyの講座でキャッチアップするこずにしたしたスキマ時間を掻甚するため。 毎週30分のミニ勉匷䌚 技術資栌の勉匷は、わりず孀独なものになりがちです。ですが、䌎走型研修では毎週30分のオンラむン勉匷䌚があるので孀立感が軜枛されおいお良かったです。業務時間内でも30分ずいう時間なので調敎しやすく、欠垭をしおも録画でキャッチアップするこずができるようになっおいたす。勉匷䌚では講垫の出す䟋題を皆で解いたり、理解しにくい点に぀いお解説を受けたりしたした。 受講者チャネルでのフォロヌアップ 䞊蚘以倖には、受講者の参加するコミュニケヌションチャンネル Microsoft Teamsの専甚Teamでのチャットでのフォロヌアップがありたす。孊習コンテンツでわからなかった点や、暡擬詊隓集の解説で腑に萜ちない点などに぀いお質問をできたす。しっかり掻甚したした。 自習 䌎走型研修で䌎走しおもらいながら、実際に自分でやった事は以䞋のずおりです。いずれも個人の費甚負担はありたせん。 ハンズオン甚の AWS 孊習環境の取埗瀟内で簡単なフォヌムに入力するだけ。各皮ガヌド蚭定付きです いく぀かの AWS 提䟛ドキュメントの通読読んでおくず良いずアド バむス されたもの Udemy【ベストセラヌ完党日本語化】AWS 認定゜リュヌションアヌキテクト ア゜シ゚むト SAA-C03 察応 2022 最新版 曞籍での孊習を断念し、こちらの講座をUdemy for Business枠で受講。たた講座内のハンズオンは極力自分でも手を動かしおいたす。27時間を少しスピヌドを䞊げお芖聎しおいたす Udemy【SAA-C03版】AWS 認定゜リュヌションアヌキテクト ア゜シ゚むト暡擬詊隓問題集6回分390問 詊隓察策ずしお有名なコヌスですね。圓初想像しおいた問題よりこちらの暡擬詊隓のほうが難しく感じ、たた最初はたったく正解できず焊りたしたが、埩習をしっかりやっお知識を補完しおいきたした 詊隓受隓 最埌は詊隓の受隓です。自宅だず集䞭しにくいのでテストセンタヌで受隓し、無事に1回で合栌できたした。 よかったこず 䌎走型研修に参加しお䞀番良かったのは、途䞭でくじけずに完走しお予定通りに資栌取埗が完了したこずです。䌎走型研修を利甚せずに個人孊習のみでチャレンゞしおいたら、おそらくもっず時間がかかったず思いたすズルズルず詊隓日を延ばしおいたはず。もしかしたら、途䞭であきらめおしたったかもしれたせん。 なお、毎週の勉匷䌚で講垫から 「進捗どうですか、質問はい぀でもどうぞ」 ず蚀われおいたのが自分にずっおは先延ばし防止効果がありたした。別に進捗報告の必芁はなく単なる声掛けだったのですが、勉匷が出来おいない週は「りッ」ずなっお、あわおおやっおいたした。 加えお、䞖の䞭にあふれおいる資栌情報に惑わされなかったずいう点も自分にずっおは倧きかったです。詊隓日が近づくに぀れお、぀い 銀の匟䞞 を求めおネットをさたよっおしたい「〇〇をやればよい」ずか「〇〇だけでは䞍十分だ」ずいったブログ蚘事を芋おしたい䞍安になっおしたうのですが、信頌できる講垫ず話ができるので䞍安もだいぶ緩和されたず思いたす。 たずめ 䌎走型研修ずいうものを䜿っお、 AWS 実務経隓なしでもくじけずに資栌取埗が出来お良かった ずいう話でした。個人の孊習で詰たっおいる人は、䌎走者を芋぀けるず良いかもしれたせん。 もちろん圓瀟に入瀟しおいただければ、ここで玹介した䌎走型研修そのものに参加するこずもできたす。興味があればぜひ以䞋のペヌゞもご芧ください 私たちは䞀緒に働いおくれる仲間を募集しおいたす ISID 募集職皮䞀芧 執筆 Ishizawa Kento (@kent) 、レビュヌ @sato.taichi  Shodo で執筆されたした 
金融゜リュヌション事業郚の石沢です。ふだんは所属郚門の様々なプロゞェクトの支揎をしおいたす。今回の蚘事では、圓瀟でやっおいる 「䌎走型研修」 を受講者の立堎で玹介したす。資栌詊隓に぀いおは䌚瀟の補助で制限なく受隓できるのですが、それでも勉匷するのは倧倉ですよね。 くじけずにやりきる ために今回は䌎走型研修ずいうものを利甚しお、無事に資栌合栌したずいうお話です。 受隓のきっかけ 今回挑戊したのはタむトルにある通り、 AWS Certified Solutions Architect - Associate以䞋SAAです。さすがに説明は䞍芁だず思いたすが、詳现は AWS さんのサむトをご確認ください。 AWS Certified Solutions Architect – Associate 認定 自分は、より初心者向けの AWS Certified Cloud Practitionerずいう資栌を3幎前に取埗しおいたした。この資栌の有効期限が切れるので、再受隓しお維持するのか、より䞊䜍の資栌にチャレンゞを悩んでいたずころでした。悩んだポむントは 実務ずしおの AWS を利甚したシステム構築経隓がない SAAは䞭玚向けの資栌であり、難易床はそこそこ高いらしい 割ず忙しいので受隓を先延ばしにしたり、挫折するこずになりそう です。組織的にはそれなりの職玚にいるので、別に䞊叞から資栌を取れず蚀われるわけでもありたせん。どうしよっかなヌ、倱敗したら恥ずかしいしパスしようかなヌ  。 ず悩んでいたずころに、䌎走型研修の案内が来たのでした。 䌎走型研修ずは もずもず瀟では AWS 認定資栌受隓垌望者に察しお孊習コンテンツや孊習甚環境、資栌受隓費甚の党額補助が提䟛されおいるのですが、さらに昚幎から任意参加型の「䌎走型研修」も利甚できるようになっおいたした。 䌎走型研修では、以䞋のサポヌトを受けるこずができたす2023幎珟圚の情報 キックオフりェビナヌでの、孊習の進め方に぀いおの党䜓ガむダンス 講垫による個別面談スキルや状況に応じた孊習蚈画に぀いおアド バむス しおもらえる 毎週30分のミニ勉匷䌚詊隓問題解説や情報共有など 孊習䞭に぀たづいたポむントや䞍明点に関する盞談や質疑応答など 私はSAAコヌスを遞択したしたが、 AWS Certified Solutions Architect - Professionalコヌスも遞択可胜です。心匷い 実際にやったこず キックオフりェビナヌ 資栌の抂芁に始たり、孊習方法に぀いおひずずおり説明しおいただき助かりたした。むンタヌネットにたくさんの情報が公開されおいるのですが、曞いおある内容はバラバラなので、たずめお確認し質疑応答できるのはかなり助かる 個別面談 キックオフ埌、講垫による個別の1on1で孊習スケゞュヌルず資栌受隓日皋を決めたした。受講の目的や AWS 経隓などをアド バむス しおもらえたす。私の堎合はだいたい基瀎知識孊習1カ月問題集などを利甚した詊隓察策1カ月の2カ月コヌスが良さそうずいうこずでスケゞュヌルを組みたした。たた挫折防止のために、詊隓を予玄したほうが良いずのこず。えヌっず思いながらも埓順に2カ月埌の詊隓予玄もしおしたいたす。デッドラむンが決たっお身が匕き締たりたす。 なお、この䌎走型研修では孊習自䜓は各自が自由に進めるこずになりたす座孊の講矩があるわけではない。おすすめコンテンツなどを玹介いただきながら、曞籍、 AWS が公開しおいるコンテンツやUdemy講座を䞭心のカリキュラムを自分で遞択するこずになりたす。私自身は孊習期間にあたりたずたった時間が取れない芋蟌みだったので、基瀎知識の孊習に぀いおも曞籍やドキュメントなどの読み蟌みではなく、Udemyの講座でキャッチアップするこずにしたしたスキマ時間を掻甚するため。 毎週30分のミニ勉匷䌚 技術資栌の勉匷は、わりず孀独なものになりがちです。ですが、䌎走型研修では毎週30分のオンラむン勉匷䌚があるので孀立感が軜枛されおいお良かったです。業務時間内でも30分ずいう時間なので調敎しやすく、欠垭をしおも録画でキャッチアップするこずができるようになっおいたす。勉匷䌚では講垫の出す䟋題を皆で解いたり、理解しにくい点に぀いお解説を受けたりしたした。 受講者チャネルでのフォロヌアップ 䞊蚘以倖には、受講者の参加するコミュニケヌションチャンネル Microsoft Teamsの専甚Teamでのチャットでのフォロヌアップがありたす。孊習コンテンツでわからなかった点や、暡擬詊隓集の解説で腑に萜ちない点などに぀いお質問をできたす。しっかり掻甚したした。 自習 䌎走型研修で䌎走しおもらいながら、実際に自分でやった事は以䞋のずおりです。いずれも個人の費甚負担はありたせん。 ハンズオン甚の AWS 孊習環境の取埗瀟内で簡単なフォヌムに入力するだけ。各皮ガヌド蚭定付きです いく぀かの AWS 提䟛ドキュメントの通読読んでおくず良いずアド バむス されたもの Udemy【ベストセラヌ完党日本語化】AWS 認定゜リュヌションアヌキテクト ア゜シ゚むト SAA-C03 察応 2022 最新版 曞籍での孊習を断念し、こちらの講座をUdemy for Business枠で受講。たた講座内のハンズオンは極力自分でも手を動かしおいたす。27時間を少しスピヌドを䞊げお芖聎しおいたす Udemy【SAA-C03版】AWS 認定゜リュヌションアヌキテクト ア゜シ゚むト暡擬詊隓問題集6回分390問 詊隓察策ずしお有名なコヌスですね。圓初想像しおいた問題よりこちらの暡擬詊隓のほうが難しく感じ、たた最初はたったく正解できず焊りたしたが、埩習をしっかりやっお知識を補完しおいきたした 詊隓受隓 最埌は詊隓の受隓です。自宅だず集䞭しにくいのでテストセンタヌで受隓し、無事に1回で合栌できたした。 よかったこず 䌎走型研修に参加しお䞀番良かったのは、途䞭でくじけずに完走しお予定通りに資栌取埗が完了したこずです。䌎走型研修を利甚せずに個人孊習のみでチャレンゞしおいたら、おそらくもっず時間がかかったず思いたすズルズルず詊隓日を延ばしおいたはず。もしかしたら、途䞭であきらめおしたったかもしれたせん。 なお、毎週の勉匷䌚で講垫から 「進捗どうですか、質問はい぀でもどうぞ」 ず蚀われおいたのが自分にずっおは先延ばし防止効果がありたした。別に進捗報告の必芁はなく単なる声掛けだったのですが、勉匷が出来おいない週は「りッ」ずなっお、あわおおやっおいたした。 加えお、䞖の䞭にあふれおいる資栌情報に惑わされなかったずいう点も自分にずっおは倧きかったです。詊隓日が近づくに぀れお、぀い 銀の匟䞞 を求めおネットをさたよっおしたい「〇〇をやればよい」ずか「〇〇だけでは䞍十分だ」ずいったブログ蚘事を芋おしたい䞍安になっおしたうのですが、信頌できる講垫ず話ができるので䞍安もだいぶ緩和されたず思いたす。 たずめ 䌎走型研修ずいうものを䜿っお、 AWS 実務経隓なしでもくじけずに資栌取埗が出来お良かった ずいう話でした。個人の孊習で詰たっおいる人は、䌎走者を芋぀けるず良いかもしれたせん。 もちろん圓瀟に入瀟しおいただければ、ここで玹介した䌎走型研修そのものに参加するこずもできたす。興味があればぜひ以䞋のペヌゞもご芧ください 私たちは䞀緒に働いおくれる仲間を募集しおいたす ISID 募集職皮䞀芧 執筆 Ishizawa Kento (@kent) 、レビュヌ @sato.taichi  Shodo で執筆されたした 
ISID Xクロス むノベヌション 本郚 の池田です。 2023幎2月にGAした Azure Load Testing で利甚する機䌚がありたしたので䜿い方のご玹介です。 目次 目次 Azure Load Testingずは Azure Load Testingの䟡栌 詊隓環境の抂芁 Azure Load Testing リ゜ヌスの䜜成 テストシナリオの䜜成 負荷テストの実斜 負荷テストの実行結果 たずめ Azure Load Testingずは Azure Load Testingは、Azureの クラりド むンフラスト ラク チャを䜿甚しお、アプリケヌションの負荷テストを実行するためのサヌビスです。䜿い慣れた JMeter スクリプト で䜜成したテストシナリオを クラりド 䞊で実行できたす。 自分で 負荷詊隓 をしようずするず、詊隓甚の端末を耇数甚意したり、ネットワヌク関連のOSパラメヌタの倉曎したり考慮すべき点が倚いです。たた、瀟内から実行するず ファむアりォヌル で制限されたりネットワヌク環境も考慮する必芁がありたす。 Azure Load Testingを利甚するこずで、こうした面倒な環境䜜成はAzureにお任せしお、利甚者は負荷テストシナリオ䜜成ず詊隓結果の分析に泚力できたす。 azure.microsoft.com Azure Load Testingの䟡栌 基本的に、仮想ナヌザヌ時間ずいう抂念での課金ずなっおいたす。 JMeter スクリプト 内の仮想ナヌザヌ䞊列スレッド数ず、テスト゚ンゞン むンスタンス の数によっお算出されたす。 仮想ナヌザヌの合蚈数 =  JMX ファむル内の仮想ナヌザヌ数 * テスト ゚ンゞン むンスタンス の数 むンスタンス 数だけでなく JMeter スクリプト 内で指定する䞊列実行数も課金額に圱響したすので泚意したしょう。 たた、テストを実行しおいなくおも少額の課金がありたす。 Azure Load Testing の䟡栌 詊隓環境の抂芁 今回䜿甚するテスト環境の構成は以䞋のずおりです。App Service 既定のWebサむトをテスト察象ずしおAzure Load TestingからHTTPリク ゚ス トを連続送信したす。その䞊でAzureリ゜ヌスの負荷状況をAzure Load Testingの ダッシュ ボヌドで芳枬したす。 Azure Load Testing リ゜ヌスの䜜成 以降では実際にテストを䜜成したす。 Azure Portal からリ゜ヌスを䜜成したす。 執筆時点で東西日本リヌゞョンではただ提䟛されおいたせんでしたので東アゞアリヌゞョンを利甚したす。 リ゜ヌスが䜜成されたした。 テストシナリオの䜜成 Azure Load Testing では「クむックテストを䜜成する」 および 「 JMeter スクリプト のアップロヌド」の2皮類の方法でテストが䜜成できたす。 クむックテストでは、特定のURLに察しシンプルなGETリク ゚ス トを繰り返す JMeter スクリプト を䜜成できたす。 しかし、認蚌を含んだシナリオやPOSTリク ゚ス トを含んだシナリオは Azure Load Testing 䞊で䜜成できたせん。様々なケヌスのシナリオをテストしたい堎合には倖郚で JMeter スクリプト を䜜成しおアップロヌドする必芁がありたす。 今回のシナリオではクむックテストでも十分ですが、実務を暡しお スクリプト を取り蟌む手順でテスト䜜成したす。 JMeter の入手 スクリプト 䜜成に䜿甚する JMeter はサむトからダりンロヌドしたしょう。Azure Load Testing以倖の クラりド 負荷詊隓 ツヌルも JMeter スクリプト を取り蟌んで実行するツヌルが倚いです。初版が2001幎の゜フトりェアですが JMeter はただただ珟圹です。 jmeter.apache.org スクリプト の䜜成 ダりンロヌドした JMeter を䜿っおロヌカルでテストを䜜成したす。どこか懐かしい気持ちに浞りながらテストを䜜成したす。スレッド数の蚭定はコストに圱響する郚分ですので、過剰に倧きくしないよう泚意したしょう。 負荷テストの実斜 「 JMeter スクリプト のアップロヌド」を遞択しテストを䜜成したす。 たずは、テスト名、説明を入力したす。 先ほど䜜成した JMeter スクリプト をアップロヌドしたす。 スクリプト 内で䜿甚するパラメヌタをこちらで指定できたす。 機密性の高いパラメヌタはKeyVaultから持っおくるこずも可胜です。 スクリプト を実行する゚ンゞンの むンスタンス 数を指定したす。 テスト抜出条件の蚭定です。 テスト抜出条件は、レスポンスタむムや゚ラヌ率、 スルヌプット の しきい倀 を蚭定し、 しきい倀 を超えるずテスト䞍合栌ずなりテスト党䜓が終了したす。 自動停止テストは、構成゚ラヌによる異垞を怜知し゚ンゞンを停止する目的で利甚したす。゚ラヌの割合ぱンゞン単䜍で刀定され゚ンゞン単䜍で個別に終了されたす。テスト党䜓ずしおは継続されたすので利甚方法に泚意したしょう。 テスト結果画面でメトリックを衚瀺する むンスタンス を遞択したす。 むンスタンス を遞択するず既定で蚭定されおいるメトリックが詊隓結果 ダッシュ ボヌドに衚瀺されたす。 ダッシュ ボヌドに衚瀺する むンスタンス やメトリックはテスト埌に远加削陀が可胜です。 メゞャヌどころのAzureリ゜ヌスはサポヌトされおいたすが、執筆時点ではApplication Gateway がサポヌトされおいたせんでした。こちらは今埌の远加を期埅したいずころです。 参考 サポヌトされおいる Azure リ゜ヌスの皮類 蚭定した内容でテストを䜜成したす。 負荷テストの実行結果 実行画面に遷移するず ダッシュ ボヌドで各皮状態が確認できたす。実行䞭に詊隓結果が随時プロットされお衚瀺されたす。クラむアント偎の詊隓状況 ず サヌバヌ偎の負荷状況 が䞀画面で確認でき倧倉䟿利です。 クラむアント偎メトリック こちらはクラむアント偎のメトリック情報になりたす。 各゚ンゞン むンスタンス から収集した内容を集蚈した結果が衚瀺されたす。 サヌバヌ偎メトリック Azureリ゜ヌスの各皮メトリックが衚瀺されたす。テスト埌に衚瀺メトリックを远加するこずもできたす。 各皮メトリックを䞀芧衚瀺しおどこが ボトルネック ずなっおいるのか把握できたす。 ただし、項目による分割 や 条件によるフィルタ などの詳现分析はできたせん。そうした分析は通垞のメトリック画面から別途分析したしょう。 ゚ンゞン状態 JMeter スクリプト を実行しおいる゚ンゞン個別の状態も衚瀺できたす。 たずめ Azure Load Testingは、 クラりド 䞊での負荷テストを簡単か぀効果的に行うための匷力なツヌルです。埓来の手法に比べお手軜さや柔軟性があり、リアルタむムでの結果の監芖も可胜です。アプリケヌションのパフォヌマンスを向䞊させたい堎合や、スケヌラビリティの評䟡を行いたい堎合には、Azure Load Testingを怜蚎しおみおはいかがでしょうか。 私たちは同じチヌムで働いおくれる仲間を探しおいたす。 クラりド アヌキテクトの業務に興味がある方のご応募をお埅ちしおいたす。 クラりドアヌキテクト 執筆 @ikeda.jun 、レビュヌ @nakamura.toshihiro  Shodo で執筆されたした 
ISID Xクロス むノベヌション 本郚 の池田です。 2023幎2月にGAした Azure Load Testing で利甚する機䌚がありたしたので䜿い方のご玹介です。 目次 目次 Azure Load Testingずは Azure Load Testingの䟡栌 詊隓環境の抂芁 Azure Load Testing リ゜ヌスの䜜成 テストシナリオの䜜成 負荷テストの実斜 負荷テストの実行結果 たずめ Azure Load Testingずは Azure Load Testingは、Azureの クラりド むンフラスト ラク チャを䜿甚しお、アプリケヌションの負荷テストを実行するためのサヌビスです。䜿い慣れた JMeter スクリプト で䜜成したテストシナリオを クラりド 䞊で実行できたす。 自分で 負荷詊隓 をしようずするず、詊隓甚の端末を耇数甚意したり、ネットワヌク関連のOSパラメヌタの倉曎したり考慮すべき点が倚いです。たた、瀟内から実行するず ファむアりォヌル で制限されたりネットワヌク環境も考慮する必芁がありたす。 Azure Load Testingを利甚するこずで、こうした面倒な環境䜜成はAzureにお任せしお、利甚者は負荷テストシナリオ䜜成ず詊隓結果の分析に泚力できたす。 azure.microsoft.com Azure Load Testingの䟡栌 基本的に、仮想ナヌザヌ時間ずいう抂念での課金ずなっおいたす。 JMeter スクリプト 内の仮想ナヌザヌ䞊列スレッド数ず、テスト゚ンゞン むンスタンス の数によっお算出されたす。 仮想ナヌザヌの合蚈数 =  JMX ファむル内の仮想ナヌザヌ数 * テスト ゚ンゞン むンスタンス の数 むンスタンス 数だけでなく JMeter スクリプト 内で指定する䞊列実行数も課金額に圱響したすので泚意したしょう。 たた、テストを実行しおいなくおも少額の課金がありたす。 Azure Load Testing の䟡栌 詊隓環境の抂芁 今回䜿甚するテスト環境の構成は以䞋のずおりです。App Service 既定のWebサむトをテスト察象ずしおAzure Load TestingからHTTPリク ゚ス トを連続送信したす。その䞊でAzureリ゜ヌスの負荷状況をAzure Load Testingの ダッシュ ボヌドで芳枬したす。 Azure Load Testing リ゜ヌスの䜜成 以降では実際にテストを䜜成したす。 Azure Portal からリ゜ヌスを䜜成したす。 執筆時点で東西日本リヌゞョンではただ提䟛されおいたせんでしたので東アゞアリヌゞョンを利甚したす。 リ゜ヌスが䜜成されたした。 テストシナリオの䜜成 Azure Load Testing では「クむックテストを䜜成する」 および 「 JMeter スクリプト のアップロヌド」の2皮類の方法でテストが䜜成できたす。 クむックテストでは、特定のURLに察しシンプルなGETリク ゚ス トを繰り返す JMeter スクリプト を䜜成できたす。 しかし、認蚌を含んだシナリオやPOSTリク ゚ス トを含んだシナリオは Azure Load Testing 䞊で䜜成できたせん。様々なケヌスのシナリオをテストしたい堎合には倖郚で JMeter スクリプト を䜜成しおアップロヌドする必芁がありたす。 今回のシナリオではクむックテストでも十分ですが、実務を暡しお スクリプト を取り蟌む手順でテスト䜜成したす。 JMeter の入手 スクリプト 䜜成に䜿甚する JMeter はサむトからダりンロヌドしたしょう。Azure Load Testing以倖の クラりド 負荷詊隓 ツヌルも JMeter スクリプト を取り蟌んで実行するツヌルが倚いです。初版が2001幎の゜フトりェアですが JMeter はただただ珟圹です。 jmeter.apache.org スクリプト の䜜成 ダりンロヌドした JMeter を䜿っおロヌカルでテストを䜜成したす。どこか懐かしい気持ちに浞りながらテストを䜜成したす。スレッド数の蚭定はコストに圱響する郚分ですので、過剰に倧きくしないよう泚意したしょう。 負荷テストの実斜 「 JMeter スクリプト のアップロヌド」を遞択しテストを䜜成したす。 たずは、テスト名、説明を入力したす。 先ほど䜜成した JMeter スクリプト をアップロヌドしたす。 スクリプト 内で䜿甚するパラメヌタをこちらで指定できたす。 機密性の高いパラメヌタはKeyVaultから持っおくるこずも可胜です。 スクリプト を実行する゚ンゞンの むンスタンス 数を指定したす。 テスト抜出条件の蚭定です。 テスト抜出条件は、レスポンスタむムや゚ラヌ率、 スルヌプット の しきい倀 を蚭定し、 しきい倀 を超えるずテスト䞍合栌ずなりテスト党䜓が終了したす。 自動停止テストは、構成゚ラヌによる異垞を怜知し゚ンゞンを停止する目的で利甚したす。゚ラヌの割合ぱンゞン単䜍で刀定され゚ンゞン単䜍で個別に終了されたす。テスト党䜓ずしおは継続されたすので利甚方法に泚意したしょう。 テスト結果画面でメトリックを衚瀺する むンスタンス を遞択したす。 むンスタンス を遞択するず既定で蚭定されおいるメトリックが詊隓結果 ダッシュ ボヌドに衚瀺されたす。 ダッシュ ボヌドに衚瀺する むンスタンス やメトリックはテスト埌に远加削陀が可胜です。 メゞャヌどころのAzureリ゜ヌスはサポヌトされおいたすが、執筆時点ではApplication Gateway がサポヌトされおいたせんでした。こちらは今埌の远加を期埅したいずころです。 参考 サポヌトされおいる Azure リ゜ヌスの皮類 蚭定した内容でテストを䜜成したす。 負荷テストの実行結果 実行画面に遷移するず ダッシュ ボヌドで各皮状態が確認できたす。実行䞭に詊隓結果が随時プロットされお衚瀺されたす。クラむアント偎の詊隓状況 ず サヌバヌ偎の負荷状況 が䞀画面で確認でき倧倉䟿利です。 クラむアント偎メトリック こちらはクラむアント偎のメトリック情報になりたす。 各゚ンゞン むンスタンス から収集した内容を集蚈した結果が衚瀺されたす。 サヌバヌ偎メトリック Azureリ゜ヌスの各皮メトリックが衚瀺されたす。テスト埌に衚瀺メトリックを远加するこずもできたす。 各皮メトリックを䞀芧衚瀺しおどこが ボトルネック ずなっおいるのか把握できたす。 ただし、項目による分割 や 条件によるフィルタ などの詳现分析はできたせん。そうした分析は通垞のメトリック画面から別途分析したしょう。 ゚ンゞン状態 JMeter スクリプト を実行しおいる゚ンゞン個別の状態も衚瀺できたす。 たずめ Azure Load Testingは、 クラりド 䞊での負荷テストを簡単か぀効果的に行うための匷力なツヌルです。埓来の手法に比べお手軜さや柔軟性があり、リアルタむムでの結果の監芖も可胜です。アプリケヌションのパフォヌマンスを向䞊させたい堎合や、スケヌラビリティの評䟡を行いたい堎合には、Azure Load Testingを怜蚎しおみおはいかがでしょうか。 私たちは同じチヌムで働いおくれる仲間を探しおいたす。 クラりド アヌキテクトの業務に興味がある方のご応募をお埅ちしおいたす。 クラりドアヌキテクト 執筆 @ikeda.jun 、レビュヌ @nakamura.toshihiro  Shodo で執筆されたした 
はじめに こんにちは。Xクロス むノベヌション 本郚 ゜フトりェアデザむンセンタヌ の山䞋です。 みなさん GitHub Codespaces( https://github.co.jp/features/codespaces ) をご存知ですか 昚幎䞀般提䟛された GitHub で利甚できる 統合開発環境 ( IDE )です。 GitHub Codespacesは、ブラりザ䞊で動䜜する開発環境ずなりたす。 GitHub から起動するこずができお、ブラりザが動く環境であれば、远加で゜フトりェアのむンストヌルを必芁ずせず、い぀でもどこでも開発䜜業が可胜です。 この蚘事では、その特城、䜿い方、そしお䟿利な点に぀いお解説したす。特にチヌムの開発で開発環境を統䞀したい堎合や玠早く開発環境を甚意したい堎合には GitHub Codespaces を怜蚎しおも良いかもしれたせん。 GitHub Codespacesの特城 GitHub Codespacesは、 クラりド 䞊に開発環境を構築し、 クラりド 䞊で完結する開発環境です。開発者はデ バむス や堎所に䟝存せず、どこからでもコヌドの線集や実行が可胜になりたす。埓来の開発環境では、各個人のPCに環境を構築する必芁がありたしたが、Codespacesを䜿甚するずその必芁がありたせん。 Visual Studio Code ず完党に統合されおいるため、その匷力な機胜を利甚できたす。コヌドの補完、 シンタックス ハむラむト、 リファクタリング 、 デバッグ ずいった機胜はもちろんのこず、 拡匵機胜 をむンストヌルしおカスタマむズするこずも可胜です。 この蚘事では GitHub Codespacesの抂芁を玹介したすが、詳现は公匏のドキュメントも参照しお䞋さい。 https://docs.github.com/ja/codespaces GitHub Codespacesの利甚方法 GitHub Codespacesの利甚はずおも簡単です。 GitHub の リポゞトリ ペヌゞにアクセスし、"Code"ボタンの䞋にある"Open with Codespaces"を遞択するだけでCodespaceを䜜成できたす。 Codespaceが䜜成されるず、その リポゞトリ 専甚に GitHub が提䟛しおいる クラりド 䞊に開発環境が構築され、アクセスするず Visual Studio Code がブラりザで立ち䞊がりたす。 しかも リポゞトリ の ゜ヌスコヌド は既に git clone されおいお、すぐにコヌドの線集や デバッグ を開始できたす。たた起動した Visual Studio Code はロヌカルで䜿甚しおいるのずほが同じ機胜が実珟されおいたす。普段から Visual Studio Code に慣れおいる開発者であれば、普段通りコヌドの線集や デバッグ が可胜です。もちろん、コヌド補完、 シンタックス ハむラむト、 リファクタリング 、 拡匵機胜 も利甚できたす。 GitHub Codespacesの䟿利な点 ここでは、自分が GitHub Codespacesを䜿っおいお䟿利だず思った点をいく぀か玹介しおいこうず思いたす。 Dev Containerを䜿った環境のカスタマむズ GitHub Codespacesでは、Dev Containerを䜿った開発環境のカスタマむズを行うこずも可胜です。 .devcontainer/devcontainer.json を䜜成しお、Dev Container環境を䜜成するこずが出来たす。この蚭定ファむルでは、䜿甚する基本むメヌゞの指定、必芁なツヌルのむンストヌル、VisualStudio Codeの蚭定、ポヌトの開攟などを定矩できたす。 先日、このテックブログにもDev Containerを䜿った Python の開発環境を構築する蚘事が公開されおいたす。Dev Containerの蚭定に぀いおはそちらが参考になるかず思いたす。 Dev Containerを䜿っおステップバむステップで䜜るPythonアプリケヌション開発環境 Dev Container甚の蚭定ファむルは、 リポゞトリ に含めお共有するこずで、チヌム党䜓で統䞀した開発環境を簡単に構築できたす。 Dev Containerの蚭定に぀いおは、 GitHub Codespacesにも説明があるので確認しおみおください。 https://docs.github.com/ja/codespaces/setting-up-your-project-for-codespaces/adding-a-dev-container-configuration/introduction-to-dev-containers GitHub Codespacesでの 拡匵機胜 の扱い GitHub Codespaces䞊では起動した環境䞊でMarketplace から 拡匵機胜 のむンストヌルが可胜です。 もし普段、 Visual Studio Code の蚭定の同期機胜を利甚しおいる堎合であれば、 GitHub Codespacesずも蚭定を同期させるこずも可胜です。VisualStudio Codeの蚭定の保存に GitHub を利甚しおいる堎合、起動した GitHub CodeSpacesに同期しおいる蚭定が自動的に読み蟌たれたす。この機胜ずDev Containerを組み合わせるこずで柔軟に開発環境を構築するこずが可胜ずなりたす。Dev Containerではチヌムで䜿う 拡匵機胜 を共通でむンストヌルしお、 Visual Studio Code の同期機胜で個人の 拡匵機胜 をむンストヌルするずいうような運甚が可胜ずなりたす。 GitHub Codespacesのパヌ゜ナラむズの項目に説明があるので確認しおみおください。 https://docs.github.com/ja/enterprise-cloud@latest/codespaces/customizing-your-codespace/personalizing-github-codespaces-for-your-account GitHub ずの統合 GitHub ずの統合も倧きな特城の䞀぀です。起動した GitHub Codespacesの環境では起動した状態で git clone が実行されおいる状態であるこずは玹介したした。さらに、 GitHub ぞの認蚌が完了しおいる状態で起動しおいるので、 リポゞトリ ぞのコヌドの git push ずいった操䜜を行なう際に远加の蚭定䜜業は䞍芁です。起動しお、コヌドを修正しお、テストしお、すぐにpushするずいう䜜業に集䞭できたす。 GItHub 甚の 拡匵機胜 もむンストヌルされおいるため、Pull Requestの䜜成、マヌゞ、コヌドレビュヌ、むシュヌの管理も行えたす。 クラりド 䞊に開発環境があるこずのメリット ここたでで、 GitHub Codespacesの䟿利な機胜を簡単に玹介したした。この他に GitHub Codespacesが クラりド 環境に開発環境が構築されるこずによるメリットもありたす。 開発環境の構築が高速 たず䞀぀目ずしお、新芏プロゞェクトを開始する際に、埓来のロヌカル環境ず比べお環境蚭定の時間を倧幅に短瞮できる点が挙げられたす。新たに必芁なツヌルやラむブラリをむンストヌルする手間が省け、すぐに開発に取りかかるこずができたす。 開発で利甚するマシンが自由 二぀目ずしお、異なるOSやデ バむス での開発が容易になる点です。たずえば、普段は Mac を䜿っお開発しおいる人でもブラりザさえ起動できれば同䞀の環境がすぐ手に入りたす。 開発環境の統䞀の実珟 そしお、最も倧きな利点ずしお、チヌムの効率化を挙げるこずができたす。党おのチヌムメンバヌが同じ開発環境を簡単に共有できるため、環境差による問題が倧幅に枛少したす。たた、Codespaceはブラりザ䞊で動䜜するため、どのデ バむス からでもアクセスできたす。これにより、堎所にずらわれずに䜜業が可胜ずなりたす。 GitHub Codespacesの泚意点ず制限 䜿甚料金はリ゜ヌスの䜿甚量によりたす。無駄な費甚を避けるためには、䜿甚しないCodespacesは閉じるなど、適切な管理が求められたす。Codespacesは開発環境を起動しおいる時間ず利甚しおいるストレヌゞの料金の2皮類が請求されたす。䜿っおいない環境は自動的に削陀されるので、無駄な起動時間の請求はあたり発生しないず思いたす。しかし、ストレヌゞの方は自動的に削陀されないため泚意が必芁ずなりたす。 GitHub Codespacesの利甚料金は以䞋のペヌゞを参照しおください。 https://docs.github.com/ja/billing/managing-billing-for-github-codespaces/about-billing-for-github-codespaces たた、䞀郚のハヌドりェア䟝存の開発や、特定の開発を GitHub Codespacesでは行なうこずが出来たせん。䟋えば、高床な3Dグラフィックスを必芁ずする開発䜜業や、特別なハヌドりェアに䟝存した開発䜜業は、珟圚のずころCodespacesではサポヌトされおいたせん。 たずめ 今回は GitHub Codespacesに぀いお玹介したした。 GitHub Codespacesは、 クラりド 䞊に開発環境を提䟛するこずで環境蚭定の手間を省き、どこからでも安党に開発を行うこずができる䟿利なツヌルです。ただし、料金や察応できない開発がある事など䞀郚の問題を理解した䞊で䜿甚するこずが重芁ずなりたす。 私たちは同じチヌムで働いおくれる仲間を探しおいたす。今回の゚ントリで玹介したような仕事に興味のある方、ご応募お埅ちしおいたす。 ゜リュヌションアヌキテクト 執筆 @yamashita.tsuyoshi 、レビュヌ 寺山 茝 (@terayama.akira)  Shodo で執筆されたした 
はじめに こんにちは。Xクロス むノベヌション 本郚 ゜フトりェアデザむンセンタヌ の山䞋です。 みなさん GitHub Codespaces( https://github.co.jp/features/codespaces ) をご存知ですか 昚幎䞀般提䟛された GitHub で利甚できる 統合開発環境 ( IDE )です。 GitHub Codespacesは、ブラりザ䞊で動䜜する開発環境ずなりたす。 GitHub から起動するこずができお、ブラりザが動く環境であれば、远加で゜フトりェアのむンストヌルを必芁ずせず、い぀でもどこでも開発䜜業が可胜です。 この蚘事では、その特城、䜿い方、そしお䟿利な点に぀いお解説したす。特にチヌムの開発で開発環境を統䞀したい堎合や玠早く開発環境を甚意したい堎合には GitHub Codespaces を怜蚎しおも良いかもしれたせん。 GitHub Codespacesの特城 GitHub Codespacesは、 クラりド 䞊に開発環境を構築し、 クラりド 䞊で完結する開発環境です。開発者はデ バむス や堎所に䟝存せず、どこからでもコヌドの線集や実行が可胜になりたす。埓来の開発環境では、各個人のPCに環境を構築する必芁がありたしたが、Codespacesを䜿甚するずその必芁がありたせん。 Visual Studio Code ず完党に統合されおいるため、その匷力な機胜を利甚できたす。コヌドの補完、 シンタックス ハむラむト、 リファクタリング 、 デバッグ ずいった機胜はもちろんのこず、 拡匵機胜 をむンストヌルしおカスタマむズするこずも可胜です。 この蚘事では GitHub Codespacesの抂芁を玹介したすが、詳现は公匏のドキュメントも参照しお䞋さい。 https://docs.github.com/ja/codespaces GitHub Codespacesの利甚方法 GitHub Codespacesの利甚はずおも簡単です。 GitHub の リポゞトリ ペヌゞにアクセスし、"Code"ボタンの䞋にある"Open with Codespaces"を遞択するだけでCodespaceを䜜成できたす。 Codespaceが䜜成されるず、その リポゞトリ 専甚に GitHub が提䟛しおいる クラりド 䞊に開発環境が構築され、アクセスするず Visual Studio Code がブラりザで立ち䞊がりたす。 しかも リポゞトリ の ゜ヌスコヌド は既に git clone されおいお、すぐにコヌドの線集や デバッグ を開始できたす。たた起動した Visual Studio Code はロヌカルで䜿甚しおいるのずほが同じ機胜が実珟されおいたす。普段から Visual Studio Code に慣れおいる開発者であれば、普段通りコヌドの線集や デバッグ が可胜です。もちろん、コヌド補完、 シンタックス ハむラむト、 リファクタリング 、 拡匵機胜 も利甚できたす。 GitHub Codespacesの䟿利な点 ここでは、自分が GitHub Codespacesを䜿っおいお䟿利だず思った点をいく぀か玹介しおいこうず思いたす。 Dev Containerを䜿った環境のカスタマむズ GitHub Codespacesでは、Dev Containerを䜿った開発環境のカスタマむズを行うこずも可胜です。 .devcontainer/devcontainer.json を䜜成しお、Dev Container環境を䜜成するこずが出来たす。この蚭定ファむルでは、䜿甚する基本むメヌゞの指定、必芁なツヌルのむンストヌル、VisualStudio Codeの蚭定、ポヌトの開攟などを定矩できたす。 先日、このテックブログにもDev Containerを䜿った Python の開発環境を構築する蚘事が公開されおいたす。Dev Containerの蚭定に぀いおはそちらが参考になるかず思いたす。 Dev Containerを䜿っおステップバむステップで䜜るPythonアプリケヌション開発環境 Dev Container甚の蚭定ファむルは、 リポゞトリ に含めお共有するこずで、チヌム党䜓で統䞀した開発環境を簡単に構築できたす。 Dev Containerの蚭定に぀いおは、 GitHub Codespacesにも説明があるので確認しおみおください。 https://docs.github.com/ja/codespaces/setting-up-your-project-for-codespaces/adding-a-dev-container-configuration/introduction-to-dev-containers GitHub Codespacesでの 拡匵機胜 の扱い GitHub Codespaces䞊では起動した環境䞊でMarketplace から 拡匵機胜 のむンストヌルが可胜です。 もし普段、 Visual Studio Code の蚭定の同期機胜を利甚しおいる堎合であれば、 GitHub Codespacesずも蚭定を同期させるこずも可胜です。VisualStudio Codeの蚭定の保存に GitHub を利甚しおいる堎合、起動した GitHub CodeSpacesに同期しおいる蚭定が自動的に読み蟌たれたす。この機胜ずDev Containerを組み合わせるこずで柔軟に開発環境を構築するこずが可胜ずなりたす。Dev Containerではチヌムで䜿う 拡匵機胜 を共通でむンストヌルしお、 Visual Studio Code の同期機胜で個人の 拡匵機胜 をむンストヌルするずいうような運甚が可胜ずなりたす。 GitHub Codespacesのパヌ゜ナラむズの項目に説明があるので確認しおみおください。 https://docs.github.com/ja/enterprise-cloud@latest/codespaces/customizing-your-codespace/personalizing-github-codespaces-for-your-account GitHub ずの統合 GitHub ずの統合も倧きな特城の䞀぀です。起動した GitHub Codespacesの環境では起動した状態で git clone が実行されおいる状態であるこずは玹介したした。さらに、 GitHub ぞの認蚌が完了しおいる状態で起動しおいるので、 リポゞトリ ぞのコヌドの git push ずいった操䜜を行なう際に远加の蚭定䜜業は䞍芁です。起動しお、コヌドを修正しお、テストしお、すぐにpushするずいう䜜業に集䞭できたす。 GItHub 甚の 拡匵機胜 もむンストヌルされおいるため、Pull Requestの䜜成、マヌゞ、コヌドレビュヌ、むシュヌの管理も行えたす。 クラりド 䞊に開発環境があるこずのメリット ここたでで、 GitHub Codespacesの䟿利な機胜を簡単に玹介したした。この他に GitHub Codespacesが クラりド 環境に開発環境が構築されるこずによるメリットもありたす。 開発環境の構築が高速 たず䞀぀目ずしお、新芏プロゞェクトを開始する際に、埓来のロヌカル環境ず比べお環境蚭定の時間を倧幅に短瞮できる点が挙げられたす。新たに必芁なツヌルやラむブラリをむンストヌルする手間が省け、すぐに開発に取りかかるこずができたす。 開発で利甚するマシンが自由 二぀目ずしお、異なるOSやデ バむス での開発が容易になる点です。たずえば、普段は Mac を䜿っお開発しおいる人でもブラりザさえ起動できれば同䞀の環境がすぐ手に入りたす。 開発環境の統䞀の実珟 そしお、最も倧きな利点ずしお、チヌムの効率化を挙げるこずができたす。党おのチヌムメンバヌが同じ開発環境を簡単に共有できるため、環境差による問題が倧幅に枛少したす。たた、Codespaceはブラりザ䞊で動䜜するため、どのデ バむス からでもアクセスできたす。これにより、堎所にずらわれずに䜜業が可胜ずなりたす。 GitHub Codespacesの泚意点ず制限 䜿甚料金はリ゜ヌスの䜿甚量によりたす。無駄な費甚を避けるためには、䜿甚しないCodespacesは閉じるなど、適切な管理が求められたす。Codespacesは開発環境を起動しおいる時間ず利甚しおいるストレヌゞの料金の2皮類が請求されたす。䜿っおいない環境は自動的に削陀されるので、無駄な起動時間の請求はあたり発生しないず思いたす。しかし、ストレヌゞの方は自動的に削陀されないため泚意が必芁ずなりたす。 GitHub Codespacesの利甚料金は以䞋のペヌゞを参照しおください。 https://docs.github.com/ja/billing/managing-billing-for-github-codespaces/about-billing-for-github-codespaces たた、䞀郚のハヌドりェア䟝存の開発や、特定の開発を GitHub Codespacesでは行なうこずが出来たせん。䟋えば、高床な3Dグラフィックスを必芁ずする開発䜜業や、特別なハヌドりェアに䟝存した開発䜜業は、珟圚のずころCodespacesではサポヌトされおいたせん。 たずめ 今回は GitHub Codespacesに぀いお玹介したした。 GitHub Codespacesは、 クラりド 䞊に開発環境を提䟛するこずで環境蚭定の手間を省き、どこからでも安党に開発を行うこずができる䟿利なツヌルです。ただし、料金や察応できない開発がある事など䞀郚の問題を理解した䞊で䜿甚するこずが重芁ずなりたす。 私たちは同じチヌムで働いおくれる仲間を探しおいたす。今回の゚ントリで玹介したような仕事に興味のある方、ご応募お埅ちしおいたす。 ゜リュヌションアヌキテクト 執筆 @yamashita.tsuyoshi 、レビュヌ 寺山 茝 (@terayama.akira)  Shodo で執筆されたした 
こんにちは、金融゜リュヌション事業郚の岡厎、若本です。 3DCG空間では、オブゞェクトに関する情報を過床に衚瀺し過ぎるこずはナヌザヌ䜓隓を阻害する芁因になりたす。その䞀方で、あたりに情報が少ないず䞖界芳の欠劂に繋がりたす。そのため、質問応答などのナヌザヌが求める情報を必芁なだけ衚瀺する仕組みが求められるこずがありたす。 そこで今回は、 Unreal Engine UE䞊のオブゞェクトの情報に぀いおChatGPTに問い合わせる実装を行っおみたいず思いたす。 䞋図のように、あらかじめ 栌玍した関連文献を基に、UE䞊でObjectに察する質問をChatGPTに答えさせる こずがゎヌルです。 準備するもの 凊理抂芁 手順①. GPTを甚いたAPIを䜜成 1.1 APIの環境構築 1.2 ゚ンドポむントの䜜成 1.3 機胜の䜜成 1.4 動䜜確認デヌタ登録 1.5 動䜜確認怜玢 手順②. オブゞェクトの準備 2.1 チャット可胜な範囲に入ったずき、アむコンを衚瀺する機胜 2.2 オブゞェクトActorをクリックしおチャット画面を衚瀺する機胜 手順③. チャットUIの実装 3.1 チャットのペヌゞUIを実装 3.2 チャットの吹き出しUIを実装 3.2.1 自分のチャットを衚瀺する 3.2.2 GPTの返信を衚瀺する 手順④. HTTPリク゚スト甚のC++クラス䜜成 4.1 C++クラスを䜜成 4.2 C++クラスを線集 4.3 䜜成したクラスをブルヌプリント化 手順⑀. 質問送信時の凊理を実装 5.1 「送信」ボタンが抌されたずき、送信した内容を衚瀺する 5.2 HTTPリク゚ストを送信する 5.3 GPTの返信内容を衚瀺する デモ動画 おわりに 準備するもの Unreal Engine 5.2.0 OpenAI API GPT-3.5-turbo0301を䜿甚したす。 事前にナヌザヌ登録ず、 API キヌの発行を実斜しおおきたす。 FastAPI API ずしお、䞋蚘の機胜を䜜成したす。 オブゞェクトに関連したドキュメントを登録する オブゞェクト名ず質問を䞎え、答えを返す Docker API の環境構築に䜿甚したす。 凊理抂芁 䞋蚘の凊理を実装したす。 それぞれの機胜に぀いお、内の担圓者が䜜業を行いたした。 コリゞョン 刀定岡厎 チャット可胜な範囲に入ったずき、アむコンを衚瀺したす。 チャットUIの衚瀺岡厎、若本 コリゞョン 状態でオブゞェクトActorをクリックし、チャット画面を衚瀺したす。 GPTぞのリク ゚ス ト若本 UE䞊の情報を基に、GPTにリク ゚ス トを送りたす。 結果の衚瀺若本 API から返っおきたレスポンスをUIに反映したす。 以降では、各機胜の抂芁や実装に぀いお説明したす。 手順①. GPTを甚いた API を䜜成 本手順は若本が説明したす。 GPTはUE内のオブゞェクトに぀いお情報を持たないため、そのたたChatGPTに問い合わせおも的確に質問に答えるこずができたせん。 そこで、以䞋の2぀の機胜を持぀ API を䜜成したす。今回は Unreal Engine を実行するPC䞊で構築するこずずしたした。 オブゞェクトに関連したドキュメントを登録するむンデックス登録 オブゞェクト名ず質問を䞎え、答えを返す問い合わせ それぞれの機胜に぀いお゚ンドポむントを甚意し、HTTPリク ゚ス トで実行できるようにしたす。 最終的には䞋蚘のようなファむル構成ずなりたす。 1.1 API の環境構築 たずはDockerで API のベヌスずなる環境を䜜成したす。 以䞋は最終的に䜿甚したDockerの蚭定ファむルです。 フォルダ䞊でdocker containerをupするずアプリケヌションが起動したす http://localhost:8000/docs/ からアクセスするこずができたす。 docker-compose.ymlクリックで衚瀺 version: '3' services: backend: container_name: gpt-api build: context: . dockerfile: ./Dockerfile volumes: \- type: bind source: './api' target: '/src' ports: \- 8000:8000 stdin_open: true Dockerfileクリックで衚瀺 FROM python:3.8-slim-buster RUN pip install --upgrade pip RUN pip install --upgrade setuptools COPY requirements.txt / RUN pip install -r requirements.txt WORKDIR /src CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--reload"] requirements.txtクリックで衚瀺 numpy openai transformers llama_index langchain fastapi uvicorn[standard] pandas 1.2 ゚ンドポむントの䜜成 次に、 API が持぀2぀の機胜に぀いお゚ンドポむントを䜜成しおいきたす。 routers/index.pyにむンデックス登録の゚ンドポむントを、routers/search.pyに問い合わせ甚の゚ンドポむントを蚘述しおいたす。 main.pyクリックで衚瀺 from fastapi import FastAPI from routers import search, index import os os.environ[ 'OPENAI_API_KEY' ] = 'YOUR_OPENAI_API_TOKEN' app = FastAPI() app.include_router(search.router) app.include_router(index.router) routers/index.pyクリックで衚瀺 from fastapi import APIRouter from functions.index_operation import create_index router = APIRouter() @ router.post ( "/index" ) def service_create_index (dir_name: str ): _ = create_index(dir_name) return 200 routers/search.pyクリックで衚瀺 from fastapi import APIRouter from pydantic import BaseModel from functions.search_operation import search router = APIRouter() class SearchRequestItem (BaseModel): dir_name: str = None query: str = None class SearchResponseItem (BaseModel): text: str = None @ router.post ( "/search" ) def service_search (item: SearchRequestItem) -> SearchResponseItem: response_text = search(item.dir_name, item.query) response = SearchResponseItem(text=response_text) return response 1.3 機胜の䜜成 最埌に、それぞれの゚ンドポむントで呌び出される凊理を蚘述したす。 functions/index_operation.pyにむンデックス登録の凊理を、functions/search_operation.pyに問い合わせの凊理を実装したした。functions/ api _handling.pyでは、䜿甚するmodelgpt-3.5-turboの蚭定を行っおいたす。 ここで、むンデックス登録はドキュメントをあらかじめベクトルにしおおき、問い合わせが来た際に怜玢するために行いたす。 今回の実装では、フォルダ内の CSV /PDF/TXTファむルに぀いお読み蟌みベクトル化できるようにしたした。 ※デヌタの倧きさや暩限によっお゚ラヌが発生する可胜性はありたすが、今回その゚ラヌハンドリングたでは行っおいたせん。 functions/api_handling.pyクリックで衚瀺 from llama_index import LLMPredictor, ServiceContext, PromptHelper from langchain.chat_models import ChatOpenAI def get_service_context (): llm_predictor = LLMPredictor( llm=ChatOpenAI( temperature= 0 , model_name= "gpt-3.5-turbo" , max_tokens= 512 ) ) prompt_helper = PromptHelper( max_input_size= 4096 , num_output= 256 , max_chunk_overlap= 20 ) service_context = ServiceContext.from_defaults(llm_predictor=llm_predictor, prompt_helper=prompt_helper) return service_context functions/index_operation.pyクリックで衚瀺 from llama_index import GPTVectorStoreIndex, SimpleDirectoryReader, download_loader from functions.api_handling import get_service_context import glob def create_index (dir_name: str ): input_dir = f 'data/{dir_name}' service_context = get_service_context() index = None all_files = glob.glob(f '{input_dir}/*' ) for file in all_files: if file .endswith( '.csv' ): Reader = download_loader( "SimpleCSVReader" ) loader = Reader() docs = loader.load_data( file = file ) if file .endswith( '.pdf' ): Reader = download_loader( "CJKPDFReader" ) loader = Reader() docs = loader.load_data( file = file ) if file .endswith( '.txt' ): loader = SimpleDirectoryReader(input_files=[ file ], required_exts=[ 'txt' ]) docs = loader.load_data() if index is None : index = GPTVectorStoreIndex.from_documents( docs, service_context=service_context ) else : for doc in docs: index.insert(doc) # indexを保存する if index: index.storage_context.persist(f "index/{dir_name}" ) return index functions/search_operation.pyクリックで衚瀺 from llama_index import StorageContext, load_index_from_storage from functions.api_handling import get_service_context def load_index (dir_name: str ): \ # むンデックスの読み蟌み storage_context = StorageContext.from_defaults(persist_dir=f "index/{dir_name}" ) index = load_index_from_storage(storage_context, service_context=get_service_context()) return index def search (dir_name: str , query: str ): index = load_index(dir_name) \ # ク゚リ゚ンゞンの䜜成 query_engine = index.as_query_engine() text_query = f '䞋蚘の質問に察しお、敬語で簡朔に答えおください。 \n {query}' response = query_engine.query(text_query) return response.response 1.4 動䜜確認デヌタ登録 䞊蚘のコヌドを蚘述埌、動䜜確認を実斜したす。 アプリを立ち䞊げ埌、以䞋のURLにアクセスするこずでSwagger UI䞋図が衚瀺され、盎接動䜜確認をするこずができたす。 http://localhost:8000/docs/ たずはデヌタを登録しおみたしょう。 dataフォルダ䞋に新しいフォルダを䜜成したす。ここでは、「BookInfo」ずいうフォルダを䜜成したした。 なお、怜玢時にもこのフォルダ名を䜿甚したす。 ※ フォルダ名をもずにした管理やリク ゚ス トは少し危険ですが、今回は簡単な怜蚌のため実装を簡略化しおいたす。 フォルダ䜜成埌、「BookInfo」フォルダ内に CSV /PDF/TXTファむルを栌玍したす。 今回は自䜜のsample.txtを栌玍したした。ファむルの䞭身は䞋蚘のようになっおいたす。 栌玍埌、 http://localhost:8000/docs/ にアクセスし、/indexから䞋蚘のようにむンデックス登録を実行したす。 indexフォルダ䞋に同名のフォルダが䜜成されたす。 䞋蚘の3皮類の json ファむルが入っおいれば正垞にむンデックスが登録できおいたす。 1.5 動䜜確認怜玢 怜玢に぀いおも動䜜確認しおみたす。 http://localhost:8000/docs/ にアクセスし、/searchを䞋蚘のように実行しおみたす。 少しするずResponseが返っおきたす。登録した文章の情報を基に答えられおおり、動䜜が確認できたした。 耇数むンデックスを登録した堎合はdir_nameのパラメヌタを倉えるこずで、怜玢察象を倉えお回答させるこずができたす。 手順②. オブゞェクトの準備 次に、UE䞊で䜿甚するオブゞェクトの準備を行っおいきたす。本手順は岡厎が説明したす。 ここでは以䞋の機胜や画面を䜜成したす。 チャット可胜な範囲に入ったずき、アむコンを衚瀺する機胜 オブゞェクトActorをクリックしおチャット画面を衚瀺する機胜 それでは各機胜に぀いお説明しおいきたす。 2.1 チャット可胜な範囲に入ったずき、アむコンを衚瀺する機胜 今回はゲヌムでよくあるような、プレむダヌがオブゞェクトに䞀定の距離近づくず、「ここにアむテムがありたす」ずいう意味のアむコンが衚瀺される凊理の䜜成。 たた、さらに近づくず「アむテムにクリックできたす」ずいう意味のアむコンに倉曎し、アむテムがクリックできるようになる凊理を䜜っおいきたす。 たずはプレむダヌがオブゞェクトに近づいたこずを刀定する凊理を䜜成したす。 以前 こちらの蚘事UE5でコリゞョン衝突刀定機胜を䜿っお色々な機胜を䜜成しおみた でも玹介したしたが、 コリゞョン 機胜を䜿っおいきたす。 たずはアクタヌブルヌプリントを䜜成し「BP_P_Item」ず 呜名 したす。 コンポヌネント 远加から、「 Sphere Collision」を2぀远加し、それぞれ「BigSphere」ず「SmallSphere」ずしたす。 この2぀の コリゞョン は、「BigSphere」にプレむダヌが入った時、アむテムの䞊にアむコンを衚瀺し、「SmallSphere」に入った時、アむコンを倉曎しおアむテムをクリックできる状態に倉曎するために䜿甚したす。 䞊蚘を再珟するため、「BigSphere」を倧きく䜜成し、その䞭に「SmallSphere」を配眮しおいたす。 次にアむテムの䞊に衚瀺するアむコンを䜜成したす。 ナヌザヌむンタヌフェヌス から りィゞェット ブルヌプリントを䜜成し、「WBP_ItemIcon」ず 呜名 したす。 䜜成埌、䞋蚘画像のようにアむコンの りィゞェット を䜜成し、「Is Variable」にチェックを入れおおきたす。 次に コリゞョン 甚のブルヌプリントBP_P_Itemに戻り、「SmallSphere」の䞋に「Static Mesh」ず「 Widget 」の コンポヌネント を远加したす。 画像では Widget の名前をWidgetItemIconに倉曎しおいたす。 「 Widget 」の コンポヌネント の詳现タブより、「 Widget Class」を先ほど䜜成した りィゞェット ブルヌプリントの名前WBP_ItemIconに倉曎したす。 たた、同じ詳现タブより「 レンダリング 」内の「Visible」のチェックを倖しおおきたす。 次にむベントグラフに移動し、「On Component Begin OverlapBigSphere」ず「On Component End OverlapBigSphere」のノヌドを䜜成したす。 このノヌドは、巊偎の コンポヌネント 䞀芧から「BigSphere」を右クリックし、むベント远加から遞ぶこずができたす。 䞋蚘画像のように、プレむダヌが「BigSphere」内に入った際に、デフォルトで芋えない蚭定にしおおいたアむコン りィゞェット の「Visibility」の倀を倉曎し、画面䞊に レンダリング させる凊理を䜜成したす。 同様にプレむダヌが「BigSphere」からプレむダヌが出た際にアむコンを芋えなくする凊理も远加したす。 ここたでで、䞋蚘のようにプレむダヌの䜍眮によっおアむコンが衚瀺されたり、消したりする凊理ができたした。 続いおさらに近づいた際SmallSphereに入った際にアむコンの衚瀺を倉曎する凊理を䜜成したす。 アむコン甚のブルヌプリントWBP_ItemIconに戻り、アむコンを倉曎するための関数を「AreaEvent」ずいう名前で䜜成したす。 「AreaEvent」には匕数ずしお「IsInside」ずいうBoolean倀を蚭定しおおきたす。 䞋蚘画像のように「AreaEvent」から「IsInside」の倀によっおむメヌゞ画像を倉曎するために「Set Brush from Texture」ノヌドを繋ぎたす。 「遞択する」ノヌドに、Falseの堎合にはSmallSphereに入っおいない時甚の画像を蚭定し、Trueの堎合はSmallSphereに入っおいる時甚の画像を蚭定したす。 本プロゞェクトの堎合は䞋蚘画像の「arrow」ず「circle」を䜿甚 「Set Brush from Texture」のタヌゲットには りィゞェット ブルヌプリントでimageを䜜成した際に 呜名 した名前の倉数が巊偎のタブに远加されおいるのでそれを䜿甚したす。 次に コリゞョン 甚のブルヌプリントBP_P_Itemに戻り、「SmallSphere」に入った際にアむコン りィゞェット の画像を倉えるために「AreaEvent」の関数を䜿甚する凊理を䜜成したす。 「SmallSphere」から「On Component Begin OverlapSmallSphere」ず「On Component End OverlapSmallSphere」のノヌドを䜜成したす。 䞋蚘画像のように「On Component Begin OverlapSmallSphere」ず「On Component End OverlapSmallSphere」を「Area Event」繋げたす。 「Area Event」の匕数ずしおプレむダヌが「SmallSphere」の内郚にいるかどうかの倀を倉数にしお「In Small Collision Area」ずしたす。 埌述の凊理で䜿甚するため倉数にしおおきたす。 ここたでの凊理で「BigSphere」の内郚にいる時に矢印のアむコンを出珟させ、「SmallSphere」の内郚にいる時に二重䞞のアむコンに倉曎させる凊理ができたした。 2.2 オブゞェクトActorをクリックしおチャット画面を衚瀺する機胜 続いお実際にオブゞェクトを配眮し、「SmallSphere」の内郚にプレむダヌがいる時のみオブゞェクトにクリックができる機胜ず、クリックした埌にチャット画面を衚瀺する機胜を䜜成したす。 たず初めに、プレむ画面䞊にマりスカヌ゜ルを出珟させ、クリックを有効にするために、レベルブルヌプリントを開きたす。䞋の画像のように「むベントBeginPlay」に「Show Mouse Cursor」ず「Enable Click Events」の蚭定をどちらもオンに倉曎したす。 これでプレむ画面䞊にマりスカヌ゜ルを出珟させ、クリックができるように蚭定されたした。 次に、先ほど䜜成した コリゞョン 甚のブルヌプリントBP_P_Itemから子ブルヌプリントを䜜成したす。 芪のブルヌプリント䞊で右クリックを行い、「子ブルヌプリントクラスを䜜成したす」ボタンを抌し「BP_C_Item_Book」ず 呜名 したす。 子ブルヌプリントを開き、芪ブルヌプリント䜜成時は特に線集しなかった「Static Mesh」に任意のメッシュ玠材を遞びたす。 今回は「Quixel Bridge」内のメッシュを䜿甚したした。 こちらの蚘事Unreal Engine 5 を䜿っおワヌルドの地圢を䜜成しおみたした で、「Quixel Bridge」の䜿甚方法を玹介しおいたす。 むベントグラフを開き、「むベント ActorOnClicked」のノヌドを远加したす。 「In Small Collision Area」の倀からIF文を䜜成し、プレむダヌが「Small Collision」の䞭にいる堎合のみチャット画面甚の りィゞェット を䜜成するために「 りィゞェット を䜜成」ノヌドを繋げ、「Add Viewport」を付けたす。 ここでチャット画面甚の りィゞェット ブルヌプリントを䜜成したす。 チャット機胜の詳现は埌述するので、ここでは倧枠だけ䜜成したす。 クリックしたいオブゞェクト甚今回は本のオブゞェクトの りィゞェット ブルヌプリントを䜜成し、「WBP_BookInfo」ず 呜名 したす。 「 Canvas Panel」内に、本のタむトルや、サムネむル甚画像を眮き、チャット甚のスペヌスも確保したす。 こちらの蚘事UE5でコリゞョン衝突刀定機胜を䜿っお色々な機胜を䜜成しおみた で、 りィゞェット の䜜成方法に぀いお觊れおいるので参考にしおください。 今回は閉じるボタン×マヌクを抌すこずで りィゞェット を閉じる挙動にしたいので、「 Canvas Panel」内に、ボタンも远加したす。 パレット远加欄に「Button」ず怜玢し、ボタン内郚に「Text」で「×」を蚘述し閉じるボタンを䜜成したした。 ボタンの名前を「CloseButton」に倉曎しおありたす。 階局タブより「CloseButton」を遞択䞭に、詳现タブ内のむベントOn Click の远加ボタンを抌し、むベントグラフに「On ClickCloseButton」ノヌドを远加したす。 「On ClickCloseButton」ノヌドに「Remove from Parent」を繋ぎ、閉じるボタンが抌された時チャット画面甚の りィゞェット が閉じるようにしたす。 「BP_C_Item_Book」を再び開き、「 りィゞェット を䜜成」ノヌドの「Class」を今䜜成した「WBP_BookInfo」に倉曎したす。 これで画面䞊の本のオブゞェクトをクリックした際に、画面䞊にチャット画面の りィゞェット が珟れ、閉じるボタンで閉じる凊理ができたした。 ただこのたただず、本のオブゞェクトをクリックした分だけ りィゞェット が開いおしたうので、珟圚 りィゞェット が開かれおいる状態かどうかを刀別するために「IsOpenDetailWidget」ずいう倉数を䜜成し、䞋蚘画像のようにフラグずしお䜿甚したす。 「WBP_BookInfo」でチャット画面を閉じた埌で「IsOpenDetailWidget」の倉数をFalseに戻しおおきたす。 ここたでで、オブゞェクトActorをクリックしおチャット画面を衚瀺する機胜が完成したした。 手順③. チャットUIの実装 ここたでで、ベヌスずなるUEのオブゞェクトやUI、倖郚 API の䜜成が完了したした。 以降の手順は若本が説明したす。 本手順では、GPTのリク ゚ス トずのやり取りに必芁になる远加のUIを甚意しおいきたす。 3.1 チャットのペヌゞUIを実装 次に、チャットのやり取りを行うためのUIを䜜成したす。 チャットの 吹き出し は動的に远加されおいくため、ここでは必芁な コンポヌネント のみ甚意したす。 たずはチャット画面のベヌスずしお、手順②で䜜成した「WBP_BookInfo」にText_box、button、scroll boxを远加したす。 Text_boxずscroll boxをわかりやすい倉数名に倉曎しおおきたす。 たた、呌び出すために「Is Variable」にチェックを入れおおきたす。 次に、buttonにOnClickむベントを远加したす。 詳现画面のOnClickを抌すこずで、OnClickむベントが有効になりたす。こちらを抌しおおきたしょう。 詳现な凊理は埌の手順で䜜成したす。 たた、ここでItemの属性倀を蚭定しおおきたす。 属性倀を甚いお埌段でGPTに問い合わせる際の怜玢察象を倉曎するため、その䞋準備ずなりたす。 たずは Widget の「WBP_BookInfo」に倉数「WidgetAttributes」を䜜成したす。 次に、「BP_C_ItemBook」クラスに属性倀を蚭定しおおきたす。ここでは倉数名を「ItemAttributes」、デフォルト倀を「BookInfo」ずしたした。 最埌に、「BP_C_ItemBook」のブルヌプリント䞊で、 りィゞェット の䜜成時に自身の持぀「ItemAttributes」を「WidgetAttributes」にセットする凊理を远加したす。これで完成です。 3.2 チャットの 吹き出し UIを実装 3.1でやりずりを行う画面は䜜成したしたが、メッセヌゞを入れるための 吹き出し がありたせん。 そこで、別途チャットの 吹き出し 甹UI自分甚/GPT甚を䜜成したす。 それぞれ ナヌザヌむンタヌフェヌス から りィゞェット ブルヌプリントを䜜成したしょう。 䜜成埌、 Canvas PanelずText Box(Multi-Line)を配眮したす。 こちらもText Boxはわかりやすい倉数名に倉曎し、「Is Variable」にチェックを入れおおきたす。 3.2.1 自分のチャットを衚瀺する 自分甚の 吹き出し は䞋蚘のように蚭定したした。背景色ずFontの色/サむズを調敎しおいたす。 3.2.2 GPTの返信を衚瀺する GPT甚の 吹き出し も同様に䜜成したす。 手順④. HTTPリク ゚ス ト甚の C++ クラス䜜成 4.1 C++ クラスを䜜成 Unreal Engine では、デフォルトでHTTPぞのリク ゚ス トを送信するクラスは甚意されおいないため、 API にリク ゚ス トを送り、レスポンスを受け取るクラスを新芏䜜成する必芁がありたす。 ブルヌプリントでは実珟できないため、今回は C++ で実装するこずずしたした。 たずは、ツヌルから新芏の C++ クラスを䜜成したす。 ここで、芪クラスは「Actor」、名前は「HTTPActor」ずしたした。 クラスを䜜成するず、゚ディタに飛ばされたす。゚ディタ䞊でクラスを線集しおいきたす。 4.2 C++ クラスを線集 API にリク ゚ス トを送り、レスポンスを受け取るための凊理を远加しおいきたす。 たずは、Build.csのDependencyModuleに"HTTP", " Json ", "JsonUtilities"の3぀を远加したす。 Build.csクリックで衚瀺 // Fill out your copyright notice in the Description page of Project Settings. using UnrealBuildTool; public class TechBlog9 : ModuleRules { public TechBlog9(ReadOnlyTargetRules Target) : base(Target) { PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs; PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore", "HTTP", "Json", "JsonUtilities" }); PrivateDependencyModuleNames.AddRange(new string[] { }); // Uncomment if you are using Slate UI // PrivateDependencyModuleNames.AddRange(new string[] { "Slate", "SlateCore" }); // Uncomment if you are using online features // PrivateDependencyModuleNames.Add("OnlineSubsystem"); // To include OnlineSubsystemSteam, add it to the plugins section in your uproject file with the Enabled attribute set to true } } たた、Engine.iniの末尟にHTTPリク ゚ス トの蚭定を蚘茉しおおきたす。 Engine.iniクリックで衚瀺 [HTTP] HttpTimeout=60 HttpConnectionTimeout=5 HttpReceiveTimeout=-1 HttpSendTimeout=-1 HttpMaxConnectionsPerServer=32 bEnableHttp=true bUseNullHttp=false HttpDelayTime=0.1 䞊蚘が終わったら、HTTPActor.hクラスを倉曎したす。 筆者はEpicのcommunityを参考に#include "CoreMinimal.h"を コメントアりト したした。 埌ほどブルヌプリント䞊で呌び出すため、HTTPリク ゚ス トを実行するHttpMethodはUFUNCTIONを、結果を栌玍する倉数である outputStringにはUPROPERTYを蚭定しおおきたす。 HTTPActor.hクリックで衚瀺 // Fill out your copyright notice in the Description page of Project Settings. #pragma once // #include "CoreMinimal.h" #include "GameFramework/Actor.h" #include "Http.h" #include "HTTPActor.generated.h" UCLASS() class TECHBLOG9_API AHTTPActor : public AActor { GENERATED_BODY() public: // Sets default values for this actor's properties AHTTPActor(); protected: // Called when the game starts or when spawned virtual void BeginPlay() override; public: // Called every frame virtual void Tick(float DeltaTime) override; FHttpModule* Http; UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Default) FString outputString; // HTTP通信 UFUNCTION(BlueprintCallable, Category = "Http") void HttpMethod(FString query, FString dir_name); // レスポンス埌のむベント凊理 void OnResponseReceived(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful); }; 最埌に、HTTPActor.cppに具䜓的な凊理内容を蚘述したす。 手順①で実装した API の仕様に合わせ、dir_nameむンデックスのフォルダ名ずquery質問の2぀を匕数ずしおHTTPリク ゚ス トを送り、返っおきた Json の「text」をoutputStringに栌玍する凊理を远加したした。 HTTPActor.cppクリックで衚瀺 // Fill out your copyright notice in the Description page of Project Settings. #include "HTTPActor.h" // Sets default values AHTTPActor::AHTTPActor() { // Set this actor to call Tick() every frame. You can turn this off to improve performance if you don't need it. PrimaryActorTick.bCanEverTick = true; Http = &FHttpModule::Get(); } // Called when the game starts or when spawned void AHTTPActor::BeginPlay() { Super::BeginPlay(); } // Called every frame void AHTTPActor::Tick(float DeltaTime) { Super::Tick(DeltaTime); } void AHTTPActor::HttpMethod(FString query, FString dir_name) { // Jsonデヌタ䜜成 TSharedPtr<FJsonObject> JsonObject = MakeShareable(new FJsonObject); JsonObject->SetStringField("dir_name", dir_name); JsonObject->SetStringField("query", query); // OutputStringに Json 栌玍 FString OutputString; TSharedRef<TJsonWriter > JsonWriter = TJsonWriterFactory<>::Create(&OutputString); FJsonSerializer::Serialize(JsonObject.ToSharedRef(), JsonWriter); // HTTPリク ゚ス ト TSharedRef Request = Http->CreateRequest(); Request->OnProcessRequestComplete().BindUObject(this, &AHTTPActor::OnResponseReceived); Request->SetURL(" http://localhost:8000/search "); Request->SetVerb("POST"); Request->SetHeader(TEXT("User-Agent"), "X-UnrealEngine-Agent"); Request->SetHeader("Content-Type", TEXT("application/ json ")); Request->SetContentAsString(OutputString); Request->ProcessRequest(); } void AHTTPActor::OnResponseReceived(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful) { TSharedPtr<FJsonObject> JsonObject; TSharedRef<TJsonReader<>> Reader = TJsonReaderFactory<>::Create(Response->GetContentAsString()); if (FJsonSerializer::Deserialize(Reader, JsonObject)) { // Json からtextを抜出 outputString = JsonObject->GetStringField("text"); } } 䞊蚘の倉曎が終わったらbuildしたす。 筆者は VS Code で線集しおいたため、Launchproject_nameEditor(development)でbuildしたした。 Unreal Engine が起動され、projectが衚瀺されれば C++ クラスの䜜成は完了です。 4.3 䜜成したクラスをブルヌプリント化 最埌に、䜜成した C++ クラスをブルヌプリントから呌び出すため、Blueprintクラスを䜜成したす。 名前は「BP_HTTPActor」ずしたした。 䜜成した「BP_HTTPActor」をワヌルドに配眮すれば完了です。 手順⑀. 質問送信時の凊理を実装 ここたででオブゞェクトの準備、GPTの準備、UIの準備が党お終わりたした。 最埌に、Brueprint䞊ですべおを繋ぎこむ凊理を「BP_C_Item_Book」のブルヌプリント䞊で実装したす。 以䞋が本手順で䜜成したブルヌプリントの党䜓像です。 長くお芋づらいため、それぞれ分けお解説したす。芋やすさのため、拡倧時にレむアりトを調敎しおいたす 3.1で䜜成したOnclickむベントチャットの送信ボタン抌䞋を起点ずしおブルヌプリントを䜜成したした。 5.1 「送信」ボタンが抌されたずき、送信した内容を衚瀺する 送信ボタンが抌䞋されるず、TextBoxの倀を取埗しお新しい 吹き出し ずしお衚瀺したす。 ここでは、3.2.1で䜜成した自分甚の 吹き出し を䜜成し、TextBoxの倀を入れおScroll Boxの䞭に入れおいたす。 5.2 HTTPリク ゚ス トを送信する 4.3で配眮した「BP_HTTPActor」クラスを呌び出し、HTTPMethodを実行したす。ナヌザヌの入力ず りィゞェット の持぀倉数「WidgetAttributes」の倀をGPTの API 偎ぞリク ゚ス トずしお送信しおいたす。 このずき、outputStringの倉数にGPTのレスポンスが栌玍されるたでは、Delayずloopを䜿甚しおwaitしたす。 たた、HTTPリク ゚ス トを送った埌、自身が入力したテキストをクリアする凊理を蚘述したした。 5.3 GPTの返信内容を衚瀺する 5.1ず同様、今床はGPT甚の 吹き出し を䜜成し、outputStringの倀を入れおScroll Boxの䞭に栌玍したす。 最埌に、outputStringの倉数をクリアすれば凊理は完了です。 デモ動画 本のオブゞェクトに近づくこずでタッチが可胜になり、タッチするこずでUIが衚瀺されたす。 さらにUI䞊で問い合わせるず、オブゞェクトの関連文章を基に回答できおいるこずを確認できたす。 これたでの実装により、ストレスの少ない問い合わせをUE䞊で実珟するこずができたした。 おわりに 今回はチャットUIを甚いた、オブゞェクトに察する問い合わせを実装したした。 以䞋、実装を担圓した岡厎ず若本のそれぞれの所感ずなりたす。 岡厎今回の実装で、ブルヌプリントでのチェンゞむベントオンクリックなどや、 コリゞョン むベントに関する䜿甚方法の基瀎的な孊習を行えたした。今埌プロダクトを䜜成する際、プレむダヌ起因で任意の情報を出したり、プレむダヌ情報を倉曎させるずいった機胜を䜜成する際に、数倚く䜿うこずになる機胜を䜜成するこずができたため、より応甚的な䜿い方など匕き続き調査したいず思いたす。 若本UE初孊者であり、今回広く Python の実装/ブルヌプリントの実装/ C++ の実装を行いたしたが、慣れお以降はストレスレスに開発を行うこずができたした。今回の実装を発展させ、非同期凊理の実装や䌚話履歎の入れ蟌み、UIのリッチ化など、さらに䜜りこむこずによっおよりよいナヌザヌ䜓隓が埗られるこずが期埅できるず感じたした。 珟圚ISIDは web3領域のグルヌプ暪断組織 を立ち䞊げ、Web3および メタバヌス 領域のR&Dを行っおおりたすカテゎリヌ「3DCG」の蚘事は こちら 。 もし本領域にご興味のある方や、䞀緒にチャレンゞしおいきたい方は、ぜひお気軜にご連絡ください 私たちず同じチヌムで働いおくれる仲間を、是非お埅ちしおおりたす ISID採甚ペヌゞ 執筆 @wakamoto.ryosuke 、レビュヌ @yamada.y  Shodo で執筆されたした 
こんにちは、金融゜リュヌション事業郚の岡厎、若本です。 3DCG空間では、オブゞェクトに関する情報を過床に衚瀺し過ぎるこずはナヌザヌ䜓隓を阻害する芁因になりたす。その䞀方で、あたりに情報が少ないず䞖界芳の欠劂に繋がりたす。そのため、質問応答などのナヌザヌが求める情報を必芁なだけ衚瀺する仕組みが求められるこずがありたす。 そこで今回は、 Unreal Engine UE䞊のオブゞェクトの情報に぀いおChatGPTに問い合わせる実装を行っおみたいず思いたす。 䞋図のように、あらかじめ 栌玍した関連文献を基に、UE䞊でObjectに察する質問をChatGPTに答えさせる こずがゎヌルです。 準備するもの 凊理抂芁 手順①. GPTを甚いたAPIを䜜成 1.1 APIの環境構築 1.2 ゚ンドポむントの䜜成 1.3 機胜の䜜成 1.4 動䜜確認デヌタ登録 1.5 動䜜確認怜玢 手順②. オブゞェクトの準備 2.1 チャット可胜な範囲に入ったずき、アむコンを衚瀺する機胜 2.2 オブゞェクトActorをクリックしおチャット画面を衚瀺する機胜 手順③. チャットUIの実装 3.1 チャットのペヌゞUIを実装 3.2 チャットの吹き出しUIを実装 3.2.1 自分のチャットを衚瀺する 3.2.2 GPTの返信を衚瀺する 手順④. HTTPリク゚スト甚のC++クラス䜜成 4.1 C++クラスを䜜成 4.2 C++クラスを線集 4.3 䜜成したクラスをブルヌプリント化 手順⑀. 質問送信時の凊理を実装 5.1 「送信」ボタンが抌されたずき、送信した内容を衚瀺する 5.2 HTTPリク゚ストを送信する 5.3 GPTの返信内容を衚瀺する デモ動画 おわりに 準備するもの Unreal Engine 5.2.0 OpenAI API GPT-3.5-turbo0301を䜿甚したす。 事前にナヌザヌ登録ず、 API キヌの発行を実斜しおおきたす。 FastAPI API ずしお、䞋蚘の機胜を䜜成したす。 オブゞェクトに関連したドキュメントを登録する オブゞェクト名ず質問を䞎え、答えを返す Docker API の環境構築に䜿甚したす。 凊理抂芁 䞋蚘の凊理を実装したす。 それぞれの機胜に぀いお、内の担圓者が䜜業を行いたした。 コリゞョン 刀定岡厎 チャット可胜な範囲に入ったずき、アむコンを衚瀺したす。 チャットUIの衚瀺岡厎、若本 コリゞョン 状態でオブゞェクトActorをクリックし、チャット画面を衚瀺したす。 GPTぞのリク ゚ス ト若本 UE䞊の情報を基に、GPTにリク ゚ス トを送りたす。 結果の衚瀺若本 API から返っおきたレスポンスをUIに反映したす。 以降では、各機胜の抂芁や実装に぀いお説明したす。 手順①. GPTを甚いた API を䜜成 本手順は若本が説明したす。 GPTはUE内のオブゞェクトに぀いお情報を持たないため、そのたたChatGPTに問い合わせおも的確に質問に答えるこずができたせん。 そこで、以䞋の2぀の機胜を持぀ API を䜜成したす。今回は Unreal Engine を実行するPC䞊で構築するこずずしたした。 オブゞェクトに関連したドキュメントを登録するむンデックス登録 オブゞェクト名ず質問を䞎え、答えを返す問い合わせ それぞれの機胜に぀いお゚ンドポむントを甚意し、HTTPリク ゚ス トで実行できるようにしたす。 最終的には䞋蚘のようなファむル構成ずなりたす。 1.1 API の環境構築 たずはDockerで API のベヌスずなる環境を䜜成したす。 以䞋は最終的に䜿甚したDockerの蚭定ファむルです。 フォルダ䞊でdocker containerをupするずアプリケヌションが起動したす http://localhost:8000/docs/ からアクセスするこずができたす。 docker-compose.ymlクリックで衚瀺 version: '3' services: backend: container_name: gpt-api build: context: . dockerfile: ./Dockerfile volumes: \- type: bind source: './api' target: '/src' ports: \- 8000:8000 stdin_open: true Dockerfileクリックで衚瀺 FROM python:3.8-slim-buster RUN pip install --upgrade pip RUN pip install --upgrade setuptools COPY requirements.txt / RUN pip install -r requirements.txt WORKDIR /src CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--reload"] requirements.txtクリックで衚瀺 numpy openai transformers llama_index langchain fastapi uvicorn[standard] pandas 1.2 ゚ンドポむントの䜜成 次に、 API が持぀2぀の機胜に぀いお゚ンドポむントを䜜成しおいきたす。 routers/index.pyにむンデックス登録の゚ンドポむントを、routers/search.pyに問い合わせ甚の゚ンドポむントを蚘述しおいたす。 main.pyクリックで衚瀺 from fastapi import FastAPI from routers import search, index import os os.environ[ 'OPENAI_API_KEY' ] = 'YOUR_OPENAI_API_TOKEN' app = FastAPI() app.include_router(search.router) app.include_router(index.router) routers/index.pyクリックで衚瀺 from fastapi import APIRouter from functions.index_operation import create_index router = APIRouter() @ router.post ( "/index" ) def service_create_index (dir_name: str ): _ = create_index(dir_name) return 200 routers/search.pyクリックで衚瀺 from fastapi import APIRouter from pydantic import BaseModel from functions.search_operation import search router = APIRouter() class SearchRequestItem (BaseModel): dir_name: str = None query: str = None class SearchResponseItem (BaseModel): text: str = None @ router.post ( "/search" ) def service_search (item: SearchRequestItem) -> SearchResponseItem: response_text = search(item.dir_name, item.query) response = SearchResponseItem(text=response_text) return response 1.3 機胜の䜜成 最埌に、それぞれの゚ンドポむントで呌び出される凊理を蚘述したす。 functions/index_operation.pyにむンデックス登録の凊理を、functions/search_operation.pyに問い合わせの凊理を実装したした。functions/ api _handling.pyでは、䜿甚するmodelgpt-3.5-turboの蚭定を行っおいたす。 ここで、むンデックス登録はドキュメントをあらかじめベクトルにしおおき、問い合わせが来た際に怜玢するために行いたす。 今回の実装では、フォルダ内の CSV /PDF/TXTファむルに぀いお読み蟌みベクトル化できるようにしたした。 ※デヌタの倧きさや暩限によっお゚ラヌが発生する可胜性はありたすが、今回その゚ラヌハンドリングたでは行っおいたせん。 functions/api_handling.pyクリックで衚瀺 from llama_index import LLMPredictor, ServiceContext, PromptHelper from langchain.chat_models import ChatOpenAI def get_service_context (): llm_predictor = LLMPredictor( llm=ChatOpenAI( temperature= 0 , model_name= "gpt-3.5-turbo" , max_tokens= 512 ) ) prompt_helper = PromptHelper( max_input_size= 4096 , num_output= 256 , max_chunk_overlap= 20 ) service_context = ServiceContext.from_defaults(llm_predictor=llm_predictor, prompt_helper=prompt_helper) return service_context functions/index_operation.pyクリックで衚瀺 from llama_index import GPTVectorStoreIndex, SimpleDirectoryReader, download_loader from functions.api_handling import get_service_context import glob def create_index (dir_name: str ): input_dir = f 'data/{dir_name}' service_context = get_service_context() index = None all_files = glob.glob(f '{input_dir}/*' ) for file in all_files: if file .endswith( '.csv' ): Reader = download_loader( "SimpleCSVReader" ) loader = Reader() docs = loader.load_data( file = file ) if file .endswith( '.pdf' ): Reader = download_loader( "CJKPDFReader" ) loader = Reader() docs = loader.load_data( file = file ) if file .endswith( '.txt' ): loader = SimpleDirectoryReader(input_files=[ file ], required_exts=[ 'txt' ]) docs = loader.load_data() if index is None : index = GPTVectorStoreIndex.from_documents( docs, service_context=service_context ) else : for doc in docs: index.insert(doc) # indexを保存する if index: index.storage_context.persist(f "index/{dir_name}" ) return index functions/search_operation.pyクリックで衚瀺 from llama_index import StorageContext, load_index_from_storage from functions.api_handling import get_service_context def load_index (dir_name: str ): \ # むンデックスの読み蟌み storage_context = StorageContext.from_defaults(persist_dir=f "index/{dir_name}" ) index = load_index_from_storage(storage_context, service_context=get_service_context()) return index def search (dir_name: str , query: str ): index = load_index(dir_name) \ # ク゚リ゚ンゞンの䜜成 query_engine = index.as_query_engine() text_query = f '䞋蚘の質問に察しお、敬語で簡朔に答えおください。 \n {query}' response = query_engine.query(text_query) return response.response 1.4 動䜜確認デヌタ登録 䞊蚘のコヌドを蚘述埌、動䜜確認を実斜したす。 アプリを立ち䞊げ埌、以䞋のURLにアクセスするこずでSwagger UI䞋図が衚瀺され、盎接動䜜確認をするこずができたす。 http://localhost:8000/docs/ たずはデヌタを登録しおみたしょう。 dataフォルダ䞋に新しいフォルダを䜜成したす。ここでは、「BookInfo」ずいうフォルダを䜜成したした。 なお、怜玢時にもこのフォルダ名を䜿甚したす。 ※ フォルダ名をもずにした管理やリク ゚ス トは少し危険ですが、今回は簡単な怜蚌のため実装を簡略化しおいたす。 フォルダ䜜成埌、「BookInfo」フォルダ内に CSV /PDF/TXTファむルを栌玍したす。 今回は自䜜のsample.txtを栌玍したした。ファむルの䞭身は䞋蚘のようになっおいたす。 栌玍埌、 http://localhost:8000/docs/ にアクセスし、/indexから䞋蚘のようにむンデックス登録を実行したす。 indexフォルダ䞋に同名のフォルダが䜜成されたす。 䞋蚘の3皮類の json ファむルが入っおいれば正垞にむンデックスが登録できおいたす。 1.5 動䜜確認怜玢 怜玢に぀いおも動䜜確認しおみたす。 http://localhost:8000/docs/ にアクセスし、/searchを䞋蚘のように実行しおみたす。 少しするずResponseが返っおきたす。登録した文章の情報を基に答えられおおり、動䜜が確認できたした。 耇数むンデックスを登録した堎合はdir_nameのパラメヌタを倉えるこずで、怜玢察象を倉えお回答させるこずができたす。 手順②. オブゞェクトの準備 次に、UE䞊で䜿甚するオブゞェクトの準備を行っおいきたす。本手順は岡厎が説明したす。 ここでは以䞋の機胜や画面を䜜成したす。 チャット可胜な範囲に入ったずき、アむコンを衚瀺する機胜 オブゞェクトActorをクリックしおチャット画面を衚瀺する機胜 それでは各機胜に぀いお説明しおいきたす。 2.1 チャット可胜な範囲に入ったずき、アむコンを衚瀺する機胜 今回はゲヌムでよくあるような、プレむダヌがオブゞェクトに䞀定の距離近づくず、「ここにアむテムがありたす」ずいう意味のアむコンが衚瀺される凊理の䜜成。 たた、さらに近づくず「アむテムにクリックできたす」ずいう意味のアむコンに倉曎し、アむテムがクリックできるようになる凊理を䜜っおいきたす。 たずはプレむダヌがオブゞェクトに近づいたこずを刀定する凊理を䜜成したす。 以前 こちらの蚘事UE5でコリゞョン衝突刀定機胜を䜿っお色々な機胜を䜜成しおみた でも玹介したしたが、 コリゞョン 機胜を䜿っおいきたす。 たずはアクタヌブルヌプリントを䜜成し「BP_P_Item」ず 呜名 したす。 コンポヌネント 远加から、「 Sphere Collision」を2぀远加し、それぞれ「BigSphere」ず「SmallSphere」ずしたす。 この2぀の コリゞョン は、「BigSphere」にプレむダヌが入った時、アむテムの䞊にアむコンを衚瀺し、「SmallSphere」に入った時、アむコンを倉曎しおアむテムをクリックできる状態に倉曎するために䜿甚したす。 䞊蚘を再珟するため、「BigSphere」を倧きく䜜成し、その䞭に「SmallSphere」を配眮しおいたす。 次にアむテムの䞊に衚瀺するアむコンを䜜成したす。 ナヌザヌむンタヌフェヌス から りィゞェット ブルヌプリントを䜜成し、「WBP_ItemIcon」ず 呜名 したす。 䜜成埌、䞋蚘画像のようにアむコンの りィゞェット を䜜成し、「Is Variable」にチェックを入れおおきたす。 次に コリゞョン 甚のブルヌプリントBP_P_Itemに戻り、「SmallSphere」の䞋に「Static Mesh」ず「 Widget 」の コンポヌネント を远加したす。 画像では Widget の名前をWidgetItemIconに倉曎しおいたす。 「 Widget 」の コンポヌネント の詳现タブより、「 Widget Class」を先ほど䜜成した りィゞェット ブルヌプリントの名前WBP_ItemIconに倉曎したす。 たた、同じ詳现タブより「 レンダリング 」内の「Visible」のチェックを倖しおおきたす。 次にむベントグラフに移動し、「On Component Begin OverlapBigSphere」ず「On Component End OverlapBigSphere」のノヌドを䜜成したす。 このノヌドは、巊偎の コンポヌネント 䞀芧から「BigSphere」を右クリックし、むベント远加から遞ぶこずができたす。 䞋蚘画像のように、プレむダヌが「BigSphere」内に入った際に、デフォルトで芋えない蚭定にしおおいたアむコン りィゞェット の「Visibility」の倀を倉曎し、画面䞊に レンダリング させる凊理を䜜成したす。 同様にプレむダヌが「BigSphere」からプレむダヌが出た際にアむコンを芋えなくする凊理も远加したす。 ここたでで、䞋蚘のようにプレむダヌの䜍眮によっおアむコンが衚瀺されたり、消したりする凊理ができたした。 続いおさらに近づいた際SmallSphereに入った際にアむコンの衚瀺を倉曎する凊理を䜜成したす。 アむコン甚のブルヌプリントWBP_ItemIconに戻り、アむコンを倉曎するための関数を「AreaEvent」ずいう名前で䜜成したす。 「AreaEvent」には匕数ずしお「IsInside」ずいうBoolean倀を蚭定しおおきたす。 䞋蚘画像のように「AreaEvent」から「IsInside」の倀によっおむメヌゞ画像を倉曎するために「Set Brush from Texture」ノヌドを繋ぎたす。 「遞択する」ノヌドに、Falseの堎合にはSmallSphereに入っおいない時甚の画像を蚭定し、Trueの堎合はSmallSphereに入っおいる時甚の画像を蚭定したす。 本プロゞェクトの堎合は䞋蚘画像の「arrow」ず「circle」を䜿甚 「Set Brush from Texture」のタヌゲットには りィゞェット ブルヌプリントでimageを䜜成した際に 呜名 した名前の倉数が巊偎のタブに远加されおいるのでそれを䜿甚したす。 次に コリゞョン 甚のブルヌプリントBP_P_Itemに戻り、「SmallSphere」に入った際にアむコン りィゞェット の画像を倉えるために「AreaEvent」の関数を䜿甚する凊理を䜜成したす。 「SmallSphere」から「On Component Begin OverlapSmallSphere」ず「On Component End OverlapSmallSphere」のノヌドを䜜成したす。 䞋蚘画像のように「On Component Begin OverlapSmallSphere」ず「On Component End OverlapSmallSphere」を「Area Event」繋げたす。 「Area Event」の匕数ずしおプレむダヌが「SmallSphere」の内郚にいるかどうかの倀を倉数にしお「In Small Collision Area」ずしたす。 埌述の凊理で䜿甚するため倉数にしおおきたす。 ここたでの凊理で「BigSphere」の内郚にいる時に矢印のアむコンを出珟させ、「SmallSphere」の内郚にいる時に二重䞞のアむコンに倉曎させる凊理ができたした。 2.2 オブゞェクトActorをクリックしおチャット画面を衚瀺する機胜 続いお実際にオブゞェクトを配眮し、「SmallSphere」の内郚にプレむダヌがいる時のみオブゞェクトにクリックができる機胜ず、クリックした埌にチャット画面を衚瀺する機胜を䜜成したす。 たず初めに、プレむ画面䞊にマりスカヌ゜ルを出珟させ、クリックを有効にするために、レベルブルヌプリントを開きたす。䞋の画像のように「むベントBeginPlay」に「Show Mouse Cursor」ず「Enable Click Events」の蚭定をどちらもオンに倉曎したす。 これでプレむ画面䞊にマりスカヌ゜ルを出珟させ、クリックができるように蚭定されたした。 次に、先ほど䜜成した コリゞョン 甚のブルヌプリントBP_P_Itemから子ブルヌプリントを䜜成したす。 芪のブルヌプリント䞊で右クリックを行い、「子ブルヌプリントクラスを䜜成したす」ボタンを抌し「BP_C_Item_Book」ず 呜名 したす。 子ブルヌプリントを開き、芪ブルヌプリント䜜成時は特に線集しなかった「Static Mesh」に任意のメッシュ玠材を遞びたす。 今回は「Quixel Bridge」内のメッシュを䜿甚したした。 こちらの蚘事Unreal Engine 5 を䜿っおワヌルドの地圢を䜜成しおみたした で、「Quixel Bridge」の䜿甚方法を玹介しおいたす。 むベントグラフを開き、「むベント ActorOnClicked」のノヌドを远加したす。 「In Small Collision Area」の倀からIF文を䜜成し、プレむダヌが「Small Collision」の䞭にいる堎合のみチャット画面甚の りィゞェット を䜜成するために「 りィゞェット を䜜成」ノヌドを繋げ、「Add Viewport」を付けたす。 ここでチャット画面甚の りィゞェット ブルヌプリントを䜜成したす。 チャット機胜の詳现は埌述するので、ここでは倧枠だけ䜜成したす。 クリックしたいオブゞェクト甚今回は本のオブゞェクトの りィゞェット ブルヌプリントを䜜成し、「WBP_BookInfo」ず 呜名 したす。 「 Canvas Panel」内に、本のタむトルや、サムネむル甚画像を眮き、チャット甚のスペヌスも確保したす。 こちらの蚘事UE5でコリゞョン衝突刀定機胜を䜿っお色々な機胜を䜜成しおみた で、 りィゞェット の䜜成方法に぀いお觊れおいるので参考にしおください。 今回は閉じるボタン×マヌクを抌すこずで りィゞェット を閉じる挙動にしたいので、「 Canvas Panel」内に、ボタンも远加したす。 パレット远加欄に「Button」ず怜玢し、ボタン内郚に「Text」で「×」を蚘述し閉じるボタンを䜜成したした。 ボタンの名前を「CloseButton」に倉曎しおありたす。 階局タブより「CloseButton」を遞択䞭に、詳现タブ内のむベントOn Click の远加ボタンを抌し、むベントグラフに「On ClickCloseButton」ノヌドを远加したす。 「On ClickCloseButton」ノヌドに「Remove from Parent」を繋ぎ、閉じるボタンが抌された時チャット画面甚の りィゞェット が閉じるようにしたす。 「BP_C_Item_Book」を再び開き、「 りィゞェット を䜜成」ノヌドの「Class」を今䜜成した「WBP_BookInfo」に倉曎したす。 これで画面䞊の本のオブゞェクトをクリックした際に、画面䞊にチャット画面の りィゞェット が珟れ、閉じるボタンで閉じる凊理ができたした。 ただこのたただず、本のオブゞェクトをクリックした分だけ りィゞェット が開いおしたうので、珟圚 りィゞェット が開かれおいる状態かどうかを刀別するために「IsOpenDetailWidget」ずいう倉数を䜜成し、䞋蚘画像のようにフラグずしお䜿甚したす。 「WBP_BookInfo」でチャット画面を閉じた埌で「IsOpenDetailWidget」の倉数をFalseに戻しおおきたす。 ここたでで、オブゞェクトActorをクリックしおチャット画面を衚瀺する機胜が完成したした。 手順③. チャットUIの実装 ここたでで、ベヌスずなるUEのオブゞェクトやUI、倖郚 API の䜜成が完了したした。 以降の手順は若本が説明したす。 本手順では、GPTのリク ゚ス トずのやり取りに必芁になる远加のUIを甚意しおいきたす。 3.1 チャットのペヌゞUIを実装 次に、チャットのやり取りを行うためのUIを䜜成したす。 チャットの 吹き出し は動的に远加されおいくため、ここでは必芁な コンポヌネント のみ甚意したす。 たずはチャット画面のベヌスずしお、手順②で䜜成した「WBP_BookInfo」にText_box、button、scroll boxを远加したす。 Text_boxずscroll boxをわかりやすい倉数名に倉曎しおおきたす。 たた、呌び出すために「Is Variable」にチェックを入れおおきたす。 次に、buttonにOnClickむベントを远加したす。 詳现画面のOnClickを抌すこずで、OnClickむベントが有効になりたす。こちらを抌しおおきたしょう。 詳现な凊理は埌の手順で䜜成したす。 たた、ここでItemの属性倀を蚭定しおおきたす。 属性倀を甚いお埌段でGPTに問い合わせる際の怜玢察象を倉曎するため、その䞋準備ずなりたす。 たずは Widget の「WBP_BookInfo」に倉数「WidgetAttributes」を䜜成したす。 次に、「BP_C_ItemBook」クラスに属性倀を蚭定しおおきたす。ここでは倉数名を「ItemAttributes」、デフォルト倀を「BookInfo」ずしたした。 最埌に、「BP_C_ItemBook」のブルヌプリント䞊で、 りィゞェット の䜜成時に自身の持぀「ItemAttributes」を「WidgetAttributes」にセットする凊理を远加したす。これで完成です。 3.2 チャットの 吹き出し UIを実装 3.1でやりずりを行う画面は䜜成したしたが、メッセヌゞを入れるための 吹き出し がありたせん。 そこで、別途チャットの 吹き出し 甹UI自分甚/GPT甚を䜜成したす。 それぞれ ナヌザヌむンタヌフェヌス から りィゞェット ブルヌプリントを䜜成したしょう。 䜜成埌、 Canvas PanelずText Box(Multi-Line)を配眮したす。 こちらもText Boxはわかりやすい倉数名に倉曎し、「Is Variable」にチェックを入れおおきたす。 3.2.1 自分のチャットを衚瀺する 自分甚の 吹き出し は䞋蚘のように蚭定したした。背景色ずFontの色/サむズを調敎しおいたす。 3.2.2 GPTの返信を衚瀺する GPT甚の 吹き出し も同様に䜜成したす。 手順④. HTTPリク ゚ス ト甚の C++ クラス䜜成 4.1 C++ クラスを䜜成 Unreal Engine では、デフォルトでHTTPぞのリク ゚ス トを送信するクラスは甚意されおいないため、 API にリク ゚ス トを送り、レスポンスを受け取るクラスを新芏䜜成する必芁がありたす。 ブルヌプリントでは実珟できないため、今回は C++ で実装するこずずしたした。 たずは、ツヌルから新芏の C++ クラスを䜜成したす。 ここで、芪クラスは「Actor」、名前は「HTTPActor」ずしたした。 クラスを䜜成するず、゚ディタに飛ばされたす。゚ディタ䞊でクラスを線集しおいきたす。 4.2 C++ クラスを線集 API にリク ゚ス トを送り、レスポンスを受け取るための凊理を远加しおいきたす。 たずは、Build.csのDependencyModuleに"HTTP", " Json ", "JsonUtilities"の3぀を远加したす。 Build.csクリックで衚瀺 // Fill out your copyright notice in the Description page of Project Settings. using UnrealBuildTool; public class TechBlog9 : ModuleRules { public TechBlog9(ReadOnlyTargetRules Target) : base(Target) { PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs; PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore", "HTTP", "Json", "JsonUtilities" }); PrivateDependencyModuleNames.AddRange(new string[] { }); // Uncomment if you are using Slate UI // PrivateDependencyModuleNames.AddRange(new string[] { "Slate", "SlateCore" }); // Uncomment if you are using online features // PrivateDependencyModuleNames.Add("OnlineSubsystem"); // To include OnlineSubsystemSteam, add it to the plugins section in your uproject file with the Enabled attribute set to true } } たた、Engine.iniの末尟にHTTPリク ゚ス トの蚭定を蚘茉しおおきたす。 Engine.iniクリックで衚瀺 [HTTP] HttpTimeout=60 HttpConnectionTimeout=5 HttpReceiveTimeout=-1 HttpSendTimeout=-1 HttpMaxConnectionsPerServer=32 bEnableHttp=true bUseNullHttp=false HttpDelayTime=0.1 䞊蚘が終わったら、HTTPActor.hクラスを倉曎したす。 筆者はEpicのcommunityを参考に#include "CoreMinimal.h"を コメントアりト したした。 埌ほどブルヌプリント䞊で呌び出すため、HTTPリク ゚ス トを実行するHttpMethodはUFUNCTIONを、結果を栌玍する倉数である outputStringにはUPROPERTYを蚭定しおおきたす。 HTTPActor.hクリックで衚瀺 // Fill out your copyright notice in the Description page of Project Settings. #pragma once // #include "CoreMinimal.h" #include "GameFramework/Actor.h" #include "Http.h" #include "HTTPActor.generated.h" UCLASS() class TECHBLOG9_API AHTTPActor : public AActor { GENERATED_BODY() public: // Sets default values for this actor's properties AHTTPActor(); protected: // Called when the game starts or when spawned virtual void BeginPlay() override; public: // Called every frame virtual void Tick(float DeltaTime) override; FHttpModule* Http; UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Default) FString outputString; // HTTP通信 UFUNCTION(BlueprintCallable, Category = "Http") void HttpMethod(FString query, FString dir_name); // レスポンス埌のむベント凊理 void OnResponseReceived(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful); }; 最埌に、HTTPActor.cppに具䜓的な凊理内容を蚘述したす。 手順①で実装した API の仕様に合わせ、dir_nameむンデックスのフォルダ名ずquery質問の2぀を匕数ずしおHTTPリク ゚ス トを送り、返っおきた Json の「text」をoutputStringに栌玍する凊理を远加したした。 HTTPActor.cppクリックで衚瀺 // Fill out your copyright notice in the Description page of Project Settings. #include "HTTPActor.h" // Sets default values AHTTPActor::AHTTPActor() { // Set this actor to call Tick() every frame. You can turn this off to improve performance if you don't need it. PrimaryActorTick.bCanEverTick = true; Http = &FHttpModule::Get(); } // Called when the game starts or when spawned void AHTTPActor::BeginPlay() { Super::BeginPlay(); } // Called every frame void AHTTPActor::Tick(float DeltaTime) { Super::Tick(DeltaTime); } void AHTTPActor::HttpMethod(FString query, FString dir_name) { // Jsonデヌタ䜜成 TSharedPtr<FJsonObject> JsonObject = MakeShareable(new FJsonObject); JsonObject->SetStringField("dir_name", dir_name); JsonObject->SetStringField("query", query); // OutputStringに Json 栌玍 FString OutputString; TSharedRef<TJsonWriter > JsonWriter = TJsonWriterFactory<>::Create(&OutputString); FJsonSerializer::Serialize(JsonObject.ToSharedRef(), JsonWriter); // HTTPリク ゚ス ト TSharedRef Request = Http->CreateRequest(); Request->OnProcessRequestComplete().BindUObject(this, &AHTTPActor::OnResponseReceived); Request->SetURL(" http://localhost:8000/search "); Request->SetVerb("POST"); Request->SetHeader(TEXT("User-Agent"), "X-UnrealEngine-Agent"); Request->SetHeader("Content-Type", TEXT("application/ json ")); Request->SetContentAsString(OutputString); Request->ProcessRequest(); } void AHTTPActor::OnResponseReceived(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful) { TSharedPtr<FJsonObject> JsonObject; TSharedRef<TJsonReader<>> Reader = TJsonReaderFactory<>::Create(Response->GetContentAsString()); if (FJsonSerializer::Deserialize(Reader, JsonObject)) { // Json からtextを抜出 outputString = JsonObject->GetStringField("text"); } } 䞊蚘の倉曎が終わったらbuildしたす。 筆者は VS Code で線集しおいたため、Launchproject_nameEditor(development)でbuildしたした。 Unreal Engine が起動され、projectが衚瀺されれば C++ クラスの䜜成は完了です。 4.3 䜜成したクラスをブルヌプリント化 最埌に、䜜成した C++ クラスをブルヌプリントから呌び出すため、Blueprintクラスを䜜成したす。 名前は「BP_HTTPActor」ずしたした。 䜜成した「BP_HTTPActor」をワヌルドに配眮すれば完了です。 手順⑀. 質問送信時の凊理を実装 ここたででオブゞェクトの準備、GPTの準備、UIの準備が党お終わりたした。 最埌に、Brueprint䞊ですべおを繋ぎこむ凊理を「BP_C_Item_Book」のブルヌプリント䞊で実装したす。 以䞋が本手順で䜜成したブルヌプリントの党䜓像です。 長くお芋づらいため、それぞれ分けお解説したす。芋やすさのため、拡倧時にレむアりトを調敎しおいたす 3.1で䜜成したOnclickむベントチャットの送信ボタン抌䞋を起点ずしおブルヌプリントを䜜成したした。 5.1 「送信」ボタンが抌されたずき、送信した内容を衚瀺する 送信ボタンが抌䞋されるず、TextBoxの倀を取埗しお新しい 吹き出し ずしお衚瀺したす。 ここでは、3.2.1で䜜成した自分甚の 吹き出し を䜜成し、TextBoxの倀を入れおScroll Boxの䞭に入れおいたす。 5.2 HTTPリク ゚ス トを送信する 4.3で配眮した「BP_HTTPActor」クラスを呌び出し、HTTPMethodを実行したす。ナヌザヌの入力ず りィゞェット の持぀倉数「WidgetAttributes」の倀をGPTの API 偎ぞリク ゚ス トずしお送信しおいたす。 このずき、outputStringの倉数にGPTのレスポンスが栌玍されるたでは、Delayずloopを䜿甚しおwaitしたす。 たた、HTTPリク ゚ス トを送った埌、自身が入力したテキストをクリアする凊理を蚘述したした。 5.3 GPTの返信内容を衚瀺する 5.1ず同様、今床はGPT甚の 吹き出し を䜜成し、outputStringの倀を入れおScroll Boxの䞭に栌玍したす。 最埌に、outputStringの倉数をクリアすれば凊理は完了です。 デモ動画 本のオブゞェクトに近づくこずでタッチが可胜になり、タッチするこずでUIが衚瀺されたす。 さらにUI䞊で問い合わせるず、オブゞェクトの関連文章を基に回答できおいるこずを確認できたす。 これたでの実装により、ストレスの少ない問い合わせをUE䞊で実珟するこずができたした。 おわりに 今回はチャットUIを甚いた、オブゞェクトに察する問い合わせを実装したした。 以䞋、実装を担圓した岡厎ず若本のそれぞれの所感ずなりたす。 岡厎今回の実装で、ブルヌプリントでのチェンゞむベントオンクリックなどや、 コリゞョン むベントに関する䜿甚方法の基瀎的な孊習を行えたした。今埌プロダクトを䜜成する際、プレむダヌ起因で任意の情報を出したり、プレむダヌ情報を倉曎させるずいった機胜を䜜成する際に、数倚く䜿うこずになる機胜を䜜成するこずができたため、より応甚的な䜿い方など匕き続き調査したいず思いたす。 若本UE初孊者であり、今回広く Python の実装/ブルヌプリントの実装/ C++ の実装を行いたしたが、慣れお以降はストレスレスに開発を行うこずができたした。今回の実装を発展させ、非同期凊理の実装や䌚話履歎の入れ蟌み、UIのリッチ化など、さらに䜜りこむこずによっおよりよいナヌザヌ䜓隓が埗られるこずが期埅できるず感じたした。 珟圚ISIDは web3領域のグルヌプ暪断組織 を立ち䞊げ、Web3および メタバヌス 領域のR&Dを行っおおりたすカテゎリヌ「3DCG」の蚘事は こちら 。 もし本領域にご興味のある方や、䞀緒にチャレンゞしおいきたい方は、ぜひお気軜にご連絡ください 私たちず同じチヌムで働いおくれる仲間を、是非お埅ちしおおりたす 私たちは共に働いおいただける仲間を募集しおいたす みなさたのご応募、お埅ちしおいたす 最新テクノロゞヌ事業䌁画・掚進担圓 ◎Web3/メタバヌス/AI AI゜リュヌション開発゚ンゞニア ◎AIビゞネス創出・掚進に関われる    株匏䌚瀟電通総研 新卒採甚 執筆 @wakamoto.ryosuke 、レビュヌ @yamada.y  Shodo で執筆されたした 
こんにちは、ISID金融゜リュヌション事業郚の孫です。 この蚘事は、私が Unreal Engine 以䞋UEのネットワヌク同期以䞋 レプリケヌション に関する知識を孊んだ知芋です。 UEの レプリケヌション 機胜は、 マルチプレむダヌ ゲヌムの開発においお非垞に重芁なコアな機胜です。 Web䞊に公開されおいるUEの レプリケヌション プログラミングは、珟圚BluePrintを甚いたビゞュアルプログラミングが䞻ずなっおいたす。 確かにBluePrintは䟿利で迅速な開発が可胜ですが、UEの内郚動䜜ロゞックをより深く理解するためには C++ プログラミングが䞍可欠です。 この蚘事では、 C++ を䜿甚しおシンプルなネットワヌク同期のデモを実装する方法を玹介したす。このデモの開発を通じお、UEの レプリケヌション 機胜の実装方法を孊ぶこずができたす。 はじめに UEの レプリケヌション 郚分に぀いお觊れるず、Dedicated Serverずいう抂念をたず理解する必芁がありたす。 ゲヌムネットワヌク アヌキテクチャ においおDedicated Serverが導入された背景に぀いおは、 金融゜リュヌション事業郚の山䞋さんの蚘事 を参照しおいただければず思いたす。 UEのDedicated Serverに぀いお、UEのクラむアントコヌドずサヌバヌコヌドは䞀䜓であるこずが特城です。 通垞、䞀般的なフロント゚ンドずバック゚ンドの分離ずは異なり、UEではクラむアントずサヌバヌが同じプロゞェクト内に存圚したす。このため、クラむアントずサヌバヌのコヌドは混圚しおいるこずになりたす。 ※コヌドの間で以䞋のマクロを䜿っおサヌバヌコヌドずクラむアントコヌドの区別が可胜 WITH_EDITOR : コヌドが゚ディタ環境で動䜜しおいるずきにTrueになりたす。 UE_SERVER : コヌドがサヌバヌ環境で動䜜しおいるずきにTrueになりたす。 UE_CLIENT : コヌドがクラむアント環境で動䜜しおいるずきにTrueになりたす。 Dedicated Serverが必芁な理由は、C/Sクラむアント/サヌバヌモヌドではサヌバヌがクラむアントの業務も担圓するため、運甚負荷が高くなるからです。Dedicated Serverの導入により、クラむアントずサヌバヌの圹割が分離され、負荷を軜枛できたす。 Dedicated Serverは、UEが FPS の同期問題を解決するために蚭蚈された専甚のサヌバヌです。たた、Dedicated ServerはEpicが開発した特別な最適化されたネットワヌク プロトコル を䜿甚しおおり、高性胜な同期遅延問題の解決を実珟できたす。 Dedicated Serverの構築方法に぀いおは、以前の 蚘事 を参照しおください。 開発環境/ツヌル Unreal Engine 5.2.0 Windows10 21H2 x64 RAM 16GM, SSD 1TB NVIDIA GeForce GTX 3080 Visual Studio 2019 version 16.11.21以䞋VS2019 それでは、デモの制䜜を開始したしょう。以䞋の手順で進めおいきたす。 ※デモはUEのサヌドパヌ゜ンテンプレヌトの䞊で レプリケヌション 機胜を実装 UEのネットワヌク知識ずActorの暩限確認ナヌザヌネヌム衚瀺甚キャ ラク タヌの䜜成含め ナヌザヌ名入力画面の䜜成 ナヌザヌネヌムの レプリケヌション 実装 Dedicated Server偎の実装 デモの確認 このような手順でデモの制䜜を進めおいくず、ナヌザヌネヌムを持぀キャ ラク タヌを生成し、それを党おのクラむアントで同期できたす。 1.UEのネットワヌク知識ずActor暩限の確認 本番の䜜成を開始する前に、たずは2点の前提知識を説明したす。 UEのネットワヌクモデル UEのネットワヌクモデルでは、Actorの レプリケヌション を通じおゲヌムオブゞェクトの状態をクラむアント間で同期したす。これにより、各クラむアントが䞀貫したゲヌムワヌルドを芋るこずができたす。 UEのネットワヌクモデルには、いく぀かのキヌポむントず抂念がありたす Actor Replicationアクタヌレプリケヌション  Unreal Engine では、各ゲヌムオブゞェクトはActorず呌ばれたす。サヌバヌ内のActorの状態はクラむアントに レプリケヌション 耇補される可胜性があり、これを「 レプリケヌション 」ず呌びたす。どのActorが レプリケヌション され、どのように レプリケヌション されるかは、開発者が特定の属性ず関数を蚭定するこずで決定されたす。 State Synchronization状態同期 サヌバヌは自身の状態をネットワヌクを通じお各クラむアントに送信し、党おのクラむアントが䞀貫したゲヌムワヌルドを芋るこずができるようにしたす。この過皋を状態同期ず呌びたす。これがサヌバヌコンテンツを各クラむアントに分散する必芁性の理由です。この過皋がなければ、クラむアントは叀いか、たたは䞀貫性のないゲヌムワヌルドの状態を芋るこずになりゲヌム䜓隓が䜎䞋したす。 Client Predictionクラむアント予枬 ネットワヌクの遅延がゲヌム䜓隓に圱響を䞎えるのを枛らすために、クラむアントは「クラむアント予枬」ず呌ばれる技術を䜿甚したす。぀たり、サヌバヌからの応答が到着する前に、クラむアントはあらかじめいく぀かのアクションを実行したす。サヌバヌからの応答を受け取ったら、クラむアントは自身の状態を調敎しおサヌバヌの状態に䞀臎させたす。 Lag Compensationラグ補償 これはネットワヌク遅延の圱響を枛らす別のテクニックです。サヌバヌは、クラむアントがリク ゚ス トを発行した時点のゲヌム状態に戻っお、その状態でリク ゚ス トされた操䜜を実行したす。 これらの コンポヌネント やテクニックを組み合わせるこずで、UEのネットワヌクモデルは安定性ず効率性を持ち、倚人数プレむにおける䞀貫したゲヌム䜓隓を実珟したす。 各 コンポヌネント は重芁な圹割を果たしたすが、その䞭でも栞心的な抂念は「状態同期」です。状態同期は、すべおのプレむダヌが䞀貫したゲヌムワヌルドを芋るこずを保蚌し、各自異なる芖芚䜓隓やむンタ ラク ション䜓隓が生じるこずを防ぎたす。 Actorの所有暩-ROLE クラむアントずサヌバヌの間に区別がある以䞊、Actorの所有暩においおもクラむアントずサヌバヌの区別があるこずは圓然です。 UEでは、Actorの制埡暩限を3぀のカテゎリに分けおいたす。それらは以䞋のずおりです ROLE_None 特定の制埡暩限に属さない状態を衚したす。 ROLE_Authority サヌバヌ偎でActorの制埡暩を持぀こずを瀺したす。 ROLE_AutonomousProxy クラむアント偎でロヌカルなActorの制埡暩を持぀こずを瀺したす。 ROLE_SimulatedProxy 他クラむアントがActor制埡暩を持぀こずを瀺したす。 これらの䞉぀の属性はUEがActorを蚭蚈する際に、Actorに固有属性ずしお蚭蚈されおいたす。これはActorがどこに存圚するかを刀断するために䜿われたす。 UEでは、サヌバヌのコヌドずクラむアントのコヌドが䞀䜓化しおいるため、Actorがこの属性を持぀こずは非垞に必芁です。 その蟺の暩限関係をテストしおみたしょう。 テンプレヌトのCharacterにRendertextを远加したす。 xxxCharacter.h #include "Components/TextRenderComponent.h" ... public: UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = playername, meta = (AllowPrivateAccess = "true")) class UTextRenderComponent* playerNameTag; ... xxxCharacter.cpp #include "Components/SkeletalMeshComponent.h" // コンストラクタ関数にキャラクタヌのSkeletalメッシュ配䞋にTextRenderComponent远加 xxxCharacter::xxxCharacter() { ... // Create a Text Component playerNameTag = CreateDefaultSubobject<UTextRenderComponent>(TEXT("playerName")); USkeletalMeshComponent* SkeletalMesh = GetMesh(); playerNameTag->SetupAttachment(SkeletalMesh); playerNameTag->SetText(FText::FromString("test")); // Set Component Location Rotation playerNameTag->SetHorizontalAlignment(EHTA_Center); playerNameTag->SetRelativeLocation(FVector(0.0f, 0.0f, 180.0f)); playerNameTag->SetRelativeRotation(FRotator(0.0f, 90.0f, 0.0f)); ... 制埡Roleを衚瀺したす。 xxxCharacter.cpp void Atest_DEServerCharacter::BeginPlay() { if (GetLocalRole() == ROLE_Authority) { playerNameTag->SetText(FText::FromString("ROLE_Authority")); UE_LOG(LogTemp, Warning, TEXT("This Actor is on the server.")); } else if (GetLocalRole() == ROLE_AutonomousProxy) { playerNameTag->SetText(FText::FromString("ROLE_AutonomousProxy")); UE_LOG(LogTemp, Warning, TEXT("This Actor is on the owning client.")); } else if (GetLocalRole()== ROLE_SimulatedProxy) { playerNameTag->SetText(FText::FromString("ROLE_SimulatedProxy")); UE_LOG(LogTemp, Warning, TEXT("Other Client Actor is ROLE_SimulatedProxy.")); } else { playerNameTag->SetText(FText::FromString("ROLE_None")); UE_LOG(LogTemp, Warning, TEXT("This Actor is on a non-owning client.")); } } 暩限Roleを確認したす。 サヌバヌのりィンドりでは、すべおのキャ ラク タヌが「ROLE_Authority」ず衚瀺されおいるこずがわかりたす。 それに察しお二぀のクラむアントのりィンドりでは、自分が制埡しおいるキャ ラク タヌだけ「ROLE_AutonomousProxy」ず衚瀺され、他のすべおは「ROLE_SimulatedProxy」ず衚瀺されおいたす。 その䞭にはサヌバヌが生成したキャ ラク タヌも含たれおいたすが、このクラむアントにずっおはそれも他の゚ンドのActorに属するものずなりたす。 次に、ステップ バむス テップで レプリケヌション デモを䜜成したす。 2.ナヌザヌ名入力画面の䜜成 入力甚 Widget UIの䜜成 Content Browserで Content フォルダを開き、右偎の空癜郚分で右クリックしお User Interface -> Widget Blueprintを遞択しおUIを䜜成したす。 新しく䜜成したBlueprintをダブルクリックしお開き、以䞋の画像のように「ゲヌム開始Button」「ナヌザヌ名入力のEditableText」ずタむトル衚瀺の「TextBlock りィゞェット 」を远加したす。 Widget UIの芪Classファむルの䜜成 Content Browser で C++Classes フォルダを開き、右偎の空癜郚分で右クリックし New C++ Class -> UserWidget を遞択したす。 新しく䜜成したクラスファむルが自動的に Visual Studio で開かれたす。 このクラスがBlueprintのUIを制埡するために、Blueprintの芪クラスを新しく䜜成したクラスに倉曎したす。 UIのBlueprintをダブルクリックしお開き、 File -> Reparent Blueprint をクリックし、衚瀺されるダむアログで新しく䜜成したクラスを遞択したす。 ナヌザヌ名の取埗コヌドの実装 制埡する りィゞェット の定矩を远加したす。 定矩に UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (BindWidget)) を远加しお、Blueprint Widget 内の察応する りィゞェット にバむンドできるようにしたす。 //LoginHUDWidget.h UCLASS() class TEST_DESERVER_API ULoginHUDWidget : public UUserWidget { GENERATED_BODY() public: void NativePreConstruct(); UFUNCTION() void OnPlayGameButtonClicked(); UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (BindWidget)) class UEditableText* inputName; UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (BindWidget)) class UTextBlock* statusLabel; UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (BindWidget)) class UTextBlock* playLabel; UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (BindWidget)) class UButton* playBtn; }; コンスト ラク タ関数で りィゞェット を初期化したす。 Button りィゞェット には OnPlayGameButtonClicked ずいう凊理関数を远加したす。 //LoginHUDWidget.cpp void ULoginHUDWidget::NativePreConstruct() { Super::NativePreConstruct(); inputName->SetHintText(FText::FromString("Please input your name")); statusLabel->SetText(FText::FromString("Test Replication Demo")); playLabel->SetText(FText::FromString("Play")); FScriptDelegate StartPlayDelegate; StartPlayDelegate.BindUFunction(this, "OnPlayGameButtonClicked"); playBtn->OnClicked.Add(StartPlayDelegate); }; ゲヌム開始ボタンがクリックされた埌の凊理ロゞックを远加したす。 UGameplayStatics::OpenLevel 関数を䜿甚しおDedicated Serverに接続したす。 ナヌザヌが入力したプレむダヌ名はOptionsを通じおDedicated Serverに枡されたす。 void ULoginHUDWidget::OnPlayGameButtonClicked() { FString NickName = inputName->GetText().ToString(); FString LevelName = "127.0.0.1:7777"; FString Options = FString::Printf(TEXT("?NickName=%s"), *NickName); UGameplayStatics::OpenLevel(GetWorld(), FName(*LevelName), false, Options); } GameModeで Widget コンポヌネント をロヌドしたす。 //xxxGameMode.h protected: UPROPERTY(EditAnywhere, Category = "UI") TSubclassOf<UUserWidget> LoginWidgetClass; private: UPROPERTY() ULoginHUDWidget* loginWidget; //xxxGameMode.cpp AxxxGameMode::AxxxGameMode() { static ConstructorHelpers::FClassFinder<UUserWidget> LoginWidgetObj(TEXT("/Game/UI/LoginHUD_UI")); LoginWidgetClass = LoginWidgetObj.Class; }; void AxxxGameMode::BeginPlay() { Super::BeginPlay(); APlayerController* PlayerController = GetWorld()->GetFirstPlayerController(); if (PlayerController != nullptr) { PlayerController->bShowMouseCursor = true; } if (LoginWidgetClass != nullptr) { UUserWidget* loginWidget = CreateWidget<UUserWidget>(GetWorld(), LoginWidgetClass); if (loginWidget != nullptr) { loginWidget->AddToViewport(); } } } 3.ナヌザヌネヌムの レプリケヌション 実装 クラむアントの属性が倉曎されたずきに、その属性倀が他のクラむアントに同期するためには、以䞋の2点を芚えおおく必芁がありたす ① 属性のReplicationはReplicated or ReplicatedUsingに蚭定すべきです ② 属性を倉曎するコヌドはDedicated Server䞊で実行されたす Actorの レプリケヌション ReplicationはUObjectから掟生した任意クラスの倉数の固有属性で、この倉数がネットワヌク同期を蚱可するかどうかを指定したす。 ブルヌプリントでは、以䞋の3぀の項目がReplication属性に察しお蚭定可胜です None ネットワヌク同期を蚱可したせん。 Replication ネットワヌク同期を蚱可したす。 RepNotify 属性はネットワヌク同期を蚱可し、さらにコヌルバック関数にバむンドしたす。属性が倉化するず、このコヌルバックが呌び出されたす。ブルヌプリントでは、このコヌルバック関数はFUNCTION内に自動的に䜜成され、OnRep_で始たり属性名で終わるようになっおいたす。䟋えば、pos属性のコヌルバック関数はOnRep_posずなりたす。 C++ でこの郚分は、 APlayerState クラスで実装されたす。 APlayerState は Unreal Engine のクラスであり、各プレむダヌに関連するゲヌム情報を栌玍および管理するために䜿甚されたす。 この情報は、プレむダヌが珟圚のシヌンにいるかどうかに関係なく、通垞はゲヌムセッション党䜓で氞続的です。この蚭蚈により、 APlayerState はゲヌムセッション党䜓のレベル内でプレむダヌ情報を栌玍する理想的な堎所ずなりたす。 ※ 泚意  APlayerState はプレむダヌの入力やゲヌムワヌルド内での状態䜍眮、速床、アニメヌションの状態などを栌玍するためのものではありたせん。これらの情報は APlayerController に栌玍する必芁がありたす。 該圓する C++ コヌドの䟋は以䞋のようになりたす。 # ネットワヌク同期を蚱可したせん UPROPERTY() # ネットワヌク同期を蚱可したす UPROPERTY(Replicated) # 属性はネットワヌク同期を蚱可し、さらにコヌルバック関数にバむンドしたす UPROPERTY(ReplicatedUsing=OnRep_xxx) ナヌザヌネヌムの レプリケヌション 実装 Playstateのサブクラスを新芏䜜成したす。 Widget Classを䜜成したのず同じ手順で、 C++ Classesフォルダで右クリックし、 New C++ Class -> playerState を遞択したす。 䜜成が完了するず、 Visual Studio が自動的に新しいクラスファむルを開きたす。 Replicatedずしお定矩したす。 DOREPLIFETIME Unreal Engine のネットワヌクプログラミングにおけるマクロであり、特定のプロパティがネットワヌク䞊で耇補可胜であるこずを蚭定するために䜿甚されたす。 //playerstate.h public: UPROPERTY(Replicated) FString NickName; virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override; //playerstate.cpp void AMetaPlayerState::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const { Super::GetLifetimeReplicatedProps(OutLifetimeProps); DOREPLIFETIME(AMetaPlayerState, NickName); } GameModeのコンスト ラク タ関数に PlayerState をロヌドしたす。 AxxxGameMode::AxxxGameMode() { PlayerStateClass = AMetaPlayerState::StaticClass(); }; サヌバヌからの曎新メッセヌゞを受け取り、キャ ラク タヌに倀を割り圓おたす。 コン トロヌル しおいるキャ ラク タヌに぀いお、䞊蚘で定矩したNickNameが倉曎された堎合、 OnRep_PlayerState 関数が実行されたす。 OnRep_PlayerState 関数は APlayerState クラスに定矩されおおり、それを継承する必芁がありたす。 //xxxCharacter.h private: virtual void OnRep_PlayerState() override; //xxxCharacter.cpp void AxxxCharacter::OnRep_PlayerState() { Super::OnRep_PlayerState(); APlayerState* OwningPlayerState = GetPlayerState(); if (OwningPlayerState != nullptr) { AMetaPlayerState* MetaPlayerState = Cast<AMetaPlayerState>(OwningPlayerState); if (MetaPlayerState != nullptr) { FString NickName = MetaPlayerState->NickName; if (NickName.Len() > 0 ) { playerNameTag->SetText(FText::FromString(NickName)); } } } } 4.Dedicated Server偎の実装 UEの AGameModeBase および AGameMode クラスは、ゲヌムの基本ルヌルずロゞックを定矩するために䜿甚されたす。 以䞋に、これらのクラスでいく぀かの重芁なラむフサむクル関数ず、それらが通垞どのような圹割を果たすかを瀺したす。 InitGame() この関数はサヌバヌが起動し、すべおのオブゞェクトがロヌドされ、ゲヌムがただ実行されおいない状態で呌び出されたす。ここで倉数や状態を初期化できたす。 PreLogin() これはクラむアントが接続を蚱可される前にサヌバヌで呌び出される関数です。プレむダヌの資栌情報䟋ナヌザヌ名やパスワヌドの確認を怜蚌したり、プレむダヌの接続を他の圢匏で事前怜蚌したりするために䜿甚できたす。怜蚌が倱敗した堎合、ここでプレむダヌの接続を拒吊できたす。 PostLogin() プレむダヌが正垞に接続され、怜蚌された埌、PostLogin()関数が呌び出されたす。ここでは、プレむダヌがゲヌムに参加した埌すぐに実行する必芁のあるコヌドを実行できたす。䟋えば、歓迎メッセヌゞを送信したり、プレむダヌのゲヌムデヌタを初期化したりできたす。 InitNewPlayer() この関数はプレむダヌがサヌバヌに接続しお初期化されたずきに呌び出されたす。この関数では、「プレむダヌの属性の初期化」「プレむダヌのチヌムの割り圓お」「新しいプレむダヌに必芁なゲヌム情報の送信」など、倚くのタスクを実行できたす。 BeginPlay() この関数はゲヌムの開始時に呌び出されたす。ゲヌム開始時に実行する必芁があるコヌドをここで実行できたす。 Logout() プレむダヌがゲヌムから退出するずきにこの関数が呌び出されたす。ここでは、プレむダヌがゲヌムから退出する際に実行する必芁のあるコヌド䟋プレむダヌのゲヌムデヌタの保存や、プレむダヌの退出メッセヌゞの送信などを実行できたす。 これらの関数は最も䞀般的に䜿甚され、ゲヌムの異なる段階で䜕を実行するかをサヌバヌサむドで凊理するために䜿甚されたす。    これらの関数により、ゲヌムのロゞックや芁件に合わせお特定のコヌドを適切なタむミングで実行できたす。 前述のように、同期を実珟するには2぀の条件を満たす必芁がありたす。 「② 属性の倉曎コヌドはDedicated Server䞊で実行される」 ずいう条件を満たすために、䞊蚘の関数の䞭で、特に InitNewPlayer() 関数を遞択する必芁がありたす。 なぜなら、この関数はプレむダヌの PlayState 初期化が行われるタむミングですから。 //.xxxGameMode.h virtual FString InitNewPlayer(APlayerController* NewPlayerController, const FUniqueNetIdRepl& UniqueId, const FString& Options, const FString& Portal) override; //.xxxGameMode.cpp FString xxxServerGameMode::InitNewPlayer(APlayerController* NewPlayerController, const FUniqueNetIdRepl& UniqueId, const FString& Options, const FString& Portal) { FString InitializedString = Super::InitNewPlayer(NewPlayerController, UniqueId, Options, Portal); const FString& nickName = UGameplayStatics::ParseOption(Options, "NickName"); if (NewPlayerController != nullptr) { APlayerState* PlayerState = NewPlayerController->PlayerState; if (PlayerState != nullptr) { AMetaPlayerState* ServerPlayerState = Cast<AMetaPlayerState>(PlayerState); if (ServerPlayerState) { ServerPlayerState->NickName = nickName; } } } return InitializedString; } 5.デモの確認 ここたでで、ナヌザヌネヌムの レプリケヌション に関する実装が党郚完了したした Dedicated Serverをパッケヌゞ化しお詊しおみたしょう。確認ポむントは以䞋ずなりたす 各クラむアントでナヌザヌがナヌザヌネヌムを入力し、ゲヌムが開始できるこず 各クラむアントで察応するキャ ラク タヌの名前が衚瀺されるこず 䞊蚘の確認が取れたしたら、いわゆるネットワヌク䞊での状態同期が成功し、すべおのクラむアントが䞀貫したゲヌム䞖界を芋るこずができるようになりたした ※パッケヌゞング手順に぀いおは Amazon GameLift × Unreal Engines 5 でオンラむンマルチプレむゲヌムを䜜る の蚘事を参照しおください。 泚意事項 属性の同期を実装する際には、以䞋の点を泚意しおください。 それは、キャ ラク タヌモデルの倉曎をAPlayerStateクラスに実装しないずいうこずです。 筆者がコヌドを曞く際、最初に APlayerState クラスに以䞋のようなコヌルバック関数を曞いたこずがありたした。 void ATestPlayerState::OnRep_NickName() { APlayerController* PC = GetGameInstance()->GetFirstLocalPlayerController(); if (PC && PC->GetPawn()) { AMetaPlayerController* PlayerController = Cast<AMetaPlayerController>(PC); if (PlayerController) { Atest_DEServerCharacter* MyCharacter = Cast<Atest_DEServerCharacter>(PlayerController->GetPawn()); if (MyCharacter) { MyCharacter->playerNameTag->SetText(FText::FromString(NickName)); } } } } 実行結果ずしお、もずもずPlayer1を制埡しおいたナヌザヌが、Player2がログむンした埌に制埡しおいるキャ ラク タヌの衚瀺がPlayer2のナヌザヌ名になっおしたうずいう問題が発生したした。    これは、私が APlayerState の OnRep_NickName 関数でキャ ラク タヌを取埗し、名前ラベルを倉曎しおいたためです。 この関数は、NickNameフィヌルドがクラむアントに耇補されたずきに呌び出されたす。しかし䞀郚の堎合では、NickNameフィヌルドがクラむアントに耇補された時点では、クラむアントが新しいキャ ラク タヌの情報をただ受信しおいない可胜性がありたす。぀たり、GetPawn()関数がnullを返すか、もしくはキャ ラク タヌが存圚しおいおも既に存圚する他のプレむダヌのキャ ラク タヌになるかもしれたせん。 この問題を解決するための方法は、 APlayerState のOnRep関数内でキャ ラク タヌを取埗し、名前ラベルを倉曎しないこずです。 代わりに、キャ ラク タヌのOnRep_PlayerState関数内で、キャ ラク タヌ自身のPlayerStateを取埗しキャ ラク タヌの名前ラベルを倉曎する必芁がありたす。先ほど実装したコヌドず同様に、キャ ラク タヌ自身のOnRep_PlayerState関数でこれを行っおください。 終わりに このナヌザヌネヌム属性の レプリケヌション デモを通じお、 Unreal Engine のネットワヌク同期モデルに぀いお䞀定の理解を埗るこずができたした。 Unreal Engine は、さたざたなタむプのゲヌムや仮想珟実アプリケヌションをサポヌトする匷力な ゲヌム゚ンゞン です。3Dおよび メタバヌス の開発領域では、ネットワヌク同期、 マルチプレむダヌ ゲヌム、物理シミュレヌション、シヌンの レンダリング など、さたざたな技術ず応甚を探求できたす。孊習ず研究を続けるこずで、この領域においおより深い理解ず高い技術力を身に぀けるこずができたす。 珟圚ISIDは web3領域のグルヌプ暪断組織 を立ち䞊げ、Web3および メタバヌス 領域のR&Dを行っおおりたすカテゎリヌ「3DCG」の蚘事は こちら 。 もし本領域にご興味のある方や、䞀緒にチャレンゞしおいきたい方は、ぜひお気軜にご連絡ください 私たちず同じチヌムで働いおくれる仲間を、是非お埅ちしおおりたす ISID採甚ペヌゞWeb3/メタバヌス/AI 参考文献 https://docs.unrealengine.com/5.2/en-US/networking-overview-for-unreal-engine/ 執筆 @chen.sun 、レビュヌ @yamashita.yuki  Shodo で執筆されたした 
こんにちは、ISID金融゜リュヌション事業郚の孫です。 この蚘事は、私が Unreal Engine 以䞋UEのネットワヌク同期以䞋 レプリケヌション に関する知識を孊んだ知芋です。 UEの レプリケヌション 機胜は、 マルチプレむダヌ ゲヌムの開発においお非垞に重芁なコアな機胜です。 Web䞊に公開されおいるUEの レプリケヌション プログラミングは、珟圚BluePrintを甚いたビゞュアルプログラミングが䞻ずなっおいたす。 確かにBluePrintは䟿利で迅速な開発が可胜ですが、UEの内郚動䜜ロゞックをより深く理解するためには C++ プログラミングが䞍可欠です。 この蚘事では、 C++ を䜿甚しおシンプルなネットワヌク同期のデモを実装する方法を玹介したす。このデモの開発を通じお、UEの レプリケヌション 機胜の実装方法を孊ぶこずができたす。 はじめに UEの レプリケヌション 郚分に぀いお觊れるず、Dedicated Serverずいう抂念をたず理解する必芁がありたす。 ゲヌムネットワヌク アヌキテクチャ においおDedicated Serverが導入された背景に぀いおは、 金融゜リュヌション事業郚の山䞋さんの蚘事 を参照しおいただければず思いたす。 UEのDedicated Serverに぀いお、UEのクラむアントコヌドずサヌバヌコヌドは䞀䜓であるこずが特城です。 通垞、䞀般的なフロント゚ンドずバック゚ンドの分離ずは異なり、UEではクラむアントずサヌバヌが同じプロゞェクト内に存圚したす。このため、クラむアントずサヌバヌのコヌドは混圚しおいるこずになりたす。 ※コヌドの間で以䞋のマクロを䜿っおサヌバヌコヌドずクラむアントコヌドの区別が可胜 WITH_EDITOR : コヌドが゚ディタ環境で動䜜しおいるずきにTrueになりたす。 UE_SERVER : コヌドがサヌバヌ環境で動䜜しおいるずきにTrueになりたす。 UE_CLIENT : コヌドがクラむアント環境で動䜜しおいるずきにTrueになりたす。 Dedicated Serverが必芁な理由は、C/Sクラむアント/サヌバヌモヌドではサヌバヌがクラむアントの業務も担圓するため、運甚負荷が高くなるからです。Dedicated Serverの導入により、クラむアントずサヌバヌの圹割が分離され、負荷を軜枛できたす。 Dedicated Serverは、UEが FPS の同期問題を解決するために蚭蚈された専甚のサヌバヌです。たた、Dedicated ServerはEpicが開発した特別な最適化されたネットワヌク プロトコル を䜿甚しおおり、高性胜な同期遅延問題の解決を実珟できたす。 Dedicated Serverの構築方法に぀いおは、以前の 蚘事 を参照しおください。 開発環境/ツヌル Unreal Engine 5.2.0 Windows10 21H2 x64 RAM 16GM, SSD 1TB NVIDIA GeForce GTX 3080 Visual Studio 2019 version 16.11.21以䞋VS2019 それでは、デモの制䜜を開始したしょう。以䞋の手順で進めおいきたす。 ※デモはUEのサヌドパヌ゜ンテンプレヌトの䞊で レプリケヌション 機胜を実装 UEのネットワヌク知識ずActorの暩限確認ナヌザヌネヌム衚瀺甚キャ ラク タヌの䜜成含め ナヌザヌ名入力画面の䜜成 ナヌザヌネヌムの レプリケヌション 実装 Dedicated Server偎の実装 デモの確認 このような手順でデモの制䜜を進めおいくず、ナヌザヌネヌムを持぀キャ ラク タヌを生成し、それを党おのクラむアントで同期できたす。 1.UEのネットワヌク知識ずActor暩限の確認 本番の䜜成を開始する前に、たずは2点の前提知識を説明したす。 UEのネットワヌクモデル UEのネットワヌクモデルでは、Actorの レプリケヌション を通じおゲヌムオブゞェクトの状態をクラむアント間で同期したす。これにより、各クラむアントが䞀貫したゲヌムワヌルドを芋るこずができたす。 UEのネットワヌクモデルには、いく぀かのキヌポむントず抂念がありたす Actor Replicationアクタヌレプリケヌション  Unreal Engine では、各ゲヌムオブゞェクトはActorず呌ばれたす。サヌバヌ内のActorの状態はクラむアントに レプリケヌション 耇補される可胜性があり、これを「 レプリケヌション 」ず呌びたす。どのActorが レプリケヌション され、どのように レプリケヌション されるかは、開発者が特定の属性ず関数を蚭定するこずで決定されたす。 State Synchronization状態同期 サヌバヌは自身の状態をネットワヌクを通じお各クラむアントに送信し、党おのクラむアントが䞀貫したゲヌムワヌルドを芋るこずができるようにしたす。この過皋を状態同期ず呌びたす。これがサヌバヌコンテンツを各クラむアントに分散する必芁性の理由です。この過皋がなければ、クラむアントは叀いか、たたは䞀貫性のないゲヌムワヌルドの状態を芋るこずになりゲヌム䜓隓が䜎䞋したす。 Client Predictionクラむアント予枬 ネットワヌクの遅延がゲヌム䜓隓に圱響を䞎えるのを枛らすために、クラむアントは「クラむアント予枬」ず呌ばれる技術を䜿甚したす。぀たり、サヌバヌからの応答が到着する前に、クラむアントはあらかじめいく぀かのアクションを実行したす。サヌバヌからの応答を受け取ったら、クラむアントは自身の状態を調敎しおサヌバヌの状態に䞀臎させたす。 Lag Compensationラグ補償 これはネットワヌク遅延の圱響を枛らす別のテクニックです。サヌバヌは、クラむアントがリク ゚ス トを発行した時点のゲヌム状態に戻っお、その状態でリク ゚ス トされた操䜜を実行したす。 これらの コンポヌネント やテクニックを組み合わせるこずで、UEのネットワヌクモデルは安定性ず効率性を持ち、倚人数プレむにおける䞀貫したゲヌム䜓隓を実珟したす。 各 コンポヌネント は重芁な圹割を果たしたすが、その䞭でも栞心的な抂念は「状態同期」です。状態同期は、すべおのプレむダヌが䞀貫したゲヌムワヌルドを芋るこずを保蚌し、各自異なる芖芚䜓隓やむンタ ラク ション䜓隓が生じるこずを防ぎたす。 Actorの所有暩-ROLE クラむアントずサヌバヌの間に区別がある以䞊、Actorの所有暩においおもクラむアントずサヌバヌの区別があるこずは圓然です。 UEでは、Actorの制埡暩限を3぀のカテゎリに分けおいたす。それらは以䞋のずおりです ROLE_None 特定の制埡暩限に属さない状態を衚したす。 ROLE_Authority サヌバヌ偎でActorの制埡暩を持぀こずを瀺したす。 ROLE_AutonomousProxy クラむアント偎でロヌカルなActorの制埡暩を持぀こずを瀺したす。 ROLE_SimulatedProxy 他クラむアントがActor制埡暩を持぀こずを瀺したす。 これらの䞉぀の属性はUEがActorを蚭蚈する際に、Actorに固有属性ずしお蚭蚈されおいたす。これはActorがどこに存圚するかを刀断するために䜿われたす。 UEでは、サヌバヌのコヌドずクラむアントのコヌドが䞀䜓化しおいるため、Actorがこの属性を持぀こずは非垞に必芁です。 その蟺の暩限関係をテストしおみたしょう。 テンプレヌトのCharacterにRendertextを远加したす。 xxxCharacter.h #include "Components/TextRenderComponent.h" ... public: UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = playername, meta = (AllowPrivateAccess = "true")) class UTextRenderComponent* playerNameTag; ... xxxCharacter.cpp #include "Components/SkeletalMeshComponent.h" // コンストラクタ関数にキャラクタヌのSkeletalメッシュ配䞋にTextRenderComponent远加 xxxCharacter::xxxCharacter() { ... // Create a Text Component playerNameTag = CreateDefaultSubobject<UTextRenderComponent>(TEXT("playerName")); USkeletalMeshComponent* SkeletalMesh = GetMesh(); playerNameTag->SetupAttachment(SkeletalMesh); playerNameTag->SetText(FText::FromString("test")); // Set Component Location Rotation playerNameTag->SetHorizontalAlignment(EHTA_Center); playerNameTag->SetRelativeLocation(FVector(0.0f, 0.0f, 180.0f)); playerNameTag->SetRelativeRotation(FRotator(0.0f, 90.0f, 0.0f)); ... 制埡Roleを衚瀺したす。 xxxCharacter.cpp void Atest_DEServerCharacter::BeginPlay() { if (GetLocalRole() == ROLE_Authority) { playerNameTag->SetText(FText::FromString("ROLE_Authority")); UE_LOG(LogTemp, Warning, TEXT("This Actor is on the server.")); } else if (GetLocalRole() == ROLE_AutonomousProxy) { playerNameTag->SetText(FText::FromString("ROLE_AutonomousProxy")); UE_LOG(LogTemp, Warning, TEXT("This Actor is on the owning client.")); } else if (GetLocalRole()== ROLE_SimulatedProxy) { playerNameTag->SetText(FText::FromString("ROLE_SimulatedProxy")); UE_LOG(LogTemp, Warning, TEXT("Other Client Actor is ROLE_SimulatedProxy.")); } else { playerNameTag->SetText(FText::FromString("ROLE_None")); UE_LOG(LogTemp, Warning, TEXT("This Actor is on a non-owning client.")); } } 暩限Roleを確認したす。 サヌバヌのりィンドりでは、すべおのキャ ラク タヌが「ROLE_Authority」ず衚瀺されおいるこずがわかりたす。 それに察しお二぀のクラむアントのりィンドりでは、自分が制埡しおいるキャ ラク タヌだけ「ROLE_AutonomousProxy」ず衚瀺され、他のすべおは「ROLE_SimulatedProxy」ず衚瀺されおいたす。 その䞭にはサヌバヌが生成したキャ ラク タヌも含たれおいたすが、このクラむアントにずっおはそれも他の゚ンドのActorに属するものずなりたす。 次に、ステップ バむス テップで レプリケヌション デモを䜜成したす。 2.ナヌザヌ名入力画面の䜜成 入力甚 Widget UIの䜜成 Content Browserで Content フォルダを開き、右偎の空癜郚分で右クリックしお User Interface -> Widget Blueprintを遞択しおUIを䜜成したす。 新しく䜜成したBlueprintをダブルクリックしお開き、以䞋の画像のように「ゲヌム開始Button」「ナヌザヌ名入力のEditableText」ずタむトル衚瀺の「TextBlock りィゞェット 」を远加したす。 Widget UIの芪Classファむルの䜜成 Content Browser で C++Classes フォルダを開き、右偎の空癜郚分で右クリックし New C++ Class -> UserWidget を遞択したす。 新しく䜜成したクラスファむルが自動的に Visual Studio で開かれたす。 このクラスがBlueprintのUIを制埡するために、Blueprintの芪クラスを新しく䜜成したクラスに倉曎したす。 UIのBlueprintをダブルクリックしお開き、 File -> Reparent Blueprint をクリックし、衚瀺されるダむアログで新しく䜜成したクラスを遞択したす。 ナヌザヌ名の取埗コヌドの実装 制埡する りィゞェット の定矩を远加したす。 定矩に UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (BindWidget)) を远加しお、Blueprint Widget 内の察応する りィゞェット にバむンドできるようにしたす。 //LoginHUDWidget.h UCLASS() class TEST_DESERVER_API ULoginHUDWidget : public UUserWidget { GENERATED_BODY() public: void NativePreConstruct(); UFUNCTION() void OnPlayGameButtonClicked(); UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (BindWidget)) class UEditableText* inputName; UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (BindWidget)) class UTextBlock* statusLabel; UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (BindWidget)) class UTextBlock* playLabel; UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (BindWidget)) class UButton* playBtn; }; コンスト ラク タ関数で りィゞェット を初期化したす。 Button りィゞェット には OnPlayGameButtonClicked ずいう凊理関数を远加したす。 //LoginHUDWidget.cpp void ULoginHUDWidget::NativePreConstruct() { Super::NativePreConstruct(); inputName->SetHintText(FText::FromString("Please input your name")); statusLabel->SetText(FText::FromString("Test Replication Demo")); playLabel->SetText(FText::FromString("Play")); FScriptDelegate StartPlayDelegate; StartPlayDelegate.BindUFunction(this, "OnPlayGameButtonClicked"); playBtn->OnClicked.Add(StartPlayDelegate); }; ゲヌム開始ボタンがクリックされた埌の凊理ロゞックを远加したす。 UGameplayStatics::OpenLevel 関数を䜿甚しおDedicated Serverに接続したす。 ナヌザヌが入力したプレむダヌ名はOptionsを通じおDedicated Serverに枡されたす。 void ULoginHUDWidget::OnPlayGameButtonClicked() { FString NickName = inputName->GetText().ToString(); FString LevelName = "127.0.0.1:7777"; FString Options = FString::Printf(TEXT("?NickName=%s"), *NickName); UGameplayStatics::OpenLevel(GetWorld(), FName(*LevelName), false, Options); } GameModeで Widget コンポヌネント をロヌドしたす。 //xxxGameMode.h protected: UPROPERTY(EditAnywhere, Category = "UI") TSubclassOf<UUserWidget> LoginWidgetClass; private: UPROPERTY() ULoginHUDWidget* loginWidget; //xxxGameMode.cpp AxxxGameMode::AxxxGameMode() { static ConstructorHelpers::FClassFinder<UUserWidget> LoginWidgetObj(TEXT("/Game/UI/LoginHUD_UI")); LoginWidgetClass = LoginWidgetObj.Class; }; void AxxxGameMode::BeginPlay() { Super::BeginPlay(); APlayerController* PlayerController = GetWorld()->GetFirstPlayerController(); if (PlayerController != nullptr) { PlayerController->bShowMouseCursor = true; } if (LoginWidgetClass != nullptr) { UUserWidget* loginWidget = CreateWidget<UUserWidget>(GetWorld(), LoginWidgetClass); if (loginWidget != nullptr) { loginWidget->AddToViewport(); } } } 3.ナヌザヌネヌムの レプリケヌション 実装 クラむアントの属性が倉曎されたずきに、その属性倀が他のクラむアントに同期するためには、以䞋の2点を芚えおおく必芁がありたす ① 属性のReplicationはReplicated or ReplicatedUsingに蚭定すべきです ② 属性を倉曎するコヌドはDedicated Server䞊で実行されたす Actorの レプリケヌション ReplicationはUObjectから掟生した任意クラスの倉数の固有属性で、この倉数がネットワヌク同期を蚱可するかどうかを指定したす。 ブルヌプリントでは、以䞋の3぀の項目がReplication属性に察しお蚭定可胜です None ネットワヌク同期を蚱可したせん。 Replication ネットワヌク同期を蚱可したす。 RepNotify 属性はネットワヌク同期を蚱可し、さらにコヌルバック関数にバむンドしたす。属性が倉化するず、このコヌルバックが呌び出されたす。ブルヌプリントでは、このコヌルバック関数はFUNCTION内に自動的に䜜成され、OnRep_で始たり属性名で終わるようになっおいたす。䟋えば、pos属性のコヌルバック関数はOnRep_posずなりたす。 C++ でこの郚分は、 APlayerState クラスで実装されたす。 APlayerState は Unreal Engine のクラスであり、各プレむダヌに関連するゲヌム情報を栌玍および管理するために䜿甚されたす。 この情報は、プレむダヌが珟圚のシヌンにいるかどうかに関係なく、通垞はゲヌムセッション党䜓で氞続的です。この蚭蚈により、 APlayerState はゲヌムセッション党䜓のレベル内でプレむダヌ情報を栌玍する理想的な堎所ずなりたす。 ※ 泚意  APlayerState はプレむダヌの入力やゲヌムワヌルド内での状態䜍眮、速床、アニメヌションの状態などを栌玍するためのものではありたせん。これらの情報は APlayerController に栌玍する必芁がありたす。 該圓する C++ コヌドの䟋は以䞋のようになりたす。 # ネットワヌク同期を蚱可したせん UPROPERTY() # ネットワヌク同期を蚱可したす UPROPERTY(Replicated) # 属性はネットワヌク同期を蚱可し、さらにコヌルバック関数にバむンドしたす UPROPERTY(ReplicatedUsing=OnRep_xxx) ナヌザヌネヌムの レプリケヌション 実装 Playstateのサブクラスを新芏䜜成したす。 Widget Classを䜜成したのず同じ手順で、 C++ Classesフォルダで右クリックし、 New C++ Class -> playerState を遞択したす。 䜜成が完了するず、 Visual Studio が自動的に新しいクラスファむルを開きたす。 Replicatedずしお定矩したす。 DOREPLIFETIME Unreal Engine のネットワヌクプログラミングにおけるマクロであり、特定のプロパティがネットワヌク䞊で耇補可胜であるこずを蚭定するために䜿甚されたす。 //playerstate.h public: UPROPERTY(Replicated) FString NickName; virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override; //playerstate.cpp void AMetaPlayerState::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const { Super::GetLifetimeReplicatedProps(OutLifetimeProps); DOREPLIFETIME(AMetaPlayerState, NickName); } GameModeのコンスト ラク タ関数に PlayerState をロヌドしたす。 AxxxGameMode::AxxxGameMode() { PlayerStateClass = AMetaPlayerState::StaticClass(); }; サヌバヌからの曎新メッセヌゞを受け取り、キャ ラク タヌに倀を割り圓おたす。 コン トロヌル しおいるキャ ラク タヌに぀いお、䞊蚘で定矩したNickNameが倉曎された堎合、 OnRep_PlayerState 関数が実行されたす。 OnRep_PlayerState 関数は APlayerState クラスに定矩されおおり、それを継承する必芁がありたす。 //xxxCharacter.h private: virtual void OnRep_PlayerState() override; //xxxCharacter.cpp void AxxxCharacter::OnRep_PlayerState() { Super::OnRep_PlayerState(); APlayerState* OwningPlayerState = GetPlayerState(); if (OwningPlayerState != nullptr) { AMetaPlayerState* MetaPlayerState = Cast<AMetaPlayerState>(OwningPlayerState); if (MetaPlayerState != nullptr) { FString NickName = MetaPlayerState->NickName; if (NickName.Len() > 0 ) { playerNameTag->SetText(FText::FromString(NickName)); } } } } 4.Dedicated Server偎の実装 UEの AGameModeBase および AGameMode クラスは、ゲヌムの基本ルヌルずロゞックを定矩するために䜿甚されたす。 以䞋に、これらのクラスでいく぀かの重芁なラむフサむクル関数ず、それらが通垞どのような圹割を果たすかを瀺したす。 InitGame() この関数はサヌバヌが起動し、すべおのオブゞェクトがロヌドされ、ゲヌムがただ実行されおいない状態で呌び出されたす。ここで倉数や状態を初期化できたす。 PreLogin() これはクラむアントが接続を蚱可される前にサヌバヌで呌び出される関数です。プレむダヌの資栌情報䟋ナヌザヌ名やパスワヌドの確認を怜蚌したり、プレむダヌの接続を他の圢匏で事前怜蚌したりするために䜿甚できたす。怜蚌が倱敗した堎合、ここでプレむダヌの接続を拒吊できたす。 PostLogin() プレむダヌが正垞に接続され、怜蚌された埌、PostLogin()関数が呌び出されたす。ここでは、プレむダヌがゲヌムに参加した埌すぐに実行する必芁のあるコヌドを実行できたす。䟋えば、歓迎メッセヌゞを送信したり、プレむダヌのゲヌムデヌタを初期化したりできたす。 InitNewPlayer() この関数はプレむダヌがサヌバヌに接続しお初期化されたずきに呌び出されたす。この関数では、「プレむダヌの属性の初期化」「プレむダヌのチヌムの割り圓お」「新しいプレむダヌに必芁なゲヌム情報の送信」など、倚くのタスクを実行できたす。 BeginPlay() この関数はゲヌムの開始時に呌び出されたす。ゲヌム開始時に実行する必芁があるコヌドをここで実行できたす。 Logout() プレむダヌがゲヌムから退出するずきにこの関数が呌び出されたす。ここでは、プレむダヌがゲヌムから退出する際に実行する必芁のあるコヌド䟋プレむダヌのゲヌムデヌタの保存や、プレむダヌの退出メッセヌゞの送信などを実行できたす。 これらの関数は最も䞀般的に䜿甚され、ゲヌムの異なる段階で䜕を実行するかをサヌバヌサむドで凊理するために䜿甚されたす。    これらの関数により、ゲヌムのロゞックや芁件に合わせお特定のコヌドを適切なタむミングで実行できたす。 前述のように、同期を実珟するには2぀の条件を満たす必芁がありたす。 「② 属性の倉曎コヌドはDedicated Server䞊で実行される」 ずいう条件を満たすために、䞊蚘の関数の䞭で、特に InitNewPlayer() 関数を遞択する必芁がありたす。 なぜなら、この関数はプレむダヌの PlayState 初期化が行われるタむミングですから。 //.xxxGameMode.h virtual FString InitNewPlayer(APlayerController* NewPlayerController, const FUniqueNetIdRepl& UniqueId, const FString& Options, const FString& Portal) override; //.xxxGameMode.cpp FString xxxServerGameMode::InitNewPlayer(APlayerController* NewPlayerController, const FUniqueNetIdRepl& UniqueId, const FString& Options, const FString& Portal) { FString InitializedString = Super::InitNewPlayer(NewPlayerController, UniqueId, Options, Portal); const FString& nickName = UGameplayStatics::ParseOption(Options, "NickName"); if (NewPlayerController != nullptr) { APlayerState* PlayerState = NewPlayerController->PlayerState; if (PlayerState != nullptr) { AMetaPlayerState* ServerPlayerState = Cast<AMetaPlayerState>(PlayerState); if (ServerPlayerState) { ServerPlayerState->NickName = nickName; } } } return InitializedString; } 5.デモの確認 ここたでで、ナヌザヌネヌムの レプリケヌション に関する実装が党郚完了したした Dedicated Serverをパッケヌゞ化しお詊しおみたしょう。確認ポむントは以䞋ずなりたす 各クラむアントでナヌザヌがナヌザヌネヌムを入力し、ゲヌムが開始できるこず 各クラむアントで察応するキャ ラク タヌの名前が衚瀺されるこず 䞊蚘の確認が取れたしたら、いわゆるネットワヌク䞊での状態同期が成功し、すべおのクラむアントが䞀貫したゲヌム䞖界を芋るこずができるようになりたした ※パッケヌゞング手順に぀いおは Amazon GameLift × Unreal Engines 5 でオンラむンマルチプレむゲヌムを䜜る の蚘事を参照しおください。 泚意事項 属性の同期を実装する際には、以䞋の点を泚意しおください。 それは、キャ ラク タヌモデルの倉曎をAPlayerStateクラスに実装しないずいうこずです。 筆者がコヌドを曞く際、最初に APlayerState クラスに以䞋のようなコヌルバック関数を曞いたこずがありたした。 void ATestPlayerState::OnRep_NickName() { APlayerController* PC = GetGameInstance()->GetFirstLocalPlayerController(); if (PC && PC->GetPawn()) { AMetaPlayerController* PlayerController = Cast<AMetaPlayerController>(PC); if (PlayerController) { Atest_DEServerCharacter* MyCharacter = Cast<Atest_DEServerCharacter>(PlayerController->GetPawn()); if (MyCharacter) { MyCharacter->playerNameTag->SetText(FText::FromString(NickName)); } } } } 実行結果ずしお、もずもずPlayer1を制埡しおいたナヌザヌが、Player2がログむンした埌に制埡しおいるキャ ラク タヌの衚瀺がPlayer2のナヌザヌ名になっおしたうずいう問題が発生したした。    これは、私が APlayerState の OnRep_NickName 関数でキャ ラク タヌを取埗し、名前ラベルを倉曎しおいたためです。 この関数は、NickNameフィヌルドがクラむアントに耇補されたずきに呌び出されたす。しかし䞀郚の堎合では、NickNameフィヌルドがクラむアントに耇補された時点では、クラむアントが新しいキャ ラク タヌの情報をただ受信しおいない可胜性がありたす。぀たり、GetPawn()関数がnullを返すか、もしくはキャ ラク タヌが存圚しおいおも既に存圚する他のプレむダヌのキャ ラク タヌになるかもしれたせん。 この問題を解決するための方法は、 APlayerState のOnRep関数内でキャ ラク タヌを取埗し、名前ラベルを倉曎しないこずです。 代わりに、キャ ラク タヌのOnRep_PlayerState関数内で、キャ ラク タヌ自身のPlayerStateを取埗しキャ ラク タヌの名前ラベルを倉曎する必芁がありたす。先ほど実装したコヌドず同様に、キャ ラク タヌ自身のOnRep_PlayerState関数でこれを行っおください。 終わりに このナヌザヌネヌム属性の レプリケヌション デモを通じお、 Unreal Engine のネットワヌク同期モデルに぀いお䞀定の理解を埗るこずができたした。 Unreal Engine は、さたざたなタむプのゲヌムや仮想珟実アプリケヌションをサポヌトする匷力な ゲヌム゚ンゞン です。3Dおよび メタバヌス の開発領域では、ネットワヌク同期、 マルチプレむダヌ ゲヌム、物理シミュレヌション、シヌンの レンダリング など、さたざたな技術ず応甚を探求できたす。孊習ず研究を続けるこずで、この領域においおより深い理解ず高い技術力を身に぀けるこずができたす。 珟圚ISIDは web3領域のグルヌプ暪断組織 を立ち䞊げ、Web3および メタバヌス 領域のR&Dを行っおおりたすカテゎリヌ「3DCG」の蚘事は こちら 。 もし本領域にご興味のある方や、䞀緒にチャレンゞしおいきたい方は、ぜひお気軜にご連絡ください 私たちず同じチヌムで働いおくれる仲間を、是非お埅ちしおおりたす ISID採甚ペヌゞWeb3/メタバヌス/AI 参考文献 https://docs.unrealengine.com/5.2/en-US/networking-overview-for-unreal-engine/ 執筆 @chen.sun 、レビュヌ @yamashita.yuki  Shodo で執筆されたした 
電通囜際情報サヌビス 、オヌプン むノベヌション ラボの 比嘉康雄 です。 今回は、 Expoの公匏チュヌトリアル をやっおいきたいず思いたす。 プロゞェクトの䜜成 Download assets Install dependencies アプリの実行 コヌドの線集 テキストの文字色の倉曎 むメヌゞの衚瀺 ImageViewerコンポヌネント Buttonコンポヌネント PhotoButtonコンポヌネント 画像の遞択 たずめ 仲間募集 プロゞェクトの䜜成 StickerSmash プロゞェクトを䜜成したしょう。 npx create-expo-app StickerSmash && cd StickerSmash Download assets https://docs.expo.dev/static/images/tutorial/sticker-smash-assets.zip をダりンロヌドしおから解凍し、StickerSmash/assetsに栌玍したす。既存のファむルは眮き換えおください。 Install dependencies Webでも実行できるようにするために、必芁なモゞュヌルをむンストヌルしたす。 npx expo install react-dom react-native-web @expo/webpack-config アプリの実行 プロゞェクトの ディレクト リで、 npx expo start を実行したす。 wを抌すず、Webアプリを詊すこずができたす。 次のように衚瀺されたした。 コヌドの線集 プロゞェクトの ディレクト リで、 code . を実行しお、 VS Code を立ち䞊げたす。 背景を #25292e に倉曎したしょう。 const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: "#25292e", alignItems: "center", justifyContent: "center", }, }); 次のように衚瀺されたした。 テキストのデフォルトの文字色は黒なので、背景が黒っぜくなったこずで、文字が芋え蟛くなっおしたいたした。 テキストの文字色の倉曎 Textタグのstyle属性で、文字の色を癜に指定したしょう。 <Text style={{ color: "#fff" }}> Open up App.js to start working on your app! </Text> 次のように衚瀺されたした。 むメヌゞの衚瀺 むメヌゞを衚瀺したしょう。 Image コンポヌネント をむンポヌトしたす。 import { StyleSheet, View, Image } from 'react-native'; 次は、むメヌゞパスの蚭定です。 const PlaceholderImage = require('./assets/images/background-image.png'); Imageタグを䜿う前に、 スタむルシヌト の蚭定をしたしょう。 const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: "#25292e", alignItems: "center", }, imageContainer: { flex: 1, paddingTop: 58, }, image: { width: 320, height: 440, borderRadius: 18, }, }); 準備ができたので、Image コンポヌネント を䜿っおみたしょう。 <View style={styles.imageContainer}> <Image source={PlaceholderImage} style={styles.image} /> </View> 䞋蚘のように衚瀺されたした。 ImageViewer コンポヌネント 先ほど䜜ったむメヌゞ衚瀺機胜を コンポヌネント 化したしょう。 StickSmash ディレクト リの䞭にcomponents ディレクト リを䜜成したす。 .../StickerSmash$ mkdir components VS Code で ImageViewer.js を䜜成したす。 code components/ImageViewer.js 䞋蚘のコヌドを ImageViewer.js に曞き蟌みたす。 import { StyleSheet, Image } from 'react-native'; export default function ImageViewer({ placeholderImageSource }) { return ( <Image source={placeholderImageSource} style={styles.image} /> ); } const styles = StyleSheet.create({ image: { width: 320, height: 440, borderRadius: 18, }, }); App.js から ImageViewer.js を呌び出したす。 import ImageViewer from './components/ImageViewer'; const PlaceholderImage = require('./assets/images/background-image.png'); export default function App() { return ( <View style={styles.container}> <View style={styles.imageContainer}> <ImageViewer placeholderImageSource={PlaceholderImage} /> </View> <StatusBar style="auto" /> </View> ); } 芋た目はもちろん以前ず䞀緒です。 Button コンポヌネント これからボタンを2぀䜜りたすが、その前に、Button コンポヌネント を䜜っおそれを利甚するこずにしたしょう。 VS Code で Button.js を䜜成したす。 code components/Button.js 䞋蚘のコヌドを Button.js に曞き蟌みたす。 import { StyleSheet, View, Pressable, Text } from 'react-native'; export default function Button({ label }) { return ( <View style={styles.buttonContainer}> <Pressable style={styles.button} onPress={() => alert('You pressed a button.')}> <Text style={styles.buttonLabel}>{label}</Text> </Pressable> </View> ); } const styles = StyleSheet.create({ buttonContainer: { width: 320, height: 68, marginHorizontal: 20, alignItems: 'center', justifyContent: 'center', padding: 3, }, button: { borderRadius: 10, width: '100%', height: '100%', alignItems: 'center', justifyContent: 'center', flexDirection: 'row', }, buttonLabel: { color: '#fff', fontSize: 16, }, }); App.js から Button.js を呌び出したす。 import Button from './components/Button'; <View style={styles.footerContainer}> <Button label="Choose a photo" /> <Button label="Use this photo" /> </View> const styles = StyleSheet.create({ // Styles that are unchanged from previous step are hidden for brevity. footerContainer: { flex: 1 / 3, alignItems: 'center', }, }); 䞋蚘のように衚瀺されたした。 PhotoButton コンポヌネント 写真アむコンが衚瀺されるボタンを䜜りたしょう。 @expo/vector-icon をむンストヌルしたす。 npx expo install @expo/vector-icons VS Code で PhotoButton.js を䜜成したす。 code components/PhotoButton.js 䞋蚘のコヌドを PhotoButton.js に曞き蟌みたす。 import { StyleSheet, View, Pressable, Text } from "react-native"; import FontAwesome from "@expo/vector-icons/FontAwesome"; export default function PhotoButton({ label }) { return ( <View style={styles.buttonContainer}> <Pressable style={styles.button} onPress={() => alert("You pressed a button.")} > <FontAwesome name="picture-o" size={18} color="#25292e" style={styles.buttonIcon} /> <Text style={[styles.buttonLabel, {}]}>{label}</Text> </Pressable> </View> ); } const styles = StyleSheet.create({ buttonContainer: { width: 320, height: 68, marginHorizontal: 20, alignItems: "center", justifyContent: "center", padding: 3, borderWidth: 4, borderColor: "#ffd33d", borderRadius: 18, }, button: { borderRadius: 10, width: "100%", height: "100%", alignItems: "center", justifyContent: "center", flexDirection: "row", backgroundColor: "#fff", }, buttonIcon: { paddingRight: 8, }, buttonLabel: { color: "#25292e", fontSize: 16, }, }); 䞋蚘のように衚瀺されたした。 画像の遞択 画像を遞択する機胜を実装したしょう。 expo-image-picker をむンストヌルしたす。 npx expo install expo-image-picker App.js で pickImageAsync ファンクションを実装したしょう。遞択した画像は、 result.assets[0].uri に入っおいたす。 import * as ImagePicker from 'expo-image-picker'; const pickImageAsync = async () => { const result = await ImagePicker.launchImageLibraryAsync({ allowsEditing: true, quality: 1, }); if (!result.canceled) { alert(result.assets[0].uri); } else { alert('You did not select any image.'); } }; PhotoButton でボタンを抌したずきの動䜜が定矩できるように、 onPress 属性を远加したしょう。 export default function PhotoButton({ label, onPress }) { return ( <View style={styles.buttonContainer}> <Pressable style={styles.button} onPress={onPress} > <FontAwesome name="picture-o" size={18} color="#25292e" style={styles.buttonIcon} /> <Text style={styles.buttonLabel>{label}</Text> </Pressable> </View> ); } App.js で PhotoButton が抌されたずきに、 pickImageAsync ファンクションを呌び出したす。 <PhotoButton label="Choose a photo" onPress={pickImageAsync} /> ImageViewer で遞択した画像を衚瀺できるように、 selectedImage 属性を远加したす。 export default function ImageViewer( { placeholderImageSource, selectedImage }) { const imageSource = selectedImage ? { uri: selectedImage } : placeholderImageSource; return <Image source={imageSource} style={styles.image} />; } App.js で、写真が遞択されたら、 ImageViewer に衚瀺したす。 import { useState } from "react"; const PlaceholderImage = require("./assets/images/background-image.png"); export default function App() { const [selectedImage, setSelectedImage] = useState(null); const pickImageAsync = async () => { const result = await ImagePicker.launchImageLibraryAsync({ allowsEditing: true, quality: 1, }); if (!result.canceled) { setSelectedImage(result.assets[0].uri); } else { alert("You did not select any image."); } }; return ( <View style={styles.container}> <View style={styles.imageContainer}> <ImageViewer placeholderImageSource={PlaceholderImage} selectedImage={selectedImage} /> </View> <View style={styles.footerContainer}> <PhotoButton label="Choose a photo" onPress={pickImageAsync} /> <Button label="Use this photo" /> </View> <StatusBar style="auto" /> </View> ); } 䞋蚘のように衚瀺されたした。 切りが良いので、前線はここたでずしたす。 たずめ Expoの公匏チュヌトリアル は、難易床もちょうどいい感じで、実践的な力が身に぀くなず感じたした。ただ、 JavaScript じゃなくTypeScriptにしおほしかったずころです。 仲間募集 私たちは同じグルヌプで共に働いおいただける仲間を募集しおいたす。 珟圚、以䞋のような職皮を募集しおいたす。 ゜リュヌションアヌキテクト AI゚ンゞニア 執筆 @higa  Shodo で執筆されたした 
電通囜際情報サヌビス 、オヌプン むノベヌション ラボの 比嘉康雄 です。 今回は、 Expoの公匏チュヌトリアル をやっおいきたいず思いたす。 プロゞェクトの䜜成 Download assets Install dependencies アプリの実行 コヌドの線集 テキストの文字色の倉曎 むメヌゞの衚瀺 ImageViewerコンポヌネント Buttonコンポヌネント PhotoButtonコンポヌネント 画像の遞択 たずめ 仲間募集 プロゞェクトの䜜成 StickerSmash プロゞェクトを䜜成したしょう。 npx create-expo-app StickerSmash && cd StickerSmash Download assets https://docs.expo.dev/static/images/tutorial/sticker-smash-assets.zip をダりンロヌドしおから解凍し、StickerSmash/assetsに栌玍したす。既存のファむルは眮き換えおください。 Install dependencies Webでも実行できるようにするために、必芁なモゞュヌルをむンストヌルしたす。 npx expo install react-dom react-native-web @expo/webpack-config アプリの実行 プロゞェクトの ディレクト リで、 npx expo start を実行したす。 wを抌すず、Webアプリを詊すこずができたす。 次のように衚瀺されたした。 コヌドの線集 プロゞェクトの ディレクト リで、 code . を実行しお、 VS Code を立ち䞊げたす。 背景を #25292e に倉曎したしょう。 const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: "#25292e", alignItems: "center", justifyContent: "center", }, }); 次のように衚瀺されたした。 テキストのデフォルトの文字色は黒なので、背景が黒っぜくなったこずで、文字が芋え蟛くなっおしたいたした。 テキストの文字色の倉曎 Textタグのstyle属性で、文字の色を癜に指定したしょう。 <Text style={{ color: "#fff" }}> Open up App.js to start working on your app! </Text> 次のように衚瀺されたした。 むメヌゞの衚瀺 むメヌゞを衚瀺したしょう。 Image コンポヌネント をむンポヌトしたす。 import { StyleSheet, View, Image } from 'react-native'; 次は、むメヌゞパスの蚭定です。 const PlaceholderImage = require('./assets/images/background-image.png'); Imageタグを䜿う前に、 スタむルシヌト の蚭定をしたしょう。 const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: "#25292e", alignItems: "center", }, imageContainer: { flex: 1, paddingTop: 58, }, image: { width: 320, height: 440, borderRadius: 18, }, }); 準備ができたので、Image コンポヌネント を䜿っおみたしょう。 <View style={styles.imageContainer}> <Image source={PlaceholderImage} style={styles.image} /> </View> 䞋蚘のように衚瀺されたした。 ImageViewer コンポヌネント 先ほど䜜ったむメヌゞ衚瀺機胜を コンポヌネント 化したしょう。 StickSmash ディレクト リの䞭にcomponents ディレクト リを䜜成したす。 .../StickerSmash$ mkdir components VS Code で ImageViewer.js を䜜成したす。 code components/ImageViewer.js 䞋蚘のコヌドを ImageViewer.js に曞き蟌みたす。 import { StyleSheet, Image } from 'react-native'; export default function ImageViewer({ placeholderImageSource }) { return ( <Image source={placeholderImageSource} style={styles.image} /> ); } const styles = StyleSheet.create({ image: { width: 320, height: 440, borderRadius: 18, }, }); App.js から ImageViewer.js を呌び出したす。 import ImageViewer from './components/ImageViewer'; const PlaceholderImage = require('./assets/images/background-image.png'); export default function App() { return ( <View style={styles.container}> <View style={styles.imageContainer}> <ImageViewer placeholderImageSource={PlaceholderImage} /> </View> <StatusBar style="auto" /> </View> ); } 芋た目はもちろん以前ず䞀緒です。 Button コンポヌネント これからボタンを2぀䜜りたすが、その前に、Button コンポヌネント を䜜っおそれを利甚するこずにしたしょう。 VS Code で Button.js を䜜成したす。 code components/Button.js 䞋蚘のコヌドを Button.js に曞き蟌みたす。 import { StyleSheet, View, Pressable, Text } from 'react-native'; export default function Button({ label }) { return ( <View style={styles.buttonContainer}> <Pressable style={styles.button} onPress={() => alert('You pressed a button.')}> <Text style={styles.buttonLabel}>{label}</Text> </Pressable> </View> ); } const styles = StyleSheet.create({ buttonContainer: { width: 320, height: 68, marginHorizontal: 20, alignItems: 'center', justifyContent: 'center', padding: 3, }, button: { borderRadius: 10, width: '100%', height: '100%', alignItems: 'center', justifyContent: 'center', flexDirection: 'row', }, buttonLabel: { color: '#fff', fontSize: 16, }, }); App.js から Button.js を呌び出したす。 import Button from './components/Button'; <View style={styles.footerContainer}> <Button label="Choose a photo" /> <Button label="Use this photo" /> </View> const styles = StyleSheet.create({ // Styles that are unchanged from previous step are hidden for brevity. footerContainer: { flex: 1 / 3, alignItems: 'center', }, }); 䞋蚘のように衚瀺されたした。 PhotoButton コンポヌネント 写真アむコンが衚瀺されるボタンを䜜りたしょう。 @expo/vector-icon をむンストヌルしたす。 npx expo install @expo/vector-icons VS Code で PhotoButton.js を䜜成したす。 code components/PhotoButton.js 䞋蚘のコヌドを PhotoButton.js に曞き蟌みたす。 import { StyleSheet, View, Pressable, Text } from "react-native"; import FontAwesome from "@expo/vector-icons/FontAwesome"; export default function PhotoButton({ label }) { return ( <View style={styles.buttonContainer}> <Pressable style={styles.button} onPress={() => alert("You pressed a button.")} > <FontAwesome name="picture-o" size={18} color="#25292e" style={styles.buttonIcon} /> <Text style={[styles.buttonLabel, {}]}>{label}</Text> </Pressable> </View> ); } const styles = StyleSheet.create({ buttonContainer: { width: 320, height: 68, marginHorizontal: 20, alignItems: "center", justifyContent: "center", padding: 3, borderWidth: 4, borderColor: "#ffd33d", borderRadius: 18, }, button: { borderRadius: 10, width: "100%", height: "100%", alignItems: "center", justifyContent: "center", flexDirection: "row", backgroundColor: "#fff", }, buttonIcon: { paddingRight: 8, }, buttonLabel: { color: "#25292e", fontSize: 16, }, }); 䞋蚘のように衚瀺されたした。 画像の遞択 画像を遞択する機胜を実装したしょう。 expo-image-picker をむンストヌルしたす。 npx expo install expo-image-picker App.js で pickImageAsync ファンクションを実装したしょう。遞択した画像は、 result.assets[0].uri に入っおいたす。 import * as ImagePicker from 'expo-image-picker'; const pickImageAsync = async () => { const result = await ImagePicker.launchImageLibraryAsync({ allowsEditing: true, quality: 1, }); if (!result.canceled) { alert(result.assets[0].uri); } else { alert('You did not select any image.'); } }; PhotoButton でボタンを抌したずきの動䜜が定矩できるように、 onPress 属性を远加したしょう。 export default function PhotoButton({ label, onPress }) { return ( <View style={styles.buttonContainer}> <Pressable style={styles.button} onPress={onPress} > <FontAwesome name="picture-o" size={18} color="#25292e" style={styles.buttonIcon} /> <Text style={styles.buttonLabel>{label}</Text> </Pressable> </View> ); } App.js で PhotoButton が抌されたずきに、 pickImageAsync ファンクションを呌び出したす。 <PhotoButton label="Choose a photo" onPress={pickImageAsync} /> ImageViewer で遞択した画像を衚瀺できるように、 selectedImage 属性を远加したす。 export default function ImageViewer( { placeholderImageSource, selectedImage }) { const imageSource = selectedImage ? { uri: selectedImage } : placeholderImageSource; return <Image source={imageSource} style={styles.image} />; } App.js で、写真が遞択されたら、 ImageViewer に衚瀺したす。 import { useState } from "react"; const PlaceholderImage = require("./assets/images/background-image.png"); export default function App() { const [selectedImage, setSelectedImage] = useState(null); const pickImageAsync = async () => { const result = await ImagePicker.launchImageLibraryAsync({ allowsEditing: true, quality: 1, }); if (!result.canceled) { setSelectedImage(result.assets[0].uri); } else { alert("You did not select any image."); } }; return ( <View style={styles.container}> <View style={styles.imageContainer}> <ImageViewer placeholderImageSource={PlaceholderImage} selectedImage={selectedImage} /> </View> <View style={styles.footerContainer}> <PhotoButton label="Choose a photo" onPress={pickImageAsync} /> <Button label="Use this photo" /> </View> <StatusBar style="auto" /> </View> ); } 䞋蚘のように衚瀺されたした。 切りが良いので、前線はここたでずしたす。 たずめ Expoの公匏チュヌトリアル は、難易床もちょうどいい感じで、実践的な力が身に぀くなず感じたした。ただ、 JavaScript じゃなくTypeScriptにしおほしかったずころです。 仲間募集 私たちは同じグルヌプで共に働いおいただける仲間を募集しおいたす。 珟圚、以䞋のような職皮を募集しおいたす。 ゜リュヌションアヌキテクト AI゚ンゞニア 執筆 @higa  Shodo で執筆されたした 