본문으로 건너뛰기

안동민 개발노트

본문 시작

C++로 Blueprint 클래스 확장

핵심 로직은 C++에 두고 표현과 조정은 Blueprint로 확장하는 부모·자식 클래스 협업 방식을 실습합니다.

UObjectAActor 차이를 이해했다면, 이제 언리얼 하이브리드 개발의 핵심으로 넘어갑니다.

이번 절은 C++ 클래스를 블루프린트로 확장하는 방법을 다룹니다.

언리얼은 C++로 기반 구조를 만들고, 블루프린트로 빠르게 기능을 확장하는 방식을 강력하게 지원하며, 이 조합이 실제 개발 효율을 크게 높여 줍니다.

핵심 흐름: C++에서 성능/구조를 고정하고, Blueprint에서 변수·연출을 빠르게 조정해 반복 속도를 높입니다.

C++ 기반은 빌드로 고정하고 Blueprint 자식은 값과 연출을 반복한다

하이브리드 개발은 단순 분업이 아니라 C++ 선언이 도구 표면을 만들고 Blueprint 조정 결과가 다시 플레이 검증으로 돌아오는 흐름이다.

  1. 1
    C++ Base

    Actor 구조, 컴포넌트, 성능 민감 로직과 팀 규칙을 구현한다.

  2. 2
    Expose

    UPROPERTY와 UFUNCTION으로 조정 가능한 값과 동작만 공개한다.

  3. 3
    Build

    UHT와 컴파일을 통과해 에디터가 읽을 타입 정보를 갱신한다.

  4. 4
    BP Child

    메시, 수치, 연출을 조정하고 레벨별 변형을 만든다.

  5. 5
    Play

    Details 값과 그래프 호출이 C++ 계약 안에서 동작하는지 검증한다.

  6. 6
    C++ 책임

    안정 동작, 성능, 엔진 깊은 제어와 공통 규칙.

  7. 7
    Blueprint 책임

    자주 바뀌는 값, 레벨별 변형, 빠른 연출 반복.

  8. 8
    경계 검수

    Details 노출, 노드 검색, Play 결과를 한 번에 본다.


C++을 블루프린트로 확장해야 하는 이유

이 질문에 대한 답은 명확합니다.

C++과 블루프린트는 각각의 장단점이 있기 때문입니다.

  • C++의 장점
  • 성능: 복잡한 연산, 물리 시뮬레이션, AI 로직 등 성능이 중요한 부분에 유리합니다.
  • 접근성: 엔진의 깊숙한 부분까지 접근하여 커스터마이징할 수 있습니다.
  • 제어: 메모리 관리 등 더 세밀한 제어가 가능합니다.
  • 표준화: 대규모 프로젝트에서 코드의 구조와 규칙을 강력하게 강제할 수 있습니다.
  • 블루프린트의 장점
  • 빠른 프로토타이핑: 시각적인 스크립팅 방식으로 아이디어를 빠르게 구현하고 테스트할 수 있습니다.
  • 비개발자 협업: 프로그래밍 지식이 없는 아티스트나 기획자도 게임 로직에 참여할 수 있습니다.
  • 쉬운 반복 작업: 간단한 로직 변경이나 데이터 수정에 즉각적으로 대응할 수 있습니다.

이상적인 워크플로는 책임을 분리하는 방식입니다.

성능 민감 구간, 엔진 핵심 경로, 복잡 로직 기반은 C++로 구현하고, 자주 변경되는 게임플레이 디테일이나 디자이너 조정 영역은 블루프린트로 처리합니다.

C++ 클래스를 블루프린트에서 상속해 사용하면 두 방식의 장점을 동시에 취할 수 있습니다.


C++ 클래스 생성 및 기본 구조

첫 번째 C++ 클래스를 생성하며 실습을 시작합니다.

이번에는 간단한 메시를 표시하고 특정 텍스트를 출력하는 액터를 구현합니다.

언리얼 에디터에서 C++ 클래스 생성: 언리얼 에디터 상단 메뉴에서 Tools (도구) > New C++ Class... (새 C++ 클래스...)를 클릭합니다.

부모 클래스 선택: 부모 클래스 선택(Choose Parent Class) 창이 나타나면, 우리는 3D 월드에 배치될 수 있는 객체를 만들 것이므로 Actor를 선택하고 다음(Next)을 클릭합니다.

