Game Development/Devlog

[UE5] 블루프린트 & C++, 블루프린트 디버깅 방법 (ProjectVT 개발기)

Dlaiml 2024. 5. 20. 09:20

Blueprint & C++

 

5월 초에 게임 개발 공부를 시작할 때, 언리얼 엔진에서 프로젝트 생성 옵션에서 블루프린트와 C++이 나누어져 있어 둘 중 하나를 골라서 개발을 쭉 하는 것으로 착각했었다. 아직 많은 게임을 완성해보진 않았지만 게임 개발은 게임에서의 로직도 중요하지만 시각적 요소가 차지하는 비중이 매우 크다고 생각한다.

 

Animation, Mesh, UI 등을 다루기 위해서는 에디터에서의 개발이 필수적이다. 따라서 에디터에서의 접근성이 매우 좋고 C++ 코드가 익숙하지 않은 직군도 쉽게 이용할 수 있는 블루프린트의 활용은 언리얼엔진 게임개발에서 매우 중요하다.

 

UMG(언리얼 모션 그래픽 UI 디자이너)는 언리얼 엔진에서 제공하는 비주얼 UI 제작 툴로 현재 팀원과 개발하고 있는 게임의 UI 또한 모두 UMG로 제작하였다. 나머지 로직의 구현과 클래스의 정의도 원활한 협업을 위해 C++ 보다 Blueprint를 주로 활용하고 있다.  

 

블루프린트를 주로 사용하며 직접 개발해보니 코드 입력보다 마우스로 함수를 선택하고 핀을 잇는 것이 더 빨라서 기본적인 기능의 경우 훨씬 빠르게 개발이 가능하다. C++ 코드로 작성하였던 로직, 클래스 설계, 디자인 패턴의 이식도 함수명이 같아서 쉽게 가능하다.

 

복잡한 로직을 구현할 때 핀과 라인이 너무 많아 깔끔하게 보기 위해 이를 정리하는데 시간이 들었고, 클래스 간 관계를 파악하는 데에는 더 어려움이 있었다. 물론 아직 익숙하지 않아 숙련도 부족의 문제도 크다고 생각한다.

 

하지만 결국 블루프린트도 코드로 실행되기 때문에 생긴 문제를 해결하기 위해서는 코드의 확인이 필수적이다.

예시를 한가지 가져왔다.

 

Blueprint Debug

 

플레이어 캐릭터와 NPC의 대화창, 대화 기록 Log 기능을 구현하기 위해 대사가 출력되는 위젯 블루프린트를 만들었고 내부에 Scroll Box와 Uniform Grid Panel을 사용하여 서로 나눈 대화가 여러 Uniform Grid에 쌓이면서 기록되는 형태이다. (대화 기록을 간단하게 보여주는 Widget, 모두 보여주는 Widget이 따로 존재하는 상황)

 

발화자, 대사 Text 구성된 위젯 블루프린트인 WBP Chat Widget이 대화 내용을 받아와서 생성되고 이를 For Each Loop를 사용하여 여러 Uniform Grid에 Child로 추가하는 블루프린트이다.

 

반복문이 있음에도 둘 중 하나의 로그창에만 대화가 기록되는 문제가 생겼다. 블루프린트만 보아서는 문제를 정확히 진단하기 어렵다. Add child의 input들을 Print 해보아도 input에서 생기는 문제가 아니었다.

 

UE Documentation

 

언리얼 엔진 공식문서를 보아도 알 수 있는 방법이 없다 (심지어 위 사진이 전문이다)

 

 

 

블루프린트의 Built-in 함수들은 더블 클릭하면 코드를 확인할 수 있다. 

아래는 내부 동작을 알지 못했고 문제가 있을 것이라 생각하던 AddChildToUniformGrid 함수의 코드이다.

UUniformGridSlot* UUniformGridPanel::AddChildToUniformGrid(UWidget* Content, int32 InRow, int32 InColumn)
{
	UUniformGridSlot* GridSlot = Cast<UUniformGridSlot>(Super::AddChild(Content));

	if (GridSlot != nullptr)
	{
		GridSlot->SetRow(InRow);
		GridSlot->SetColumn(InColumn);
	}

	return GridSlot;
}

 

부모 클래스의 AddChlid를 사용하고 나머지는 UniformGridSlot에서 행과 열을 설정하는 부분이다. Child가 추가되지 않는 문제를 해결하고 싶기 때문에 부모 클래스를 확인해보아야 한다.

 

 

UUniformGridPanel 클래스를 문서에서 찾으면 아래와 같이 Inheritance Hierarchy를 알 수 있다. (코드에서는 모든 IDE에 있는 Go To Definition 기능을 사용하여 바로 이동할 수 있다) 

 

UPanelWidget의 AddChild 함수의 코드를 확인해보자.

UPanelSlot* UPanelWidget::AddChild(UWidget* Content)
{
	if ( Content == nullptr )
	{
		return nullptr;
	}

	if ( !bCanHaveMultipleChildren && GetChildrenCount() > 0 )
	{
		return nullptr;
	}

	Content->RemoveFromParent();

	EObjectFlags NewObjectFlags = RF_Transactional;
	if (HasAnyFlags(RF_Transient))
	{
		NewObjectFlags |= RF_Transient;
	}

	UPanelSlot* PanelSlot = NewObject<UPanelSlot>(this, GetSlotClass(), NAME_None, NewObjectFlags);
	PanelSlot->Content = Content;
	PanelSlot->Parent = this;

	Content->Slot = PanelSlot;

	Slots.Add(PanelSlot);

	OnSlotAdded(PanelSlot);

	InvalidateLayoutAndVolatility();

	return PanelSlot;
}

 

Object Flag 관련 부분을 빼고 보면 Content를 Parent에서 떼고 새 PanelSlot을 만들고 Conent를 등록하여 Panel Widget의 Slot에 추가하는 코드를 볼 수 있다.

 

포인터를 사용하였기 때문에 앞서 블루프린트에서 만든 하나의 대화 위젯을 가리키는 포인터가 파라미터로 들어간다. Content->RemoveFromParent()가 있기 때문에 앞 Loop 에서 다른 Widget의 UnifromGridPanel의 Child로 있던 위젯이 해당 Widget에서 떨어져 나오는 것이 문제의 원인이었다.

 

Widget의 Parent 멤버는 하나이기 때문에 위 AddChild가 RemoveFromParent를 포함하고 있는 것이다.

Widget을 매번 생성하거나, DeepCopy 기능을 찾거나, 미리 만들어진 Slot에 데이터를 변경하도록 하는 등 해결책이 존재한다.

 

 

 

Lyra와 같이 Blueprint와 C++을 모두 잘 활용한 예시들을 보면서 언리얼 엔진에서 개발 패턴에 대해 익힐 예정이다.

 

 

Reference

[0] https://docs.unrealengine.com/4.27/en-US/

 

Unreal Engine 4 Documentation

Complete resources for learning to use Unreal Engine 4

docs.unrealengine.com