UnrealEngine

UFUNCTION 정리 - 언리얼 오브젝트 매크로 2

Muji-in 2025. 1. 3. 23:44

저번 포스트 UPROPERTY가 변수에 마킹을 하는 것이라면 UFUNCTION은 함수에 마킹을 하는 매크로이다. UFUNCTION은 다음과 같은 기능들을 구현할 수 있다. 네트워크와 콘솔 명령어 부분은 제외하고 에디터에서 적용할 수 있는 부분에 대해서 정리하기로 했다.

  • 블루프린트에서 사용 가능하게 한다.
  • 네트워크 콜백에 대응하도록 한다.
  • 에디터에서 사용 가능하게 한다.(디테일창 명령어, 콘솔 명령어)

블루프린트에서의 사용

플래그 설명
블루프린트에서 네이티브로 통신
BlueprintCallable 블루 프린트에서 호출 가능
BlueprintPure 블루 프린트에서 상태를 변경하지 않는 함수, 실행핀이 없는 노드
네이티브에서 블루프린트로 통신
BlueprintImplementableEvent C++에서 구현하지 않고 블루 프린트에서 이벤트로 구현할 수 있는 함수. 블루 프린트에서 구현되고 사용된다.
BluepritnNativeEvent C++, 블루 프린트에서 구현 가능한 함수

블루 프린트에서는 특정한 객체의 세부적인 내용을 결정할 수 있는데 이러한 함수를 재정의하거나 사용함으로써 블루 프린트로 기능을 변경할 수 있게 한다. 이 때 기반이 되는 C++ 클래스를 네이티브라고 부른다.

BluepirntCallable

C++에서 정의한 함수를 블루 프린트에서 활용할 수 있게 하는 함수로 호출 중인 오브젝트나 다른 글로벌한 상태를 변경하는 네이티브 코드를 호출한다.

const 한정자가 있어서 내부에서 값을 변경하지 않고 출력값이 있을 경우 BlueprintPure와 같이 노드가 바뀐다. 단순히 값만을 얻는 함수들은 가급적 const를 이용해서 실행핀이 줄어들게 해야 한다.

BlueprintPure

Callable과 유사하지만 순수 함수로 구현된다. 호출 중인 오브젝트나 다른 글로벌한 상태를 변경하지 않는 네이티브 코드를 호출한다. 호출을 해서 변경되는 것은 없고 출력을 알려주기만 해서 실행 핀이 존재하지 않는다.

BlueprintImplementableEvent

블루프린트에서 정의되는 함수로써 네이티브 함수가 블루프린트와 통신하는 주요한 방법이다. 블루프린트가 구현하는 가상함수 같은 것이다. 출력값이 없으면 이벤트그래프 창에서 이벤트 형식으로 구현할 수 있다. 출력값이 있을 경우는 함수에서 오버라이드를 할 수 있다. C++에서 블루프린트 함수를 이용하는 것이기 때문에 함수에서의 입력값은 블루프린트 노드에서의 출력값이 된다.

BlueprintNativeEvent

블루 프린트와 C++ 양쪽에서 정의해서 사용할 수 있는 함수이다. 블루 프린트가 상속되는 구조인지라 블루 프린트 이벤트를 정의해서 사용하게 되면 오버라이드 되어서 블루 프린트 이벤트가 실행이 된다. 만약에 구현이 되어 있지 않으면 C++ 함수를 호출하게 된다. 또한, 오버라이드하는 방식이기 때문에 C++의 구현을 호출하고 싶으면 부모 클래스의 함수를 호출해서 사용할 수 있다.

이러한 작동 방식은 오버라이딩과 완전히 동일하다. 기본적으로 원하는 동작을 정의하고 하위 클래스에서 변형이 필요하면 오버라이딩해서 고유의 동작을 추가한다. 사이드이펙트는 동일하게 비용이 더 소모된다.

블루프린트 함수 인자 설계

BlueprintCallable에서 인자를 설계할 때는 몇 가지 원칙이 적용된다.

  • 값 타입은 입력 핀이 된다.
  • 반환값은 출력 핀이 된다.
  • 참조 타입은 출력 핀이 된다.
  • 상수 참조 타입은 입력 핀이 된다.
  • UPARAM(ref) 참조 타입은 입력 핀이 된다.

C++에서 반환 타입이 여러 개일 경우 tuple을 반환할 수도 있지만 고전적인 해법은 참조 타입을 이용하는 것이다. 함수 메소드 호출 시에 참조 타입의 값은 변경이 될 수가 있고 따라서 함수 메소드의 호출 시에는 값이 변경된다고 간주할 수 있다. 따라서 해당 변수는 출력 핀으로 정의가 되는 것이다.

반대로 참조가 변경이 되지 않는다고 하면 입력 핀으로 작용이 된다는 의미이다. 상수 참조 인자의 경우는 내부에서 변경할 수 없으므로 입력 핀으로 정의가 된다.

참조를 통해서 입력을 받는다고 해도 해당 값을 변경하는 설계가 있을 수도 있다. 이러한 경우는 UPARAM(ref)를 이용하면 출력핀이 아니라 입력핀으로 변경할 수 있게 된다.

에디터에서의 사용

CallInEditor를 이용하면 에디터에서 호출할 수 있게 한다. default 그룹에 버튼을 생성하게 되고 해당 버튼을 누르게 되면 해당 함수를 호출한다. 실질적으로 사용해 본 결과 다음과 같은 결과를 얻을 수 있었다.

  • 에디터에서 호출이 가능하고 플레이 모드에서도 호출이 가능하다. 하지만, 블루프린트 에디터에서는 호출할 수 없다.
  • 플레이 모드에서는 디버그 메시지와 로그를 모두 이용할 수 있었으나 에디터 모드에서는 디버그 메시지를 사용할 수 없었고 로그만 사용할 수 있었다.
  • 유니티와 마찮가지로 객체를 수정하는 것은 추가적인 메커니즘이 필요했다. 첫번째로 DO, Undo를 적용하기 위해서 값을 변경하기 전에 블록을 만드는 과정이 필요했다. 두번째로는 더티로 변경하는 것이 필요하다. 씬에서 저장을 하게 되면 직렬화되어서 유지가 되지만 유니티 기준이었다면 에디터 에셋의 경우는 더티를 설정 안 하면 제대로 동작 안 할 우려가 있었다. 언리얼에서도 마찮가지로 더티를 이용해야 에디터에서 저장 기능이 활성화 된다.
void AMacroTest::CallInEditorFunction()
{
    // 에디터, 플레이 모드에서 출력
    UE_LOG(LogTemp, Warning, TEXT("This function was called in the editor!"));

    // 에디터에서는 출력 X, 플레이 모드에서는 출력
    GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Orange, TEXT("Editor Function"));

    FMessageLog("PIE").Info(FText::FromString(TEXT("This is an editor debug message.")));

    // Do, Undo를 지원하기 위한 함수 호출
    Modify();
    // UPROPERTY 변수를 수정
    CallInEditorVal = 100;
    // Dirty로 변경해 저장되어야 함을 표시
    MarkPackageDirty();
}

참고

  1. UFunctions
  2. 블루프린트에 게임플레이 요소 노출하기
  3. 블루프린트에 c++ 노출하기
  4. Why is C++ function's inputs become outputs in the blueprint?
  5. ben ui - ufunction