[UnrealC++]LandscapeのHeightmapをC++から変更する

今回は、図1のような、Landscapeの高さをC++コードによって変更する方法について紹介します。
図1 C++によって変更されたLandscape

はじめに

本記事はUnreal Engine 4 Scripting with C++に記載されていた、LandscapeのHeighmapを変更する方法を参考にしています。
個人的には、Landscapeをランタイムで生成するようなものを期待していたのですが、これは上手くいきませんでした。Editorモジュールも使っており、現状、ゲーム制作で使えるレベルではありません。それでも、Landscape関連のAPIを扱う際の取っ掛かりになるかもしれないので、方法をまとめておきます。

実装する

Build.csにモジュールを追加する

Landscape、LandscapeEditorモジュールをPrivateDependencyModuleNamesに追加しました。LandscapeEditorモジュールはEditorモジュールなので、Gameモジュールに追加するのは不適切なのですが、今回は簡略化のためにGameモジュールに追加しました。
// Fill out your copyright notice in the Description page of Project Settings.
using UnrealBuildTool;
public class BlogTest416 : ModuleRules
{
public BlogTest416(ReadOnlyTargetRules Target) : base(Target)
{
PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;
PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore" });
PrivateDependencyModuleNames.AddRange(new string[] { "Landscape", "LandscapeEditor" });
// Uncomment if you are using Slate UI
// PrivateDependencyModuleNames.AddRange(new string[] { "Slate", "SlateCore" });
// Uncomment if you are using online features
// PrivateDependencyModuleNames.Add("OnlineSubsystem");
// To include OnlineSubsystemSteam, add it to the plugins section in your uproject file with the Enabled attribute set to true
}
}


GenerateLandscape関数を作成する

BlueprintFunctionLibraryで作成します。まず、.hを次のようにしました。
#pragma once
#include "CoreMinimal.h"
#include "Kismet/BlueprintFunctionLibrary.h"
#include "BPFL_GenerateLandscape.generated.h"
UCLASS()
class BLOGTEST416_API UBPFL_GenerateLandscape : public UBlueprintFunctionLibrary
{
GENERATED_BODY()
public:
UFUNCTION(BlueprintCallable, Category = LandscapeTest, meta = (HidePin = "worldContextObject_", DefaultToSelf = "worldContextObject_"))
static bool GenerateLandscape(const UObject* worldContextObject_);
};
次に、.cppを次のようにしました。
#include "BPFL_GenerateLandscape.h"
#include "EngineUtils.h"
#include "Landscape.h"
#include "LandscapeInfo.h"
#include "LandscapeEditorUtils.h"
bool UBPFL_GenerateLandscape::GenerateLandscape(const UObject* worldContextObject_)
{
// 1: WorldからLandscapeActorを取得する
UWorld* world = worldContextObject_->GetWorld();
for (TActorIterator<ALandscape> actorItr(world); actorItr; ++actorItr)
{
ALandscape* landscape = *actorItr;
if (landscape != nullptr)
{
// 2: ULandscapeInfoの初期化
ULandscapeInfo::RecreateLandscapeInfo(world, false);
// 3: Landscapeの大きさを取得
FIntRect rect = landscape->GetBoundingRect();
int32 w = rect.Width() + 1;
int32 h = rect.Height() + 1;
// 4: Heighmapの作成
TArray<uint16> Data;
Data.Init(0, w * h);
for (int32 x = 0; x < w; x++)
{
for (int32 y = 0; y < h; y++)
{
float offseted_x = (x - w / 2.0f) / 8.0f;
float offseted_y = (y - h / 2.0f) / 8.0f;
Data[x * h + y] = (uint16)FMath::FloorToInt(2048.0f * (FMath::Sin(FMath::Sqrt(offseted_x * offseted_x + offseted_y * offseted_y)) + 1.0f) * 0.5f);
}
}
// 5: Heightmapの適用
LandscapeEditorUtils::SetHeightmapData(landscape, Data);
return true;
}
}
return false;
}
順にコードについて説明します。
  1. (11~15行目)レベルにあるLandscapeActorを取得しています。TActorIteratorによって列挙していますが、有効なものが1つ見つかれば、それだけをHeightmapの変更対象とします。特定のLandscapeを変更対象としたいときは、ActorTag等を使うべきかもしれません。
  2. (18行目)レベル内全てのLandscapeInfo(コンポーネントサイズやサブセクションサイズなどの情報を管理するオブジェクト)をリセットし、再生成する関数のようです。Unreal Engine 4 Scripting with C++に必要と書いてあるのですが、必要性がイマイチ分かりませんし、今回に限っては無くても動作しました。一応記述しておきました。
  3. (21~23行目)対象のLandscapeの大きさを取得しています。これはLandscapeのOverall Resolutionと一致します。これを用いてHeighmapのデータ長を決定します。
  4. (26~36行目)Heightmapを作成しています。ここでやっていることのイメージとしては、Heightmapをテクスチャと見て、各座標のピクセルにおける「高さ」の値を設定している、と考えると理解しやすいと思います。式が定数だらけで見にくいですが、やりたいことは図2のグラフのようなHeighmapを作ることです。
  5. (39行目)最後に、対象のLandscapeに作成したHeightmapを適用します。
これで、関数は完成しました。
図2 作りたいグラフ:sin(sqrt(x*x+y*y))

CallInEditorを使って呼び出す

作ったBlueprintFunctionLibraryを実際に使ってみます。
まず図3のように、Landscapeをデフォルト設定のまま作成します。
図3 デフォルト設定でLandscapeを作成する

次に、エディタ上で呼び出すための適当なActorを作り、図4のような実装にします。先程作ったGenerateLandscapeを呼び出すイベントを作り、CallInEditorにチェックを入れます。
図4 GenerateLandscapeを呼び出すイベント

最後に、図5のように、作成したActorをレベルに配置し、ActorからCallInEditor化したイベントを呼び出します。これを実行すると、図1のような、C++コードによって高さを変更されたLandscapeを得ることができます。
図5 エディタ上でイベントを呼び出す

おわりに

今回はLandscape関連のAPIを扱うための導入として、これを紹介しました。本来はランタイムで出来ることが望ましいのですが、実現するのはそれなりに難しそうに見えます。とはいえ、以前似たようなことを実現している有料サンプルを見かけたことがあるので、出来ない訳ではないのかなと考えています。現状はHeightmapを別の方法で用意した方が良いレベルなので、実用的ではないというのが個人的な感想です。


この記事は次のバージョンで作成されました。
Unreal Editor(4.16.2-3514769+++UE4+Release-4.16)
Microsoft Visual Studio Community 2017 Version 15.1 (26403.7)

コメント