UE4の勉強記録

UE4の勉強の記録です。個人用です。

12章14節 HTTP API – ウェブリクエスト

<前文>

f:id:kazuhironagai77:20190317214008p:plain

とうとう、この教科書のレシピも最後の二つになりました。この教科書で勉強を始めた時はまさかこんなに時間を取られるとは思っていませんでした。1章の勉強に一週間で12章なので3か月が当初の予定でした。途中でこの勉強内容を何処かに記録した方がいいと思いブログを開始しましたがそれが更に時間を取るようになりました。ブログを開始してからですら1年経っています。

この本で勉強を開始した時はUE4C++についての本格的な教科書はこの本以外なかったです。少なくとも私が見つけられた範囲ではこの教科書は最も本格的な物でした。しかしこの教科書の評価も再現出来ないなどの多くの批判が述べられていて実際買った後も勉強しないでいました。いや、こんな大事な話をここで述べるべきではないですね。全部のレシピが終わってから最後にこの教科書についての総括をします。

<本文>

<目的>

HTTPのリクエストをしてサーバーに接続する方法を学びます。勿論UE4C++なのでUE4のHTTP APIを使用します。今のゲームは必ずといっていいほどサーバーに接続しますがUE4におけるサーバーへの接続方法をここで学ぶのでしょうか?勿論、UE4におけるサーバーへの接続方法を学ぶには枚数的に少なすぎますが触りぐらいは学べるようです。

Step.0

今回も新しいプロジェクトを作成します。プロジェクト名は、Chapter12Part13とします。バージョンは4.22です。

Step.1

f:id:kazuhironagai77:20190317214143p:plain

はい。ですがサンプルコードを見てからやります。

f:id:kazuhironagai77:20190317214159p:plain

HTTPだけでいいんでしょうか?Networking、Sockets、Messagingなどもひょっとしたら必要になるかもしれないですね。

f:id:kazuhironagai77:20190317214220p:plain

取りあえず、HTTPだけ追加しました。

Step.2

f:id:kazuhironagai77:20190317214244p:plain

f:id:kazuhironagai77:20190317214254p:plain

とのファイルを作成すれば良いのかが分りませんね。こんな時はサンプルコードをみるのが一番です。

Gamemode.hファイルでHttpRetrySystem.hをインクルードしてました。

f:id:kazuhironagai77:20190317214317p:plain

しかし、Gamemode.hファイルでは他のヘッダーファイルはインクルードしていません。ウーン。他のファイルを探してみた所、Gamemode.cppファイルで他のヘッダもインクルードしていました。

f:id:kazuhironagai77:20190317214344p:plain

IHtttpResponse.hファイルもインクルードする必要があるのでしょうか?

取りあえずgamemodeクラスの派生クラスを作成します。

f:id:kazuhironagai77:20190317214404p:plain

f:id:kazuhironagai77:20190317214426p:plain

こんな感じで作りました。

f:id:kazuhironagai77:20190317214455p:plain

Cppファイルがまたエラーを吐いています。VSをリフレッシュします。

f:id:kazuhironagai77:20190317214519p:plain

直りました。

HttpManager.h、HttpModule.h、そしてHttpRetrySystem.hをgamemode.hファイルにインクルードしました。

f:id:kazuhironagai77:20190317214540p:plain

Step.3

f:id:kazuhironagai77:20190317214615p:plain

f:id:kazuhironagai77:20190317214632p:plain

サンプルコードを見ると、

f:id:kazuhironagai77:20190317214656p:plain

Gamemode.cppファイルのTestHttp()内で作成していますので、

f:id:kazuhironagai77:20190317214716p:plain

f:id:kazuhironagai77:20190317214724p:plain

TestHttp()メンバー関数を先に作成します。

更にガワの方を作成します。

f:id:kazuhironagai77:20190317214751p:plain

f:id:kazuhironagai77:20190317214937p:plain

ガワだけ動くかテストします。

コンパイルしてplayを実行するとメッセージが表示されたので、期待通りに動いているようです。

f:id:kazuhironagai77:20190317214959p:plain

それでは、

