UE4の勉強記録

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

12章6節 Landscape API –パーリンノイズ(Perlin Noise)を使用したランドスケープ(landscape)の生成

<前文>

f:id:kazuhironagai77:20181028232812p:plain

今回は、ランドスケープ(landscape)とC++スクリプトについて勉強します。ランドスケープ(landscape)はブループリントでも1,2回しかやってないので理解出来るか不安です。あまりUE4C++とは関係ありませんが、このランドスケープ(landscape)の正しい設計とは何なのかが今一分からないのです。自作したランドスケープ(landscape)を見ても、いいのか悪いのかが分からないのでどうしてもこの部分を勉強する気が起きません。

後、更に関係ありませんが、レベルデザイナー(level designer)は、レベル(level)をデザインする人(designer)なので、純粋に言葉の定義から考えれば、このランドスケープ(landscape)とその上にどのように木や草、川や山、そして建物などの配置をデザインする人だと思うんですが、なぜかアーティストとプログラマーの両方の調整が出来る人みたいなニュアンスで使われていますね。(アーティストとプログラマーの両方の調整をする人を指す言葉にテクニカルデザイナー(technical designer)があったはずなのに。)

そこから閃いたのですが、レベルデザイナー(level designer)はプログラマーが兼用しているのでは! ある種のプログラマーは異常に空間認識能力が高いです。一緒に旅行に行くと一回も行っていない土地なのに、どっちに行けばいいか分かる人がいますよね。そんな人みたいな。プログラムのバグを直す時に、プログラム上でデータの流れを追っていく能力と、3次元上の空間認識能力にリニアな相関関係があるのではないかとか考えています。で、そういう人がプログラマー兼レベルデザイナーをしているかなと。(これは完全な想像なので話半分で聴いてください。)

もしそうならば、正しいランドスケープ(landscape)のデザインが見えて来ます。アーティストなら見た目の美しさがデザインの基本になる(ここで前回のランドスケープ(landscape)の作成は詰まってしまった。私が作ったのは全然美しくなかった。)はずですが、プログラマーならデザインの基準は整合性と正確さになるでしょう。例えば、高低差がある山では自生する木や植物の種類を変えるとか(北側か南側かでも変わるでしょう。)、二つの町を繋ぐ道は高低差が最も少ないルートになるとか、科学や工学に基づいたデザインになるはずです。

急にランドスケープ(landscape)に興味が出て来ました。では今週のレシピを始めます。

<本文>

<目的>

パーリンノイズ(Perlin Noise)を使用したランドスケープ(landscape)の生成を行うそうです。

パーリンノイズ(Perlin Noise)はOpenGLを勉強した時、何回も使用したので特に問題はないです。教科書を読むとALandscapeクラスがありそこでコードからランドスケープの高さを指定できるみたいです。更に教科書にはパーリンノイズ(Perlin Noise)について、前に使用したと書かれていました(正直覚えていません。)。

<方法>

Step.0

今回は、新しいプロジェクトを作成します。プロジェクト名はChapter12part4にします。

f:id:kazuhironagai77:20181104204111p:plain

Step.1

f:id:kazuhironagai77:20181104204139p:plain

Packet Publish社のサンプルコードを見たら、以下に示すようにNoise1234.hとnoise1235.cppがありました。これを使用します。

f:id:kazuhironagai77:20181104204201p:plain

Sourceファイルに貼り付けました。

f:id:kazuhironagai77:20181104204220p:plain

Add->exit itemで追加しました。

f:id:kazuhironagai77:20181104204237p:plain

noise1235.cppの#include "Chapter12.h"を#include "Chapter12part4.h"に変更しました。

f:id:kazuhironagai77:20181104204307p:plain

試しにビルドしてみると、

f:id:kazuhironagai77:20181104204324p:plain

すっごいエラーが出て来ました。Hファイルのnoiseの宣言がおかしいと言っています。試しにnoise1234.hファイルを見てみると、確かにこの宣言方法はおかしいですね。

f:id:kazuhironagai77:20181104204355p:plain

Noise1234::は取ってしまいます。リビルドしてみると、以下のエラーが出ました。

f:id:kazuhironagai77:20181104204415p:plain

Noise1234.hは最初にインクルードしてくださいとあります。はい。すっかりこれを忘れていました。

f:id:kazuhironagai77:20181104204434p:plain

しました。リビルドします。

f:id:kazuhironagai77:20181104204450p:plain

今度は成功しました。

Step.2

f:id:kazuhironagai77:20181104204523p:plain

はい。

サンプルコードのchapter12.build.csを見ると以下の様に追加されていたのでそれを真似て追加します。

f:id:kazuhironagai77:20181104204543p:plain

追加しました。

f:id:kazuhironagai77:20181104204608p:plain

Step.3

f:id:kazuhironagai77:20181104204631p:plain

