C++ ときどき ごはん、わりとてぃーぶれいく☆

Wonder Rabbit Projectのなかのひとのブログ。主にC++。

UE4/C++: メッシュのマテリアルやテクスチャーをC++コードで制御する方法

要点を整理しようと思ったらソースの例示しかない記事になりました(ノω・)テヘ

// マテリアルをロードしてメッシュへ設定する
// mesh: UPrimitive またはその派生型( UStaticMeshComponent, UProceduralMeshComponent など)のポインターオブジェクト
// element_index: メッシュ内の要素番号。1つしか無ければ 0 のみ。
mesh->SetMaterial
( element_index
, Cast< UMaterial >
  ( StaticLoadObject
    ( UMaterial::StaticClass()
    , nullptr
    , TEXT( "/Game/MyMaterial" ) // Game/MyMaterial は UE4Editor で作り込んでおく
    )
  )
);
// マテリアルをロードしてマテリアルインスタンスを作成した上でメッシュへ設定する
// material_instance は UMaterialInstanceDynamic* 型。クラスメンバー変数として保持して使う事が多いと思う。
auto material_instance =
  mesh->CreateAndSetMaterialInstanceDynamicFromMaterial
  ( element_index
  , Cast< UMaterial >
    ( StaticLoadObject
      ( UMaterial::StaticClass()
      , nullptr
      , TEXT( "/Game/MyMaterial" )
      )
    )
  );

// マテリアルインスタンスへスカラーパラメーターを設定する。与える値の型は float
material_instance ->SetScalarParameterValue( "scalar_parameter_name", 1.23f );
// マテリアルインスタンスへベクターパラメーターを設定する。与える値の型は FLinearColor
material_instance ->SetVectorParameterValue( "vector_parameter_name", { 1.0f, 1.0f, 1.0f, 1.0f } );
// テクスチャーを作成する。
// my_generated_texture は UTexture2D* 型
constexpr int32 width  = 2;
constexpr int32 height = 2;
auto my_generated_texture = UTexture2D::CreateTransient( width, height, PF_R8G8B8A8 )
struct r8g8b8a8 { uint8 r = 255, g = 255, b = 255, a = 255; };
std::array< r8g8b8a8, width * height > my_data{};
auto locked_bulk_data = my_generated_texture->PlatformData->Mips[ 0 ].BulkData.Lock( LOCK_READ_WRITE );
FMemory::Memcpy( locked_bulk_data, my_data.data(), sizeof( r8g8b8a8 ) * my_data.size() );
my_generated_texture->PlatformData->Mips[ 0 ].BulkData.Unlock();
my_generated_texture->UpdateResource();

// マテリアルインスタンスへテクスチャーパラメーターを設定する。与える値の型は UTexture2D*
material->SetVectorParameterValue( "texture_parameter_name", my_generated_texture );
// 作成した my_generated_texture を破棄する(ことにする)。
my_generated_texture->ConditionalBeginDestroy();
my_generated_texture = nullptr;

// 作成した material_instance を破棄する(ことにする)。
material_instance->ConditionalBeginDestroy();
material_instance = nullptr;
// おまけ: アクターのメンバー関数内などで、UE4のCGが破棄する事になっているオブジェクトをに強制的に破棄させる
// GetWorld は AActor のメンバー関数
GetWorld()->ForceGarbageCollection(true);

参考

  1. UMeshComponent::SetMaterial | Unreal Engine API Reference
  2. UPrimitiveComponent::CreateAndSetMaterialInstanceDynamicFromMaterial | Unreal Engine API Reference
  3. UMaterialInstanceDynamic | Unreal Engine API Reference
  4. EPixelFormat | Unreal Engine API Reference
  5. UTexture2D | Unreal Engine API Reference
  6. Unreal Engine | インスタンス化マテリアル
  7. Is it possible to load bitmap or JPG files at runtime and use them on assets as textures? - UE4 AnswerHub
  8. How to set a texture parameter in C++? - UE4 AnswerHub

Boost-1.65 の PolyCollection, Stacktrace, Sync の概要を確認

