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が立っていない(どちらかだけではだめ)
}