メモ

CG 関連、ゲームエンジン関連メモ

Unreal Engine HTTP リクエスト / レスポンス処理 (JSON デシリアライズ)

Unreal Engine で HTTP リクエストする方法

モジュールを追加

  • プロジェクトに HTTP , Json モジュールを追加する
    • /Games/<プロジェクト名>/Source/<プロジェクト名>/<プロジェクト名>.Build.cs
    • PublicDependencyModuleNames.AddRange()
      • “HTTP” , “Json” を追加

HTTP リクエストするクラスを作成

クラスを追加する

  • Content Browser > C++ Classes > 右クリック
    • New C++ Class…
    • None 選択 > Next 押下
    • Name を HttpRequest に設定 > Create Class 押下

HttpRequest を実装する

// HttpRequest.h
// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "http.h"

class HTTPREQUESTSAMPLE_API HttpRequest
{
public:
    HttpRequest();
    ~HttpRequest();
    void OnRequest(const FString Url, const FString Verb);

private:
    static void OnResponseReceived(FHttpRequestPtr Request, FHttpResponsePtr Response, bool ConnectionSuccessfully);
};
// Fill out your copyright notice in the Description page of Project Settings.

#include "HttpRequest.h"

HttpRequest::HttpRequest()
{
}

HttpRequest::~HttpRequest()
{
}

void HttpRequest::OnRequest(const FString Url, const FString Verb)
{
    // HTTP リクエストオブジェクト作成
    const FHttpRequestRef Request = FHttpModule::Get().CreateRequest();
    // HTTP リクエスト完了時に実行する関数ポインタをリセット
    Request->OnProcessRequestComplete().BindStatic(&HttpRequest::OnResponseReceived);

    // HTTP リクエストの URL を設定
    Request->SetURL(Url);
    // HTTP リクエストのメソッドを設定
    Request->SetVerb(Verb);
    // HTTP リクエスト
    Request->ProcessRequest();
}

void HttpRequest::OnResponseReceived(FHttpRequestPtr Request, FHttpResponsePtr Response, bool ConnectionSuccessfully)
{
    // レスポンスコードのチェック
    if (EHttpResponseCodes::IsOk(Response->GetResponseCode()))
    {
        // JSON オブジェクト格納用変数の初期化
        TSharedPtr<FJsonObject> ResponseObj = MakeShareable(new FJsonObject());
        // 文字列から JSON を読み込むための Reader 初期化
        TSharedRef<TJsonReader<>> Reader = TJsonReaderFactory<>::Create(Response->GetContentAsString());

        // 文字列から JSON オブジェクト
        if (FJsonSerializer::Deserialize(Reader, ResponseObj))
        {
            // UE でログ出力 (API から受け取った文字列そのまま)
            UE_LOG(LogTemp, Display, TEXT("Response %s"), *Response->GetContentAsString());
            // UE でログ出力 (JSON から title キーの文字列のみ出力)
            UE_LOG(LogTemp, Display, TEXT("Title %s"), *ResponseObj->GetObjectField("slideshow")->GetStringField("title"));
        }
    }
    else
    {
        UE_LOG(LogTemp, Error, TEXT("Reponse Code : %d"), Response->GetResponseCode());
    }
}
  • http リクエストするアクターを作成
    • C++ Classes > 右クリック > Add C++ Class …
    • Actor クラスを親に設定する
  • ヘッダファイル
    • HttpRequest を include する
    • SendRequest メンバ関数を定義する
      • Blueprint から呼び出すため UFUNCTION マクロを指定する
        • BlueprintCallable
        • Category = ”Http”
// ApiTest.h

// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "HttpRequest.h" // <= include

#include "GameFramework/Actor.h"
#include "ApiTest.generated.h"

UCLASS()
class HTTPREQUESTSAMPLE_API AApiTest : public AActor
{
    GENERATED_BODY()

public:
    // Sets default values for this actor's properties
    AApiTest();

protected:
    // Called when the game starts or when spawned
    virtual void BeginPlay() override;

public:
    // Called every frame
    virtual void Tick(float DeltaTime) override;

    UFUNCTION(BlueprintCallable, Category = "Http") // <= メンバ関数定義
    void SendRequest();
};
// ApiTest.cpp

// Fill out your copyright notice in the Description page of Project Settings.

#include "ApiTest.h"

// Sets default values
AApiTest::AApiTest()
{
    // 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;

}

// Called when the game starts or when spawned
void AApiTest::BeginPlay()
{
    Super::BeginPlay();

}

// Called every frame
void AApiTest::Tick(float DeltaTime)
{
    Super::Tick(DeltaTime);

}

void AApiTest::SendRequest()  // <= メンバ関数の実装
{
    HttpRequest Request;
    Request.OnRequest("https://httpbin.org/json", "GET");
}
  • Blueprint クラスを作成する
    • C++ Classes > <プロジェクト名> > ApiTest > MRB (マウス右クリック)
    • Create Blueprint Class Based on ApiTest
    • 作成した Blueprint クラスを任意のレベルに配置

