KoreanFoodie's Study
[언리얼] ENUM_CLASS_FLAGS 사용하기 (언리얼 플래그 사용법) 본문
[언리얼] ENUM_CLASS_FLAGS 사용하기 (언리얼 플래그 사용법)
GoldGiver 2023. 3. 14. 21:29
ENUM_CLASS_FLAGS 사용하기 (언리얼 플래그 사용법)
핵심 :
1. 여러 조건을 동시에 가질 수 있는 상황을 다를 때는, Enum Class 의 값을 Shift 연산자를 활용해 Bit Flag 처럼 사용하는 것이 좋다.
2. EnumClassFlags 파일에 정의된 템플릿 함수들(e.g. EnumHasAnyFlags)을 활용하면, Enum Class 에 정의된 플래그들을 이용해 케이스들의 중첩을 효율적으로 체크할 수 있다.
3. 일반적으로 Enum Class 내의 값들을 Flag 로 표현하는 경우는 UI 나 환경설정 세팅 등이 있다.
일반적으로 Enum Class 는 같은 주제 내에서 여러 조건들을 다룰 때 사용한다.
그런데 만일 여러 복합적인 상황을 동시에 다뤄야 하는 케이스가 있다고 하면 어떨까?
예를 들자면, 전투 시스템을 만드는 과정에서 직업별로 취할 수 있는 행동의 조합을 다르게 만든다고 가정해 보자. 그럼 궁수는 활을 쏠 수 있고(ATTACK), 나무 위를 오를 수 있는(CLIMB_TREE) 등의 행동을 할 수 있지만, 워리어는 공격(ATTACK) 을 할 수는 있지만 나무 위를 오르지는 못하고, 대신 방어나 반격을 하는 기능을 넣을 수 있을 것이다.
다음 예시 코드를 보자 :
#include "CoreMinimal.h"
/*
* 블루프린트에서 사용할 수 있게 만드려면 UENUM 을 붙여주면 된다.
* UseEnumValuesAsMaskValuesInEditor 를 이용해서 Enum 값을 에디터에서도 사용할 수 있다.
* E 는 Enum 을, Tp 는 TestProject 를 나타내는 접두어이다.
*/
UENUM(BlueprintType, meta = (Bitflags, UseEnumValuesAsMaskValuesInEditor = "true"))
enum class ETpCombatSystemFlags : uint32
{
RUN = 1 << 1,
JUMP = 1 << 2,
BACK_JUMP = 1 << 3,
ATTACK = 1 << 4,
DEFEND = 1 << 5,
AVENGE = 1 << 6,
EVADE = 1 << 7,
SNIPE = 1 << 8,
CLIMB_TREE = 1 << 9,
// 직업별 플래그 조합
TRASH = 0,
WARRIOR = RUN | JUMP | ATTACK | DEFEND | AVENGE,
THEIF = RUN | JUMP | BACK_JUMP | ATTACK | EVADE,
HUNTER = RUN | JUMP | ATTACK | SNIPE | CLIMB_TREE,
GOD = 0xff,
}
ENUM_CLASS_FLAGS(ETpCombatSystemFlags);
위처럼 ETpCombatSystemFlags 에 각 직업별로 플래그를 조합했다. 그리고 TRASH 는 어떤 동작도 할 수 없는 녀석이고, GOD 은 모든 것을 할 수 있는 녀석으로 설정했다.
마지막에 ENUM_CLASS_FLAGS(ETpCombatSystemFlags) 는 EnumClassFlags.h 에 정의된 매크로로, 해당 Enum Class 의 Operator 를 재정의하는 역할을 한다.
// Defines all bitwise operators for enum classes so it can be (mostly) used as a regular flags enum
#define ENUM_CLASS_FLAGS(Enum) \
inline Enum& operator|=(Enum& Lhs, Enum Rhs) { return Lhs = (Enum)((__underlying_type(Enum))Lhs | (__underlying_type(Enum))Rhs); } \
inline Enum& operator&=(Enum& Lhs, Enum Rhs) { return Lhs = (Enum)((__underlying_type(Enum))Lhs & (__underlying_type(Enum))Rhs); } \
inline Enum& operator^=(Enum& Lhs, Enum Rhs) { return Lhs = (Enum)((__underlying_type(Enum))Lhs ^ (__underlying_type(Enum))Rhs); } \
inline constexpr Enum operator| (Enum Lhs, Enum Rhs) { return (Enum)((__underlying_type(Enum))Lhs | (__underlying_type(Enum))Rhs); } \
inline constexpr Enum operator& (Enum Lhs, Enum Rhs) { return (Enum)((__underlying_type(Enum))Lhs & (__underlying_type(Enum))Rhs); } \
inline constexpr Enum operator^ (Enum Lhs, Enum Rhs) { return (Enum)((__underlying_type(Enum))Lhs ^ (__underlying_type(Enum))Rhs); } \
inline constexpr bool operator! (Enum E) { return !(__underlying_type(Enum))E; } \
inline constexpr Enum operator~ (Enum E) { return (Enum)~(__underlying_type(Enum))E; }
그리고 편하게 쓸 수 있게 정의된 클래스들은 다음과 같다 :
template<typename Enum>
constexpr bool EnumHasAllFlags(Enum Flags, Enum Contains)
{
return ( ( ( __underlying_type(Enum) )Flags ) & ( __underlying_type(Enum) )Contains ) == ( ( __underlying_type(Enum) )Contains );
}
template<typename Enum>
constexpr bool EnumHasAnyFlags(Enum Flags, Enum Contains)
{
return ( ( ( __underlying_type(Enum) )Flags ) & ( __underlying_type(Enum) )Contains ) != 0;
}
template<typename Enum>
void EnumAddFlags(Enum& Flags, Enum FlagsToAdd)
{
Flags |= FlagsToAdd;
}
template<typename Enum>
void EnumRemoveFlags(Enum& Flags, Enum FlagsToRemove)
{
Flags &= ~FlagsToRemove;
}
만약 특정 클래스의 직업이 특정 기능(예를 들어 DEFEND) 를 할 수 있는지 알고 싶다고 하면, 다음과 같이 쓰면 될 것이다 :
if (EnumHasAnyFlags(MyCombatClass, ETpCombatFlags::DEFEND))
{
/* 방어와 관련된 어떤 동작 수행이 기대됨 ... */
}
만약 저주 아이템을 먹어서, 일시적으로 해당 클래스의 특정 기능(예를 들어 ATTACK)을 제거한다고 하면, 다음과 같을 것이다 :
if (EnumRemoveFlags(MyCombatClass, ETpCombatFlags::ATTACK))
{
/* ... */
}
/* 저주 해제... */
if (EnumHasAnyFlags(MyCombatClass, ETpCombatFlags::ATTACK))
{
/* ... */
}
만약 해당 Bitmask 기반 Enum Class 를 이용한 변수를 선언하고 싶으면, 다음과 같이 하면 된다 :
UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (Bitmask, BitmaskEnum = ETpCombatFlags))
int32 CombatFlags = 0;
함수는 다음처럼 만들면 된다(블루프린트에서 사용 가능하도록 설정할거라면 BlueprintCallable) :
UFUNCTION(BlueprintCallable)
bool CanDefend() const
{
return CombatFlags & ETpCombatFlags::DEFEND;
}
만약 블루프린트 파라미터로 사용하고 싶으면(블루프린트에서 해당 함수를 조작하고 싶으면) :
// CombatTest.h
UFUNCTION(BlueprintCallable)
bool IsMatch(UPARAM(meta = (Bitmask, BitmaskEnum = EAnimDescriptorFlags)) int32 Bitmask);
// CombatTest.cpp
bool UCombatTest::IsMatch(int32 Bitmask)
{
return Bitmask & CombatFlags == Bitmask;
}
위 함수는 다음과 같이 나온다!
참고로, Enum class 에 UPROPERTY 를 붙이고 싶을 경우, 다음과 같이 쓸 수도 있다 :
UPROPERTY()
TEnumAsByte<EnumName> VarName;
참고 : 블로그 1
'Game Dev > Unreal C++ : Dev Log' 카테고리의 다른 글
[언리얼] Visual Studio 한글 깨짐 (Log 한글 깨짐) (0) | 2023.03.28 |
---|---|
[언리얼] TSharedRef 를 클래스 멤버 변수로 선언할 때 에러 (0) | 2023.03.23 |
[언리얼] TimerHandle 배열 사용하기 (TimerHandle Array) (0) | 2023.03.10 |
[언리얼] 동적으로 액터 컴포턴트 생성하기 (Dynamically create ActorComponent) (0) | 2023.03.10 |
SetTimer 에 함수 및 람다(Lambda) 연결하기 (0) | 2023.02.20 |