f:id:kazuhironagai77:20190317215025p:plain

をTestHttp関数に追加します。

f:id:kazuhironagai77:20190317215046p:plain

ビルドも成功しました。

f:id:kazuhironagai77:20190317215106p:plain

f:id:kazuhironagai77:20190317215124p:plain

はい。

直接関係はないですが、最新のソフトウェアデザインの教科書やチュートリアルではシングルトンは使うべきではないと良く書かれていますが実際は結構使われているみたいですね。

Step.4

f:id:kazuhironagai77:20190317215225p:plain

f:id:kazuhironagai77:20190317215246p:plain

サンプルコードを見ると、

f:id:kazuhironagai77:20190317215308p:plain

f:id:kazuhironagai77:20190317215317p:plain

となっていました。関数の実装部については教科書はまだ何も述べていないので今は関数の宣言と定義のみコピーします。

f:id:kazuhironagai77:20190317215342p:plain

f:id:kazuhironagai77:20190317215350p:plain

Step.5

f:id:kazuhironagai77:20190317215413p:plain

f:id:kazuhironagai77:20190317215532p:plain
サンプルコードをみると、以下に示すようにTestHttp()関数内に実装されていました。

f:id:kazuhironagai77:20190317215609p:plain

以下のように追加しました。

f:id:kazuhironagai77:20190317215631p:plain

サンプルコードではこの部分のコードは手順の三番目になっています。

教科書の説明では、以下に示す手順2の部分が抜けています。

f:id:kazuhironagai77:20190317215658p:plain

TestHttp()関数内のサンプルコードの手順をまとめると以下のようになります。

f:id:kazuhironagai77:20190317215719p:plain

1.FHttpModuleのシングルトンのインターフェイスからIHttpRequestオブジェクトを作成します。

f:id:kazuhironagai77:20190317215754p:plain

2.Httpのリクエストの進行を見るために、OnRequestProgress()関数を追加します。貴方がこれを見たい見たくないに関わらず、これはHttpのリクエストを熱望するプログレスバーを表示するのに使用されます。

f:id:kazuhironagai77:20190317215827p:plain

3.何かをするためにOnProcessRequstComplete()関数を追加します。そのHTTPのリクエストが完了した時、我々は7種類のデリゲートを追加する方法を以下に示します。

これを見ると、2番目の手順は今は実装しなくてもいいのかもしれません。TestHttp()関数内のサンプルコードには更に、

f:id:kazuhironagai77:20190317215919p:plain

がありますが、後で説明があるのでしょう。

f:id:kazuhironagai77:20190317215941p:plain

やっぱり、TestHttp()関数内の手順2の実装がありました。

f:id:kazuhironagai77:20190317220003p:plain

f:id:kazuhironagai77:20190317220012p:plain

まずは、サンプルコードをそのままコピーします。

f:id:kazuhironagai77:20190317220032p:plain

Requestのエラーを消すために、IHttpResponse.hを追加します。ついでにIHttpRequest.hも足します。

f:id:kazuhironagai77:20190317220102p:plain

f:id:kazuhironagai77:20190317220110p:plain

Requestのエラーが消えました。

