UE4 C++ BitFlags について

この記事はUnreal Engine 4 (UE4) Advent Calendar 2020 その1、8日目の記事です。

qiita.com

7日目は 0xUMAさんの『命名規則に従ったアセットの自動リネームとアセットバリデーション』でした。

massa8080.hatenablog.com

この記事について

UE4 C++のBitflags がいろんなオプションがあって、何が何を意味しているかわからなかったので調査してみました。

今回のサンプルコード

まずはシンプルな実装コードを提示します。

今回説明するのはこのコードの内容に関することになります。

// 定義
UENUM(BlueprintType, meta = (Bitflags, UseEnumValuesAsMaskValuesInEditor = "true"))
enum class EMyFlag : uint8
{
  None = 0     UMETA(hidden),

  Flag1 = 0x01,
  Flag2 = 0x02,
  Flag3 = 0x04,
};
ENUM_CLASS_FLAGS(EMyFlag)
// フラグをメンバ変数にする
UCLASS()
class MYPROJECT_API AMyActor : public AActor
{
  GENERATED_BODY()
    
public:
  UPROPERTY(BlueprintReadWrite, EditAnywhere, meta = (Bitmask, BitmaskEnum = "EMyFlag"))
  uint8 MyFlags;
};
// 判定部分
const EMyFlag Flags = (EMyFlag)MyFlags;
if (EnumHasAnyFlags(Flags, EMyFlag::Flag1 | EMyFlag::Flag2))
{
  // bit on
  // EMyFlag::Flag1 か EMyFlag::Flag2 のbitが立っている
}
else
{
  // bit off
  //  EMyFlag::Flag1 か EMyFlag::Flag2 のbitが立っていない
}

Bitflags, UseEnumValuesAsMaskValuesInEditor

UENUM(BlueprintType, meta = (Bitflags, UseEnumValuesAsMaskValuesInEditor = "true"))

UENUMのmetaに Bitflags を付加すると、その列挙型の変数がビットフラグかどうか判断できるようになります。ですが、今回の調査ではこれを付けることで、どのような変化が起こるかよくわかりませんでした。 列挙に対してこちらで値を代入していなければ(例: Flag1 = 0x01 ではなく Flag1, にした場合)、勝手にビット毎の値を順番に入れてくれるかと思いましたが、通常の列挙と変わりませんでした。

また、 UseEnumValuesAsMaskValuesInEditortrue だと、ビットフラグに合っていない列挙値がBlueprint上で表示されないようになります。

たとえば値が 0x010x02 の場合はBlueprintのリストボックスの選択肢に表示されるのですが、 0x03 は複数のビットフラグが立ってしまう値のためBlueprintのリストボックスで表示されなくなります。

コードに補足を加えると、次のようになります。

enum class EMyFlag : uint8
{
  None = 0     UMETA(hidden), // 初期値とかはhiddenにして隠しておくとよい

  Flag1 = 0x01,
  Flag2 = 0x02,
  Flag3 = 0x04,

  // 下記は説明用に追加
  TestValue = 0x0e, // UseEnumValuesAsMaskValuesInEditor=trueだと、これはエディタ上で表示されない
  TestValue2 = 0x08, // こちらのようなビットになっている部分はリストボックスに表示される
};

ENUM_CLASS_FLAGS(EMyFlag)

ENUM_CLASS_FLAGS(EMyFlag) を宣言すると、 EMyFlag 型同士は、 &| などのビットフラグでおなじみのOperator演算子系が使えるようになります。

そのため、とりあえず書いておきましょう。

BitmaskEnum

次に、ビットフラグを保持するメンバ変数部分についてです。

UPROPERTY(BlueprintReadWrite, EditDefaultsOnly, meta = (Bitmask, BitmaskEnum = "EMyFlag"))
uint8 MyFlags;

フラグを複数立てたい変数に対しては、列挙型ではなく、通常の整数型を使用します。今回だと uint8 です。 値を uint8 よりも大きく取りたければ int32 で定義しましょう。 uint32 だと Blueprintで使えないのでエラーになります。また、型を EMyFlag にすると、複数のビットを立てることができません。( UseEnumValuesAsMaskValuesInEditorfalse で、列挙値に複数のビットを立てているものがあれば代用は可能)

