● 너의 이름은
지금 구현한 클래스 선택 레벨에서 당장 추가할 것을 말하자면 현재 이름 입력 기능이 없다. 기획에 따르면 클래스를 선택하면 이름을 입력한 뒤에 튜토리얼을 시작해야 하지만 지금은 그런거 없이 바로 튜토리얼을 시작한다. 그래서 이름 입력 기능을 구현해야 한다.
1. 코드 구성
class DESECRATION_API UT3InputNamePanel : public UUserWidget
{
GENERATED_BODY()
friend UT3SelectClassWidget;
protected:
virtual void NativeOnInitialized() override;
private:
//확인 버튼
UFUNCTION()
void OnClickConfirmButton();
//취소 버튼
UFUNCTION()
void OnClickCancelButton();
//이름 입력 칸
UPROPERTY(meta = (AllowPrivateAccess = true, BindWidget))
TObjectPtr<UEditableTextBox> InputNameBox;
//확인 버튼
UPROPERTY(meta = (AllowPrivateAccess = true, BindWidget))
TObjectPtr<UButton> ConfirmButton;
//취소 버튼
UPROPERTY(meta = (AllowPrivateAccess = true, BindWidget))
TObjectPtr<UButton> CancelButton;
//클래스 선택 위젯
UPROPERTY()
TWeakObjectPtr<UT3SelectClassWidget> SelectClassWidget;
};
이름 입력 패널인 유저 위젯 클래스다. 여기서 몇 가지 설명할 것이 있다.
- friend 키워드로 특정 클래스를 지정하면 그 클래스는 이 클래스의 protected 및 private를 public인 것처럼 접근할 수 있다. (객체 지향 프로그래밍 관점에서는 은닉성을 망치는 기능이니 적당히 사용하자) 여기서는 클래스 선택 위젯이 이 패널 클래스 내 모든 것을 접근할 수 있다.
- 새로 알게 된 언리얼 엔진 함수 : NativeOnInitialized는 객체 자체가 파괴되지 않는 이상 단 1번만 실행되는 함수라고 한다. 나는 NativeConstruct 함수가 1번만 실행되는 함수인 줄 알았는데 그게 아니었다. 이 프로젝트에서는 보여주고 숨길 때 SetVisibility를 사용하여 보여주고 숨기기 때문에 다시 실행될 일은 없지만, 만약 패널을 닫기 위해 뷰포트에서 뺀 다음 다시 뷰포트에 추가하면 NativeConstruct 함수가 실행된다고 한다. NativeConstruct 함수는 뷰 포트에 추가되는 시점에서 실행하고 NativeOnInitialized함수는 CreateWidget 시점에서 실행되기 때문이다.
- 이 패널은 이름 입력 칸인 EditableTextBox와 확인, 취소 버튼이 있다.
- 클래스 선택 위젯 내에 배치되는 위젯으로 만들 생각이다. 그래서 클래스 선택 위젯을 캐싱할 변수를 선언한다. (클래스 선택 위젯 클래스에 friend 키워드를 사용한 이유) 다만 TObjectPtr로 선언하면 순환 참조 문제가 발생할 염려가 있어 약한 참조인 TWeakObjectPtr로 선언한다.
- 확인, 취소 버튼에 대응되는 함수를 선언한 상태다.
void UT3InputNamePanel::NativeOnInitialized()
{
Super::NativeOnInitialized();
//버튼 바인딩
ConfirmButton->OnClicked.AddDynamic(this, &ThisClass::OnClickConfirmButton);
CancelButton->OnClicked.AddDynamic(this, &ThisClass::OnClickCancelButton);
//이름 입력 칸 초기화
InputNameBox->SetText(FText::GetEmpty());
}
void UT3InputNamePanel::OnClickConfirmButton()
{
//이름 미입력시 무시
const FString InputName = InputNameBox->GetText().ToString();
if (InputName.IsEmpty())
{
return;
}
//튜토리얼 시작
if (const TObjectPtr<UT3SelectClassWidget> WidgetInstance = SelectClassWidget.Get())
{
WidgetInstance->TutorialStart(InputName);
}
}
void UT3InputNamePanel::OnClickCancelButton()
{
SetVisibility(ESlateVisibility::Collapsed);
}
NativeOnInitialized함수에서는 함수 바인딩과 같은 기본적인 초기화를 진행한다.
확인 버튼을 누르면 클래스 선택 위젯의 튜토리얼 시작 함수를 실행한다. 이때 이름 입력칸에 입력한 이름을 가져와서 매개변수로 넘겨준다. 만약 가져온 이름이 빈 값이면 실행하지 않는다.
취소 버튼의 함수에서는 이 패널을 닫도록 한다.
2. 이게 무슨 버그야
위 함수 중 확인 버튼의 함수를 처음 만들었을 때는 아래와 같은 모습이었다.
void UT3InputNamePanel::OnClickConfirmButton()
{
//이름 미입력시 무시
const FText InputName = InputNameBox->GetText();
if (InputName.IsEmpty())
{
return;
}
UE_LOG(LogTemp, Warning, TEXT("이름 입력함"));
}
이름 입력칸을 FText로 가져온 다음 변환하려는 생각이었고 일단 잘 가져오는지 부터 확인하려고 했다. 그런데 여기서 문제가 발생했다. 저 FText 변수를 디버그로 확인해보면 빈 값으로 나온다. 그래서 매우 당황했다.
UPROPERTY(BlueprintReadWrite, meta = (AllowPrivateAccess = true))
FText InputNameBoxText;