Boost-1.65 から3つの新しいライブラリーが入るらしいので概要( Introduction )を確認したメモ。

  1. Chapter 28. Boost.PolyCollection - develop
  2. Chapter 38. Boost.Stacktrace 1.0 - develop
  3. http://www.boost.org/doc/libs/develop/libs/sync/index.html (公式のリンクURLなんだけど執筆現在リンク切れ・w・)

Boost.PolyCollection

抽象型を基にした派生クラスのインスタンスとしてのオブジェクト「群」に対して、基礎的なC++の実装では起こり得る次の2点の実行時のパフォーマンス問題:

  1. ヒープアロケーションによってメモリーが不連続化する事によるCPUキャッシュパフォーマンスの低下
  2. ポリモーフィックなオブジェクト「群」に対する仮想関数の操作に対する分岐予測失敗によるパフォーマンス低下

これらの影響を極力避けたい場合に有用なオブジェクト「群」を扱うコレクション型を用意したもの。以下の3種類が少なくとも Boost-1.65 時点から使用可能となる:

  1. boost::base_collection // 古典的な基底型と派生型によるオブジェクト指向プログラミング的なオブジェクト「群」向け
  2. boost::function_collection // std::function による関数のラッピングを用いたオブジェクト「群」向け
  3. boost::any_collection // Boost.TypeEreasure を用いたダッグタイピング的なオブジェクト「群」向け

ゲームとか物理シミュレーションとか書いてると役に立てやすいかもしれないなーと。 UE4 みたいなフレームワークを使っていてもユーザーコードレベルで明らかなボトルネック要因を局所的に Boost.PolyCollection で扱うように細工するとかでも十分に役立てられるのではないかな、と思う。そのうちパフォーマンスの変化を手元で計測してそこそこ効果ありそうなら使ってみようかな。

Boost.Stacktrace

C++の実行時に、関数の呼び出し順の状況や"今"呼ばれている関数は何か、あるいは例外の発生時にどこからその例外が飛ばされてきたのか、そんなような情報を人間にとって読み易く表示する機能を提供するシンプルな、C++03標準以降の規格で動作可能なライブラリー。

そういえば、 Boost.Stacktrace とは直接は関係無いのだけど、さいきん アイエエー 例外? なんで例外? - お前の血は何色だ!! 4 という事で id:rti7743 さんが “この例外を投げたのは誰だー” ってライブラリーを紹介していたなーと思い出すなど。

Boost.Sync

この記事の執筆現在、公式のドキュメントがリンク切れになっている上に、 Boost.Sync のぐぐらびりてぃー的な問題なのか情報がぱっとぐぐった程度ではあまり確かな事がでてきませんでした。

Boost の ML のアーカイブBoost mailing page: [boost] [thread] Dividing into Boost.Thread and Boost.Sync などから Boost-1.65 の Boost.Sync 推量するに、ライブラリーの名前は “同期処理” の意味の “Sync(-ronization)” で、これまで Boost の実装では Boost.SmartPtr, Boost.Log, Boost.Atomic などでそれぞれに実装されていた mutex ( spin_mutex, yield_spin_mutex ) を共通化するために新たに作ったライブラリー、といった具合になっているかもしれない。

私は Boost.Sync 周りの事情を詳しく追いかけていたわけではないし、↑のMLアーカイブもぱっとでてきたので掻い摘んで見ただけだし、実際に Boost-1.65 に取り込まれた(採択された)段階の Boost.Sync の話ではないのでこれについてはほとんど実際どうから疑わしい、くらいの概要確認に留まった。

UE4/C++ Tick が来ない!そんなときのトラブルシュート備忘録

「Tick が来ない!」、たまによくあるのは私だけでしょうか。 UHogeFugaComponentTickComponent が来ない、 AHogeFugaActorTick が来ない、などなど。そんな場合の備忘録です。

疑い(1): bCanEverTickfalse

コンストラクター等で bCanEverTicktrue になっていない場合に Tick が来なくなる。 true にすれば良いし、そもそも通常はデフォルトで true なのでこのミスの可能性はわりと低い。低いけど根本的なところなので一応最初に疑って確認、必要なら修正。

// Actor の場合
PrimaryActorTick.bCanEverTick = true;

// Component の場合
PrimaryComponentTick.bCanEverTick = true;

疑い(2): bStartWithTickEnabledfalse