それに対して、整数値の型(今回だと uint8 )の UPROPERTYmetaBitmask を付けると、Blueprint編集上でビットマスク用のリストボックスで編集できます。

また、このビットマスク用のリストボックスは BitmaskEnum で列挙型(今回だと EMyFlag )を指定するとリストボックスでそのUEnum型の値から選択できるようになります。 BitmaskEnum を付けていないと、Flag 1~32から選択という形になりますが、ビット選択できなくなるわけではありません。

ビット判定方法

最後にビットの判定方法についてです。

ビットが立っているか判定

ビットが立っているか判定するときには、一旦列挙型に変換して、 EnumHasAnyFlags 関数で判定します。

const EMyFlag Flags = (EMyFlag)MyFlags; // 一旦列挙型にキャストする(|や&演算子を使えるようにするため)
if (EnumHasAnyFlags(Flags, EMyFlag::Flag1 | EMyFlag::Flag2))
{
  // bit on
  // EMyFlag::Flag1 か EMyFlag::Flag2 のbitが立っている
}
else
{
  // bit off
  //  EMyFlag::Flag1 か EMyFlag::Flag2 のbitが立っていない
}

すべてのフラグが立っているか判定

また、すべてのフラグが立っているときにtrueを返したい場合には EnumHasAllFlags で判定します。

if (EnumHasAllFlags(Flags, EMyFlag::Flag1 | EMyFlag::Flag2))
{
  // bit on
  // EMyFlag::Flag1 と EMyFlag::Flag2 両方のbitが立っている
  // 両方のbitが立っていれば、他のフラグが立っていてもよい
}
else
{
  // bit off
  // EMyFlag::Flag1 か EMyFlag::Flag2 両方のbitが立っていない(どちらかだけではだめ)
}

UE4 C++ビルドエラー対応覚書

この記事は何?

この記事はUnreal Engine 4 Advent Calender 2019その2の6日目の記事です。

qiita.com

UE4 C++でのコンパイルビルド時のエラーの対応メモです。 慣れればエラーを見てどんな問題が起こっているのかわかるのですが、慣れていないと原因がわかりづらいです。

そんなエラー文からどのような対応をすればよいのかのメモになります。

2020/01/14:少し追記しました。

クラス名.cpp(1): error : Expected クラス名.h to be first header included.

