● 버튼이 100개면 함수를 100개 만들 것인가?
1. 이전에도 언급했지만 마음에 들지 않는 코드
private:
//클래스 선택 버튼1
UFUNCTION()
void OnClickSelectClassButton1();
//클래스 선택 버튼2
UFUNCTION()
void OnClickSelectClassButton2();
//클래스 선택 버튼3
UFUNCTION()
void OnClickSelectClassButton3();
//클래스 선택
void SelectClass(int32 ClassNum);
//클래스 버튼1
UPROPERTY(meta = (AllowPrivateAccess = true, BindWidget))
TObjectPtr<UButton> SelectClassButton1;
//클래스 버튼2
UPROPERTY(meta = (AllowPrivateAccess = true, BindWidget))
TObjectPtr<UButton> SelectClassButton2;
//클래스 버튼3
UPROPERTY(meta = (AllowPrivateAccess = true, BindWidget))
TObjectPtr<UButton> SelectClassButton3;
클래스 선택 화면의 위젯 코드는 마음에 들지 않는 구조로 짜여있다. 왜냐하면 버튼이 3개라고 함수를 3개 만들어서 바인딩 했기 때문이다. 이 3개의 버튼의 목적이 완전히 다르다면 모르겠는데 이들은 선택에 따라 값 1개만 달라질 뿐 나머지는 동일하다. 그런 상황에서 함수를 각각 따로 만든 것은 비효율적인 코드다. 이 구조를 따를경우, 버튼이 100개라면 함수를 100개 만들어야 한다. 설마 진짜로 그러지는 않겠지...
2. 생각하거나 시도했지만 정답이 아닌 경우
void UT3SelectClassWidget::NativeConstruct()
{
for (int32 iNum = 0; iNum < 3; iNum++)
{
//에러가 발생함
ClassButtons[iNum]->OnClicked.AddDynamic(this, &ThisClass::SelectClass);
}
}
void UT3SelectClassWidget::SelectClass(int32 ClassNum)
{
}
처음에는 매개변수 1개를 받는 함수를 for문으로 돌려서 버튼에 바인딩할까 생각했지만 버튼의 OnClicked는 매개변수가 없는 함수만 받을 수 있는 델리게이트라서 그럴 수는 없다. 위 코드는 에러가 발생한다.
void UT3SelectClassWidget::NativeConstruct()
{
for (int32 iNum = 0; iNum < 3; iNum++)
{
//이런게 실제로 존재할까?
ClassButtons[iNum]->OnClicked.AddDynamic(this, () =>
{
SelectClass(iNum)
});
}
}
void UT3SelectClassWidget::SelectClass(int32 ClassNum)
{
}
OnClicked에 람다 함수 같은걸 만들어서 할당할 수는 없을까? 아쉽게도 언리언 엔진에서 사용하는 코드는 C++라서 C#과 같은 람다식은 존재하지 않는다. 그래서 위 코드와 같은 구조는 불가능하다.
혹시나 언리얼 엔진에서 구현한 람다 생성 함수(ex : TimerManager의 SetTimer함수를 사용할 경우) 중에서 위젯 버튼에 사용가능한 것이 존재할까 해서 정보를 찾아봤다. 그리고 나는 답을 찾았다. 과정은 다소 복잡하니 제작한 코드에 대한 개념을 순서대로 밟아본다.
3. User위젯 내부에는 Slate위젯이 포함된다.