HTTP リクエストを行う

  • 画面のボタンを押下すると HTTP リクエストを行うようにする

  • Widget Blueprint を追加する

    • Content Browser > Widget Blueprint
    • 適当な名前に変更 > (ex) WBP_ApiRequest

  • Widget Blueprint を編集する (Designer) (画面のボタン追加のため)
    • Canvas Panel を配置
      • Palette > PANEL > Canvas Panel > Hierarchy へドラッグ
      • Palette > COMMON > Button > Hierarchy へドラッグ
      • Palette > COMMON > Text > Hierarchy へドラッグ
    • Button
      • Details > Slot > Anchors > Center
      • Details > Slot > Alignment > 0.5 0.5
    • Text
      • Details > Content > Text > Send Request

  • Widget Blueprint を編集する (Graph) (ボタン押下イベントに、API リクエスト処理を割り当てるため)
    • My Blueprint > VARIABLES > Navigation > Button_0 > 選択
    • Details > Events > On Cliecked > + ボタン押下 > On Clicked Event を追加
    • Get Actor Of Class Blueprint を追加
      • Actor Class > BP_ApiTest を指定
      • Return Value > BP_ApiTest クラスを取得する
    • Send Request を実行する

  • Level Blueprint を編集する
    • Editor > Open Level Blueprint
    • Event BeginPlay 以下に追加
      • Create Widget
        • Class > WBP_ApiRequst を指定
      • Add To Viewport

  • Play
    • Send Request ボタン押下 > Output Log にレスポンス結果表示

クラスの関連の概観 (PlantUML)

@startuml class diagram
' ---------- 
' definition 
' ----------
skinparam BackgroundColor #WhiteSmoke
skinparam PackageStyle rectangle

' ---------- 
' definition 
' ----------

package /Engine/Source/Runtime/Online/HTTP/Public
{
    class FHttpModule
    {
        {static} + Get() : FHttpModule&
        + CreateRequest() : TSharedRef<IHttpRequest, ESPMode::ThreadSafe>
    }

    package Interfaces
    {   
        class IHttpBase
        
        class IHttpResponse
        {
            + GetContentAsString(): FString
        }

        class IHttpRequest
        {
            + SetVerb(const FString& Verb) : void
            + SetURL(const FString& URL) : void 
            + ProcessRequest() : bool
            + OnProcessRequestComplete() : FHttpRequestCompleteDelegate&
        }
        note right of IHttpRequest::OnProcessRequestComplete
            * マクロを利用して Delegate の宣言  
            ** DECLARE_DELEGATE_ThreeParams(
                FHttpRequestCompleteDelegate, 
                FHttpRequestPtr /*Request*/, 
                FHttpResponsePtr /*Response*/, 
                bool /*bConnectedSuccessfully*/)
        end note

    }
}

package /Engine/Source/Runtime/Json/Public
{
    package Dom
    {
        class FJsonObject
    }

    package Serialization
    {
        class TJsonReader{}

        class TJsonReaderFactory
        {
            {static} + Create(FString&& JsonString) : TSharedRef<TJsonReader<TCHAR>>
        }

        class FJsonSerializer
        {
            {static} +Deserialize(const TSharedRef<TJsonReader<CharType>>& Reader, TSharedPtr<FJsonObject>& OutObject, EFlags InOptions = EFlags::None) : bool
        }
    }
}

package C++
{
    class HttpRequest
    {
        + OnRequest(const FString Url, const FString Verb) : void
        {static} - OnResponseRecieved(FHttpRequestPtr Request, FHttpResponsePtr Response, bool ConnectionSuccessfully) : void
    }
    note right of HttpRequest::OnRequest 
    * OnRequest の処理内容
    ** FHttpModule::Get で FHttpModule のインスタンスを取得 (Singleton ライクな実装)
    ** FHttpModule.CreateRequest で IHttpRequet インタフェースを取得 
        (プラットフォームに合わせたインタフェース。SharedReference かつ スレッドセーフ)
    ** IHttpRequest->OnProcessRequestComplete デリゲートに、OnResponseRecieved 関数をバインド
    ** IHttpRequest->SetURL に、問い合わせ URL を設定
    ** IHttpRequest->SetVerb に、HTTP メソッドを設定
    ** IHttpRequest->ProcessRequest でリクエスト実行
    end note

    note right of HttpRequest::OnResponseRecieved
    * OnResponseRecieved の処理内容 (IHttpRequest の OnProcessRequestComplete デリゲートに登録)
    ** FJsonObject (SharedPointer) を作成
    ** TJsonReader (SharedReference) を TJsonReaderFactory::Create で作成 
    ** FJsonSerializer::Desirialize で、FJsonObject と TJsonReader を受け取りデシリアライズ
    ** デシリアライズしたレスポンスをログに表示する
    end note

    class ApiTest
    {
        + SendRequest() : void
    }
}

package Blueprint
{
    class BP_ApiTest

    class WBP_ApiRequest
    note right of WBP_ApiRequest
    * Widget Blueprint Class
    ** Button の Onclicked イベントに BP_ApiTest の SendRequest を紐づけ
    end note
}

' ---------- 
' relation 
' ----------
TJsonReader <-- TJsonReaderFactory : use

IHttpBase <|-- IHttpRequest : extends 
IHttpBase <|-- IHttpResponse : extends

FHttpModule <---- HttpRequest : use
IHttpRequest <---- HttpRequest : use
IHttpResponse <---- HttpRequest : use
TJsonReaderFactory <---- HttpRequest : use
FJsonObject <---- HttpRequest : use
FJsonSerializer <---- HttpRequest : use

HttpRequest <-- ApiTest : use

ApiTest <|-- BP_ApiTest : extends

BP_ApiTest <-- WBP_ApiRequest : use

@enduml

参考

#UnrealEngine5 でHTTPリクエスト機能を実装してAPIコールしてみた | DevelopersIO

https://youtu.be/vLGZp5hl6qU

https://www.youtube.com/watch?v=c6gad7tXfTM

[UE4] HTTP通信 その1 ~基本編~|株式会社ヒストリア

[UE4] HTTP通信 その2 ~HTTPモジュールの構成~|株式会社ヒストリア

HTTP

Json

Unreal スマート ポインタ ライブラリ

デリゲート

httpbin.org