이름 지정 및 생성: 클래스 이름은 AMyAwesomeActor로 입력하고, 저장 경로를 확인한 후 클래스 생성(Create Class)을 클릭합니다.

언리얼 엔진이 필요한 파일을 생성하고 비주얼 스튜디오가 자동으로 열리면서 방금 생성한 AMyAwesomeActor.hAMyAwesomeActor.cpp 파일이 나타날 것입니다.

이제 생성된 파일의 기본 구조를 살펴보겠습니다.

MyAwesomeActor.h
#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "Components/StaticMeshComponent.h" // 추가할 컴포넌트를 위해 미리 포함
#include "AMyAwesomeActor.generated.h" // 항상 마지막

UCLASS()
class MYFIRSTCPPPROJECT_API AMyAwesomeActor : public AActor
{
    GENERATED_BODY()

public:
    // Sets default values for this actor's properties
    AMyAwesomeActor();

protected:
    // Called when the game starts or when spawned
    virtual void BeginPlay() override;

public:
    // Called every frame
    virtual void Tick(float DeltaTime) override;

    // StaticMeshComponent를 위한 포인터 선언
    UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Components")
    UStaticMeshComponent* MyStaticMesh;

    // 블루프린트에서 변경 가능하도록 노출할 속성
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Custom Properties")
    FString WelcomeMessage;

    // 블루프린트에서 호출할 수 있는 함수
    UFUNCTION(BlueprintCallable, Category = "Custom Functions")
    void PrintWelcomeMessage();
};
MyAwesomeActor.cpp
// AMyAwesomeActor.cpp
#include "AMyAwesomeActor.h"
#include "Components/StaticMeshComponent.h" // StaticMeshComponent를 위해 다시 포함 (보통 헤더에서 미리 함)
#include "Engine/Engine.h" // GEngine->AddOnScreenDebugMessage를 위해 포함

// Sets default values
AMyAwesomeActor::AMyAwesomeActor()
{
    // Set this actor to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
    PrimaryActorTick.bCanEverTick = true;

    // UStaticMeshComponent 생성 및 Root Component로 설정
    MyStaticMesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("MyMesh"));
    RootComponent = MyStaticMesh;

    // 변수 기본값 설정
    WelcomeMessage = TEXT("Hello from C++!");
}

// Called when the game starts or when spawned
void AMyAwesomeActor::BeginPlay()
{
    Super::BeginPlay();

    // BeginPlay 시 메시 출력
    PrintWelcomeMessage();
}

// Called every frame
void AMyAwesomeActor::Tick(float DeltaTime)
{
    Super::Tick(DeltaTime);

}

void AMyAwesomeActor::PrintWelcomeMessage()
{
    if (GEngine)
    {
        GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Green, WelcomeMessage);
    }
    UE_LOG(LogTemp, Warning, TEXT("%s"), *WelcomeMessage);
}

몇 가지 주요 변경사항과 설명을 덧붙이자면:

  • UStaticMeshComponent 추가: 액터에 시각적인 형태를 부여하기 위해 UStaticMeshComponent를 추가했습니다. 생성자에서 CreateDefaultSubobject를 사용하여 컴포넌트를 생성하고, RootComponent로 설정하여 액터의 위치와 회전을 결정하는 기준점으로 만들었습니다.
  • WelcomeMessage (UPROPERTY): 블루프린트에서 편집 가능하도록 UPROPERTY(EditAnywhere, BlueprintReadWrite)로 선언했습니다. 초기값은 생성자에서 Hello from C++!로 설정했습니다.
  • PrintWelcomeMessage (UFUNCTION): 블루프린트에서 호출 가능하도록 UFUNCTION(BlueprintCallable)로 선언했습니다. 이 함수는 화면에 디버그 메시지를 출력하고, 로그에도 기록합니다.
  • #include "Engine/Engine.h": GEngine 전역 객체에 접근하여 화면에 메시지를 출력하기 위해 이 헤더를 포함해야 합니다.

C++ 선언이 블루프린트에서 어떻게 보이는지는 매크로와 specifier 조합으로 결정됩니다.

C++ 선언이 Blueprint 표면이 되는 루프