先の(1)と同様コンストラクター等で true になっていない事があれば開始時に自動的に Tick が発生しないので、とりあえず確認し、必要なら修正。これも通常デフォルトで true なのでコレジャナイ場合がほとんど。

// Actor
PrimaryComponentTick.bStartWithTickEnabled = true;

// Component の場合
PrimaryComponentTick.bStartWithTickEnabled = true;

疑い(3): bAllowTickOnDedicatedServerfalse // サーバーの場合のみ

疑い(1)、(2)と同レベルの段階の問題で、サーバー動作の場合はまた別のフラグが false だと Tick が来なくなる。もしサーバーの Tick が来ない時には一応疑う。

// Actor
PrimaryComponentTick.bAllowTickOnDedicatedServer= true;

// Component の場合
PrimaryComponentTick.bAllowTickOnDedicatedServer= true;

疑い(4): 実行中に SetActorTickEnabled( false ) あるいは SetComponentTickEnabled( false ) している

実行時の状況に応じた負荷制御等でプログラマーが明示的に SetActorTickEnabled( false ) を制御している Actor あるいは SetComponentTickEnabled( false ) を制御している Component の場合、本来意図した必要なタイミングでも false がセットされたままになっていると Tick が来ない。

この機能は Actor ないし Component 自身が Tick 内で false をセットするようなコードがある場合、当然自身の Tick は呼ばれなくなるので true に復帰するためには、どこか外部の何らかのトリガーによって true 引数で呼んであげて、かつ意図せず false が再セットされるような事が無いようにコード実装されていないと Tick が来ない怪が発生してしまう。

疑い(5): BeginPlay() override の実装で Super::BeginPlay() を呼んでいない

よくある Actor や Component の BeginPlayoverride での “うっかり” パターン。

// MyActor.h
protected:
  // よくある BeginPlay の override 宣言。ここまでは特に問題ない・w・
  void BeginPlay() override;
// MyActor.cpp ( Tick  来ないダメ版)
void MyActor::BeginPlay()
{
  // MyActor で必要な BeginPlay タイミングでの初期化とかなんとか実装して・・・
  hoge();
  
  // 実装して・・・
  fuga();
  
  // ”何か忘れていないだろうか・・・”
}

わざとらしくコメントを書いて置いたのでたいていの UE4/C++er は既に気づいたと思う。 Super::BeginPlay() を呼んでいない。

// MyActor.cpp ( Tick 来る良い版)
void MyActor::BeginPlay()
{
  // 実は Actor や Component の BeginPlay の中で Tick 関連のフラグや関数登録が実装されている。
  Super::BeginPlay();
  hoge();
  fuga();
}

↑今日はこのパターンにやられました(ノω・)テヘ

参考

  1. FTickFunction | Unreal Engine API Reference
  2. AActor::Tick | Unreal Engine API Reference
  3. UActorComponent::TickComponent | Unreal Engine API Reference
  4. Tick is never called, even though bCanEverTick is set to true. - UE4 AnswerHub
  5. Tick is never being called even though CanEverTick and StartWithTickEnables is true - UE4 AnswerHub

だそく

  1. Wizardry#4/ウィザードリィ#4 プレイ日記 Metal Page
    • 非常に重要な警告: このリンク先には重大なネタバレが含まれます。

UE4/C++ Interface の作り方

おさらい: ふつうの C++ の “インターフェース"(=抽象型)

C++er 的には virtual なメンバー関数に =0; 定義を入れた Abstruct な class ( or struct ) を作れば広義のプログラミング言語の意味での Interface になるって思うじゃん・w・

// ふつうの C++ 的インターフェース(=抽象型)
struct my_interface
{
  virtual float get_my_value() = 0;
};

// ふつうの C++ 的インターフェースの実装
struct my_implementation
  : public my_interface
{
  float get_my_value() override { }
};

ほんだい: UE4/C++ で Blueprint 対応の「 UE4 の世界のインターフェース」

UE4/C++ で Blueprint 対応の「 UE4 の世界のインターフェース」を定義するのはこれに比べてしまうと少々めんどくさくなる。