この以下の部分は訳さないでいいならそのまま行きたかったのですが訳さないと話が通じなそうですね。

f:id:kazuhironagai77:20181104204652p:plain

f:id:kazuhironagai77:20181104204702p:plain

作りました。

f:id:kazuhironagai77:20181104204727p:plain

まずボタンを作りました。

f:id:kazuhironagai77:20181104204744p:plain

GameModeBase.hファイルにUFUNCTION(BlueprintCallable)を持ったGen()関数を作成しました。

f:id:kazuhironagai77:20181104204808p:plain

f:id:kazuhironagai77:20181104204819p:plain

教科書の図にある通りにGenerationボタンのOnClickedをブループリント上でバインドしました。

f:id:kazuhironagai77:20181104204836p:plain

f:id:kazuhironagai77:20181104204845p:plain

レベルブループリント上で作りました。

f:id:kazuhironagai77:20181104204906p:plain

ここまでで一応テストしてみます。

テスト結果を見るためにUE_LOGを追加しました。

f:id:kazuhironagai77:20181104204921p:plain

コンパイルして実行すると

f:id:kazuhironagai77:20181104204942p:plain

まず、当たり前ですがボタンは表示されています。そのボタンをクリックしてみます。

f:id:kazuhironagai77:20181104204959p:plain

Output LogにクリックするためにGen()関数が実行されているのが確認出来ました。ここまでは成功しています。

Step.4

f:id:kazuhironagai77:20181104205030p:plain

遂に恐れていた事が来てしまいました。ランドスケープ(landscape)を作成しなければなりません。

f:id:kazuhironagai77:20181104205056p:plain

とりあえず、何とかランドスケープ(landscape)を作成しました。

Step.5

f:id:kazuhironagai77:20181104205142p:plain

はい。

f:id:kazuhironagai77:20181104205157p:plain

f:id:kazuhironagai77:20181104205224p:plain

まず、このコードを追加する前に、PacketPublish社のサンプルコードを見てみます。今まではこのレシピのサンプルコードはないと思っていたのですが、GameModeファイルにあるかもと今は思っています。

f:id:kazuhironagai77:20181104205248p:plain

を開いて見ると、

f:id:kazuhironagai77:20181104205306p:plain

f:id:kazuhironagai77:20181104205317p:plain

Gen()関数がありました。これで、情報量が2倍になりました。

では、GetLandscapes()関数をAChapter12part4GameModeBaseクラス内に作成します。

f:id:kazuhironagai77:20181104205355p:plain

f:id:kazuhironagai77:20181104205403p:plain

f:id:kazuhironagai77:20181104205413p:plain

サンプルコードの方には、UFunction()がなかったのですが、UFunction()がなくてもUE4C++のガーベジコレクション(garbage collection)は働いてくれたのでしたっけ。コードの方はポインターを使いまくっていて、UE4C++の性質上Newは表面上はありませんが、ヒープ領域のメモリーを使用しまくっているように見えます。ので一応UFunction()は足しておきました。

試しにビルドしてみると、普通に出来ました。

f:id:kazuhironagai77:20181104205436p:plain

f:id:kazuhironagai77:20181104205445p:plain

f:id:kazuhironagai77:20181104205500p:plain

はい。しかしどこに?とりあえず、サンプルコードのgamemode.cppを見てみるとGen()関数内にありました。

f:id:kazuhironagai77:20181104205516p:plain

これは、一言書いてもらわないと分からないですよね。In gen functionと書いてあればすぐ分かるのですから。

f:id:kazuhironagai77:20181104205534p:plain

はい。加えました。ついでに#include “LandscapeInfo.h”も追加しました。

f:id:kazuhironagai77:20181104205550p:plain

うーん。この記述をみると、ULandscapeInfo::RecreateLandscapeInfo…の追加は、どこでもいいのかもしれませんね。それで教科書はどこにを書いていないのかもしれませんね。

f:id:kazuhironagai77:20181104205608p:plain

はい。具体的な方法はどうすればいいのでしょうか?取りあえず次を見てみます。

f:id:kazuhironagai77:20181104205626p:plain

このままコードの提供はなしで進むのか!コードは自分で考えないといけないの?と焦ってちょっと先を見たら、次のページにまとめて具体例が挙げてありました。教科書では一例としての紹介ですが、このサンプルコードが無ければこのレシピの再現は無理な私としては結構焦りました。

とりあえず、教科書のサンプルコードを見てみます。

f:id:kazuhironagai77:20181104205643p:plain

まず、ULandscapeInfo::RecreateLandscapeInfo…についてです。この部分は既に解明したので次に行きます。

f:id:kazuhironagai77:20181104205659p:plain

