C++로 Blueprint 클래스 확장
앞서 UObject
와 AActor
의 차이점을 명확히 이해하셨다면, 이제 언리얼 엔진 C++ 개발의 핵심적인 강점 중 하나인 C++ 클래스를 블루프린트로 확장하는 방법에 대해 알아볼 차례입니다. 언리얼 엔진은 C++로 엔진의 기반을 다지고, 그 위에 블루프린트로 빠르고 유연하게 기능을 추가하는 하이브리드(Hybrid) 개발 방식을 강력히 지원합니다. 이 방식은 개발의 효율성을 극대화하는 데 큰 도움이 됩니다.
왜 C++을 블루프린트로 확장해야 할까요?
이 질문에 대한 답은 명확합니다. C++과 블루프린트는 각각의 장단점이 있기 때문입니다.
- C++의 장점
- 성능: 복잡한 연산, 물리 시뮬레이션, AI 로직 등 성능이 중요한 부분에 유리합니다.
- 접근성: 엔진의 깊숙한 부분까지 접근하여 커스터마이징할 수 있습니다.
- 제어: 메모리 관리 등 더 세밀한 제어가 가능합니다.
- 표준화: 대규모 프로젝트에서 코드의 구조와 규칙을 강력하게 강제할 수 있습니다.
- 블루프린트의 장점
- 빠른 프로토타이핑: 시각적인 스크립팅 방식으로 아이디어를 빠르게 구현하고 테스트할 수 있습니다.
- 비개발자 협업: 프로그래밍 지식이 없는 아티스트나 기획자도 게임 로직에 참여할 수 있습니다.
- 쉬운 반복 작업: 간단한 로직 변경이나 데이터 수정에 즉각적으로 대응할 수 있습니다.
따라서 이상적인 언리얼 엔진 개발 워크플로는 성능이 중요하거나, 엔진의 핵심 기능을 건드리거나, 복잡한 로직의 기반을 다지는 부분은 C++ 로 구현하고, 자주 변경되거나, 디자이너나 아티스트가 직접 수정해야 하는 부분, 또는 빠른 테스트가 필요한 부분은 블루프린트로 구현하는 것입니다. C++로 만든 클래스를 블루프린트에서 상속받아 사용하면 이러한 장점들을 모두 누릴 수 있습니다.
C++ 클래스 생성 및 기본 구조
우리의 첫 번째 C++ 클래스를 만들어보며 시작해봅시다. 이번에는 간단한 메시를 표시하고 특정 텍스트를 출력하는 액터를 만들어볼 거예요.
-
언리얼 에디터에서 C++ 클래스 생성: 언리얼 에디터 상단 메뉴에서
Tools (도구)
>New C++ Class... (새 C++ 클래스...)
를 클릭합니다. -
부모 클래스 선택: '부모 클래스 선택(Choose Parent Class)' 창이 나타나면, 우리는 3D 월드에 배치될 수 있는 객체를 만들 것이므로
Actor
를 선택하고 '다음(Next)'을 클릭합니다. -
이름 지정 및 생성: 클래스 이름은
AMyAwesomeActor
로 입력하고, 저장 경로를 확인한 후 '클래스 생성(Create Class)'을 클릭합니다. 언리얼 엔진이 필요한 파일을 생성하고 비주얼 스튜디오가 자동으로 열리면서 방금 생성한AMyAwesomeActor.h
와AMyAwesomeActor.cpp
파일이 나타날 것입니다.
이제 생성된 파일의 기본 구조를 살펴보겠습니다.
// AMyAwesomeActor.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();
};
// 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++ 클래스 컴파일
코드를 수정한 후에는 반드시 컴파일해야 합니다. 언리얼 에디터로 돌아가서 상단의 Compile (컴파일)
버튼을 클릭합니다. 또는 비주얼 스튜디오에서 Ctrl + Shift + B
를 눌러 솔루션을 빌드할 수도 있습니다. 성공적으로 컴파일되면, 여러분이 작성한 C++ 코드가 엔진에 반영됩니다.
C++ 클래스 기반으로 블루프린트 클래스 생성
이제 C++ 클래스를 컴파일했으니, 이를 기반으로 블루프린트 클래스를 생성해볼까요?
-
콘텐츠 브라우저에서 생성: 언리얼 에디터의 콘텐츠 브라우저에서 원하는 폴더(예:
Content/Blueprints
)로 이동합니다. 빈 공간에 마우스 오른쪽 버튼을 클릭하여 컨텍스트 메뉴를 엽니다. -
블루프린트 클래스 선택:
Blueprint Class (블루프린트 클래스)
를 선택합니다. -
부모 클래스 선택 (중요!): '모든 클래스(All Classes)' 섹션에서 방금 만든 C++ 클래스인
AMyAwesomeActor
를 검색하여 선택하고 '선택(Select)'을 클릭합니다. -
이름 지정: 새로운 블루프린트 클래스의 이름을
BP_MyAwesomeActor
로 지정합니다. (블루프린트 클래스는BP_
접두사를 붙이는 것이 일반적인 컨벤션입니다.)
블루프린트에서 C++ 속성 및 함수 확인/수정
이제 BP_MyAwesomeActor
블루프린트 클래스를 더블 클릭하여 블루프린트 에디터를 엽니다.
-
디테일 패널 확인: 블루프린트 에디터의 좌측 상단 '컴포넌트(Components)' 패널에서
MyStaticMesh
컴포넌트가 추가된 것을 확인할 수 있습니다. 이를 선택하면 디테일 패널에서 메시나 머티리얼을 할당할 수 있습니다. 그리고 우측 디테일(Details) 패널을 살펴보세요. 'Custom Properties' 카테고리 아래에 우리가 C++에서 선언한WelcomeMessage
변수가 보이는 것을 확인할 수 있습니다! 이 변수의 기본값을 여기서 자유롭게 변경할 수 있습니다. 예를 들어 "Hello from Blueprint!"로 바꿔보세요. -
그래프 편집기에서 함수 확인: '이벤트 그래프(Event Graph)'로 이동한 후, 마우스 오른쪽 버튼을 클릭하여 컨텍스트 메뉴를 엽니다. 검색창에
Print Welcome Message
를 검색하면 우리가 C++에서UFUNCTION(BlueprintCallable)
로 선언했던 함수가 노드로 나타나는 것을 확인할 수 있습니다. 이 노드를 이벤트(Event BeginPlay
등)에 연결하여 C++ 함수를 블루프린트에서 호출할 수 있습니다.
레벨에 배치 및 테스트
블루프린트 클래스까지 만들고 내용을 확인했으니, 이제 레벨에 배치하고 테스트해봅시다.
- 레벨에 블루프린트 배치:
콘텐츠 브라우저에서
BP_MyAwesomeActor
블루프린트 에셋을 드래그하여 뷰포트에 놓습니다. - Static Mesh 할당 (선택 사항):
뷰포트에서
BP_MyAwesomeActor
인스턴스를 선택한 후, 디테일 패널에서MyStaticMesh
컴포넌트를 선택합니다. 'Static Mesh' 항목에 원하는 스태틱 메시(예: 'Shape_Cube' 등 스타터 콘텐츠의 기본 메시)를 할당해 줍니다. - 게임 플레이:
언리얼 에디터 상단의
Play (플레이)
버튼을 클릭하여 게임을 실행합니다.
화면 좌측 상단에 여러분이 블루프린트에서 변경한 WelcomeMessage
가 출력되는 것을 확인할 수 있을 것입니다! 이는 C++로 기본 기능을 구현하고, 블루프린트에서 이를 손쉽게 확장하고 데이터를 변경한 결과입니다.
정리: C++과 블루프린트의 시너지
이 실습을 통해 여러분은 C++ 클래스를 생성하고, UPROPERTY
와 UFUNCTION
매크로를 사용하여 속성과 함수를 정의한 다음, 이를 블루프린트에서 상속받아 시각적으로 확장하고 조작하는 과정을 직접 경험했습니다.
이것이 바로 언리얼 엔진 개발의 강력한 시너지입니다. 성능이 중요한 핵심 로직은 C++로 견고하게 구축하고, 게임의 변화무쌍한 콘텐츠나 디자이너의 빠른 이터레이션(Iteration)이 필요한 부분은 블루프린트로 유연하게 처리할 수 있습니다. 이 유기적인 협업이야말로 언리얼 엔진이 가진 가장 큰 매력 중 하나입니다.
다음 절에서는 이제 막 만들어본 우리의 C++ 클래스 내에서 BeginPlay
, Tick
과 같은 액터의 수명 주기 함수들을 활용하여 실제 동작을 구현하는 방법을 더 자세히 알아보겠습니다. 코드를 통해 생명을 불어넣는 작업은 언제나 즐거운 일이죠!