// === MyInterface.h ===
// 手間暇(1): UINTERFACE で UInterface を public 継承した U プリフィックスのクラスを定義する
UINTERFACE(BlueprintType)
class MyApp_API UMyInterface: public UInterface
{
  // 要注意: ここでもこちらは U プリフィックス版を使う
  GENERATED_UINTERFACE_BODY()
};

// 手間暇(2): UE4のクラス定義マクロを付けずに先に定義した U プリフィックスのクラスの I プリフィックス版のクラスを定義する
class MyApp_API IMyInterface
{
  // 要注意: ここでもこちらは I プリフィックス版を使う
  GENERATED_IINTERFACE_BODY()
public:
  // 手間暇(3) Blueprint へ関数を公開するめんどくさいマクロをだらだら書く(めんどくさいでござる@w@)
  UFUNCTION( BlueprintNativeEvent, BlueprintCallable, Category = "MyInterface" ) float GetMyValue();
};
// === MyInterface.cpp ===
// 手間暇(4): GENERATED_UINTERFACE_BODY が宣言だけ自動的に生成する U プリフィックス版の方の ctor を定義する
UMyInterface::UMyInterface( const class FObjectInitializer& ObjectInitializer ): Super( ObjectInitializer ) { }

// 手間暇(5): ここでようやく C++ 的な virtual-override における virtual 宣言された基底クラスのメンバー関数を定義
// Note: どこで↓の宣言に相当する virtual retur_type FunctionName_Implementation(); が行われているのかは後述。
float GetMyValue_Implementation() { return 3.14159265358979f; }
// === MyImplementation.h
UCLASS()
class MyImplementation
  : public AActor
  , public IMyInterface
{
  GENERATED_BODY()
protected:
  // 手間暇(6): インターフェースを実装する UE4 オブジェクトクラスでの override 実装の宣言
  float GetMyValue_Implementation() override;
};
// === MyImplementation.cpp
// 手間暇(7): インターフェースを実装する UE4 オブジェクトクラスでの override 実装の定義
float MyImplementation::GetMyValue_Implementation() { return 1.41421356f; }

これで、例えば IMyUnitFunctions など適当なインターフェースで共通の何らかの Getter 群を継承、実装しつつ APawn から派生した AMyPawnACharacter から派生した AMyCharacter について、ブループリントで GetPlayerPawn から Cast To IMyUnitFunctions して IMyUnitFunctions インターフェースを持つオブジェクトを得て、共通の Getter で得た値を表示したり、あるいは何かに使えるようになる。

インターフェースを使わない場合は、 Tag で頑張ったり、 Get All Actors of XXXX から For EachBranch したり、あるいはそこまででなくとも Branch 地獄を書けば同じような事はたぶん表面上は実用可能な程度で実装できなくもないだろうけど、少々準備が面倒でも UE4 のインターフェースとして C++ コードを実装して綺麗に扱った方が良いだろう。

おまけ: xxxx_Implementation の宣言はいつのまにどこでされるのか?

xxxx_Implementation なる override 定義している関数の元の virtual な定義は、 xxxx.generated.h の中で、

#define MyApp_Source_MyApp_MyImplements_h_14_RPC_WRAPPERS \
   virtual float GetMyValue_Implementation(); \
 \
   DECLARE_FUNCTION(execGetMyValue) \
   { \
       P_FINISH; \
       P_NATIVE_BEGIN; \
       *(float*)Z_Param__Result=this->GetMyValue_Implementation(); \
       P_NATIVE_END; \
   }

などのように UE4 のヘッダープリプロセッサーと CPP 黒魔術によって生成されている。興味があればビルドプロセスのヘッダープリプロセッサーによって生成後の xxxx.generated.h を読んでみるとよい。

参考

  1. Interfaces in C++ - Epic Wiki

UE4 の UCLASS の定義の基底クラスに名前空間を使っている子が居る場合の不幸と安直な回避策について

現状の UE4C++namespace を使おうと思っても面倒事が増えるだけなので、現実問題としてはわざわざ UE4C++ コードを記述するに当たっては namespace を用いないほうが賢い。少なくとも UE-4.16 現在は。

