<前文>
前回は、エディター上から枝葉(Foliage)を自動作成する方法を勉強しました。今回は、C++からする方法を学ぶようです。
ここで、前々回のレシピでランドスケープ(landscape)を自動生成した時の紹介があり同じ方法を使用して今回はランドスケープ(landscape)と枝葉(foliage)を自動作成するとあり、もう一度UMGをgame modeから作成してゲーム実行中にランドスケープ(landscape)を自動生成する方法が紹介されています。これについては、ランドスケープ(landscape)を自動生成した時は残念ながら出来ないという結論に達しました(注1)。しかしながら、アクタークラスなどの子クラスの変数をエディター上から操作可能にして、ランドスケープ(landscape)の自動生成を行う事は可能ですので、今回はその方法で行います。
(注1)ここで出来ないと言っているのは、客観的に絶対出来ないと言っているのではなくあくまで私が試した限りではと言う意味です。
繰り返しになりますが、以下に簡単に理由をまとめます。
LandScapeモジュールのクラスを使用するためには、以下に示すULandscapeInfo::RecreateLandscapeInfo()関数を最初に使用する必要があります。
しかしこの関数をゲーム実行中に使用すると必ず例外を投げて来ます。理由は、
IsGameWorld()関数をcheckしているからです。
なので、ゲーム中以外でこのモジュールのクラスは使用しなければなりません。しかしLandScapeを実行するボタンを備えたUMGを実行するためには、ゲームを実行しなければなりません。
UMGを使用するためには、ゲームを実行しなければならないが、ゲームを実行するとRecreateLandscapeInfo()関数が例外を投げます。このため教科書の方法では少なくとも私は出来ません。
<本文>
<目的>
ランドスケープ(landscape)と枝葉(Foliage)APIを使用したマップの生成を勉強します。
<方法>
サラッと読んだのですが、非常に少ない説明でこの情報量ではレシピの再現は無理だと思いました。念のためにサンプルコードを見てみると、そちらの方は実際のコードが紹介されていました。そのコードとHow to do it…の説明を基にやって見たいと思います。
Step.0
前回、作成したプロジェクトをそのまま使用するつもりですが、教科書やサンプルコードに書かれているようにGame modeに直接書いても出来ないので大胆な改良が必要な事は明らかです。ので、まずは空のアクターのサブクラスの作成とそのクラスにエディター内から呼べる関数、Gen()の作成を行います。
アクターのサブクラスであるForGenFunctionクラスを作成しました。
関数Gen()をヘッダーファイル内で宣言します。エディター上から操作しますので、CallInEditorをUFUNCTION()に追加します。
正常に作動しているか確認するために、実行された時LOGにメッセージを表示するようにします。
押しました。
表示されたのでこのクラス内で作成していきます。
それでは、教科書のHow to do it…の説明文とサンプルコードを見ていこうと思います。
Step.1
教科書の説明はこれだけです。UPROPERTY()内で何を指定すべきなのか、その変数のタイプは単に整数でいいのかなど全く分かりません。サンプルコードを見てみましょう。
はい。思いっきり書いてありました。
そのまま追加しました。
コンパイルするとエラーになりました。
EditAnywhereはprivateでは使えないそうです。Publicに直してもう一度コンパイルしました。
今度は出来ました。
ループについても、サンプルコードには
完全な形で紹介されていました。
Step.2
はい。まずサンプルコード内から何が2D のボックスなのかを調べて見ます。
ボックスは、landscapeから得ているようです。ではこれをGen()関数内に再現します。
以下に示すように再現しました。
Info()の部分は要らないので省きましたが、取りあえずボックスは得られました。
次にそのボックス内のランダムなXYの座標を獲得します。以下にサンプルコードの例を示します。
ここからはちょっと複雑になるので、まずコードを全部読んで理解する事にします。
一行一行見ていきましょう。
FVectorはUE4のAPIによると
3D空間のベクトルはフロート(float)の精度における(X,Y,Z)の要素で構成されています。
FVetorは3D空間における点もしくは方向です。
となっているので、ボックス内のランダムな点がposに保持されてます。
少し脱線しますが3D graphicsの研究室で私が最初に作ったクラスがPoint3dで3D空間内のポイントを表すクラスでした。そのクラスを作成した時Vector3dクラスも作成すべきなのかPoint3dクラスで代用すべきなのか真剣に悩みました。幾ら考えてもこの二つは同じものとして扱えるのですが概念的には全く違うもので同じクラスで代用した場合、教授にこの生徒はポイントとベクトルの違いも分かっていないのかと思われてしまうかもとかいろいろ考えました。懐かしい思い出です。
次にランダムで得た点の高さの値をボックスの最高点に置き換えています。
FHitResultクラスを使用しています。
衝突の関数内にパスするパラメーターを決定するストラクチャー
とあります。さらにコンストラクターについて見てみると、
2番目のコンストラクターを使用しているみたいです。そのremarkをみると
非推奨!
他のコンストラクターを使用するFCollisionQueryParamsのオブジェクトを構築(construct)する時、FNameのパラメーターのみの提供をしないでください。
例えばTEXT(“foo”)のような明快なFNAMEの代わりに唯一つのストリング定数のアギュメント(argument)を提供すると、このコンストラクターが他のプログラマーが呼ぼうとしているコンストラクターの代わりに呼ばれてしまいます。
このコンストラクターは最後には、この潜在的な曖昧なケースを避けるために非推奨になります。
非推奨かよ! 直さないと使えないの?と焦りましたが、良く読んでみると、他のコンストラクターを呼びたいときに間違ってこのコンストラクターが呼ばれるから非推奨にしますと説明しているので、このコンストラクターを使用したいときに他のコンストラクターが呼ばれるわけではないようです。
取りあえずそのまま使用してみます。
線(ray)の終の位置を計算しています。
BoxHeightは
RayEndは、
rayEnd.X=pos.X
rayEnd.Y=pos.Y
rayEnd.Z= box.Min.Z
となります。
ActorLineTraceSingleのUE4のAPIによると
このアクターのコンポーネント(component)から線(ray)で追跡します。もし何かブロックする物が発見されたら最初のブロックの衝突(first blocking hit)をtrueで返します。
とありました。パラメーターを見てみると、
OutHit: 最初の衝突によって発見されたブロック
Start: その線分(ray)の開始の位置
End: その線分(ray)の終わりの位置
TraceChannel: この線分(ray)が入っているチャンネル。どのコンポーネント(component)が衝突するが決定するために使用
Params: 追跡に使用する追加のパラメーター
となっており、更に、シンタックスには
となっているので、この関数からTrueが返されれば、hitオブジェクトが初期化され、衝突した点のzの値が判明するはずです。
衝突したアクターがある場合は、その衝突点の値をposに代入しています。
これで、ランドスケープ上におけるx、yのランダムな値を得る事が出来ました。
FFoliageInstanceはAPIによると
個々のインスタンスのエディターの情報
とだけあります。これだけでは何の事だかわかりませんね。次を見てみましょう。
InstanceのLocationをposと指定しました。
FFoliageInstanceの変数をみてもLocationはありませんが、
その親クラスのFFoliageInstancePlacementInfoの変数をみると
Locationがありました。解説はありませんが、FFoliageInstanceを作成する位置を示していると考えられます。
0度から360度の間からランダムな値を一個選んでるようです。何に使用するのでしょうか?
0.05radは2.864789度なので、大体6度としても大変小さい変化ですが、何に使用するのでしょうか?木の生える方向でしょうか?
正規化しましたね。
Rotationの変数は、親クラスのFFoliageInstancePlacementInfoにありました。
FQuatについて調べます。
一般的なクォータニオンの説明みたいなので、訳はスキップします。
それよりも、このコンストラクターの解説に注目したいです。
新しいクォータニオンを与えられた軸からの回転から作成しかつ初期化
とありました。これでdirとangleが何をしているのかが分かりました。
因みに、.Rotator()関数はそのオブジェクトのFRotatorを返すだけです。
パラメーターのScalingMin, ScalingMaxは
ヘッダーファイルで宣言されていました。一般的なC++の場合、プリミティブの宣言は初期化を含むんでしたっけ。コンストラクターが呼ばれる呼ばれない論争があったのは覚えているのですが肝心な所は覚えていません。UE4のC++なのでエディターで値は追加されるから関係ないのでしょうか?
この変数も、親クラスのFFoliageInstancePlacementInfoにありました。
これも解説がありませんが枝葉(foliage)のサイズを決めていると思われます。
ZOffset変数も親クラスのFFoliageInstancePlacementInfoにありました。
なんと、ZoffsetのAPIがありました。
オフセットを枝葉(foliage)のインスタンスのZ locationに提供するために最小値と最大値からオフセットの範囲を特定します
このZは位置のzのようです。
パーリン(perlin)サンプリングのために位置を正規化します。
とありますので、続きはStep.3で行います。
Step.3
何故か、Zをゼロにしてから正規化しています。それ以外は特に不思議な事はないです。
前回のレシピで散々、パーリンノイズ(Perlin noise)を勉強したので、今回はサラッと行きます。位置(x, y)における濃さpnがパーリンノイズによって計算されました。
まず、fabsfから調べますか。(ここから引用)
単にfloatの絶対値を計算する関数でした。
次は、PerlinTreeValueとPerlinTreeRangeです。
説明文に書いてある通り、pnの値がPerlinTreeValueの値からPerlinTreeRangeの範囲に存在する場合はtrueになります。その条件に当てはまった場合のみ木を作成するのでしょう。
ああ…分かりました。高度によって、自生する木の種類は変わりますね。それをここでコントロールしているのですね。この木はこの高度+-10mにしか生えない。みたいなのをこれで実現しているのですね。
次の行はSpawnFoliageInstance()関数を使用しているので、Step.4に移ります。
Step.4
まず、SpawnFoliageInstance()関数を調べて見ましょう。
GoogleでUE4のAPI見たら、この関数がなかったです。えーと思ってゲームモード内のサンプルコードを見たらありました。うーん。自作の関数だったのか。
これで枝葉(foliage)の作成方法がやっと判明するのか。と思うと随分ここまで来るのにかかったなと思います。
最初のパラメーターのUWORLDは単なるUWORLDなのでスキップして次のUFoliageTypeクラスから調べます。
ここにAPIはありましたがremarkがありません。前回作成したFoliageTypeのC++バージョンに間違いないですはずです。
宣言はTArrayを使用して行われています。
三番目のパラメーターはFFoliageInstanceタイプです。これは散々調べたので今はスキップします。
最期のパラメーターはUActorComponentです。これは何を表しているのでしょうか?この関数の実際のアギュメント(argument)を見ないと分かりません。
RootComponentをパスしているようです。
では実装部をみていきましょう。
本来ならコードから見ていくのですが、最初のコメントが気になります。
インスタンス(instance)は常にベースコンポーネント(base component )のレベル内で生成(spawn)します。
ベースコンポーネントのレベル(base component level)とは何でしょうか?
これの事でしょうね。
レベルオブジェクトはレベルのアクターのリスト、BSPの情報、そしてブラシ(brush)のリストを保持している。
全てのレベルは世界(world)をその外枠として持つが、レベルがOwningWorld**内からその世界(world)の一部としてストリームされる場合PersistentLevel*として使用出来る。
レベルはアクター(ライト、ボリューム、メッシュインスタンスなど)の集合体である。
複数のレベルはストリームする経験を作成するために世界(world)にロードされるしアンロードもされる。
何を言っているのか良く分かないです。特に2番目の文章は良く分からないです。取りあえず、PersistentLeventとOwningWorldについて以下に示します。
* PersistentLevelについてUE4のAPIから
Persistent レベルは世界の情報(world info)、デファルトのブラシ(default brush)そしてゲームプレイ(gameplay)中に生成したアクターなどを保持する。
OwningWorldについてUE4のAPIから
レベルの配列内のこのレベルを持つ世界。これはGetOuter()と同じではない。
それはストリームするレベルのGetOuter() は使用されていない痕跡の世界(vestigial world)である。
他のUObjectの参照と同じようにそれはBeginDestroy()中にアクセスすべきではない。GCがすぐにでも発生するかもしれないから。
ますます分からないです。
まず、レベル (ULevel)と世界(UWorld)の関係から調べて見ます。
ULevelをみると
変数の一つにUWorldがあります。レベル(ULevel)があってその中に世界(UWorld)があるようです。これは、UE4のエディター上で、それぞれのレベルに応じて、
World Settingがある事でも納得できます。
次にUWorldを見てみます。
なんと、UWorldもULevelを変数として持っていました。
分かりました。
全てのレベルは世界(world)をその外枠として持つが、レベルがOwningWorld**内からその世界(world)の一部としてストリームされる場合PersistentLevel*として使用出来る。
まずULevelとUWorldの関係ですが、ULevelの外側にUWorld があります。しかしそのULevelの変数であるOwnWorldはそのULevelが属するUWorldを指します。そのULevelが指すUWorldが持つ変数であるPersistentLevelはそのレベルでストリームしている限りそのULevelを返します。
UWorldとULevelの関係については分かりましたので、次は、BaseComponentについて調べます。
BaseComponentはUActorComponentクラスのオブジェクトですので、UActorComponentのAPIを見てみます。
このコンポーネントが一部であるULevelを返します。
ULevelをUActorComponentから得ているようです。実際のコードではランドスケープを使用していますが、このレベル内に存在するアクターなら何でもいいみたいです。
次の行に行きます。
まず、AInstancedFoliageActorについて調べます。
Remarkがありませんでした。
AInstancedFoliageActor::GetInstancedFoliageActorForLevelはありました。
特定のストリームしているレベルのFoliageのアクターのインスタンスを獲得します。
うーん。これってない場合はどうなんでしょうか?作って置かないといけないのでしょうか?
今度はFFoliageMeshInfoクラスから変数を宣言しています。
全ての一致する枝葉(foliage)メッシュのためのエディター情報
まあ、枝葉(foliage)メッシュの情報が保持される変数と考えておきましょう。
と説明されており、OutInfoと書かれているので、ここでMeshInfoはFoliageTypeから枝葉(foliage)メッシュの情報を受け取ると考えられます。またIFAもここで、FoliageTypeからFoliageのアクターのインスタンスの情報を得るみたいです。
パスしているパラメーターからこの関数が使用されているようですが、それ以上の説明はなく、インスタンスをMeshInfoに追加しているみたいとしか言えません。
これでこの関数の実装部は終わりです。
うーん。一応、C++から枝葉(Foliage)を自動作成する方法の流れは解明しましたが、完全に理解したとはいえません。
しかし今週はここまでにして、実際の実装は来週行います。
<結果>
来週行います。
<考察>
来週行います。
<まとめ>
来週行います。
<おまけ>
来週行います。