UCLASSのcppはクラス名.hを最初にインクルードしないといけないというエラー。 最初のインクルード(#include するファイル)をクラス名のものに変更する。 (このファイルに使うクラス名は、接頭詞U,F,A,Iなどを外したものです)

#include "クラス名.h"

関数名 inconsistent dll linkage

なんとか_APIがそのクラス, 関数, 変数を定義したモジュールとあってない。コピペしたときとかにクラス名だけしか変更してなくて起こりがち。

class MYMODULE_API UMyActor : public AActor
//    ^^^^^^^^^^^^
//     ここに注目
//
// この場合、MyModuleというモジュール名(プラグイン名)の中で定義されていないといけない

~関数が未定義 error LNK2019: unresolved external symbol 関数名

関数の実装が見つからないエラー。

Build.csに必要なモジュールがDependencyに追加されていない。 Build.cs以外にuprojectのAdditionalDependenciesに追加されているパターンもあるので、他のプロジェクトから移植する際にはBuild.cs以外にも.uprojectの設定も気をつける。

または定義されていない関数がどこかで呼ばれている可能性もあり。(他のプロジェクトからコピペしたときや、外部公開されていない関数などを呼んでしまった場合に上記とエラー理由と勘違いして起こりがち?)

解決策の例:

PrivateDependencyModuleNames.AddRange(
new string[]
{
 "UnrealEd",
  "LevelEditor",
  "CoreUObject",
  "Engine",
  "Slate",
  "SlateCore",
// こういうところに指定の関数が定義されているモジュール、プラグインを追加する

関数名の前にあるクラス名をUE4APIリファレンス(Unreal Engine API Reference | Unreal Engine Documentation)で検索して、そのクラスのReferencesのModuleに書いてあるモジュール名を追加するとだいたい解決すると思います。

error LNK2019: unresolved external symbol "declspec(dllimport) private: static class UClass * cdecl クラス名::GetPrivateStaticClass(void)

 Privateなクラスを使おうとしたときに出るエラー。(Privateフォルダ以下にあるファイルで定義されているUCLASS) もしくは、Publicであっても外部モジュールの_APIが無いクラスを使おうとしたときとか、DependencyModule足りない場合でも起こる。

その対象のクラスはアクセス権がないので、Privateフォルダにある場合、編集できる領域ならPublicフォルダに移動したり、Publicアクセス権で出たならなんとか_APIを追加する、編集できない場合には他の代替クラスを使用しましょう。 エンジン内にあるクラスのときには、クラス丸ごとコピーして自身のプロジェクトで使う方法もあります。それで使うことが可能ならエンジン改造せずに済むので有効です。

UnrealBuildTool : error : Expecting to find a type to be declared in a module rules named 'モジュールA名' in モジュールB名ModuleRules, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null. This type must derive from the 'ModuleRules' type defined by Unreal Build Tool.

Build.csのModuleRuleを継承しているクラス名がModuleA名と同じではない……ときに出たエラー。あまり見たことはないのでそういう制限によるエラーではないかもです。(IMPLEMENT_MODULEの名前とクラス名が違うからかも?) UnrealBuildToolではクラス名とModule名を同じにする必要がある。

class FMyModule : public ModuleRule
// この場合、MyModuleというモジュール名でないといけない

もしくは、Target.csに追加しているモジュール名が、存在しないモジュール名になっているのが原因かも?

.gen.cpp(23): error C4430: missing type specifier - int assumed. Note: C++ does not support default-int

37>.gen.cpp(23): error C2146: syntax error: missing ';' before identifier 'UClass'
37>.gen.cpp(110): error C2065: 'Z_Construct_UClass_クラス名_NoRegister': undeclared identifier
37>.gen.cpp(151): error C2065: 'Z_Construct_UClass_クラス名_NoRegister': undeclared identifier

コンパイラが型を理解していない。

たとえばクラス名がALevelSequenceActorだった場合、エラーの行数に飛ぶと、 LEVELSEQUENCE_API UClass* Z_Construct_UClass_ALevelSequenceActor_NoRegister(); となっています。 なので、LEVELSEQUENCE_APIの対象モジュールをPublicDependencyやPrivateDependencyに追加して解決します。対象モジュールというのが、今回の例だとLevelSequenceモジュールです。

PublicDependencyModuleNames.AddRange(new string[] { "LevelSequence" });

Please verify that you have sufficient rights to run this command.

VisualStudioをアップデートしたときに起こったエラー。 プリコンパイルヘッダーとかがアップデート前のコンパイラコンパイルしたものが残っていたりして失敗しているみたいです。

リビルドすると治ります。

UnrealBuildTool : error : Unhandled exception: System.IO.DirectoryNotFoundException: パス '\Plugins\HogeHogePlugin\Intermediate\Build\Win64\UE4Editor\Inc\HogeHoge' の一部が見つかりませんでした。

プラグインの中にgenerated.h されるクラスが1つも存在しなくてIncフォルダ内のコード生成が通らなかったため、フォルダが見つからずエラーになってます。 なにか1つUCLASSかUSTRUCTを対象プラグインのコード内で作成して、GENERATED_BODYされるようにしてみましょう。

HogeHoge.generated.h(16): error C2007: #define syntax

#define syntax がたくさん出たときの対処法。対象の行に飛んでみると、なんとかRPC_WRAPPERSやらなんとかINCLASS_NO_PURE_DECLSの部分に飛びます。 これは、フォルダ名の先頭に数字がついているものを使っていると、#defineの定義が失敗してビルドエラーになってしまいます。uprojectを入れているフォルダ名を、数字を先頭にしないか、英字のみにしてみましょう。 (おそらく日本語フォルダ名でも同じエラーが出る)

終わりに

UE4独自の守らなくてはいけないこと、対処法などは調べて対応していくうちに理解していけると思います。 頑張って覚えて楽しいUE4 C++ライフを過ごしましょう。(宣伝:同人誌でUnreal C++本とボイチェン本書いているのでよければどうぞ

明日はNaotsunさんの「自作ブレンドアニメーションノードを作る」です。よろしくお願いします。

UE4のVisualStudioソリューションファイルの作り方について

UE4プロジェクトのVisualStudioソリューションファイルの作り方について

どこがどう繋がって、どのエンジンバージョンのソリューションが作成されるのか理解していなかったので、まとめました。 あくまでUE4でのVSソリューションファイルの作り方についてなので、UE4プロジェクトのバージョンコントロール方法ではありません。そのような情報がほしい場合には、ソース コントロールとして SVN を使用する | Unreal Engineなどを参考にしましょう。

ちなみにVer4.19ぐらいのときにメモしたやつなので、最新だと変わっていたりするかもしれません。

YOUR_PROJECT という部分は、自身の作成したプロジェクト名に置き換えてください。 方法はパターンA、パターンBみたいに列挙していきますので、お好きな手段を選ぶとよいです。

【エンジンソースの取得方法】

  • A. GithubからCloneして取得(お好きな作業フォルダへどうぞ)
  • B. EpicGamesLauncherからInstallOption->エンジンソースにチェック (デフォルトパス設定は、 [C:\Program Files\Epic Games\UE_4.xx\Engine])

【ソリューションファイルの作成方法】

  • A. Editor起動して、メニューの[File]->[Generate Visual Studio Project]、もしくは[File]->[RefreshProjects]
  • B. Setup.batと同階層に存在する、[GenerateProjectFiles.bat]を実行
  • C. YOUR_PROJECT.uprojectを右クリック->[Generate Visual Studio project files]
  • D. VisualStudioExtensionを入れておき、VisualStudioのツールバーから[Refresh Projects]ボタンを押す

【非改造・改造別 作成例】

リリースされているエンジンを使用したC++開発(非改造エンジン)

  • A. EpicGamesLauncherからEditorを起動、YOUR_PROJECT.uprojectをEditorで開き、メニューの[File]->[RefreshProjects]
  • B. YOUR_PROJECT.uprojectを右クリック->[Open]でEditorを開く->メニューの[File]->[Refresh Projects]
  • C. YOUR_PROJECT.uprojectを右クリック->[Generate Visual Studio project files]
  • D. YOUR_PROJECT.slnをVisualStudioで開き、VisualStudioのツールバーから[Refresh Projects]ボタンを押す(要:VisualStudioExtension)

改造エンジンを使用したC++開発

  • A. 改造エンジンと同階層にそのエンジンで開発するプロジェクトのフォルダを入れ、さらに同階層にGenerateProjectFiles.batがある形にする
例:
  MyUE4Engine/Engine/ (エンジンソース)
  MyUE4Engine/YOUR_PROJECT/ 
  MyUE4Engine/GenerateProjectFiles.bat

 MyUE4Engine/GenerateProjectFiles.batでソリューションファイルを作成する。 また、.uprojectのEngineAssociation項目は空にしておく。 "EngineAssociation": "", こうすることで、なぜかYOUR_PROJECTの上の階層にあるEngineフォルダ内のUE4をエンジンとして使ってくれるらしい。

(設定参考: https://github.com/EpicGames/UnrealTournament

  • B. 改造エンジンのEditor起動、YOUR_PROJECT.uprojectをEditorで開き、メニューの[File]->[Refresh Projects]
  • C. YOUR_PROJECT.slnをVisualStudioで開き、VisualStudioのツールバーから[Refresh Projects]ボタンを押す(要:VisualStudioExtension)

補足

Aの方法だと、MyUE4Engine/UE4.slnが作られるので、それを開きます。 BとCの方法だと、開くのはUE4.slnではなく、YOUR_PROJECT.slnになって、パスはMyUE4Engine/YOUR_PROJECT/YOUR_PROJECT.slnになると思います。

おすすめの方法は?

非改造 -> どれでもやりやすい方法でいいと思います。

改造 -> AのGenerateProjectFiles.batを使用したもの。同じフォルダにまとまっていることで管理しやすくなり、uprojectのEngineAssociationを空白で管理できるので、環境によってEngineAssociationが異なるハッシュタグになってuprojectファイルに差分が出てしまう問題を防げる。

例:改造エンジンでゲームを作ってみたい(Windows環境)

Engineソースをダウンロードする。develop/MyUE4GameパスにてGitからCloneした。

develop/MyUE4Game/Engine/
develop/MyUE4Game/Samples/
develop/MyUE4Game/Templates/
develop/MyUE4Game/Setup.bat
develop/MyUE4Game/GenerateProjectFiles.bat
develop/MyUE4Game/README.md

Setup.batで必要なものをインストール。 GenerateProjectFiles.batでエンジン用ソリューションUE4.slnを作成。

develop/MyUE4Game/Engine/
develop/MyUE4Game/Samples/
develop/MyUE4Game/Templates/
develop/MyUE4Game/Setup.bat
develop/MyUE4Game/GenerateProjectFiles.bat
develop/MyUE4Game/README.md
develop/MyUE4Game/UE4.sln <- New!

エンジンをビルドしてEditor起動。 テンプレートからC++プロジェクトを作成する。作成パスはdevelop/MyUE4Gameにして、プロジェクト名はFPSGameとする。

develop/MyUE4Game/Engine/
develop/MyUE4Game/Samples/
develop/MyUE4Game/Templates/
develop/MyUE4Game/FPSGame/ <- New!
  develop/MyUE4Game/FPSGame/FPSGame.uproject <- New!
develop/MyUE4Game/Setup.bat
develop/MyUE4Game/GenerateProjectFiles.bat
develop/MyUE4Game/README.md
develop/MyUE4Game/UE4.sln

develop/MyUE4Game/UE4.slnを開くと、FPSGameソリューションが含まれているので、それをUE4.slnのスタートアッププロジェクトにすれば完成。 (出来ていなかったらGenerateProjectFiles.batで再作成する)

エンジンいじらない状態でゲーム部分だけ開発するときには、develop/MyUE4Game/FPSGame/FPSGame.slnを開いてビルドして実行でもよいとかそんな感じだったと思います。(多分)

トラブルシューティング

UE4のVisualStudioExtensionってどこにあるの?

エンジンコードフォルダの、Engine\Extras\UnrealVSにあります。 (詳細: https://docs.unrealengine.com/latest/JPN/Programming/Development/VisualStudioSetup/UnrealVS/index.html

.uprojectを右クリックでUnrealのメニューが出てこない

EpicGamesLauncherと拡張子の関係付けができていない。 EpicGamesLauncherを起動して開くと、復旧するか聞かれるので復旧してください。

VisualStudioのツールバーの[Refresh Projects]がグレーアイコンで押せない

同上?使っていないので条件は詳しく調べていません……とりあえず次のを試してみてはいかがでしょうか?

EpicGamesLauncherと拡張子の関係付けができていないため?EpicGamesLauncherを起動して開くと、復旧するか聞かれるので復旧してみてください。 もしくは、GenerateProjectFiles.batがソリューションファイルと同じフォルダにないから? 改造エンジンだと非対応?

.uprojectで生成されるエンジンコードのバージョンが決まる基準は何なの?

 .uprojectの"EngineAssociation": の値で決まる。.uprojectをテキストファイルとして開くと書いてあります。

  • 単純な数値 -> エンジンのバージョン(Epic Games Launcherのエンジンを使用する)
  • ハッシュ値 -> PC固有のパスがレジストリに登録されていて、それが基準になる
  • 空欄 -> 親フォルダにエンジンフォルダがあればそれを使用する。無ければバージョン指定をGUIで設定する必要がある

 参考: http://historia.co.jp/archives/2316/

GenerateProjectFiles.batが見当たりません

非改造エンジン(Launcherからダウンロードしたエンジン)には存在しません。 代わりにuprojectファイルを右クリックして、[Generate Visual Studio project files]からslnなどのファイルを更新しましょう。

VisualStudio2017のソリューション作りたいのに2015のが作られてしまう

対応しているエンジンバージョンの場合、GenerateProjectFiles.batのコマンドライン引数に -2017 を加えると作ってくれます。 2019でも同じだと思います。

$ ./GenerateProjectFiles.bat -2017