icon안동민 개발노트

C++에서 블루프린트 함수 호출


 C++와 블루프린트의 통합은 언리얼 엔진의 강력한 기능 중 하나입니다.

 이 절에서는 C++ 코드에서 블루프린트로 정의된 함수를 호출하는 다양한 방법을 살펴보겠습니다.

UFUNCTION 매크로를 사용한 블루프린트 호출 가능 함수 선언

 블루프린트에서 구현될 함수를 C++에서 선언할 때 UFUNCTION 매크로를 사용합니다.

UCLASS()
class MYGAME_API AMyActor : public AActor
{
    GENERATED_BODY()
 
public:
    UFUNCTION(BlueprintImplementableEvent, Category = "MyFunctions")
    void MyBlueprintFunction(int32 IntParam, FString StringParam);
 
    UFUNCTION(BlueprintNativeEvent, Category = "MyFunctions")
    float CalculateValue(float BaseValue);
};
 
float AMyActor::CalculateValue_Implementation(float BaseValue)
{
    // C++에서의 기본 구현
    return BaseValue * 2.0f;
}

 이렇게 선언된 함수는 C++에서 일반 멤버 함수처럼 호출할 수 있습니다.

void AMyActor::SomeFunction()
{
    MyBlueprintFunction(42, "Hello from C++");
    float Result = CalculateValue(10.0f);
}

UObject::CallFunctionByNameWithArguments 사용법

 함수 이름을 문자열로 지정하여 동적으로 블루프린트 함수를 호출할 수 있습니다.

void AMyActor::CallDynamicFunction(UObject* TargetObject, const FString& FunctionName)
{
    if (TargetObject)
    {
        FString Command = FString::Printf(TEXT("%s %d %s"), *FunctionName, 42, TEXT("\"Dynamic Hello\""));
        FOutputDeviceNull OutputDevice;
        TargetObject->CallFunctionByNameWithArguments(*Command, OutputDevice, nullptr, true);
    }
}

UFunction을 직접 사용하여 블루프린트 함수 호출

 UFunction을 직접 사용하면 더 많은 제어가 가능하지만, 코드가 복잡해질 수 있습니다.

void AMyActor::CallFunctionUsingUFunction(UObject* TargetObject, const FName& FunctionName)
{
    if (TargetObject)
    {
        UFunction* Function = TargetObject->FindFunction(FunctionName);
        if (Function)
        {
            struct FMyParams
            {
                int32 IntParam;
                FString StringParam;
            };
            FMyParams Params;
            Params.IntParam = 42;
            Params.StringParam = "Hello via UFunction";
 
            TargetObject->ProcessEvent(Function, &Params);
        }
    }
}

블루프린트 함수 호출 시 인자 전달 방법

 인자 전달 시 타입 일치에 주의해야 합니다.

UFUNCTION(BlueprintCallable, Category = "MyFunctions")
void PassComplexArguments(FVector Location, TArray<FString> StringArray, UObject* SomeObject)
{
    // 구현...
}
 
void AMyActor::SomewhereElse()
{
    FVector Loc(100.0f, 200.0f, 300.0f);
    TArray<FString> Strings;
    Strings.Add("First");
    Strings.Add("Second");
    UObject* Obj = GetWorld();
 
    PassComplexArguments(Loc, Strings, Obj);
}

반환 값 처리 방법

 반환 값은 함수의 마지막 매개변수로 처리됩니다.

UFUNCTION(BlueprintCallable, Category = "MyFunctions")
void GetLocationAndRotation(FVector& OutLocation, FRotator& OutRotation)
{
    OutLocation = GetActorLocation();
    OutRotation = GetActorRotation();
}
 
void AMyActor::UseReturnValues()
{
    FVector Location;
    FRotator Rotation;
    GetLocationAndRotation(Location, Rotation);
    // Location과 Rotation 사용...
}

오류 처리 및 디버깅 기법

 함수 호출 실패를 처리하고 디버그 정보를 출력합니다.

