DECLARE_MULTICAST_DELEGATE관련 이슈
- 최초에는 FOnTurnEnemyDead OnDead를 UPROPERTY를 통해 BlueprintAssignable 속성을 부여했으나DECLARE_MULTICAST_DELEGATE는 정적(C++) 타입이어서 등록되지 않음 이를 해결하기위해서는 동적(DYNAMIC) 델리케이트를 사용하거나 C++전용으로 UPROPERTY를 제거해야함 결국 아래와 같이 수정
// 완전 C++ 전용으로, UPROPERTY 제거
DECLARE_MULTICAST_DELEGATE(FOnTurnEnemyDead);
FOnTurnEnemyDead OnDead;- 오류 해결 이후에도 브로드캐스트 호출 시그니처 관련 이슈로 델리게이트 선언과 Broadcast 호출의 매개변수가 일치하지 않아 아래와 같이 수정
//수정 전
OnDead.Broadcast(DamageCauser);
//수정 후
OnDead.Broadcast();- 리스너(바인딩) 방식에서도 AddDynamic이 아닌 AddUObject로 수정
//수정 전
Enemy->OnDead.AddDynamic(this, &ATurnGameMode::OnEnemyDied);
//수정 후
Enemy->OnDead.AddUObject(this, &ATurnGameMode::OnEnemyDied);- 만일 델리게이트에서 파라미터로 AActor* 같은 인자를 넘기고 싶다면 아래와 같이 사용해야함
// 헤더
DECLARE_MULTICAST_DELEGATE_OneParam(FOnTurnEnemyDead, AActor*);
FOnTurnEnemyDead OnDead;
// CPP
OnDead.Broadcast(DamageCauser);
// 바인딩
Enemy->OnDead.AddUObject(this, &ATurnGameMode::OnEnemyDiedWithParam);
void OnEnemyDiedWithParam(AActor* Causer) { … }- 블루프린트 바인딩이 필요할때는 DYNAMIC 버전 필요
// 헤더
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnTurnEnemyDead, AActor*, DamageCauser);
UPROPERTY(BlueprintAssignable, Category="Combat")
FOnTurnEnemyDead OnDead;- 순수 C++전용 타입이라면 델리게이트선언에서 DYNAMIC 제거
- Broadcast호출과 시그니처(매개변수 개수)는 일치해야함
ex) DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnTurnEnemyDead, AActor*, DamageCauser); - 리스너등록(바인딩)은 순수 C++타입은 AddUObject 사용 블루프린트 바인딩 시 AddDynamic 사용
- 필요시 파라미터 버전은 OneParam등 사용
//순수 C++ 델리게이트 (파라미터 없음)
//헤더
DECLARE_MULTICAST_DELEGATE(FOnTurnEnemyDead);
FOnTurnEnemyDead OnDead;
//Broadcast
OnDead.Broadcast();
//바인딩
Enemy->OnDead.AddUObject(this, &ATurnGameMode::OnEnemyDied);
void ATurnGameMode::OnEnemyDied()
{
// 파라미터 없는 콜백
}
//순수 C++ 델리게이트 (파라미터 1개)
//헤더
DECLARE_MULTICAST_DELEGATE_OneParam(FOnTurnEnemyDead, AActor*);
FOnTurnEnemyDead OnDead;
//Broadcast
OnDead.Broadcast(DamageCauser);
//바인딩
Enemy->OnDead.AddUObject(this, &ATurnGameMode::OnEnemyDiedWithParam);
// 콜백
void ATurnGameMode::OnEnemyDiedWithParam(AActor* Causer)
{
// Causer 처리
}
//동적(Dynamic) 델리게이트 (블루프린트 바인딩, 파라미터 없음)
//헤더
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnTurnEnemyDead);
UPROPERTY(BlueprintAssignable, Category="Combat")
FOnTurnEnemyDead OnDead;
//Broadcast
OnDead.Broadcast();
//바인딩
Enemy->OnDead.AddDynamic(this, &ATurnGameMode::OnEnemyDied);
// 콜백
UFUNCTION() // 반드시 UFUNCTION() 필요
void OnEnemyDied();
void ATurnGameMode::OnEnemyDied()
{
// 파라미터 없는 블루프린트 바인딩 콜백
}
//동적(Dynamic) 델리게이트 (블루프린트 바인딩, 파라미터 1개)
//헤더
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnTurnEnemyDead, AActor*, DamageCauser);
UPROPERTY(BlueprintAssignable, Category="Combat")
FOnTurnEnemyDead OnDead;
//Broadcast
OnDead.Broadcast(DamageCauser);
//바인딩
Enemy->OnDead.AddDynamic(this, &ATurnGameMode::OnEnemyDiedWithParam);
// 콜백
UFUNCTION() // 반드시 UFUNCTION() 필요
void OnEnemyDiedWithParam(AActor* Causer);
void ATurnGameMode::OnEnemyDiedWithParam(AActor* Causer)
{
// 파라미터로 전달된 Causer 처리
}| 용도 | 선언 매크로 | 바인딩 | Broadcast |
|---|---|---|---|
| 순수 C++, 파라미터 없음 | DECLARE_MULTICAST_DELEGATE | AddUObject | Broadcast() |
| 순수 C++, 파라미터 있음 | DECLARE_MULTICAST_DELEGATE_OneParam | AddUObject | Broadcast(param) |
| 블루프린트, 파라미터 없음 | DECLARE_DYNAMIC_MULTICAST_DELEGATE | AddDynamic | Broadcast() |
| 블루프린트, 파라미터 있음 | DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam | AddDynamic | Broadcast(param) |
이슈 해결 후 추가로 찾아본 언리얼 엔진에서의 델리게이트 이론정리
델리게이트란?
- 함수 포인터의 안전한 버전 == 함수를 저장해두었다가 나중에 호출할 수 있는 객체
왜 사용하는지?
의존성 제거: Player가 UI 나 Sound 클래스를 몰라도 됨
독립적 개발: 각 시스템을 따로 개발 가능
테스트 용이: Player만 단독으로 테스트 가능
// 직접 호출 방식 class APlayer : public APqwn { private: AUIManager* UIManager; //직접 참조 필요 ASoundManager* SoundManager; //직접 참조 필요 AGameMode* GameMode; // 직접 참조 필요 public: void TakeDamage(float Damage) { CurrentHealth -= Damage; // 모든 시스템을 직접 호출 if (UIManager) UIManager->UpdateHealthBar(CurrentHealth); if (SoundManager) SoundManager->PlayDamageSound(); if (GameMode) GameMode->CheckGameOver(); //새로운 시스템 추가시 이어서 수정 } }; // 델리게이트 방식 class APlayer : public APawn { public: DECLARE_MULTICAST_DELEGATE_OneParam(FOnHealthChaged, float); FOnHealthChange OnHealthChanged; void TakeDamage(float Damge) { CurrentHealth -= Damage; // 누가 듣고 있는지 몰라도 됨 OnHealthChanaged.Broadcast(CurrentHealth); } };
확장성
- 이벤트 기반 아키텍처
- Observer 패턴: 하나의 이벤트에 여러 관찰자
- 발행 구독 모델: 이벤트 발생자와 처리자 분리
- 비동기 처리: 이벤트 발생과 처리의 분리
- 유지보수성
- 단일 수정: 이벤트 로직만바꾸면 모든 곳에 적용
- 버그 격리: 한 시스템 오류가 다른 시스템에는 영향 X
- 코드 재사용: 같은 이벤트를 여러 곳에서 활용
//새로운 시스템 추가가 쉬움 class AParticleManager : public AActor { void BeginPlay() override { //기존 Player 클래스 수정 없이 새 기능 추가 Player->OnHealthChanged.AddUObject(this,&AParticleManager::PlayBloodEffect); } };- 이벤트 기반 아키텍처
'일 > 포트폴리오' 카테고리의 다른 글
| 포트폴리오 log #5 (2) | 2025.07.31 |
|---|---|
| 포트폴리오 log #4 (4) | 2025.07.17 |
| 포트폴리오 log #3 (0) | 2025.07.16 |
| 포트폴리오 log #2 (0) | 2025.07.15 |
| 포트폴리오 log #1 (0) | 2025.07.11 |