しかし、そうは言っても、世の中の素敵な C++ ライブラリーを UE4 プロジェクトへ取り込んで使いたい場合もある。単にどこかのメンバー関数から呼び出すだけだとか、そういう場合はたいてい問題は起こらない。しかし、深刻な問題となる場合に遭遇したので tips として紹介したい。

UCLASS で定義する UHogeComponent なり AHogeActor なり、そういったクラスの定義で、素敵な C++er 仕様のきちんと namespace を使ったライブラリーのクラスを継承して使いたい場合があったとしよう。継承じゃなくてコンポジションすればいいとかは本題ではないのでここでの議論は割愛する。

具体例は次の通り:

// MyComponent.h
#pragma once
#include "CoreMinimal.h"
#include <memory>
#include "MyComponent.generated.h"

// UCLASS() 付きで定義するクラスの基底クラスに名前空間付きの
// クラスを使用するコードを書いてしまうとビルド不能に陥る例
UCLASS()
class MyComponent
  : public std::enable_shared_from_this< MyComponent >
{
  GENERATED_BODY()
};

なお、 UE4 では std::enable_shared_from_thisstd::shared_ptr に対応する TSharedFromThisTSharedPtr が用意されているのでこの例の場合はそれらを使うだけでも問題は回避できるが、この例で std::enable_shared_from_this を使ったのは、誰でも C++ 標準ライブラリーのクラスとしてすぐに include してどのような問題が発生するのか試しやすいために選んだ。ついで、 enable_shared_from_thisコンポジションではなく継承で使う理由のある例としても有意性が高い事も選んだ理由に含まれる。

このコードはビルドすると次のようなエラーに見舞われる。

MyComponent.h(11) : Error: Missing '{' in 'Class'

このエラーは C++ コンパイラーの前に UE4UCLASS() などを解釈して独自の追加のヘッダーファイル “MyComponent.generated.h” 等を生成するために動かす UnrealHeaderTool が UCLASS で定義されるクラスについて基底クラス部分やその他あちらこちらでそもそも C++ の namespace 機能が使われる事をまったく想定していないためにパースに失敗して発生してしまう。

悲しい。

しかし、現実問題として我々プログラマーUE4 の世界とは無関係に開発された有用な多くの C++ コード資源を活用して、さくっと目的のプロダクトを開発したい。このような開発に対しては本質的でないトラブルの対応のためにオレオレライブラリーやオレオレラッパーを書いていいのはそれ自体が趣味の場合だけだ。そこで、

// MyComponent.h
#pragma once
#include "CoreMinimal.h"
#include <memory>
#include "MyComponent.generated.h"

// 名前空間を使わないエイリアスを定義
template < typename T > using std_enable_shared_from_this = std::enable_shared_from_this< T >;
// 名前空間を使わないエイリアスを介して UCLASS() 付きのクラスの基底クラスとして使用
UCLASS()
class MyComponent
  : public std_enable_shared_from_this< MyComponent >
{
  GENERATED_BODY()
};

これでいい。少なくとも UnrealHeaderTool が namespace (というか名前空間解決演算子)を考慮していない UE-4.16 執筆現在は。悲しい事だがプログラマーは現実の製品を作ってなんぼ・w・ 未来の UE4 が namespace に美しく対応する日を夢見て現実を乗り越えよう。

UE4 における enum class の「フラグ」化

// ↓ブループリントで使わない場合はこの1行は不要
UENUM( BlueprintType, meta=( Bitflags ) )
enum class EHoge: uint8
{ EV_Nothing = 0
, EV_Flag1 = 0x01
, EV_Flag2 = 0x02
, EV_Flag3 = 0x04
, EV_Flag4 = 0x08
, EV_FlagAll =
    EV_Flag1 |
    EV_Flag2 |
    EV_Flag3 |
    EV_Flag4
};
// ↑ こんな enum class に ↓ このマクロを通しておく(今回の本題)
ENUM_CLASS_FLAGS( EHoge )

ENUM_CLASS_FLAGS マクロに通しておくと、 EHoge で次のようなフラグ計算でお馴染みの機能についてユーザー独自に定義したり、 underlying_type_tstatic_cast して云々など必要なくなり、楽ができるようになる。

auto a = EHoge::Nothing;
// フラグ追加
a |= EHoge::EV_Flag3;
// フラグ判定
auto result_of_any = ( a & EHoge::EV_FlagAll ) != EHoge::EV_Nothing;