Info(FS…のエラーは、サンプルコードのLoggingファイルを基にしたLoggingファイルを作成して

f:id:kazuhironagai77:20190317220214p:plain

f:id:kazuhironagai77:20190317220224p:plain

それをインクルードする事で、

f:id:kazuhironagai77:20190317220254p:plain

f:id:kazuhironagai77:20190317220310p:plain

エラーしなくなりました。勿論ビルドも成功しました。

f:id:kazuhironagai77:20190317220334p:plain

次の行は来週のレシピ用みたいなので今は消しておきます。

サンプルコードのTestHttp()関数内には、更に以下に示すコードがあります。

f:id:kazuhironagai77:20190317220401p:plain

この中の、7番がBindUObjectですが、それが教科書の説明の

f:id:kazuhironagai77:20190317220425p:plain

この部分に当たるのでしょうか?

もしそうならばこの7番だけコピーすればいいのか?それとも1から全部コピーしないといけないのでしょうか?

教科書に答えが書かれていました。ちょっと長いけど全部引用します。

f:id:kazuhironagai77:20190317220512p:plain

f:id:kazuhironagai77:20190317220526p:plain

となるとやっぱり全部追加しないといけないみたいですね。でもBindRaw()関数は手順2ですね。手順1は要らないのでしょうか?

良く読んだらコメントに答えが既に書かれていました。

f:id:kazuhironagai77:20190317220623p:plain

デリゲートを作成した所のコメントです。このコメントを信じれば、7種類のデリゲートを追加する方法を紹介しているので、その中から一種類だけ選べば良い事になります。

あら?

ここまで来て、大変な間違いをしている事に気が付きました。

f:id:kazuhironagai77:20190317220709p:plain

f:id:kazuhironagai77:20190317220718p:plain

の部分は、

f:id:kazuhironagai77:20190317220738p:plain

IHttpRequestクラスのオブジェクトであるhttpのBindLambda()関数と思っていたのですが、教科書で説明されているのはFHttpRequestCompleteDelegateクラスのオブジェクトであるdelegateのBindLamda()関数でした。

本来なら消して書き直すのですが、間違いは間違いとして残すのがこのブログの主旨なので消すわけにはいきません。

色々考えたのですがStep.5からやり直します。そうすれば大丈夫なはずです。

Step.5(やり直し)

何か小学生の時のプラモデル制作で右足を作っていたのに左足のパーツを使ってしまい途中からそれに気が付いて絶望した時の事を思い出しました。プログラミングは消して書き直せばいいだけなので楽チンです。

何故先程間違えてしまったかと言うとサンプルコードのTestHttp()関数にはIHttpRequestクラスのオブジェクトであるhttpの関数を呼び出す工程とFHttpRequestCompleteDelegateクラスのオブジェクトであるdelegateの関数を呼び出す工程の二つがあります。IHttpRequestクラスのオブジェクトであるhttpの関数を呼び出す工程の3でFHttpRequestCompleteDelegateクラスのオブジェクトであるdelegateを作成するのでごちゃごちゃになってしまったのです。以下にそれぞれの工程を整理した図を示します。

f:id:kazuhironagai77:20190317220810p:plain

f:id:kazuhironagai77:20190317220824p:plain

この部分に気を付けて今度はやって行きます。

まず現在の私のTestHttp()関数の実装部を見ると、

f:id:kazuhironagai77:20190317220846p:plain

となっています。IHttpRequestクラスのオブジェクトであるhttpの関数を呼び出す工程の3までが実装された状態です。つまり5の最初の部分が終了した状態です。

f:id:kazuhironagai77:20190317220908p:plain

f:id:kazuhironagai77:20190317220943p:plain

しかし前のstep.5ではIHttpRequestクラスのオブジェクトであるhttpの関数を呼び出す工程の2についての記述が教科書に全くないので2を実装する必要があるのか分からなかったのでした。以下にもう一度示しますがhttpオブジェクトの工程の2の部分を読むとプログレスバーについての関数なので今回は実装しなくても良いみたいとの結論を前のstep.5で出しました。

f:id:kazuhironagai77:20190317221020p:plain

ところがその後で、delegateの方のBindLamda()関数と勘違いしてやっぱり実装しないといけないと間違って解釈してしまいました。と言う事はhttpオブジェクトの工程の2は実装しなくて良いと言う事になります。

f:id:kazuhironagai77:20190317221043p:plain

のでhttpオブジェクトの工程の工程2は抜きました。

f:id:kazuhironagai77:20190317221113p:plain

ここからがFHttpRequestCompleteDelegateクラスのオブジェクトであるdelegateについてです。

f:id:kazuhironagai77:20190317221136p:plain

5の続きではコールバックの関数をデリゲートに追加する必要があると述べています。そのための方法としてこっちのBindLamda()関数を紹介しています。と言う事はdelegateのbindLambda()関数は必ず追加しないといけないはずです。サンプルコードの方も以下に示します。

f:id:kazuhironagai77:20190317221157p:plain

基本的には全く同じですね。サンプルコードの方をコピーします。

f:id:kazuhironagai77:20190317221218p:plain

Warningがエラーを吐いています。ので、LoggingファイルにWarning()関数を追加します。

f:id:kazuhironagai77:20190317221238p:plain

f:id:kazuhironagai77:20190317221246p:plain

勿論エラーも消えました。

f:id:kazuhironagai77:20190317221327p:plain

まあ。いい訳になってしまうのですが、このdelegate.BindLambda()関数の実装とhttpのBindLambda()関数の実装部って結構似ていますよね。気が付かないで間違えてしまっても仕方ないですね。

f:id:kazuhironagai77:20190317221349p:plain

うーん。なるほど。やっと意味が分かって来ました。“コールバックの関数をデリゲートに追加するためのいくつかの方法がある。”と5の続きで述べられていますが、最初のラムダを使うのがその方法の一つ、今回のUObjectを使うのが別な方法の一つと言う事ですね。となるとこのBindUObject()関数は実装しなくてもいいみたいですね。

Step.6

f:id:kazuhironagai77:20190317221416p:plain

取りあえず、step.5の残りのコードは実装しなくてもいいとの仮定が正しいとして、次のstepを実装します。以下にもう一度httpオブジェクトの工程を示しますがこの手順の4を実装します。

f:id:kazuhironagai77:20190317221436p:plain

サンプルコードでは以下に示すように実装されていました。

f:id:kazuhironagai77:20190317221500p:plain

教科書と全く同じですね。

f:id:kazuhironagai77:20190317221523p:plain

私のコードにも実装しました。

Step.7

f:id:kazuhironagai77:20190317221548p:plain

サンプルコードの方も同じです。

f:id:kazuhironagai77:20190317221607p:plain

私のコードにも実装します。

f:id:kazuhironagai77:20190317221628p:plain

<結果>

ビルドします。

f:id:kazuhironagai77:20190317221651p:plain

成功しました。

Playを実行します。

f:id:kazuhironagai77:20190317221710p:plain

f:id:kazuhironagai77:20190317221721p:plain

途中省略

f:id:kazuhironagai77:20190317221801p:plain

f:id:kazuhironagai77:20190317221813p:plain

途中省略

f:id:kazuhironagai77:20190317221838p:plain

最後に、Request successの文字も確認出来ました。

f:id:kazuhironagai77:20190317221921p:plain

成功しているみたいですね。

確認のために、

f:id:kazuhironagai77:20190317221942p:plain

ここから

f:id:kazuhironagai77:20190317222004p:plain

ここまでをテキストにコピーしてhtmlファイルにして開いて見ると、

f:id:kazuhironagai77:20190317222026p:plain

f:id:kazuhironagai77:20190317222038p:plain

f:id:kazuhironagai77:20190317222049p:plain

UE4のホームページの最初に現れるイラストの一部が表示されました。

取りあえずは成功したみたいです。

<考察>

Delegateオブジェクトは以下に示す図の1にあるBindLambda()関数を使用して5の続きで述べられている「コールバックの関数をデリゲートに追加する。」を行いました。

f:id:kazuhironagai77:20190317222125p:plain

f:id:kazuhironagai77:20190317222134p:plain

しかし教科書には、以下に示すように

f:id:kazuhironagai77:20190317222153p:plain

工程表(FHttpRequestCompleteDelegateクラスのオブジェクトであるdelegateの関数を呼び出す工程)の7に示すUObjectを使用して「コールバックの関数をデリゲートに追加する。」方法についての解説があります。この部分はかなり詳しく説明されているので、ここで勉強しておきます。

まず、確認ですが、step.4 で作成した関数HttpRequestComplete()が「コールバックの関数をデリゲートに追加する。」におけるコールバックの関数と考えられます。上記の5の続きでもはっきりとHttpRequestCompleteと書かれています。以下にstep.4をもう一度示しておきます。

f:id:kazuhironagai77:20190317222214p:plain

f:id:kazuhironagai77:20190317222222p:plain

早速BindUObject()関数を試してみましょう。

私のHttpRequestComplete()関数は実装していないのでサンプルコードのHttpRequestComplete()関数の実装部をコピーします。

f:id:kazuhironagai77:20190317222242p:plain

更に、1のBindLambda()関数をコメントして7のBindUObject()関数を追加します。

f:id:kazuhironagai77:20190317222303p:plain

ここまで書いて思い出したのですが、はるか昔、確か5章のHaving Events and DelegatesでUFunctionでない生の関数はコールバック出来ないみたい事を勉強した記憶があります。が取り敢えず試してみます。

f:id:kazuhironagai77:20190317222332p:plain

普通にビルド出来ましたね。Playを実行すると

f:id:kazuhironagai77:20190317222352p:plain

普通に出来ました。HttpRequestComplete()関数を持つgamemodeクラスがUObjectだからHttpRequestComplete()関数そのものがUFunctionが無くても良いみたいですね。5章のHaving Events and Delegatesを読み直せばはっきり分るのですが今はそこまでの気力がないのでこの解釈で行きます。

となると、FHttpRequestCompleteDelegateクラスのオブジェクトであるdelegateの関数を呼び出す工程の6であるBindUFunction()はHttpRequestComplete()関数には使えないのかもしれませんね。

f:id:kazuhironagai77:20190317222416p:plain

サンプルコードの6のBindUFunction()にしっかりとその事が書かれていました。この文章は今の今まで何回も読んでいたのですが意味が分かっていませんでした。今やっとこの6のコメントの意味が分かりました。となると残りの教科書の解説もdelegateについてのようでそれなら5章のHaving Events and Delegatesを読めばいいのでここで敢えてやる必要はないみたいですね。

IHttpRequestクラスとFHttpRequestCompleteDelegateクラスについて>

この二つは多分クラスではないと思いながら敢えて調べないでクラスとして扱っていました。その方が理解しやすくかつ扱いやすかったからです。一応ここで調べておきます。

UE4APIによるとIHttpRequestは、

f:id:kazuhironagai77:20190317222508p:plain

HttpリクエストのためのインターフェイスFHttpFactoryを使用して作成される)