언리얼 엔진에서 UI를 만들때 사용하는 버튼, 텍스트 등의 위젯은 내부에 Slate 위젯이 있다. 언리얼 엔진에서 제공되는 User 위젯의 기능은 내장된 Slate 위젯을 빌려서 구현된 경우가 많다. 당장 지금 다루는 중인 버튼도 마찬가지다.
그래서 이 Slate위젯이 왜 언급됐을까? 바로 Slate위젯용 람다 생성 함수가 존재하기 때문이다.
SlateButton->SetOnClicked(FOnClicked::CreateLambda([this, TempClassValue]()
{
OnClickSelectClassButton(TempClassValue);
return FReply::Handled();
}));
버튼을 예로 들자면, SButton에 있는 SetOnClicked 함수로 클릭시 실행할 기능을 부여할 수 있다. 이 함수의 매개변수로 람다 함수를 넣을 수 있는데 FOnClicked::CreateLambda함수를 사용하면 된다.
주의할 점으로 이 람다 함수는 FReply::Handled()가 반환 값인 함수로 만들어야 한다.
TSharedPtr<SButton> SlateButton = StaticCastSharedPtr<SButton>(MyUserButton->GetCachedWidget());
다만 이 Slate위젯은 public으로 선언된 상태가 아니라서 직접 참조할 수 없다. 대신 여기에 접근할 수 있는 함수인 GetChachedWidget()이 존재한다. 이 함수의 반환값은 C스타일 포인터라서 TSharedPtr로 사용하려면 변환 함수인 StaticCastCharedPtr()를 사용해야 한다.
4. 선언된 버튼 변수를 제거하고 그 부모인 변수를 선언한다.
//T3SelectClassWidget.h
private:
//캐릭터 선택 버튼이 있는 패널
UPROPERTY(meta = (AllowPrivateAccess = true, BindWidget))
TObjectPtr<UHorizontalBox> SelectClassBox;


이렇게 하는 데에는 이유가 있다. 원래는 그 버튼으로 구성된 TArray를 선언하려고 했다. 하지만 생각해보니 이러면 버튼을 생성할 때마다 일일이 그 TArray에 추가해줘야 하니 불편할 것이다. 그래서 생각한 것은 부모인 가로 박스(Horizontal Box)에서 그 자식인 위젯을 가져오게 하는 것이다. 이러면 코드에 직접 추가하지 않아도, 디테일 패널에 일일이 추가하지 않아도 자식을 가져오는 함수 하나만으로 가져올 수 있게 된다.
5. 그래서 완성된 코드
//각 클래스 버튼은 SButton에 Lambda로 바인딩한다.
int32 TempClassValue = 0;
for (TObjectPtr<UWidget> ChildWidget : SelectClassBox->GetAllChildren())
{
TObjectPtr<UButton> ChildButton = Cast<UButton>(ChildWidget);
if (!ChildButton)
{
UE_LOG(LogTemp, Warning, TEXT("%s : 버튼이 아님"), *ChildWidget.GetName());
continue;
}
TSharedPtr<SButton> SlateButton = StaticCastSharedPtr<SButton>(ChildButton->GetCachedWidget());
SlateButton->SetOnClicked(FOnClicked::CreateLambda([this, TempClassValue]()
{
SelectClass(TempClassValue);
return FReply::Handled();
}));
++TempClassValue;
}
- 버튼의 부모인 SelectClassBox에서 GetAllChildren() 함수를 사용하면 모든 자식으로 구성된 위젯 배열이 반환된다.
- 이 위젯 배열을 for문을 통해 순회한다.
- 만약을 위해 자식 위젯마다 버튼 위젯인지 검증하는 구간이 필요하다. GetAllChildren()는 자식이 어떤 종류인지는 가르쳐 주지 않는다.
- 버튼 위젯이면 여기서 Slate위젯을 추출한다. 그리고 OnClicked에서 람다를 생성하여 여기에 매개변수가 있는 함수를 집어넣는다.
이 과정을 통해 아무리 버튼이 많이 추가되어도 동일한 함수 1개를 사용할 수 있게 됐다.
● 확인 좀 하겠습니다
1. 기획상 어디서 확인을 요구하는가



현재 내가 담당한 범위 중 확인을 요구하는 것은 타이틀 화면에만 있다.
- 게임을 새로 시작할 때(이미 저장된 게임이 있다면)
- 저장된 게임을 이어할 때
- 종료할 때.
하지만 지금 여기에는 확인을 거치는 과정이 없으며 무조건 그 기능을 실행한다. 이제 그 기능을 구현해야겠다.
'언리얼엔진 학습 및 과제' 카테고리의 다른 글
| 소울라이크 팀 프로젝트 Desecration : 시작부터 MVP까지 (6) (0) | 2026.03.25 |
|---|---|
| 소울라이크 팀 프로젝트 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 |