Blueprint에서 보이는 변수와 함수는 C++ 매크로, UHT 처리, 컴파일, 에디터 확인이 한 세트로 맞아야 나타납니다.

  1. 선언

    헤더에서 변수와 함수를 매크로로 표시합니다.

  2. UHT 반영

    generated 파일과 리플렉션 정보가 갱신됩니다.

  3. Blueprint 확인

    Details, Components, Event Graph에서 노출 여부를 봅니다.

  4. 런타임 테스트

    변경한 값이 화면 메시지와 로그에 반영되는지 확인합니다.


C++ 클래스 컴파일

코드를 수정한 후에는 반드시 컴파일해야 합니다.

언리얼 에디터로 돌아가서 상단의 Compile (컴파일) 버튼을 클릭합니다.

또는 비주얼 스튜디오에서 Ctrl + Shift + B를 눌러 솔루션을 빌드할 수도 있습니다.

성공적으로 컴파일되면, 여러분이 작성한 C++ 코드가 엔진에 반영됩니다.


C++ 클래스 기반으로 블루프린트 클래스 생성

이제 C++ 클래스를 컴파일했으니, 이를 기반으로 블루프린트 클래스를 생성합니다.

콘텐츠 브라우저에서 생성: 언리얼 에디터의 콘텐츠 브라우저에서 원하는 폴더(예: Content/Blueprints)로 이동합니다.

빈 공간에 마우스 오른쪽 버튼을 클릭하여 컨텍스트 메뉴를 엽니다.

블루프린트 클래스 선택: Blueprint Class (블루프린트 클래스)를 선택합니다.

부모 클래스 선택 (중요!): 모든 클래스(All Classes) 섹션에서 방금 만든 C++ 클래스인 AMyAwesomeActor를 검색하여 선택하고 선택(Select)을 클릭합니다.

이름 지정: 새로운 블루프린트 클래스의 이름을 BP_MyAwesomeActor로 지정합니다. (블루프린트 클래스는 BP_ 접두사를 붙이는 것이 일반적인 컨벤션입니다.)


블루프린트에서 C++ 속성 및 함수 확인/수정

Blueprint 노출은 C++ 선언, 지정자, UHT, 에디터 사용으로 이어진다

값이 보이지 않는 문제는 대부분 매크로 대상과 노출 범위가 맞지 않는 데서 시작한다.

단계하는 일확인할 오류
C++ 선언멤버가 프로퍼티인지 함수인지 표시한다UPROPERTY/UFUNCTION 누락
SpecifierEditAnywhere, BlueprintCallable 등 범위를 정한다private 접근과 meta 설정 불일치
UHT빌드 중 메타데이터를 생성해 엔진에 등록한다generated.h와 매크로 위치 오류
BlueprintDetails 패널이나 노드 그래프에서 사용한다노출 권한과 카테고리 문제

이제 BP_MyAwesomeActor 블루프린트 클래스를 더블 클릭하여 블루프린트 에디터를 엽니다.

핵심 포인트: C++ 선언, 컴파일(UHT), 블루프린트 반영 확인, 런타임 테스트를 한 루프로 묶어야 노출 누락을 빠르게 잡을 수 있습니다.

디테일 패널 확인: 블루프린트 에디터의 좌측 상단 컴포넌트(Components) 패널에서 MyStaticMesh 컴포넌트가 추가된 것을 확인할 수 있습니다.

이를 선택하면 디테일 패널에서 메시나 머티리얼을 할당할 수 있습니다.

그리고 우측 디테일(Details) 패널을 살펴보세요.

Custom Properties 카테고리 아래에 우리가 C++에서 선언한 WelcomeMessage 변수가 보이는 것을 확인할 수 있습니다!

이 변수의 기본값을 여기서 자유롭게 변경할 수 있습니다.

예를 들어 Hello from Blueprint!로 바꿔보세요.

그래프 편집기에서 함수 확인: 이벤트 그래프(Event Graph)에서 우클릭 후 컨텍스트 메뉴를 열고, Print Welcome Message를 검색합니다.

C++에서 UFUNCTION(BlueprintCallable)로 선언한 함수가 노드로 나타나면, 이를 Event BeginPlay 같은 이벤트에 연결해 블루프린트에서 직접 호출할 수 있습니다.


레벨에 배치 및 테스트

블루프린트 클래스까지 만들고 내용을 확인했으니, 이제 레벨에 배치하고 테스트해봅시다.

