<前文>
FMessageLogはメッセージログとアウトプットログに同時にメッセージを発信する事が出来るオブジェクトだそうです。今回はこのオブジェクトの使用方法を勉強するみたいです。
<本文>
<目的>
12章2節「FMessageLog からメッセージをメッセージログに書く」を勉強します。これのどこが前回と違うのか今一、分からなかったので、ちょっと調べて見ました。このサイトによると
最も大切なログのメッセージ、エラーや警告、は普通のメッセージログのウィンドウに記録されます。
とありました。前回はoutput logへの表示方法を勉強したので、今回、メッセージログへの表示方法を勉強するようです。
<方法>
Step.0
前回作成したプロジェクトをそのまま使用します。プロジェクト名はChapter12Part1です。
教科書は色々言っていますが、UE4のエディター上でメッセージログを表示させればいいだけと解釈しました。
Step.1
色々書いていますが、取りあえずChapter12Part1.hファイルに上記のコードを追加します。
取りあえず#define でマクロを作成した事は流石に私でも分かります。これからどこかのコードにLOCTEXT_NAMESPACEを入れるたびに、コンパイルする前にそのLOCTEXT_NAMESPACEは”Chapter12Namespace”に置き換わると言う事です。
気になる事は、PacketPublish社のサンプルコードの中に、log.hファイルがありそれが、どうもprojectname.hの代わりに入っているみたいなのですがそこには、
“Chapter12”と書かれていました。この部分の名前は何でもいいんでしょうか?それともプロジェクト名と同じにしないといけないのでしょうか?
良く分からないですがこのまま行きます。
Step.2
以下のようになりました。
FMessageLogはエラー表示になったので#include “MessageLog.h”を追加しました。2.を読むとextern FName LoggerName;は要らないようにも思えますが、一応入れておきます。
まず、#define FTEXT(x) LOCTEXT(x,x)ですが、FTEXT(x)はLOCTEXT(x,x)として扱うという意味と考えられますがLOCTEXT(x,x)がネットで検索しても出て来ません。LOCTEXT(x,x)もどこかで定義するのでしょうか?
次の行は2. で宣言したextern FName LoggerName;に”Chapter12Log”と定義しています。extern FName LoggerName; 余計かもと思いつつ残しておいて良かったです。(しかし考え直してみるとやっぱりいらない?)
そして関数CreateLog(FName name)の定義です。この関数の宣言はヘッダーファイルにはなかったです。後、最後の方でこの関数を使っているような個所があるのですがそこではCreateLogger()と成っています。PacketPublish社のサンプルコードのlog.cppファイルでは
CreateLogger()となっていました。更にlog.hファイルでは、以下に示すように
CreateLogger()関数の宣言がされていました。
多分CreateLogger()が正しいのでしょう。
他にも参考になる箇所がないかとサンプルコードをみていると、log.hに
#define FTEXTS(x) LOCTEXT(x, x)がコメントされていて、その下に#define FTEXTS(x) FText::FromString(x)となっていました。LOCTEXT(x, x)は何処を探しても定義が見つからなかったのでひょっとすると廃棄されたマクロではと思っているのですがその可能性はあるかと。注意が必要な部分ですが取り敢えずはこのままでいきます。
CreateLog()もしくはCreateLogger()関数の定義を見ていきます。
FMessageLogModule& MessageLogModule = FModuleManager::LoadModuleChecked<FMessageLogModule>("MessageLog");
最初の行は教科書もサンプルコードも全く同じでした。FMessageLogModuleクラスのレファレンスオブジェクト、MessageLogModuleの宣言と定義です。まずFMessageLogModuleクラスやFModuleManagerクラスが何のクラスか全く分かりません。APIで調べてみました。
Remarkがありませんでした。これではこのクラスが何なのかよく分かりません。特に今の時点では。
モジュールマネジャーを実装する。
そのモジュールマネジャーはモジュールをロードしたりアンロードしたりするのに使用される。
またそのモジュールマネジャーは現在ロードされている全てのモジュールの記録を保つためにも使用される。
このシングルトンにはFModuleMagager::Get()を使用する事でアクセスできる。
こっちは沢山説明があり、非常に良く分かりました。
では本題であるFModuleManagerクラスのメンバー関数、LoadModuleChecked()を見てみると、二つありました。
どっちか分からん。更に最初のやつにはオレンジ色のSマークがついています。こんなの気にした事ありませんでした。(スタテックの意味でした。)
両方のシンタックスを見てみると最初の関数はテンプレートを使用しています。もう一方の関数は使用していません。LoadModuleChecked<FMessageLogModule>ですから最初の関数が正解ですね。
最初の関数のRemarkには、
特定のモジュールをロードします。
それが存在するかチェックします。
ロードされたモジュールかもしロード操作が失敗した場合はnullptr(返り値の事?)
となっていました。
更に、この行の最期の("MessageLog")の部分はInModuleNameですね。大体分かりました。
次の行は、FMessageLogInitializationOptions InitOptions;となっています。FMessageLogInitializationOptionsクラスは、
メッセージログのUIのセットのためのオプション
とありました。メッセージログにメッセージを表示するために書いているのですからここをTrueにすればいいのかと思ったら実際は更に複雑でした。
まず、上に示したように教科書とサンプルコードは同じでした。FMessageLogInitializationOptionsクラスの変数をまず見てみましょう。
ブーリアン:bAllowClear:このログに対してユーザーがclearを使用出来るかどうか
ブーリアン:bDiscardDuplicates:2重のメッセージかどうかチェックしてそれを捨てるかどうか
ブーリアン:bshowFilters:フィルターメニューを見せるかどうか
ブーリアン:bShowLogWindow:メインのログウィンドウ内にこのログを見せるかどうか
ブーリアン:bShowpages:最初にページウィジェットを見せるかどうか
Uint32:MaxPageCount:このログが持てる最大のページ数。ページはファーストインラストアウトで管理
とありました。この変数からbShowpages、「最初にページウィジェットを見せるかどうか」とbshowFilters、「フィルターメニューを見せるかどうか」の設定をオンにしたと言う事のようです。
正直、ページウィジェットやフィルターメニューが何なのか分からない私はこれをオンにしてもピンと来ません。次に行きましょう。
遂にマクロの関数FTEXTを使用しているコードに出会ってしまいました。果たして、#define FTEXT(x) LOCTEXT(x,x)は実行されるのでしょうか?サンプルコードの#define FTEXTS(x) FText::FromString(x)が正しいのではないでしょうか?
後、今気が付いたのですが、サンプルコードの方はFTEXTでなくFTEXTSだったんですね。まあそれによって何かが変わるわけではないですが…。
CreateLog()もしくはCreateLogger()関数の最後の行、
を見てみましょう。
まずRegisterLogListing()メンバー関数についてです。APIによると、
IModuleInterfaceのエンドインターフェイスは、メッセージログウィジェットにリスト化されたログを
登録します。このログはデファルトでグローバルメッセージログのウィンドウに表示されます。
しかしこれは初期の設定で無効にも出来ます。
AddMesseageなどを通してログをアウトプットする前にこの関数を
呼ぶ事は必要ではありません。
この呼び出しは単純にUIがログデータから見えるようにするために登録するだけです。
この関数がメッセージログにログを表示させる働きをすると解釈しました。次にこの関数のパラメーターを見てみます。
LogName:登録するためのログの名前
LogLabel:ログのために表示するラベル
InitializationOptions:このメッセージログのための初期化のオプション
それぞれのパラメーターのタイプも分かっておきたいのでシンタックスも載せておきます。
まず最初のパラメーターであるLogName(タイプはFName)ですが、サンプルコードと教科書が微妙に違っています。サンプルコードは、
logName、この関数のパラメーターをパスしています。
教科書の方は、パスしていません。
サンプルコードの場合は、この関数(CreateLooger()の事)を使用する個所で、LoggerNameをアギュメントとしてパスすればいいようにみえます。問題は教科書のコードです。まずCreateLog関数内にパラメーターであるnameを使用している箇所がありません。??です。
RegisterLogListing()メンバー関数のパラメーターに戻りましょう。二番目のパラメーターは、
LogLabel:ログのために表示するラベル
タイプは、FTextとなっています。
まず、教科書の方ですが、パスされているアギュメントはLogListingName. タイプはFTextです。ただしこの変数の初期化のために、
FTEXTマクロが使用されています。
このLOCTEXT(x、x)を指定しないで動くのかが大変不安な所です。
PacketPublish社の提供しているサンプルコードの方は、
FTEXTSマクロが使用されていて、projectname.hファイルの方で、
FTEXTSマクロの定義がしっかり指定されています。
RegisterLogListing()メンバー関数の最後のパラメーターは、
InitializationOptions:このメッセージログのための初期化のオプション
で、タイプは、FMessageLogInitializationOptionsです。教科書でもサンプルコードでもinitOptionsをアギュメントとしてパスしています。
ここで、CreateLog()もしくはCreateLogger()関数はお終いです。あれ、FMessageLogクラスのオブジェクトを返していませんね。サンプルコードの方はvoidになってますね。
この後には、gameModeクラスのコンストラクターにでも書いて下さいと以下のコードが紹介されていました。
CreateLog()ではなくCreateLogger()になっていますね。
サンプルコードのgameModeクラスのコンストラクターにも同じコードがありました。
では、実際にやってみましょう。
まずは、教科書通りに、#define FTEXT(x) LOCTEXT( x, x )を追加してみました。この関数が働かない可能性があるので、//#define FTEXTS(x) FText::FromString( x )も入れておきました。
次にCreateLogもしくはCreateLogger関数ですが、名前はCreateLoggerにします。教科書ですらgameModeクラスのコンストラクターで使用する際にCreateLoggerと書いているからです。返り値はvoidにします。
最初の行を書いてみました。
思いっきりエラーが出ています。FMessageLogModuleのAPIに以下のように出ているので、
そのまま足しました。
見事にエラーが消えました。
更に、ModuleがMessageLogになっているので、build.csファイルにMessageLogを足します。
Publicかprivateか分からないですが取り敢えずpublicの方に足しておきます。
念のために、サンプルコードのbuild.csを見てみると、
MessageLogモジュールはありませんね。何か嫌な感じです。ですがとりあえず、ビルドしてみましょう。
ビルドは通りましたね。実行してみます。
これも問題ないです。がCreateLogger関数を実際に呼んだ時に問題になるかもしれません。取りあえず様子見で行きます。
FTEXTで取りあえずは行きます。ここまではエラーもなく問題なく進んでいます。
最終的には以下のようになりました。
更に、LoggerNameを忘れていたので、追加しました。
なるべく教科書の例に近づけましたが、この場合なら(FName name)は要らないですね。取ってしまいます。
たったこれだけのコードなの?ちょっと驚きです。
残りのコードはゲームモードクラスにでも入れて置けと書かれているので専用のゲームモードクラスでやります。(勿論、私が作成したのはGameModeBaseクラスからの派生クラスです。)
こんな感じになりました。まずCreateLogger()を書いたら速攻でエラーを食らいました。サンプルコードに書かれているようにChapter12part1.hに
を足しました。
更に、FTEXTを認識しないので、#define FTEXT(x) LOCTEXT( x, x )をもう一度足しました。
Tipには
と書かれていましたがこの事なんでしょうか?
<結果>
普通に表示されました。
#define FTEXTS(x) FText::FromString( x )の方も試してみましたが、
どちらでも出来ました。
Step.4
以下に示すようにコードを足しました。
結果は、
完璧ですね。
最期に、
のMessageLogが本当に必要だったのか、取り除いて試してみます。
プロジェクトをcleanしてからビルドすると、
普通に出ていますね。何故でしょうか?
<考察>
12章のレシピの説明はアバウトだったので、考察しながら進めざるえず、今回はここに書くべき内容は全て<方法>と<結果>に書いてしまいました。
その中で2つだけ分からない事があります。
1. #define FTEXT(x) LOCTEXT( x, x )のLOCTEXT()はどこに定義されているの?
2. MessageLogモジュールをBuild.csに加えなくてもFMessageLogModuleが使えるのは何故?
この二つに関しては良く分かりません。
2.についてですが、ここで少し考察してみます。正しいかどうかは分かりません。
まず、FMessageLogModuleですがAPIには、
モジュールはMessageLogと書かれていますがそこにはIModuleInterfaceが親クラスであるとも書かれており
そのIModuleInterfaceクラスのモジュールはAPIによると
Coreモジュールになっています。Coreモジュールは勿論、build.csで
元々追加されていますから、ひょっとするとこのせいかもしれません。
<まとめ>
今回は、FMessageLog からメッセージをメッセージログに書く方法を勉強しました。この方法の最も重要なポイントは、FMessageLogModuleクラスのメンバー関数RegisterLogListing()を使う事です。このメンバー関数の目的は、APIによると以下のようになっています。
これを一言で言ってしまえばメッセージログにログのリストを登録する事です。今回のレシピで使用したその他の総てのコードはこのメンバー関数を正しく呼ぶためのものです。例えば、CreateLogger()関数はその全てがRegisterLogListing()のアギュメントを正しく設定しそれをパスする事を行っているだけです。
FMessageLog からメッセージをメッセージログに書く方法で大切な事は「RegisterLogListing()を使う。」これだけです。
今回のレシピにおいて、2つ良く分からない箇所がありました。一つは、#define FTEXT(x) LOCTEXT( x, x )のLOCTEXT()はどこに定義されているのか。もう一つは、MessageLogモジュールをBuild.csに加えなくてもFMessageLogModuleが使える理由です。
<おまけ>
最近、C++の基礎を非常に深く考察しながらも大変分かり易く解説したサイトを見つけてC++の復習にはまっています。作者の方はEAでゲームエンジンを作成されているそうです。最先端のC++の使い方を考慮しつつ基本の大切さを具体的な例を使って説明するので非常に納得出来ます。C++はほとんど独学(学校ではJavaで基礎を教えていたのでC/C++は研究室に入るまでほとんど使わなかった。)なので、C++でコードは書けますが、正しいコードが書けているのか、いつも不安に思っていましたが、このサイトで勉強してC++のコードを書くときのモヤモヤが完全に吹き飛びました。例えばMember Initialization listなんで自分で使用した事はなかったですがこれからは毎回使用する事にしました。