<前文>
で勉強しています。今回は、「9章7節 スタイルを使用してウィジェットの外観をコントロールする」を勉強します。
<本文>
<目的>
今回はボタンのデザインのカスタム化の方法を習います。ただ、実際にゲーム内でオリジナルなデザインのボタンを作成する場合はHUDで作成すべきだと思います。勿論、プログラマーはC++側からでもボタンのデザインのカスタム化が出来ると言う事を知っておくべきですが。
<方法>
Step.0
今回からは、version 20.0を使用します。Projectの名前は、Chapter9part7_0724とします。私はVisual Studio 2017をかなり昔にダウンロードして放置しておいたのですが、今回Project作成時にエラーが出ました。Visual studio 2017をupdateした後Projectを作成し直したら今度は普通に作れました。
今回もslateとSlateCoreモジュールを使用するはずなので、build.csの以下に示す箇所を
アンコメントしました。
Step.1
今回は、生のC++なので、VS側から作成しようと思ったのですが、クラスウィザードに
Noneという選択肢があり、使って見たくなりましたので、これで作成してみます。
ファイルの場所は、VSで作成した場合と違い変更する必要はないみたいです。他のモジュールからアクセスされる予定はないのでprivateに設定します。
この時VSを開いたままCreateClassボタンを押してしまったと焦ったのですが、何の問題もなくCookBookStyleクラスが作成出来ました。Ver20はVSを開いたままクラスウィザードからクラスを作成しても大丈夫みたいです。
Step.2
タイプするのが面倒なので、Packet Publish社が提供するサンプルコードをコピーしました。
Step.3
これも面倒なので、サンプルコードをそのままコピーしました。
ただし、#include "Chapter9.h"は#include "Engine.h"に変更しました。以下のようになりました。
全体が大きいので、NotePad++で開いて撮影しました。
まず、BOX_BRUSHがエラーを吐いています。
更に、教科書に書いてある”include ”UE4Cookbook.h”がサンプルコードにはありません。
とりあえず、ビルトしてみます。
ビルド出来ちゃいましたので、このまま行きます。
Step.4
はい。まずクラスウィザードからGameModeを選択します。名前をStyledHUGGameModeとして新しいGameModeクラスを作成します。
勿論、Privateを選択します。
思いっきりエラーが表示されていますが、
以下に示すように、ビルドは成功しているのでエラー表示は無視します。
コードを一々写すのは面倒なので、Packet Publish社のsample codeからコピーします。
Step.5
こちらも一々写すのは、面倒なので、Packet Publish社のsample codeからコピーします。
BeginPlay関数がエラーになっていますが、とりあえず、ビルドしてみます。
思った通り普通にビルト出来ました。
Step.6
Contentフォルダー内にSlateフォルダーを作成し自作したButton.pngを保存しました。
この方法では、Button.uassetは作成されません。と思っていたら
エディターの方で、勝手に作成してくれました。
Step.7
今一、何処を変更すればいいのか不明ですが、以下に示すクラスが、FDefaultGameModelImplを使用しているので、このクラスを上記に示されているように変更します。
とりあえず、以下のようにコードを足してみました。
サンプルコードにこの部分はなかったので全部手打ちでコピーしました。ケアレスミスがない事を祈りましょう。
<結果>
Step.8
ビルドをしてみます。
ビルドは成功しました。GameModeをStyledHUGGameModeに変更しました。
途端にエラーでエディターが停止してしまいました。
ウーン。色々、調べて見ると、ヒストリアさんのブログに答えが載っていました。
Step.7と同じコードですが、最後の一行が加えられています。
もう一度、ビルトしてエディターを起動し、GameModeをStyledHUGGameModeでオーバーライドしてゲームをプレイしてみます。
今度は、しっかり出来ました。
Step.9
Step.8に示すように出来ています。
<考察>
今回もHow it works…を読んでいきます。
コードのどこを説明しているのかまだ不明です。多分まだ今回のレシピ全体の説明をしているのでしょう。1.では以下の事が述べられています。
(ア) 複数のSlateウィジェットを通して共有できるスタイルを作成したい。
(イ) そのためには、そのスタイルを保持するオブジェクトを作成する必要がある。
(ウ) 更に、そのオブジェクトをスコープの範囲内に留める必要がある。
まず、スタイル(style)について考察しましょう。プログラミングとは関係ない一般的なstyleの訳は「思想の表現方法」だそうです。ここではウィジットの形状を示しているのでしょうか。全然関係ない話ですが大学の授業でscientific visualizationについて学んだとき教授が「科学実験の可視化のためのデザインは必要最低限の要素しか入れてはいけない。絶対に如何なる思想を入れてもいけない。」と言っていたのを思い出しました。その時、私はデザインは思想そのもので、科学もある種の思想なので、そんなのは無理じゃないかと思ったのを思い出しました。
兎に角、ウィジェットのスタイルを自由に作成する方法がUnrealC++にはあるみたいですね。しかしそれを使用するためには、(イ)と(ウ)の条件を満たさないといけないそうです。(イ)は単純にあるクラスを自作してその中で、ウィジェットのスタイルを指定すればいいと考えられますが(勿論具体的な方法は分かりませんが…)、(ウ)については?です。オブジェクトのスコープなんで勝手に適切にやってくれるじゃないの?と思ってしまいます。まあ、次を読んでいくうちに判明するでしょう。
FSlateStyleSetクラスについて解説しています。これはFCookBookStyleクラスの解説が始まるのでしょうか?いずれにせよFSlateStyleSetクラスが1.で述べている要件を満たす機能を持つクラスと言う事が分かりました。FSlateStyleSetのAPIを見てみると、
名付けられたプロパティの収集を含むSlateスタイルの塊。
名付けられたプロパティはSlateの外観を案内する。
その瞬間は基本的にFEditorStyle.
とありました。2.とAPIのRemarkからFSlateStyleSetクラスが沢山のスタイルを保持しているのは間違いないみたいです。それらのスタイルがウィジェットの外観を決定しているのでしょう。それをウィジェットのプロパティ一つ一つに当てはめるのでしょうか?
ここに、スタイルとプロパティについての解説がありました。
ここでの解説は、UMGにおけるウィジェットとプロパティの関係をエディターを通して解説してましたが、FSlateStyleSetクラスにおいても同じような気がします。
実際のソースコードも探してみました。
ウーン。よくは分からないですが、FSlateImageBrushクラスがウィジェットの外観を決定しているみたいです。ただFSlateStyleSetクラスは「名付けられたプロパティの収集を含むSlateスタイルの塊」とあったので、上記のような設定を沢山保持している、もしくは保持できると思っていました。
2.で「ウィジェットのスキンのために…」とありましたが、スキンとは具体的には外観を指す単語とここでは考えています。
はい。StyleSetのオブジェクトは、本当は一個あればいいのですね。
3.の条件を満たすためにはStyleSetクラスはシングルトンが良かったのですがStyleSetはシングルトンではないので、あるクラスを作成しそのクラスに3.の条件を満たせるようにします。そのクラスは、
(ア) StyleSetのオブジェクトを管理
(イ) たった一つのインスタンスしか持てない
と言う条件を満たしています。このクラスがFCookbookStyleクラスなのでしょう。
まさに、その通りでしたね!
6.では以下の事が述べられています。
(ア) FCookbookStyleクラスにはInitialize()関数がある。
(イ) Initialize()関数はモジュールのStartupのコードから呼ばれます。
実際のコードを見て確認してみましょう。まずFCookbookStyleクラスのヘッダーファイルの一部を以下に示します。
確かに、Initialize()関数はあります。ありますが、モジュールのStartupのコード云々についてはここからは不明ですね。次にソースファイルを見てみましょう。
CookbookStyleInstanceが既に作成されている場合は、Initialize関数は何もせず、CookbookStyleInstanceが作成されていない場合のみCreate関数を使用して、CookbookStyleInstanceを新たに作成してRegisterSlateStyle関数を使用して新たに作成したCookbookStyleInstanceを登録するようです。この方法なら常に一個のオブジェクトしか持てないと言う4.の条件を満たせるはずです。
ただここから、6 .で 述べられているStartupのモジュール云々については不明です。Startupのモジュール云々は、ひょっとすると、FSlateStyleRegistryクラスの事について述べているのかもしれません。調べて見ましょう。
FSlateStyleRegistryクラスのAPIによると
セントラルレポジトリ
Slateスタイルのデータの塊の追跡や管理に使用される
とありました。FSlateStyleRegistryクラスが属しているモジュールはSlateCoreモジュールでした。
当たり前と言えば当たり前ですね。「…モジュールのStartupのコードから呼ばれます。」についてはちょっと不明です。
はい。以下の部分のコードの話ですね。
はい。以下に該当する部分のコードを示します。
はい。以下に該当する部分のコードを示します。
7.、8.、と9.についてはInitialize関数の実装部についての解説ですが、6.で考察した以上の解説はなかったですね。とりあえず、こう書くものだと覚える以外ないようですね。
ここからは、Create関数についての解説でしょうか?以下にCreate関数のコードを示しておきます。
ウーン。このコードの解説が次から始まるのでしょうか?ちょっと12.を見て確認します。どうやら、Create関数のために必要なマクロの定義についての解説から始まるみたいです。
まず、以下にCreate関数に使用されているマクロを示します。
はい。マクロについて復習が必要です。以下にここから引用したマクロの関数の例を示します。
まず、マクロの名前とパラメーターが来て、その後で実際の実装が来ると言う事ですか。ここを見ると、正しく使用するのは簡単ではないようですね。Create関数に使用されているマクロでは、パラメーターの…と_VA_ARG_が良く分からないです。調べて見ましょう。
_VA_ARG_から調べて見ます。このサイトによると
関数と同じだけの数のアギュメントを受け入れるために宣言されたマクロ。
このマクロを定義するためのシンタックスは関数のそれと似ている。
…
となっていました。このサイトはGNU.orgのコンパイラーについての解説ですが、多分他も同じでしょう。ついでに、…の謎も解けてしまいました。…と_VA_ARG_は、アギュメントを増やせるだけ増やせると言う意味だったのですね。となると、FSlateImageBrushなどのパラメーターの宣言が気になります。それも調べて見ます。FSlateImageBrushのAPI によると、
となっていました。分かりました。どのコンストラクターが来ても、…と_VA_ARG_のコンビで定義したマクロなら、正しいアギュメントをパス出来るのですね。
ウーン。それぞれのマクロが何を意味しているのか分かりましたが新たな疑問も出て来ました。これってあえてマクロを書かないといけないのでしょうか?ここで言われているようにマクロを正しく使用するのは簡単ではないですし、絶対マクロを使用した方がいい理由がないのにマクロを使用するのはあまり乗り気ではありません。
はい。
Create関数の前後でマクロの#defineと#undefが行われています。
14.では以下の事が述べられてます。
(ア) “ FPaths::GameContentDir() / "Slate"/ RelativePath + TEXT(".png")”をマクロで使用したので、Create関数内のコードがシンプルになった。
これがマクロを使用する最大の理由だったのですね。12.における疑問も片付きました。ウーン…これが唯一の理由だったらあえてマクロを使用しない選択肢もあったのではないでしょうか?
はい。試してみます。
エラーになってしまいました。
ウーン…。出来るとは思うんですが、どこかにケアレスミスがあるのかもしれません。ただ出来ない以上はマクロを使って書くしかありません。
以下に実際のコードを示します。
はい。15.で説明してる通りですね。FSlateGameResourceのAPIを見てみると、Remarkがありませんでした。しかしFSlateGameResources::NewのAPIのRemarkには以下の解説がありました。
かなり長い文なので以下にまとめました。
一見分かり易いですが、良く考えると分からない解説です。まず、ScopeToDirectoryが何なのか分かりません。次にInBashPathとAssetPathも何を指しているのかが不明です。例1と例2はファイルの探索が厳密にScopeToDirectory内しか行わないと言う事を具体的に示していて非常に分かりやすいですが、例文がResources.Initialize() Resources.GetBrush()となっていてFSlateGameResources::New()関数ではありません。ここでResources.Initialize()がFSlateGameResources::New()と同じなのかどうかも良く分かりません。うーんとなっていたら、syntaxを見逃していました。
syntaxを見たらScopeToDirectoryとInBashPathが何なのかすぐに分かりました。と同時にResources.Initialize()、Resources.GetBrush()がそれぞれ何を意味しているのかも分かりました。Resources.Initialize(ScopeToDirectory, InBasePath)Resources.GetBrush(AssetPath)と言う意味でした。この例では、
ScopeToDirectory = "/Game/Widgets/SFortChest"
InBasePath= "/Game/Widgets"
AssetPath= "SFortChest/SomeBrush"(例1)もしくは、"SSomeOtherWidget/SomeBrush"(例2)
となっています。例1の場合は、実際のパスはInBashPath+”/”+AssetPathのルールにより、/Game/Widgets/ SFortChest/SomeBrushとなります。これはScopeToDirectoryの範囲である/Game/Widgets/SFortChest内にあるフォルダーです。よってここで指定されたパス内のフォルダーは探索されます。例2の場合は実際のパスは/Game/Widgets/SSomeOtherWidget/SomeBrushとなり、これはScopeToDirectoryの範囲である/Game/Widgets/SFortChest外にあるフォルダーです。よって例2の場合は失敗します。
大変良く分かりましたので、実際のサンプルコードの場合を見てみましょう。
ScopeToDirectory = "/Game/Slate"
InBasePath= "/Game/Slate"
となっていました。両方同じならスコープの範囲外を探索するような命令は出せないですね。ちなみに、サンプルコードでは、AssetPathはマクロで設定されていますが、
AssetPath=“FPaths::GameContentDir() / "Slate"/ RelativePath”
となっていました。私の場合ですが、実際のパスが"D:\2018\UE4_2018\UE4C++Tutorial_ForBlog\Chapter9\Chapter9part7_0724\Content\Slate\button.png"なのでFPaths::GameContentDir()はD:\2018\UE4_2018\UE4C++Tutorial_ForBlog\Chapter9\Chapter9part7_0724\Contentに変換されるはずです。
実際に以下のようなテストをしてパスを見てみると、
../../../../../2018/UE4_2018/UE4C++Tutorial_ForBlog/Chapter9/Chapter9part7_0724/Content/
となっていました。
../は一つ上のファイルを意味するはずなので、Chapter9part7_0724->Chapter9->UE4C++Tutorial_ForBlog ->UE4_2018->2018->D->2018->UE4_2018->UE4C++Tutorial_ForBlog->Chapter9->Chapter9part7_0724->Contentと言う事を実際はしているのでしょうか?
はい。15.で既に考察した通りです。
17.は15,16,で設定した方法が以下に述べる性質を持っている事を述べています。
(ア) 違うフォルダー内にある同じ名前を持つイメージを指す複数のスタイルセットを宣言する事を可能にします。
(イ) 単純に他のフォルダーのスタイルセットに変える事で、UI全体をスキンする事、もしくは再度スタイルする事を可能にします。
ここで、大体は同意なのですが、同じ名前のイメージである必要なないと思います。イメージの名前は、以下に示すマクロの
RelativePathによって定義されているので、同じ名前である必要はないと思いますがどうでしょうか?
はい。以下に実際のコードを示します。
はい。
はい。以下にその部分のコードを示します。
Setは2回使われています。
2回のセットの使用におけるパラメーターの種類と数が一致しているので、同じオーバーロードを使用していると考えられます。さらにsetのAPIを調べて見ると、
コードはInclineではだめ
となっています。マクロよりInclineを使用すべきと述べるC++の教科書は多いですが、このSet関数内ではinclineは使用出来なかったのですね。しかし12.でやった実験のそのままコードを書いてもエラーになる理由はまだ分かりません。
はい。一番目のパラメーターは、FName PropertyNameなのでスタイルの名前なのでしょう。2番目のパラメーターは、APIのsyntaxによるとDefinitionType & InStyleDefintionとなっていてこれがスタイルオブジェクトなのでしょうか。実際のサンプルコードを見てみると、FButtonStyle、FTextBlockStyleとなっています。これらはスタイルオブジェクトなのでしょうか?
FButtonStyleのAPIやFTextBlockStyleのAPIをみてみると両方とも、FSlateWidgetStyleのサブクラスでした。このFSlateWidgetStyleのサブクラスから作成されるオブジェクトをスタイルオブジェクトと呼んでいるみたいです。なので以下に示すFSlateWidgetStyleのサブクラスならばどれでもいいみたいです。
はい。Slateのチェーン化と同じですね。以下に実際のコードを示します。
FButtonStyle()は、SetNormal()を使用しています。FButtonStyle()のAPIを見てみるとSetNormal()以外にも以下のメンバー関数がありました。
何か簡単なメンバー関数を試してみたいと思っていたら、SetHovered()がありました。パラメーターがFSlateBrushでSetNormalと同じです。コードを以下に示すように改造してテストしてみました。
とりあえず、ビルドは出来ました。
実行して、エディターを開き、ゲームモードをStyledHUGGameModeにオーバーライドして、ゲームをプレイすると、以下に示したような白色のボタンが現れました。
更に、マウスをボタン上に重ねると、以下に示したように、カスタム化したボタンのイメージが現れました。
FTextBlockStyle関数の方も色々試したかったですが、学習内容的にはFButtonStyle関数と同じ事の繰り返しになるのでここでは省略します。
はい。23.は21.の解説の追加です。21.で述べた「一番目のパラメーターは、FName PropertyName…」の考察が正しいかったと言う事ですね。
はい。
はい。これも、22.で考察した結果が正しかった証明ですね。22.で「Slateのチェーン化と同じですね。」と述べていますが、まさしくその事をここで述べています。
26.では、SetNormal()関数についての解説が述べられています。
はい。SetNormal()関数については散々やったのでここではスキップします。
はい。今まで散々考察して来たマクロを使用する話です。そこは問題なく理解出来るのですが、次の9スライススケール(nine-Slice scaling) 云々が良く分かりません。この辺をここでは掘り下げてみましょう。
ウィキペディアの9スライススケール(nine-Slice scaling)によると、
9スライススケールは、一つのイメージを9つの部分に分解してそれぞれのサイズに比例してサイズを調節する事で2Dのイメージをリサイズするテクニックです。
とありました。どうやらアドビ―で開発されたテクニックみたいですね。この9つに分割されたイメージの中で角にあたる4つの部分のイメージの最低のピクセル数を14にしたいと28.では言っているみたいですね。やっと意味が分かりました。
以下にもう一度、SetNormal()関数を示しました。
このFMargin内の14.0fが角のサイズの最低ピクセルを指定しているみたいですね。ただし、このマクロであるBOX_BRRUSHに使用されている関数であるFSlateBoxBrushのオーバーロードされたパラメーターのどのタイプともこのパターンは当てはまらないみたいです。なので、この方法は次のversionのUE4では動かない可能性もあるみたいですね。ここではこの問題については深入りしないで次にいきます。
はい。と言いましたが、SlateBoxBrush.hに9スライススケールのビジュアル的な説明があるとは思えません。APIの事を言っているのかなと思って、SlateBoxBrushのページを見ましたが、全くそれらしいものはありませんでした。一応、SlateBoxBrush.hを開いて見たら、以下に示すようにSlateBoxBrush.hに9スライススケールのビジュアル的な説明がありました。スゴイ!!
29.はFTextBlockStyle についての解説です。
29.によればこの関数は、スタイルの全体をデフォルトのままで、一つのプロパティ―のみを変える事が出来るそうです。まず、FTextBlockStyleのAPI を見てみましょう。
ウーン。APIにはあまり役立つ情報はなかったです。兎に角、サンプルコードのように書けば、スタイルの全体をデフォルトのままで、一つのプロパティ―のみを変える事が出来るのは証明されているので、これはこのまま覚える事にしましょう。
おお、本当にHow it works…の解説は素晴らしいです。自分で調べて分からない所が、スパッと説明されています。
この部分のコードはデファルトのクローンをコピーコンストラクターを使用して作成していたのですね。コピーコンストラクターなんて、すっかり忘れていました。
以下のコードについての解説です。
特に解説は要らないと思います。リニアカラーとは、RGBAの事を指すのでしょうか。いつも訳す時にへんな日本語になってしますので、そこだけ調べて見ましょう。FLinearColorのAPIを見てみました。
線形の、32bitsのフロート型によるRGBAカラー
とありました。その通りでしたね。後、リニアは線形と訳すと変な日本語にならなくていいみたいです。
それって何なんだと、訳だけみると分け分からなくなりますが、コードをみれば一目瞭然です。
はい。Initialize関数に返しているそれとは、StyleRefです。まあ、32.はこのCreate関数の総括的な解説ですね。
今度は、Initialize関数についての解説です。以下にInitialize関数のコードを示します。
コードの意味する処と、33.の解説に特別な矛盾はないです。CookbookStyleInstanceは、
で宣言初期化されていますので、StyleRef と同じTSharedPtr< FSlateStyleSet >タイプになるので、特に問題もないですし。
次のFSlateStyleRegistryクラスとそのメンバー関数であるRegisterSlateStyleについてAPIを調べて見ました。
セントラルレポジトリ
Slateスタイルのデータの塊の追跡や管理に使用される
あれ、何か前に訳したような。と思ったらすでに6.で訳していましたね。
レポジトリにslateのスタイルを追加
スタテックな関数
この二つのRemarkから明らかなようにEU4のエディターにはslateのスタイルなどを管理する巨大なレポジトリが存在しています。そこに、Createで作成したSlateのスタイルを登録すれば、いつでもその登録したSlateのスタイルが使用出来ると言う事みたいですね。
以下に示したコードの解説ですね。
「Get関数は実際のSlate内で使用されるStyleSetを回収するために使われます。」とありますが、どのように使用されるのかまだ不明です。それ以外は普通のget関数と考えていいと思います。
以下にモジュールスタートアップを含むゲームモジュールのコードを示します。
35.で述べられているように、StartupModuleでInitialize関数が呼ばれています。ので35.の解説のように既に、StyleSetのインスタンスは作成されていますので、Get関数はそれを単に返すだけです。
そう言えば、6.で「…モジュールのStartupのコード云々についてはここからは不明ですね。…」と言ってかなり見当違いの推測をしていましたが、6.の解説はこのクラスの事を述べています。今となっては明白ですね。
36.は「ゲームモジュールでSlateのスタイルを作成し登録するInitalzie()関数とshutdown()関数を呼ぶので、いつでも我々は、我々がカスタム化したSlateのスタイルへのレファレンスが持てます。」と述べています。その通りであるし、この方法は大変便利だと思います。
37.は以下に示すの部分の操作についての説明で、前のレシピで行った方法と全く同じなので、特に考察する必要はありません。
38.以降はAStyledHUGGameModeクラスのBeginPlay関数の実装に関する解説です。
NormalButtonBrushやNormalButtonTextが普通に呼び出されています。とても不思議な気持ちです。
やはり、教科書でもそう思ったんでしょうか?NormalButtonBrushやNormalButtonTextについての解説が来ましたね。
勿論そうです。
これは、ちょっと何を指しているのか始めは分からなかったです。これは
を解説してます。<Class>Style()メソッドは、SNew(SButton).ButtonStyle(FCookbookStyle::Get(), "NormalButtonBrush")とSNew(STextBlock).TextStyle(FCookbookStyle::Get(), "NormalButtonText")をそれぞれ指します。更にこの二つの関数はそれぞれ二つのパラメーターをバスします。
35.でどのように使用されるか分からないと考察したFCookBookStyle クラスのGet関数がここで使われていました。
Get関数はTSharedPtr< FSlateStyleSet >タイプを返します。
最初のパラメーターのタイプは、ISlateStyle となっています。ISlateStyleのAPIを見たら、
となっていましたので、何も問題なく使用出来るはずです。
43.はうまくまとまりませんでしたが、言いたい事はこのレシピの総括です。「我々が作成したカスタム化したスタイルを使用したウィジェットがプレイヤーがゲームを開始した時に表示されます。」と言い出いのだと思います。
<まとめ>
今回のレシピは、最初の予測とは逆に大変濃い内容でした。今までは、新しいデザインのイメージをUE4に追加するためには、UE4のエンジンのソースコードをGithubからdownloadして自分でコンパイルする必要がありました。今回習った方法は、そんな面倒な事は必要ないです。今回のレシピにおけるカスタム化したデザインを持つボタンの作成とゲームプレイ中における表示には、3つのクラスが必要です。以下にその3つのクラスをまとめました。
となります。その3つのクラスの関係は、
となっています。FCookbookStyleで新しいデザインのウィジェットを作成し、それをFDefaultGameModelImplクラスのサブクラスで登録します。FDefaultGameModelImplクラスのサブクラスで登録したFCookbookStyleクラスで登録した新しいデザインのウィジェットはAStyleHUGGameMoideで使用許可をもらい実際のコードにAStyleHUGGameMOdeで実装されます。
<おまけ>
今週はなし。