void AMyActor::SafeCallBlueprintFunction(UObject* TargetObject, const FName& FunctionName)
{
    if (!TargetObject)
    {
        UE_LOG(LogTemp, Error, TEXT("TargetObject is null"));
        return;
    }
 
    UFunction* Function = TargetObject->FindFunction(FunctionName);
    if (!Function)
    {
        UE_LOG(LogTemp, Warning, TEXT("Function %s not found"), *FunctionName.ToString());
        return;
    }
 
    TargetObject->ProcessEvent(Function, nullptr);
    UE_LOG(LogTemp, Log, TEXT("Function %s called successfully"), *FunctionName.ToString());
}

C++와 블루프린트 간의 데이터 변환 이슈

 복잡한 데이터 구조를 전달할 때는 주의가 필요합니다.

USTRUCT(BlueprintType)
struct FMyComplexStruct
{
    GENERATED_BODY()
 
    UPROPERTY(EditAnywhere, BlueprintReadWrite)
    int32 IntValue;
 
    UPROPERTY(EditAnywhere, BlueprintReadWrite)
    FString StringValue;
};
 
UFUNCTION(BlueprintCallable, Category = "MyFunctions")
void HandleComplexData(const FMyComplexStruct& Data)
{
    // 구현...
}

성능 고려사항

 블루프린트 함수 호출은 C++ 함수 호출보다 약간의 오버헤드가 있습니다. 성능이 중요한 경우 다음을 고려하세요.

  1. 자주 호출되는 함수는 C++로 구현
  2. 대량의 데이터를 처리하는 로직은 C++로 구현
  3. 블루프린트 함수 호출을 최소화하고 배치 처리 고려
UFUNCTION(BlueprintCallable, Category = "Optimization")
void ProcessActorsInBatch(const TArray<AActor*>& Actors)
{
    for (AActor* Actor : Actors)
    {
        // 배치 처리 로직...
    }
}

대규모 프로젝트에서 C++와 블루프린트 함수 호출의 효과적인 관리 전략

  1. 인터페이스 사용
UINTERFACE(MinimalAPI, Blueprintable)
class UMyInterface : public UInterface
{
    GENERATED_BODY()
};
 
class IMyInterface
{
    GENERATED_BODY()
 
public:
    UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category = "MyInterface")
    void InterfaceFunction();
};
 
UCLASS()
class MYGAME_API AMyActor : public AActor, public IMyInterface
{
    GENERATED_BODY()
 
public:
    virtual void InterfaceFunction_Implementation() override;
};
  1. 함수 라이브러리 사용
UCLASS()
class MYGAME_API UMyBlueprintFunctionLibrary : public UBlueprintFunctionLibrary
{
    GENERATED_BODY()
 
public:
    UFUNCTION(BlueprintCallable, Category = "MyFunctions")
    static void SharedFunction(AActor* TargetActor);
};
  1. 데이터 주도 설계
UCLASS(BlueprintType)
class MYGAME_API UMyDataAsset : public UDataAsset
{
    GENERATED_BODY()
 
public:
    UPROPERTY(EditAnywhere, BlueprintReadOnly)
    TArray<TSubclassOf<AActor>> ActorClasses;
 
    UPROPERTY(EditAnywhere, BlueprintReadOnly)
    TMap<FName, float> Parameters;
};
 
UFUNCTION(BlueprintCallable, Category = "DataDriven")
void ProcessDataAsset(UMyDataAsset* DataAsset)
{
    // 데이터 에셋 처리 로직
}

 이러한 접근 방식들을 조합하여 사용하면, C++와 블루프린트 간의 효과적인 통합이 가능해집니다. C++의 성능과 유연성, 그리고 블루프린트의 빠른 이터레이션과 시각적 스크립팅의 장점을 모두 활용할 수 있습니다.

 블루프린트 함수를 C++에서 호출할 때는 항상 타입 안전성과 성능을 고려해야 합니다. 가능한 한 강력한 타입 검사를 사용하고, 동적 호출은 필요한 경우에만 사용하세요. 또한, 대규모 프로젝트에서는 모듈화와 인터페이스 사용을 통해 C++와 블루프린트 코드의 결합도를 낮추는 것이 중요합니다.

 마지막으로, 디버깅과 프로파일링을 통해 C++와 블루프린트 간의 상호작용을 지속적으로 모니터링하고 최적화하는 것이 좋습니다. 이를 통해 게임의 전반적인 성능과 안정성을 향상시킬 수 있습니다.