#pragma once
#include "CoreMinimal.h"
#include "HazardTypes.generated.h"
UENUM(BlueprintType, meta = (Bitflags, UseEnumValuesAsMaskValuesInEditor = "true"))
enum class EHazardFlags : uint8
{
None = 0 UMETA(Hidden),
Skid = 1 << 0,
HighLateralG = 1 << 1,
YawInstability = 1 << 2,
LaneDeparture = 1 << 3,
RolloverRisk = 1 << 4,
HarshManeuver = 1 << 5,
FallOff = 1 << 6,
};
ENUM_CLASS_FLAGS(EHazardFlags);
UENUM(BlueprintType)
enum class EHazardPhase : uint8
{
Enter UMETA(DisplayName = "진입"),
Sustain UMETA(DisplayName = "지속"),
Exit UMETA(DisplayName = "해제"),
};
USTRUCT(BlueprintType)
struct FHazardEvent
{
GENERATED_BODY()
UPROPERTY(BlueprintReadOnly)
double TimeStamp = 0.0;
UPROPERTY(BlueprintReadOnly, meta = (Bitmask, BitmaskEnum = "EHazardFlags"))
int32 ActiveFlags = 0;
UPROPERTY(BlueprintReadOnly)
EHazardPhase Phase = EHazardPhase::Enter;
UPROPERTY(BlueprintReadOnly)
FVector WorldLocation = FVector::ZeroVector;
UPROPERTY(BlueprintReadOnly)
float Speed = 0.f;
UPROPERTY(BlueprintReadOnly)
float SlipAngleDeg = 0.f;
UPROPERTY(BlueprintReadOnly)
float LateralG = 0.f;
UPROPERTY(BlueprintReadOnly)
float YawRate = 0.f;
UPROPERTY(BlueprintReadOnly)
float CrossTrackError = 0.f;
UPROPERTY(BlueprintReadOnly)
float RollDeg = 0.f;
};
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(
FOnHazardDetected, const FHazardEvent&, HazardEvent);
struct
관련된 변수들을 하나의 묶음으로 정의하는 양식.
차 한 대를 표현할 때 색깔/속도/연료를 따로 변수로 풀어 쓰면 차 100대에 변수 300개가 된다.
struct로 한 묶음으로 만들면 차 한 대가 변수 하나로 표현된다.
struct FCar {
FString Color;
float Speed;
float Fuel;
};
FCar MyCar;
MyCar.Color = "Red";
MyCar.Speed = 80.0f;
점(.)은 묶음 안의 멤버에 접근하는 표시. 함수에 넘길 때도 차 한 대 통째로 전달 가능.
언리얼 관례: struct 이름 앞에 F를 붙인다.
Class는 U 또는 A, Enum은 E, Struct는 F. 코드 첫 글자만 봐도 데이터 타입이 보인다.
HazardTypes.h의 FHazardEvent가 이거다.
위험 이벤트 한 건이라는 양식을 미리 정의해 두고, 감지 시 통째로 채워서 던진다.
enum
의미 있는 이름이 붙은 숫자 목록. 코드에서 3 같은 숫자만 쓰면 그게 뭔지 머리로 외워야 한다.
enum으로 이름을 붙이면 의미가 코드에 보인다.
enum class EWeekday {
Monday, // 0
Tuesday, // 1
Wednesday, // 2
};
EWeekday Today = EWeekday::Wednesday;
값을 명시 안 하면 0부터 자동 증가.
enum class의 class는 이 enum의 이름들이 자기 영역 안에서만 유효하다는 뜻. EWeekday::Monday처럼 어느 enum의 Monday인지 명시해야 한다. 예전 C 스타일 enum은 이름 충돌이 잘 났는데 enum class는 그게 없다.
: uint8은 내부적으로 uint8(0~255)로 저장하라는 뜻. 안 쓰면 기본은 int(4바이트).
uint8이면 1바이트라 메모리 절약.
TArray
언리얼이 만든 가변 길이 배열. 같은 타입의 여러 개를 목록으로 들고 다닐 때 쓴다.
TArray<int32> Numbers;
Numbers.Add(10);
Numbers.Add(20);
Numbers[0]; // 10
Numbers.Num(); // 2
<int32>는 이 배열에 담을 타입을 지정. 컴파일러가 타입 안 맞으면 에러를 내준다.
비트플래그: 일반 enum 대신 1 << N으로 정의하는 이유
위험은 동시에 여러 개가 발생할 수 있다. 미끄러지면서 차선도 이탈하고 휘청거리는 식으로. 그런데 enum 변수 하나엔 값 하나만 담긴다. 그래서 각 위험에 비트 한 칸씩 배정해서 정수 한 칸 안에 동시 발생을 표현한다.
1 << 0 = 00000001 (= 1)
1 << 1 = 00000010 (= 2)
1 << 2 = 00000100 (= 4)
1 << 3 = 00001000 (= 8)
<<는 비트 왼쪽 시프트 연산자. 1 << 3은 1을 3칸 왼쪽으로 밀어서 8.
비트플래그를 안 쓰고 일반 enum이었다면 동시 발생 표현하려고 TArray을 따로 들고 다녀야 한다.
메모리 사용량 들쭉날쭉, 비교는 배열 순회 필요, 직렬화 복잡.
비트플래그는 int32 한 칸(4바이트)에 최대 32가지 위험 조합을 다 담는다.
위험이 1개든 7개든 메모리 동일. 비교도 한 줄.
비트 연산: OR로 켜고, AND로 확인
비트 켜기는 OR(|). 둘 중 하나라도 1이면 1.
ActiveFlags |= (int32)EHazardFlags::Skid;
ActiveFlags |= (int32)EHazardFlags::LaneDeparture;
// 결과: 00001001
비트 확인은 AND(&). 둘 다 1이어야 1.
00001001 (ActiveFlags)
& 00000001 (Skid 마스크)
─────────
00000001 (Skid 자리만 통과)
Skid 값(00000001)이 가면 역할을 한다. 가면 구멍이 뚫린 자리(=1인 자리)는 비치고, 막힌 자리(=0인 자리)는 안 보인다. LaneDeparture가 켜져 있어도 그 자리는 가면에 막혀서 결과에 안 나타난다.
오직 Skid 자리만 통과. 이게 마스킹.
if ((ActiveFlags & (int32)EHazardFlags::Skid) != 0) {
// Skid 켜져 있음
}
결과가 0이 아니면 해당 비트가 켜진 상태.
XOR(^)은 토글. 같으면 0, 다르면 1.
켜져 있으면 끄고, 꺼져 있으면 켜는 동작.
ENUM_CLASS_FLAGS 매크로가 하는 일
enum class는 타입이 엄격해서 비트 연산자를 직접 못 쓴다. 컴파일러가 에러를 낸다.
EHazardFlags A = EHazardFlags::Skid;
EHazardFlags B = EHazardFlags::LaneDeparture;
EHazardFlags C = A | B; // 컴파일 에러
매크로 없으면 일일이 정수로 변환해야 한다.
EHazardFlags C = static_cast<EHazardFlags>(
static_cast<int32>(A) | static_cast<int32>(B)
);
static_cast<타입>(값)은 타입 강제 변환.
EHazardFlags를 int32로 봐 → 비트 OR → 다시 EHazardFlags로 봐.
ENUM_CLASS_FLAGS(EHazardFlags) 한 줄을 쓰면 이 변환들을 자동 처리하는 |, &, ^ 연산자가 EHazardFlags 타입에 등록된다. 그러면 그냥 A | B 자연스럽게 쓸 수 있다.
포인터와 -> 화살표
언리얼에선 액터, 컴포넌트, 서브시스템 같은 무거운 객체를 포인터로 다룬다
객체를 통째로 복사하면 비싸니까 그 객체가 있는 주소만 들고 다닌다
점과 화살표의 차이는 원본이냐 포인터냐
FCar MyCar; // 직접 가지고 있음
MyCar.Speed = 80; // 점으로 접근
FCar* CarPointer = &MyCar; // 포인터 (주소만 가진 종이쪽지)
CarPointer->Speed = 80; // 화살표로 접근
->는 객체의 멤버나 함수에 접근하는 표시
AgentDataLogger->RecordHazard(Event);
// = AgentDataLogger가 가리키는 객체의 RecordHazard 함수를 Event 넘기면서 호출
언리얼 코드 전반에 ->가 정말 자주 나온다.
UENUM, USTRUCT, UPROPERTY: 언리얼 리플렉션 시스템
이 enum/struct/변수를 언리얼 리플렉션 시스템에 등록해달라는 뜻.
등록되면 블루프린트에서 변수 타입으로 쓸 수 있고, 에디터에서 표시되고, 직렬화도 된다.
UENUM(BlueprintType, meta = (Bitflags, UseEnumValuesAsMaskValuesInEditor = "true"))
- BlueprintType: 블루프린트에서 이 타입을 변수로 만들 수 있게.
- Bitflags: 비트플래그 enum이라고 에디터에 알림. 체크박스 여러 개로 표시.
- UseEnumValuesAsMaskValuesInEditor = "true":
1 << 0처럼 직접 명시한 값을 그대로 마스크로 쓰라는 뜻. 안 쓰면 0,1,2,3을 자동으로 1,2,4,8로 해석하던 옛 동작.
UPROPERTY(BlueprintReadOnly, meta = (Bitmask, BitmaskEnum = "EHazardFlags"))
int32 ActiveFlags = 0;
타입은 int32지만 에디터에서 보여줄 땐 EHazardFlags 체크박스 목록으로 표시.
디자이너가 어떤 위험이 활성화됐는지 체크박스로 볼 수 있다.
USTRUCT 위의 GENERATED_BODY()도 비슷한 매크로. 리플렉션에 필요한 코드를 자동 생성한다.
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam
방송 채널을 만드는 매크로. 어떤 일이 일어났을 때 호출될 함수들의 묶음을 정의한다.
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(
FOnHazardDetected, const FHazardEvent&, HazardEvent);
이름을 단어별로 뜯어보면:
- DELEGATE: 위임자, 콜백 시스템.
- MULTICAST: 여러 명이 동시에 들을 수 있음. 단일 채널은 듣는 사람이 하나뿐.
- DYNAMIC: 블루프린트에서도 구독 가능. 약간 느리지만 호환성을 얻는다.
- OneParam: 방송할 때 인자 1개를 같이 보냄. 여기선 FHazardEvent.
방송 쪽:
// 헤더에 채널 선언
UPROPERTY(BlueprintAssignable)
FOnHazardDetected OnHazardDetected;
// cpp에서 방송
FHazardEvent Event;
// ... 채워넣기
OnHazardDetected.Broadcast(Event);
구독 쪽:
HazardDetector->OnHazardDetected.AddDynamic(
this, &UAgentDataLogger::OnHazardEventReceived);
UFUNCTION() // Dynamic 델리게이트는 UFUNCTION이 필수
void OnHazardEventReceived(const FHazardEvent& HazardEvent) {
// 받아서 처리
}
핵심 설계 포인트: 방송하는 쪽은 구독자의 존재를 모른다.
델리게이트를 안 쓰고 직접 호출하는 방식이면 이렇게 된다.
// 결합도 높은 방식
AgentDataLogger->RecordHazard(Event);
UIWidget->ShowAlert(Event);
SoundManager->PlayWarning(Event);
방송하는 쪽이 듣는 쪽 모두를 직접 알아야 한다.
듣는 시스템이 추가될 때마다 방송하는 쪽 코드를 수정해야 한다. 결합도가 높아진다.
델리게이트는 이 결합을 끊는다. 한쪽이 변해도 다른 쪽이 안 흔들린다.
다음에 비슷한 패턴 만났을 때 참고용 코드
패턴 1: 동시 발생 가능한 상태들을 비트플래그로
UENUM(BlueprintType, meta = (Bitflags, UseEnumValuesAsMaskValuesInEditor = "true"))
enum class EMyStateFlags : uint8
{
None = 0 UMETA(Hidden),
StateA = 1 << 0,
StateB = 1 << 1,
StateC = 1 << 2,
StateD = 1 << 3,
};
ENUM_CLASS_FLAGS(EMyStateFlags);
사용:
int32 ActiveStates = 0;
ActiveStates |= (int32)EMyStateFlags::StateA; // 켜기
if ((ActiveStates & (int32)EMyStateFlags::StateA) != 0) {
// StateA 활성화 상태
}
ActiveStates &= ~(int32)EMyStateFlags::StateA; // 끄기 (NOT으로 비트 반전한 후 AND)
패턴 2: 이벤트 한 건을 통째로 전달하는 struct
USTRUCT(BlueprintType)
struct FMyEvent
{
GENERATED_BODY()
UPROPERTY(BlueprintReadOnly)
double TimeStamp = 0.0;
UPROPERTY(BlueprintReadOnly, meta = (Bitmask, BitmaskEnum = "EMyStateFlags"))
int32 ActiveFlags = 0;
UPROPERTY(BlueprintReadOnly)
FVector WorldLocation = FVector::ZeroVector;
// 측정값들...
};
패턴 3: 단계가 있는 이벤트(Enter/Sustain/Exit)
UENUM(BlueprintType)
enum class EMyEventPhase : uint8
{
Enter UMETA(DisplayName = "진입"),
Sustain UMETA(DisplayName = "지속"),
Exit UMETA(DisplayName = "해제"),
};
패턴 4: 멀티캐스트 델리게이트로 이벤트 방송
선언(보통 헤더 상단 또는 별도 Types.h에):
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(
FOnMyEvent, const FMyEvent&, EventData);
방송하는 쪽 헤더:
UPROPERTY(BlueprintAssignable)
FOnMyEvent OnMyEvent;
방송하는 쪽 cpp:
FMyEvent EventData;
// 채워넣기
OnMyEvent.Broadcast(EventData);
구독하는 쪽 헤더:
UFUNCTION()
void HandleMyEvent(const FMyEvent& EventData);
구독하는 쪽 cpp(보통 BeginPlay나 초기화 시점):
if (SourceComponent)
{
SourceComponent->OnMyEvent.AddDynamic(this, &UMyClass::HandleMyEvent);
}
해제(EndPlay나 소멸 시점):
if (SourceComponent)
{
SourceComponent->OnMyEvent.RemoveDynamic(this, &UMyClass::HandleMyEvent);
}
패턴 변형 가이드
- 인자 0개면
DECLARE_DYNAMIC_MULTICAST_DELEGATE, 2개면_TwoParams, 3개면_ThreeParams. - 블루프린트 호환 필요 없으면
DYNAMIC빼고DECLARE_MULTICAST_DELEGATE_OneParam. 더 빠르지만 BP에서 못 씀. - 듣는 쪽이 하나만이면
MULTICAST빼고DECLARE_DYNAMIC_DELEGATE_OneParam.
'학습 > Unreal' 카테고리의 다른 글
| 언리얼 접두사 규칙과 모듈 구조 (1) | 2026.06.02 |
|---|---|
| 리플렉션, GC, CDO, 메모리 (0) | 2026.05.28 |
| 언리얼 사격 시스템 정리 (라인트레이스 사격, 연발, 부분 랙돌, 부위 판정) (0) | 2026.05.16 |
| 언리얼 C++ 모듈 추가 정리 (Module, Build.cs, ini Config, 비동기 로딩) (0) | 2026.05.16 |
| 언리얼 UI 정리 (WidgetComponent, HUD, 초기화 타이밍 함정) (0) | 2026.05.16 |