はい。5.3の具体的なやり方がありました。ひとまず安心です。とはいえ、一例として紹介されている以上、自分でも調べなくてはならないでしょう。まずFIntRectクラスについて調べて見ます。APIによると

f:id:kazuhironagai77:20181104205749p:plain

2次元上の整数の長方形のためのストラクチャー

とありました。長方形の作画のためのクラスのようです。面白いのは最大値と最小値を指定する事でFIntPointオブジェクトの2つだけで4点の値を指定しています。

f:id:kazuhironagai77:20181104205838p:plain

こう言う地味な工夫がゲーム全体のサイズの小ささや動きの機敏さに繋がるのでしょうか?勉強になります。

GetBoundingRect()関数はALandscape の親クラスのALandscapeProxyクラスの関数でした。APIによると、

f:id:kazuhironagai77:20181104205904p:plain

クウァド(quad)空間の長方形の境界の現状におけるサイズ

を返すそうです。まあ至極全うなやり方ですね。まあ、理解したのでこのコードを足しましょう。と思ったのですが、一応PacketPublish社のサンプルコードも見てみます。

f:id:kazuhironagai77:20181104205945p:plain

GetBoundingRect()を使用する前に、レベル上にあるすべてのLandScapesオブジェクトを 取得していますね。当たり前と言えば当たり前ですね。ここは学習者が自分で気が付かなければいけない個所ですね。

以下に示すようになりました。

f:id:kazuhironagai77:20181104210031p:plain

次に行きます。

f:id:kazuhironagai77:20181104210141p:plain

これは、5.4の説明に対する具体的なやり方ですね。まず、Width()とHeight()ですが+1が気になります。両端にも点があるとするとそれまでですが、一応調べておきます。

FIntRectクラスのAPIによると

f:id:kazuhironagai77:20181104210219p:plain

f:id:kazuhironagai77:20181104210228p:plain

となっていました。うん。両端の点の分が+1と言う事でしょう。

行(rows)と列(cols)の値が指定されていませんね。Rows = width +1, cols = height+1,でしょうね。その次のコードの%、/はパズルゲームの作成の時に使用する盤面の作成と同じですね。Colsかrowsで割るのは正規化のためと書かれています。その値をx、y値としてぺーリンノイズ(PerlinNoise)の関数にパスしています。PerlinNoise2D()関数が見つからないのですが、取りあえず書いてみます。

f:id:kazuhironagai77:20181104210309p:plain

やっぱりエラーになっていますね。今度はサンプルコードを見てみます。

f:id:kazuhironagai77:20181104210332p:plain

はい。思いっきりメンバー関数にありました。流石にこれは、教科書にも書いていないと分からないでしょうと。教科書をもう一度見直して見たら、このレシピの最後のThere’s more…に、

f:id:kazuhironagai77:20181104210349p:plain

ありました。There’s more…で紹介する関数ではないと思うんですが、まあこの関数の紹介は教科書にあったので良しとします。

f:id:kazuhironagai77:20181104210406p:plain

よし。コードも一応書けたし、PerlinNoise2D関数について解析するぞと意気込んで、まず最初に教科書のPerlinNoise2D関数の解説を読んだら、どうやらHow it works…の最後の方で説明されている事実を知らないと理解出来ないみたいなので、取りあえず先に進みます。

f:id:kazuhironagai77:20181104210435p:plain

f:id:kazuhironagai77:20181104210444p:plain

Step.5の最期の5.5です。PerlinNoise2D関数で得たDataとlandscapeオブジェクトをLandscapeEditorUtils:SetHeightmapData()を使ってランドスケープ(Landscape)の高さの値を決定しています。

f:id:kazuhironagai77:20181104210506p:plain

追加したら思いっきりエラーに成っています。更にPerlinNoise2D()関数もエラーになってました。

因みに#includeは以下のようになっています。

f:id:kazuhironagai77:20181104210528p:plain

取りあえず、直すのが簡単なPerlinNoise2D()関数を直します。「アギュメント(argument)が足りないよ。」とVSが親切に教えてくれたので直すのは簡単です。PerlinNoise2D()関数は6個のパラメーターが必要なのであと一個足すだけです。で教科書見たら、教科書の例は5個のアギュメント(argument)しかありません。サンプルコードの方を見たら、

f:id:kazuhironagai77:20181104210549p:plain

となっていたので、1000.0fを足しました。

f:id:kazuhironagai77:20181104210607p:plain

こっちのエラーはそんな関数ないよと言っています。ウーン。分からん。

調べても調べても全く分からないので、取りあえずビルトし直してみたら、

f:id:kazuhironagai77:20181104210633p:plain

普通に通っちゃいました。

f:id:kazuhironagai77:20181104210700p:plain

How it works…の方もここに載せておきます。

f:id:kazuhironagai77:20181104210719p:plain

これが教科書がPerlinNoise2D()関数を最後に書いた理由なの? まあ大切ではありますね。

