나만의 작은 도서관

[TIL][C++] 251023 MMO 서버 개발 121일차: [언리얼] C++에서 현재 Actor에 부착된 Component를 검색하는 여러가지 방법, BP 노드에서 “No value will be returned by reference.” Warning 발생 원인 본문

Today I Learn

[TIL][C++] 251023 MMO 서버 개발 121일차: [언리얼] C++에서 현재 Actor에 부착된 Component를 검색하는 여러가지 방법, BP 노드에서 “No value will be returned by reference.” Warning 발생 원인

pledge24 2025. 10. 23. 22:30
주의사항: 해당 글은 일기와 같은 기록용으로, 다듬지 않은 날것 그대로인 글입니다. 

[언리얼] C++에서 현재 Actor에 부착된 Component 검색하기

  • Actor는 여러 개의 Component를 가질 수 있다. 또한 여러 종류의 Component를 가질 수도 있다. 만약 내가 C++에서 현재 Actor가 보유하고 있는 Component들 중에서 특정 조건에 맞는 Component들을 검색해서 가져오고 싶다면 어떻게 해야 할까?

특정 타입의 모든 컴포넌트 반환 -GetComponents<T>()*

GetComponents<T*>(OutArray);
  • AActor에는 GetComponents <>() 함수가 정의되어 있다. 이 함수는 템플릿 인자로 넣어준 Component 타입에 해당하는 컴포넌트들을 검색해서 인자로 넣어준 변수에 저장한다. 검색된 Component가 여러 개일 수 있으므로 인자로는 TArray를 넣어준다.

 

MyActor에 존재하는 모든 Static Mesh Component들을 가져오기

TArray<UStaticMeshComponent*> StaticMeshComponents;
MyActor->GetComponents<UStaticMeshComponent>(StaticMeshComponents);
// => StaticMeshComponents에 저장됨
  • 기본적으로 GetComponents는 템플릿 인자로 넣어준 컴포넌트 타입의 파생 컴포넌트들도 가져온다. 따라서, 최상위 컴포넌트 타입인 UActorComponent를 템플릿 인자로 설정하면, 모든 컴포넌트들을 가져오게 된다.
TArray<UActorComponent*> AllComponents;
MyActor->GetComponents<UActorComponent>(AllComponents);
// => 보유 중인 모든 컴포넌트를 AllComponents에 저장

 


이름으로 검색하기 GetName(), 태그로 검색하기 ComponentTags.Contain()

 

이름으로 컴포넌트를 검색할 때 사용하는 함수 GetName()

  • 특정 타입의 컴포넌트들을 찾아서 가져왔다면, 이제 그중 하나를 선택해서 작업을 해야 한다.(보통은 그러기 위해 가져오니까) 이때 사용할 수 있는 것이 바로 컴포넌트의 이름이다.
  • 컴포넌트의 이름은 대게 생성자 또는 BeginPlay()에서 컴포넌트를 만들 때 인자로 넣어주는 방식으로 결정된다. 아래와 같이 말이다.
// **MyComponentName라는 이름을 가진 StaticMeshComponent 만들기**

// 1. 생성자에서 생성(Constructor)
AMyActor::AMyActor()
{
	MyStaticMesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("MyComponentName"));
	// 또는
	FName CompName = TEXT("MyComponentName");
	MyStaticMesh = CreateDefaultSubobject<UStaticMeshComponent>(CompName);
	
	SetRootComponent(MyStaticMesh);
}

// 2. BeginPlay에서 생성
AMyActor::BeginPlay()
{
	Super::BeginPlay();
	
	UClass* ComponentClass = UStaticMeshComponent::StaticClass();
	
	// BeginPlay 방식은 동적으로 Component를 생성하는 방식이므로,
	// 만약 MyActor를 부모 클래스로 두는 BP를 만들면,
	// BP에서는 해당 컴포넌트가 보이지 않고, Spawn된 이후 보인다.
	MyStaticMesh = NewObject<UStaticMeshComponent>(this, ComponentClass, TEXT("MyComponentName"));
	// 또는
	FName CompName = TEXT("MyComponentName");
	MyStaticMesh = NewObject<UStaticMeshComponent>(this, ComponentClass, CompName);
	
	SetRootComponent(MyStaticMesh);
}
  • 이렇게 설정한 이름은 에디터에서 소괄호로 감싸진 형태로 컴포넌트 이름 옆에 표시되며, C++에서 GetName()을 통해 반환값으로 반환된다. 이를 이용하여 이름으로 컴포넌트를 검색할 수 있다.
// **이름으로 컴포넌트 검색하기 예제**
void AMyCharacter::BeginPlay()
{
    Super::BeginPlay();

    TArray<USkeletalMeshComponent*> BPSkeletalMeshes;
    
    // 이 액터에 부착된 모든 USkeletalMeshComponent를 배열에 담습니다.
    GetComponents<USkeletalMeshComponent>(BPSkeletalMeshes);

    if (BPSkeletalMeshes.Num() > 0)
    {
        // 배열을 순회하며 원하는 컴포넌트를 식별합니다.
        for (USkeletalMeshComponent* Mesh : BPSkeletalMeshes)
        {
            // 예시: 이름으로 식별 (BP에서 이름이 "Weapon_Mesh"라고 가정)
            if (Mesh->GetName().Equals(TEXT("Weapon_Mesh")))
            {
                UE_LOG(LogTemp, Warning, TEXT("무기 메쉬를 찾았습니다."));
                // 해당 컴포넌트에 대한 참조를 멤버 변수에 저장하거나 로직을 수행합니다.
                // MyWeaponMesh = Mesh;
            }
        }
    }
}

 

 