参考

  1. Bitmask Enum declaration in 4.12, what's missing? - UE4 AnswerHub

UE4: C++er 視点での実行中の AActor への USceneComponent の動的なオブジェクトの実行中の生成、レジスター、アタッチ、デタッチ、アンレジスター、破棄の方法

状況 (½) おさらい編: AActor::AActor (アクターのコンストラクター) で生成する場合

// .h
  UHogeComponent* HogeComponent;
// .cpp
AActor::AActor()
{
  RootComponent = CreateDefaultSubobject< USceneComponent >( TEXT( "RootComponent" ) );
  HogeComponent = CreateDefaultSubobject< UHogeComponent >( TEXT( "Hoge" ) );
  HogeComponent->SetupAttachment( RootComponent );
}

以上のように ctor で"デフォルト"のサブオブジェクトとして生成する場合には CreateDefaultSubobject を使う。

Note: コンポーネント間のアタッチメントは SetupAttachment を使うのがナウい方式。やや古い参考文献を見ると [[deprecated]]API を使っている事があるのでコンパイラーの warning を見てモダン化する。

状況 (2/2) 本編: 実行中の任意のタイミングで生成する場合

// .h
  UHogeComponent* HogeComponent;
  void Fuga();
  void Piyo();
// .cpp
AActor::AActor()
{
  // 比較用: ctor では CreateDefaultSubobject を使った。
  RootComponent = CreateDefaultSubobject< USceneComponent >( TEXT( "RootComponent" ) );
}

// 実行中の任意のタイミングでコンポーネントを生成
AActor::Fuga()
{
  // 実行中の任意のタイミングで生成する場合は NewObject を使う。
  // (ここでは AActor のデフォルトではないオブジェクト生成なので CreateDefaultSubobject は使えない事に注意)
  HogeComponent = NewObject< UHogeComponent >( this );
  // NewObject で生成したオブジェクトは RegisterComponent が必要。
  HogeComponent->RegisterComponent();
  // ctor 以外でのコンポーネントのアタッチメント(取り付け)も SetupAttachemnt ではなく AttachToComponent を使う
  // ( SetupAttachment を使っても 4.16 時点では大量の警告とエラーを垂れ流しつつ一応は動作するのでうっかり使わないよう注意)
  HogeComponent->AttachToComponent( RootComponent, { EAttachmentRule::SnapToTarget, true } );
}

// 実行中の任意のタイミングでコンポーネントを破棄
AActor::Piyo()
{
  // RootComponent への Attachement (取り付け)を Detach (解除)する
  HogeComponent->DetachFromComponent( { EDetachmentRule::KeepRelative, true } );
  // RegisterComponent によるコンポーネントの登録を UnregisterComponent で解除する
  HogeComponent->UnregisterComponent();
  // コンポーネントオブジェクトの破棄をGCへ明示する
  // (ここで突然 DestroyComponent を呼び出すコードを書くとアクセス違反で死ぬことになるので注意)
  HogeComponent->ConditionalBeginDestroy();
  // "立つ鳥跡を濁さず"
  HogeComponent = nullptr;
}

実行中の AActor へ任意のタイミングで USceneComponent (というか実際にはその派生コンポーネントなど)を生成したり、付け足したり、消したり。公式の C++er 向けチュートリアルでも軽く触れるような ctor での生成とは違うポイントがいくつもあるので少し手間取った@w@

参考

  1. Unreal Engine | UObject インスタンスの作成
  2. What is the correct way to create and add components at runtime ?
  3. Explicitely Delete a UObject - UE4 AnswerHub
  4. その他としてコンパイラーの deprecated 警告、 Debug/Development ビルドの実行中の Warning, Error レベルのログなど。

事後修正

  1. 2017-06-19T22:20+09:00: 記事中のソース例で AttachToComponent をモダンなAPIとしてコードすべきところが AttachTo になっていて [[deprecated]] 警告を受ける実装を示していたので修正しました。
  2. 2017-06-21T15:17+09:00: 記事中のソース例で NewObject の引数が誤って TEXT( "Hoge" ) となっていたところを this に変更。