<結果>

取りあえず、実行してみます。

f:id:kazuhironagai77:20181104210818p:plain

例外(exception)が投げられて止まってしまいました。ウーン。何か長くなりそうです。

f:id:kazuhironagai77:20181104210837p:plain

これが例外(exception)を投げているみたいです。以下にエラーのメッセ―ジの写しを示します。

Assertion failed: !OwningWorld->IsGameWorld() [File:D:\Build\++UE4\Sync\Engine\Source\Runtime\Landscape\Private\Landscape.cpp] [Line: 859]

Landscape.cppの859行目を見てみました。

f:id:kazuhironagai77:20181104210916p:plain

これが例外(exception)を投げています。この問題を直せばいいみたいです。でもGameWorldとは何なんでしょうか?UWorldクラスのAPIには、

f:id:kazuhironagai77:20181104210937p:plain

もしこの世界が何らかのゲームの世界(PIEの世界を含む)ならばtrueを返します。

と書かれていますので、PIE WorldはGameWorldなのでしょうが。その上に、IsEditorWorld()関数の説明として

f:id:kazuhironagai77:20181104211021p:plain

もしこの世界が何らかのエディターの世界(エディタープレブュー(editor preview)の世界を含む)ならばtrueを返します。

とあるんですが、ひょっとしてゲーム中じゃなくで、エディター上でないと、ULandscapeInfo::RecreateLandscapeInfo…は機能しないのでしょうか?

嘘でしょう。と思いながらネットを見ていたら、このサイトにほとんど全て説明されていました。やっぱりエディター上じゃないとULandscapeInfo::RecreateLandscapeInfo…は機能しないみたいです。

そのサイトを参考にして、取りあえず、アクターを作りその中にGen()関数を写しました。

f:id:kazuhironagai77:20181104211106p:plain

f:id:kazuhironagai77:20181104211115p:plain

そして実行してみると、

f:id:kazuhironagai77:20181104211134p:plain

Gen()関数の実行ボタンがエディター上に現れました。押して見ましたが、ランドスケープ(landscape)に変化はありません。

f:id:kazuhironagai77:20181104211155p:plain

ただし。Gen()関数が呼ばれているのは間違いないみたいで、Genボタンを押すたびに、Logにメッセ―ジは表示されます。

f:id:kazuhironagai77:20181104211215p:plain

これは、PerlinNoise2D()関数に何か問題があるんじゃないかと思い、取りあえず

f:id:kazuhironagai77:20181104211242p:plain

としてもう一度実行しました。

f:id:kazuhironagai77:20181104211300p:plain

今度は、思いっきり変わってくれました。

では、PerlinNoise2D()関数を直していきます。

pnoise( float x, float y, int px, int py )関数が0しか返してこないのが原因でした。どうもxとyの値は0から1の間しか受け付けないらしく、1を超えると返り値が0になってしまうみたいです。バグとして報告している人もいるようです。今回は正しいパーリンノイズ(Perlin noise)を使用しなければならない訳ではないので、単純にパラメーターの数値を変えて対応します。

ので、PerlinNoise2D()関数のアギュメントを以下のように変えました。

f:id:kazuhironagai77:20181104211346p:plain

次に、uint16 PerlinNoise2D(float x, float y, const float amp, int32 octaves, int32 px, int32 py)のampの値がどんな値をパスしても0.89…に代わってしまいます。理由は分かりませんが、以下のように変えました。

f:id:kazuhironagai77:20181104211403p:plain

その結果、

f:id:kazuhironagai77:20181104211426p:plain

(結果の確認を簡単にするために、ランドスケープ(landscape)は最小にしています。)

f:id:kazuhironagai77:20181104211447p:plain

一応は出来ました。

<考察>

教科書のやり方では出来ない典型的なレシピでした。しかし、C++側からランドスケープ(landscape)の値をコントロールする方法を学べたのは有益ではあります。今回考察しなければならない内容は3項目あります。

  1. 再現出来る「Landscape API –パーリンノイズ(Perlin Noise)を使用したランドスケープ(landscape)の生成」の方法について
  2. 「本当に教科書に載っているレシピでは出来ないのか?」の検証
  3. 今回のレシピ作成で得た知見は正しいのかの検証。以下の事についての検証が必要です。
    • 「エディターの世界(editor world)とゲームの世界(game world)」の違い
    • 「パーリンノイズ(Perlin noise)」を作成するためのコードPerlinNoise1234は本当に間違っているのか?」
    • UFUNCTION()は必要?

これらの検証はかなりの大きな内容になるので、来週、「12章6節 Landscape API –パーリンノイズ(Perlin Noise)を使用したランドスケープ(landscape)の生成 part2」としてやります。

<まとめ>

来週まとめます。

<おまけ>

これも来週にします。