언리얼 엔진의 이벤트 시스템은 게임 로직을 효과적으로 구현하고 관리할 수 있게 해줍니다. 이 절에서는 다양한 이벤트 유형과 C++에서의 이벤트 바인딩 기법을 살펴보겠습니다.
언리얼 엔진의 이벤트 유형
액터 이벤트 : 액터의 생명주기와 관련된 이벤트
컴포넌트 이벤트 : 컴포넌트의 상태 변화와 관련된 이벤트
입력 이벤트 : 사용자 입력과 관련된 이벤트
콜리전 이벤트 : 물체 간 충돌과 관련된 이벤트
타이머 이벤트 : 시간 기반 이벤트
C++에서의 이벤트 선언 및 바인딩
액터 이벤트 예시
UCLASS ()
class MYGAME_API AMyActor : public AActor
{
GENERATED_BODY ()
public:
AMyActor ();
protected:
virtual void BeginPlay () override ;
virtual void EndPlay ( const EEndPlayReason :: Type EndPlayReason ) override ;
UFUNCTION ()
void OnActorBeginOverlap ( AActor * OverlappedActor , AActor * OtherActor );
};
AMyActor :: AMyActor ()
{
OnActorBeginOverlap . AddDynamic ( this , & AMyActor ::OnActorBeginOverlap);
}
void AMyActor :: BeginPlay ()
{
Super :: BeginPlay ();
UE_LOG (LogTemp, Log, TEXT ( "Actor BeginPlay" ));
}
void AMyActor :: EndPlay ( const EEndPlayReason :: Type EndPlayReason )
{
Super :: EndPlay (EndPlayReason);
UE_LOG (LogTemp, Log, TEXT ( "Actor EndPlay" ));
}
void AMyActor :: OnActorBeginOverlap ( AActor * OverlappedActor , AActor * OtherActor )
{
UE_LOG (LogTemp, Log, TEXT ( "Actor Begin Overlap with %s " ), * OtherActor -> GetName ());
}
Copy
컴포넌트 이벤트 예시
UCLASS (ClassGroup=(Custom), meta=(BlueprintSpawnableComponent))
class MYGAME_API UMyComponent : public UActorComponent
{
GENERATED_BODY ()
public:
UMyComponent ();
protected:
virtual void BeginPlay () override ;
UFUNCTION ()
void OnComponentActivated ( UActorComponent * Component , bool bReset );
};
UMyComponent :: UMyComponent ()
{
PrimaryComponentTick . bCanEverTick = true ;
}
void UMyComponent :: BeginPlay ()
{
Super :: BeginPlay ();
OnComponentActivated . AddDynamic ( this , & UMyComponent ::OnComponentActivated);
}
void UMyComponent :: OnComponentActivated ( UActorComponent * Component , bool bReset )
{
UE_LOG (LogTemp, Log, TEXT ( "Component Activated: %s " ), * Component -> GetName ());
}
Copy
입력 이벤트 예시
UCLASS ()
class MYGAME_API AMyPlayerController : public APlayerController
{
GENERATED_BODY ()
public:
AMyPlayerController ();
protected:
virtual void SetupInputComponent () override ;
void MoveForward ( float Value );
void MoveRight ( float Value );
};
AMyPlayerController :: AMyPlayerController ()
{
}
void AMyPlayerController :: SetupInputComponent ()
{
Super :: SetupInputComponent ();
InputComponent -> BindAxis ( "MoveForward" , this , & AMyPlayerController ::MoveForward);
InputComponent -> BindAxis ( "MoveRight" , this , & AMyPlayerController ::MoveRight);
}
void AMyPlayerController :: MoveForward ( float Value )
{
if (Value != 0.0f && GetPawn ())
{
GetPawn ()-> AddMovementInput ( GetPawn ()-> GetActorForwardVector (), Value);
}
}
void AMyPlayerController :: MoveRight ( float Value )
{
if (Value != 0.0f && GetPawn ())
{
GetPawn ()-> AddMovementInput ( GetPawn ()-> GetActorRightVector (), Value);
}
}
Copy
동적 바인딩 vs 정적 바인딩
동적 바인딩 : 런타임에 바인딩되며, UFUNCTION() 매크로가 필요합니다.
UFUNCTION ()
void OnSomeEvent ();
SomeActor -> OnEventHappened . AddDynamic ( this , & AMyActor ::OnSomeEvent);
Copy
정적 바인딩 : 컴파일 시간에 바인딩되며, 더 빠르지만 리플렉션을 지원하지 않습니다.
void OnSomeEvent ();
SomeActor -> OnEventHappened . AddUObject ( this , & AMyActor ::OnSomeEvent);
Copy
람다 함수를 이용한 이벤트 바인딩
SomeActor -> OnEventHappened . AddLambda ([ this ]()
{
UE_LOG (LogTemp, Log, TEXT ( "Event happened!" ));
// 추가 로직
});
Copy
이벤트 바인딩 해제
// 특정 함수 바인딩 해제
SomeActor -> OnEventHappened . RemoveDynamic ( this , & AMyActor ::OnSomeEvent);
// 모든 바인딩 해제
SomeActor -> OnEventHappened . Clear ();
Copy
이벤트 기반 프로그래밍의 장점과 주의점
장점
느슨한 결합 : 시스템 간 의존성 감소
모듈성 : 기능 추가 및 제거가 용이
확장성 : 새로운 기능을 쉽게 추가 가능
주의점
복잡성 증가 : 과도한 사용 시 코드 흐름 파악이 어려울 수 있음
성능 오버헤드 : 많은 이벤트 발생 시 성능 저하 가능성
디버깅 어려움 : 이벤트 체인 추적이 복잡할 수 있음
성능 최적화를 위한 이벤트 사용 전략
이벤트 발생 빈도 최적화 : 불필요한 이벤트 발생 줄이기
이벤트 핸들러 최적화 : 핸들러 내 로직을 가볍게 유지
정적 바인딩 활용 : 성능이 중요한 경우 정적 바인딩 사용
이벤트 풀링 : 자주 사용되는 이벤트 객체 재사용
예시
// 이벤트 풀링
TArray<FMyEvent*> EventPool;
FMyEvent * GetEventFromPool ()
{
if ( EventPool . Num () > 0 )
{
return EventPool . Pop ();
}
return new FMyEvent ();
}
void ReturnEventToPool ( FMyEvent * Event )
{
Event -> Reset ();
EventPool . Push (Event);
}
Copy
멀티스레드 환경에서의 이벤트 처리
스레드 안전성 확보 : 동기화 메커니즘 사용
락 프리 알고리즘 활용 : 성능 향상을 위해 가능한 경우 사용
이벤트 큐 사용 : 다중 스레드에서 안전하게 이벤트 처리
예시
// 스레드 안전 이벤트 큐
class FThreadSafeEventQueue
{
private:
TQueue<FEvent*> EventQueue;
FCriticalSection QueueLock;
public:
void EnqueueEvent ( FEvent * Event )
{
FScopeLock Lock (&QueueLock);
EventQueue . Enqueue (Event);
}
bool DequeueEvent ( FEvent *& OutEvent )
{
FScopeLock Lock (&QueueLock);
return EventQueue . Dequeue (OutEvent);
}
};
Copy
Best Practices
명확한 이벤트 명명 규칙 사용
DECLARE_EVENT (FMyClass, FOnSomethingHappenedEvent)
Copy
이벤트 문서화
/** 플레이어가 데미지를 받을 때 발생하는 이벤트 */
DECLARE_EVENT_TwoParams (AMyCharacter, FOnTakeDamageEvent, float , AActor*)
Copy
이벤트 핸들러의 범위 제한
SomeActor -> OnEventHappened . AddWeakLambda ( this , [ this ]()
{
if ( IsValid ( this ))
{
// 이벤트 처리 로직
}
});
Copy
컴포넌트 기반 이벤트 시스템 구축
UCLASS (ClassGroup=(Custom), meta=(BlueprintSpawnableComponent))
class MYGAME_API UEventManagerComponent : public UActorComponent
{
GENERATED_BODY ()
public:
DECLARE_EVENT (UEventManagerComponent, FOnLevelCompleteEvent)
FOnLevelCompleteEvent OnLevelComplete;
void TriggerLevelComplete ()
{
OnLevelComplete . Broadcast ();
}
};
Copy
이벤트 우선순위 설정
SomeActor -> OnEventHappened . AddUObject ( this , & AMyActor ::HighPriorityHandler, FEventPriority ::High);
SomeActor -> OnEventHappened . AddUObject ( this , & AMyActor ::NormalPriorityHandler, FEventPriority ::Normal);
Copy
이벤트 바인딩 기법은 언리얼 엔진에서 유연하고 확장 가능한 게임 시스템을 구축하는 데 핵심적인 역할을 합니다. 다양한 이벤트 유형을 적절히 활용하고, 동적/정적 바인딩을 상황에 맞게 선택하며, 람다 함수를 통한 간결한 이벤트 처리 등을 통해 효율적인 코드를 작성할 수 있습니다.
그러나 이벤트 기반 프로그래밍의 장점을 최대한 활용하면서도, 과도한 사용으로 인한 복잡성 증가와 성능 저하를 주의해야 합니다. 멀티스레드 환경에서의 안전한 이벤트 처리와 성능 최적화 전략을 적용함으로써, 안정적이고 효율적인 게임 시스템을 구축할 수 있습니다.
지속적인 프로파일링과 코드 리뷰를 통해 이벤트 시스템의 효율성을 모니터링하고 개선하는 것이 중요합니다.