나만의 작은 도서관
[TIL][C++] 250829 MMO 서버 개발 90일차: UCLASS(ClassGroup=(Custom), meta=(BlueprintSpawnableComponent)), [언리얼] 서버가 보낸 패킷을 처리하고 반영하는 과정은 어떻게 이루어져야 할까?, [언리얼] 델리게이트 이것저것 본문
Today I Learn
[TIL][C++] 250829 MMO 서버 개발 90일차: UCLASS(ClassGroup=(Custom), meta=(BlueprintSpawnableComponent)), [언리얼] 서버가 보낸 패킷을 처리하고 반영하는 과정은 어떻게 이루어져야 할까?, [언리얼] 델리게이트 이것저것
pledge24 2025. 8. 30. 00:18주의사항: 해당 글은 일기와 같은 기록용으로, 다듬지 않은 날것 그대로인 글입니다.
UCLASS(ClassGroup=(Custom), meta=(BlueprintSpawnableComponent))
- 클래스를 언리얼 오브젝트(UObject) 시스템의 일부로 지정하는 데 사용되는 매크로. 각 지정자의 의미는 다음과 같다.
- (ClassGroup=(Custom))
- 언리얼 에디터의 액터 배치(Actor Placement) 패널에서 해당 클래스를 “Custom’ 그룹 아래에 분류하도록 지정하는 역할. 클래스를 용도별로 정리하여 에디터 사용 편의성을 높이기 위해 사용한다.
- meta=(BlueprintSpawnableComponent)
- 이 컴포넌트 클래스가 블루프린트 스크립트 내에서 컴포넌트로 생성될 수 있도록 허용하는 역할. 해당 지정자를 사용함으로써 프로그래머가 아닌 디자이너나 아티스트도 블루프린트에서 이 컴포넌트를 사용하여 게임 로직을 만들 수 있게 된다.
- 이러한 특성 때문에 해당 지정자는 UActorComponent를 상속받은 클래스가 사용한다.
- (ClassGroup=(Custom))
[언리얼] 서버가 보낸 패킷을 처리하고 반영하는 과정은 어떻게 이루어져야 할까?
- 클라이언트로 언리얼 엔진을 사용한다면 서버가 보낸 패킷을 반영하는 건 어디서 이루어져야 할까? 예를 들어, 캐릭터 인벤토리 정보를 수신받았다면 이를 UI에 반영하거나 인벤토리 아이템 정보를 관리하는 것은 어디에서 해야 할까?
- 아이템 정보 자체를 관리한다면 전용 클래스를 따로 만들어서 관리하는 것이 좋을 것이다. 그래야 나중에 이것저것 기능이 많이 만들어졌을 때 Player 계열 클래스가 복잡해지지 않기 때문.
- 전용 클래스를 만들 경우, 컴포넌트 시스템을 활용해도 좋겠지만 필수로 사용해야 하는 부분은 아니라고 본다. 왜냐하면 MMORPG의 경우, 인벤토리 시스템은 다른 곳에서 활용될 일은 거의 없기 때문에 굳이 재사용성을 높이기 위한 컴포넌트를 만들 필요는 잘 모르겠다. 오히려 재사용에 대한 여지를 남기면서 더 혼란스럽기만 하지 않을까?
UI 반영은 어디서? - PlayerController에서!
- 데이터를 저장했다고 끝이 아니다. 데이터를 저장하는 것은 어디까지나 캐싱의 역할일 뿐, 실제로 저장된 데이터를 UI에 반영하는 것은 별개의 문제이다.
- 그렇다면 UI에 변경된 데이터를 반영하는 것은 어디서 맡아야 할까? 바로 PC에서 한다. PC는 뷰포트에 보이는 UI들을 관리하기도 한다. 따라서 UI에 관련된 작업들은 PC에서 하는 것이 옳다.
데이터 갱신에 대해서 UI에 반영만 하면 될까?
- 여러 경우에 있어 데이터 갱신되는 이벤트가 발생했을 때 실행해야 하는 작업들이 UI 반영만 있지 않을 수 있다. 여러 작업들이 이벤트가 발생했을 때 진행되어야 할 수 있고, 이런 경우 델리게이트를 활용해 브로드캐스트 방식을 사용하면 보다 유연하게 처리할 수 있다.
- 주의할 점은 멀티캐스트 델리게이트를 활용하는 경우, 등록된 함수들은 실행 순서가 보장되지 않는다는 것이다. 따라서, 순서를 보장받고 싶다면 순서대로 실행해야 할 작업은 델리게이트에 등록하지 않고 따로 실행시켜야 한다.(아니면 하나만 등록해 두고 호출될 경우 연쇄적으로 실행되도록 해야 한다.)
[언리얼] 델리게이트 이것저것
UCLASS가 붙은 클래스에서 일반 델리게이트는 사용하면 안 된다?
- UCLASS가 붙었다고 일반 델리게이트를 사용하는 것이 부적절하지는 않다. 다만 일반 델리게이트는 GC나 리플렉션을 고려하지 않기 때문에 UObject의 수명이 끝났을 때 자동으로 연결이 해제되는 걸 기대할 수 없다.
- 따라서 일반 델리게이트를 사용할 경우 수동으로 Unbind를 해줘야 한다. 그렇지 않으면 댕글링 포인터 문제가 발생한다.
다이내믹 델리게이트의 인자 타입 제한
- 다이나믹 델리게이트는 UFUCTION 시스템을 통해 리플렉션을 지원하기 때문에, 매개변수 타입은 반드시 리플렉션이 가능한 타입 이어야 한다. 따라서, 리플렉션이 알고 있는 타입인 int32, float, bool, FString 등은 가능하지만, 리플렉션 시스템이 모르는 타입인 std::string, TArray <int> 등은 사용할 수 없다.
- 이러한 문제 때문에 프로토버프 객체들을 다이내믹 델리게이트 방식으로 넘겨줄 수는 없다. 이 경우 다이내믹이 아닌 일반 델리게이트를 사용해야 한다.
[언리얼] 그 외 기타 등등…
TSubClassOf
- TSubclassOf는 특정 클래스 타입 또는 그 하위 클래스만 가리킬 수 있도록 제한하는 템플릿 레퍼이다. 따라서, 위젯을 C++ 클래스로 정의하고, 정의한 클래스를 BP에서 상속받아 블루프린트 위젯을 제작하는 상황이라면, 해당 위젯을 임의의 클래스에서 관리하고 싶다면 아래와 같이 사용하는 것이 적합하다.
UPROPERTY(EditDefaultsOnly, Category = "UI")
TSubclassOf<UStatusWindowWidget> StatusWindowWidgetClass;
// 잘못된 방식: UUSerWidget 타입으로 설정하면 제대로 확인하지 못한다.
//UPROPERTY(EditDefaultsOnly, Category = "UI")
//TSubclassOf<UUserWidget> StatusWindowWidgetClass;
생성한 위젯은 AddToViewport를 해야 visibility도 할 수 있다.
- 위젯을 생성하긴 하되, 눈에 보이지는 않도록 하고 싶다고 AddToViewport를 하지 않으면 안 된다. 반드시 AddToViewport는 해야 하고, 그다음에 setvisibility를 호출해 Collapsed와 같은 걸로 시야에서 안 보이도록 설정해야 한다.
Widget->AddToViewport();
Widget->SetVisibility(ESlateVisibility::Hidden)