심지어는 FText를 블루프린트에 노출시키고 블루프린트에서 입력을 하는 경우에도 코드에서는 빈 값으로 나온다.
UPROPERTY(BlueprintReadWrite, meta = (AllowPrivateAccess = true))
FString InputNameBoxString;

그런데 여기서 이상한 현상을 발견했다. FText 대신 FString을 사용하면 정상적으로 값을 받아온다.
FString InputName = InputNameBox->GetText().ToString();
이 현상을 발견한 직후 혹시나 하는 마음에 C++ 코드에서도 FText를 받자마자 바로 FString으로 변환하니 값이 정상적으로 확인 가능했다. 분명 FText일때는 빈 값이던 것이 FString으로 변환하니 문자열이 되살아났다. 이게 무슨 어처구니 없는 상황인가...
이 상황을 해결하고자 찾아본 자료에 따르면 언리얼 엔진의 버그로 추측된다. 나만 겪은 현상이 아니었다. 황당한 문제였지만 어쨌든 FString으로 사용하면 문제가 없을 것이고 그 결과로 위에 적어둔 확인 버튼의 함수 형태다.
3. 위젯 블루프린트 제작 및 클래스 선택 위젯에 배치하기

이름 입력 패널은 화면에 가득 찬 모습이 아니라서 작은 크기로 제작된다. 커스텀(Custom) 크기를 사용하는데 너비를 600, 높이를 400으로 정했다.


기획서에 있는 내용대로 위젯을 배치한다. 그리고 코드의 변수와 연동이 잘되는지도 확인한다.

그리고 배치해봤는데 여기서 문제점이 하나 예상된다. 이름 입력 패널이 띄워지면 패널 뒤에 있는 버튼은 눌려지면 안되는데 그걸 막아낼 것이 없다.


여기서 내가 선택한 것은 패널을 바로 배치하는 것이 아니라 보더를 먼저 배치한 다음 보더 안에 패널을 배치하는 방법이다. 이때 패널 크기를 조절해야 하니 캔버스 패널까지 포함해서 2중으로 감싼 형태가 됐다.

보더를 화면 전체로 늘려서 뒤쪽 버튼을 누르지 못하게 만든다. 이때 보더의 색상은 반투명한 검은색으로 만든다. 그리고 보더의 자식인 캔버스 패널의 크기는 보더 전체에 채우도록 만든다. 이제 패널은 캔버스 패널의 자식으로 배치하면 된다. 이르면 패널이 띄워진 동안 클래스 선택 위젯의 버튼은 건드릴 수 없다.
다만 이렇게 만든 다음 이름 입력 패널의 취소 버튼을 누르면 이 보더는 사라지지 않고 패널만 사라진다. 그래서 코드를 수정할 겸 클래스 선택 위젯 C++ 클래스에서도 이 패널을 추가한다..
//T3SelectClassWidget.h
public:
//이름 입력 패널 활성화 여부
void SetActiveInputNamePanel(bool bActive);
private:
//이름 입력 패널이 있는 부모 위젯
UPROPERTY(meta = (AllowPrivateAccess = true, BindWidget))
TObjectPtr<UWidget> InputNamePanelParent;
//이름 입력 패널
UPROPERTY(meta = (AllowPrivateAccess = true, BindWidget))
TObjectPtr<UT3InputNamePanel> InputNamePanel;
보더 와 이름 입력 패널을 클래스 선택 위젯에 추가한다. 보더는 비지빌리티만 건드리니 변수 타입을 UWidget으로 해도 상관없다. 그리고 이름 입력 패널을 보여주고 숨기는 것을 이 보더의 비지빌리티로 구현할 것인데 그 내용을 담을 함수도 선언한다.
void UT3SelectClassWidget::NativeConstruct()
{
Super::NativeConstruct();
...
//이름 입력 패널 초기화
SetActiveInputNamePanel(false);
InputNamePanel->SelectClassWidget = this;
//그외 나머지 초기화
SelectedPlayerClass = -1;
}
void UT3SelectClassWidget::SetActiveInputNamePanel(bool bActive)
{
InputNamePanelParent->SetVisibility(bActive ? ESlateVisibility::Visible : ESlateVisibility::Collapsed);
}
이름 입력 패널을 초기화 한다. 해당 패널 클래스에서 이 위젯 클래스를 friend로 선언했으니 마음대로 변수에 접근할 수 있다. 이름 입력 패널은 시작시 숨긴다.
//T3InputNamePanel.cpp
void UT3InputNamePanel::OnClickCancelButton()
{
if (const TObjectPtr<UT3SelectClassWidget> WidgetInstance = SelectClassWidget.Get())
{
WidgetInstance->SetActiveInputNamePanel(false);
}
}
이름 입력 패널의 취소 버튼의 내용은 자기 자신을 직접 숨기는 것이 아니라 클래스 선택 위젯을 통해 숨기도록 한다.
'언리얼엔진 학습 및 과제' 카테고리의 다른 글
| 소울라이크 팀 프로젝트 Desecration : 시작부터 MVP까지 (7) (0) | 2026.04.01 |
|---|---|
| 소울라이크 팀 프로젝트 Desecration : 시작부터 MVP까지 (5) (0) | 2026.03.24 |
| 소울라이크 팀 프로젝트 Desecration : 시작부터 MVP까지 (4) (0) | 2026.03.23 |
| 소울라이크 팀 프로젝트 Desecration : 시작부터 MVP까지 (3) (0) | 2026.03.20 |
| 소울라이크 팀 프로젝트 Desecration : 시작부터 MVP까지 (2) (0) | 2026.03.19 |