태그로 검색할 때 사용하는 함수 ComponentTags.Contain

  • 컴포넌트는 여러 개의 태그를 가질 수 있다. 태그는 사용자가 직접 추가해야 하며, 처음에는 아무런 태그도 없다.
  • C++, BP에서 태그를 추가하는 방법은 아래와 같다.

MyComponent->ComponentTags.Add(FName("MyTag"));
  • 이렇게 추가된 태그를 이용해서 컴포넌트를 검색하는 방식은 여러 개 있는데, 그중 GetComponents 함수와 연계해서 사용하는 경우엔 ComponentTags의 Contain함수를 사용한다. 사용방법은 아래와 같다.
#include "Components/ActorComponent.h" // UActorComponent 또는 원하는 컴포넌트 클래스 포함

void AMyCharacter::FindComponentByTag(FName TagToFind)
{
    // 1. 특정 클래스 유형의 모든 컴포넌트를 가져올 배열 선언
    // UActorComponent를 사용하면 모든 컴포넌트를 검색할 수 있습니다. 
    // 특정 유형(예: USkeletalMeshComponent)으로 제한할 수도 있습니다.
    TArray<UActorComponent*> Components;
    
    // 2. 액터에서 지정된 클래스(UActorComponent)의 모든 컴포넌트를 채웁니다.
    GetComponents<UActorComponent>(Components); 

    // 3. 배열을 순회하며 태그를 확인합니다.
    for (UActorComponent* Component : Components)
    {
        if (Component)
        {
            // 4. 컴포넌트의 ComponentTags 배열에 원하는 태그가 포함되어 있는지 확인합니다.
            if (Component->ComponentTags.Contains(TagToFind))
            {
                // 원하는 태그를 가진 컴포넌트를 찾았습니다!
                UE_LOG(LogTemp, Warning, 
                    TEXT("태그 '%s'를 가진 컴포넌트 '%s'를 찾았습니다."), 
                    *TagToFind.ToString(), 
                    *Component->GetName());
                
                // 만약 특정 유형(예: UStaticMeshComponent)이 필요하다면 여기서 캐스팅합니다.
                // UStaticMeshComponent* Mesh = Cast<UStaticMeshComponent>(Component);
                
                // 원하는 컴포넌트를 찾았으므로 함수를 종료하거나 추가 로직을 수행합니다.
                return; 
            }
        }
    }
}
  • 애초에 특정 태그를 가진 컴포넌트만 검색하려고 했다면, GetComponents 함수 대신, GetComponentsByTag를 사용하면 보다 코드가 간결해진다.

 

 

GetComponentsByTag() 함수 사용 예제

TArray<UActorComponent*> Components = GetComponentsByTag(UActorComponent::StaticClass(), FName("MyTag"));
for (UActorComponent* Comp : Components)
{
    // 원하는 타입으로 캐스팅
    if (UMyComponent* MyComp = Cast<UMyComponent>(Comp))
    {
        // 사용
    }
}

특정 타입의 컴포넌트 딱 하나만 반환 - GetComponentByClass<T>

  • Actor에 1) 특정 타입의 컴포넌트가 유일하게 존재함을 보장할 수 있거나, 2) 컴포넌트 순서가 엄격하게 지켜져서 컴포넌트 배치 순서가 보장된다면 GetComponents 함수 대신, GetComponentByClass 함수를 사용할 수 있다.
  • GetComponentByClass <>() 함수는 액터에 부착된 해당 타입의 컴포넌트들 중 첫 번째 컴포넌트를 반환하는 함수이다. 반환값이 T*이기 때문에 코드 한 줄로 컴포넌트의 포인터를 얻을 수 있다는 장점이 있다.
UStaticMeshComponent* Mesh = MyActor->GetComponentByClass<UStaticMeshComponent>();
if(Mesh != nullptr)
{
// 메시가 존재함
}
else
{
// 메시가 존재하지 않음
}
  • FindComponentByClass <>() 함수를 사용해도 기능적으로 동일한 결과를 얻을 수 있다. (GetComponentByClass의 이명(Alias)이기 때문)
// 이 두 코드는 같은 코드
UStaticMeshComponent* Mesh = MyActor->GetComponentByClass<UStaticMeshComponent>();
UStaticMeshComponent* Mesh = MyActor->FindComponentByClass<UStaticMeshComponent>();

 

 


[언리얼] “No value will be returned by reference.” Warning 발생 원인

UFUNCTION(BlueprintImplementableEvent, Category = "AttackSystem") 
void M_PerformNormalAttack(UPARAM(ref) int32& Combo);
  • 위와 같이 코드를 작성하고 블루프린트에서 레퍼런스로 넘겨준 combo를 수정하려고 했다. 하지만 위 제목과 같은 경고가 블루프린트에서 발생했다.

 

원인은?

  • ImplementableEvent 타입의 경우 반환 경로가 단방향(즉, C++ → BP)이라서, BP의 로직이 C++ 데이터에 영향을 못준다는 것 같다.
  • 만약 ref 파라미터를 통한 값 수정을 하고 싶다면, BlueprintNativeEvent를 사용하면 된다고 한다.
UFUNCTION(BlueprintNativeEvent, Category = "AttackSystem")
void M_PerformNormalAttack(UPARAM(ref) int32& Combo);
virtual void M_PerformNormalAttack_Implementation(int32& Combo);
  • 아니면 반환값을 받아 쓰는 방식인데, 이러면 BP에서 이벤트 노드가 아닌 함수 노드로 오버라이드 되어서 그리 맘에 드는 방식은 아니다.