레벨에 블루프린트 배치: 콘텐츠 브라우저에서 BP_MyAwesomeActor 블루프린트 에셋을 드래그하여 뷰포트에 놓습니다.

Static Mesh 할당 (선택 사항): 뷰포트에서 BP_MyAwesomeActor 인스턴스를 선택한 후, 디테일 패널에서 MyStaticMesh 컴포넌트를 선택합니다.

Static Mesh 항목에 원하는 스태틱 메시(예: Shape_Cube 등 스타터 콘텐츠의 기본 메시)를 할당해 줍니다.

게임 플레이: 언리얼 에디터 상단의 Play (플레이) 버튼을 클릭하여 게임을 실행합니다.

화면 좌측 상단에 블루프린트에서 변경한 WelcomeMessage가 출력되면 정상입니다.

이는 C++로 기본 기능을 구현하고, 블루프린트에서 이를 확장해 데이터를 변경한 결과입니다.

실무에서는 여기서 끝내지 않고, C++ 선언부터 레벨 테스트까지 한 바퀴를 다시 돌며 노출 누락과 기본값 반영 여부를 점검합니다.

C++ 기반 Blueprint는 선언, 빌드, 상속, Details, Play로 반복 검수한다

빌드 성공만으로 끝내면 노출 누락과 기본값 불일치를 놓치기 쉽다.

반복 단계확인할 것의심 신호
C++ BaseActor, Component, UPROPERTY, UFUNCTION 기준선을 선언한다부모 클래스 검색 실패
Build + UHT컴파일 로그와 generated 반영 여부를 본다모듈명, UCLASS 위치 오류
BP ChildBP_ 에셋이 올바른 부모를 상속한다이전 부모 또는 다른 클래스 기반
Details / Play카테고리, 기본값, 인스턴스 수정값, 로그를 확인한다값은 보이나 Play 결과가 다름

작업을 반복할수록 C++과 블루프린트의 책임 경계를 명확히 유지해야 협업과 디버깅 비용이 줄어듭니다.

C++은 뼈대, Blueprint는 조정면

C++ 클래스와 Blueprint 자식은 같은 기능을 나눠 구현합니다. 기준은 성능, 반복 조정, 디버깅 책임입니다.

  1. 책임 배치 기준
  2. 성능 반복 계산, 엔진 API 호출 빈도 낮

    이벤트 연결 프레임 중 로그가 과도하지 않음

  3. 조정 기본값

    유효 범위 메시, 색, 수치 튜닝 컴파일 없이 값 변경 확인

  4. 디버깅 UE_LOG

    타입 오류 그래프 연결과 에셋 누락 화면 메시지와 로그가 같은 원인


정리: C++과 블루프린트의 시너지

이 실습을 통해 여러분은 C++ 클래스를 생성하고, UPROPERTYUFUNCTION 매크로를 사용하여 속성과 함수를 정의한 다음, 이를 블루프린트에서 상속받아 시각적으로 확장하고 조작하는 과정을 직접 경험했습니다.

언리얼 엔진에서는 성능이 중요한 핵심 로직을 C++로 작성하고, 콘텐츠 조정이나 빠른 반복이 필요한 부분을 블루프린트로 노출하는 식으로 역할을 나눌 수 있습니다.

다음 절에서는 이제 막 만들어본 우리의 C++ 클래스 내에서 BeginPlay, Tick과 같은 액터의 수명 주기 함수들을 활용하여 실제 동작을 구현하는 방법을 더 자세히 알아보겠습니다.

C++ 기본 구현과 Blueprint 조정이 어디서 만나는지 마지막 루프로 확인해 봅니다.

C++은 안정 동작, Blueprint는 빠른 값 조정으로 책임을 나눈다

양쪽에 다 넣는 것이 아니라 바뀌는 속도에 맞춰 책임을 나누는 구조가 핵심이다.

맡길 책임경계 규칙
C++ Base상속 구조, 컴포넌트 생성, 핵심 함수빈번히 바꾸지 않을 규칙만 둔다
ExposeEditDefaultsOnly, BlueprintReadWrite 등 조정 범위열어 둔 값의 이유가 분명하다
Blueprint Child메시, 머티리얼, 입력 에셋, 수치 밸런스값 변경으로 반복 실험한다
Level Test기본값과 인스턴스 오버라이드 반영부모 재지정과 새로고침을 확인한다