とありますがシンタックスを見ると、

f:id:kazuhironagai77:20190317222554p:plain

普通にクラスですよね。

f:id:kazuhironagai77:20190317222614p:plain

FHttpModuleのUE4APIによるとCreateRequestメンバー関数を持ちそれが

f:id:kazuhironagai77:20190317222632p:plain

TSharedRef<IHttpRequest>を返すようです。IHttpRequestはクラスと呼んでも問題はなさそうですね。

FHttpRequestCompleteDelegateについて調べます。

UE4APIによるとFHttpRequestCompleteDelegateは、

f:id:kazuhironagai77:20190317222751p:plain

Httpリクエストが終了した時に呼ばれるデリゲート

とあり

f:id:kazuhironagai77:20190317222836p:plain

タイプと紹介されていました。ので、「FHttpRequestCompleteDelegateクラスのオブジェクトであるdelegateは…」と言う言い方は「FHttpRequestCompleteDelegateタイプの変数であるdelegateは…」と言った方が良いみたいですね。ただC++の変数とタイプとオブジェクトとクラスの関係は抽象的な概念も入ってくるので人それぞれかもしれません。「FHttpRequestCompleteDelegateタイプの変数であるdelegateは…」と言った方が良いと考える人の方が多そうみたいです。ぐらいにしておいた方が良いかもしれません。
<まとめ>
UE4のHTTP APIを使用してHTTPのリクエストをしてサーバーに接続する方法を学びました。以下に示す工程でIHttpRequestクラスのオブジェクトの関数を呼び出します。今回はgamemodeクラスから呼び出しました。

f:id:kazuhironagai77:20190317222937p:plain

上の工程の3でFHttpRequestCompleteDelegateクラスからのオブジェクトを作成しますがいかに示す1から7のいずれかの方法でコールバックの関数をデリゲートに追加します。

f:id:kazuhironagai77:20190317222957p:plain

今回は、1と7で試してみました。

<おまけ>

ラムダについての復習です。これは来週まとめます。