위젯에 텍스트로 타이머 기능을 넣는 부분을 진행했다.
BaseHUD, BaseWidget 설계
위젯을 쓰려고하니 모드마다 공통으로 들어가는 위젯들이 있어서 Base 위젯을 만들면 좋겠다고 생각했다.
베이스 HUD와 Widget의 구조를 잡는데 오늘 대부분의 시간을 썼었던 것 같다.
HUD와 Widget의 역할을 잘 몰랐었는데 HUD는 UI를 담는 컨테이너 같은 역할이고 그 안에 실질적으로 유저가 보는 것들이 UI라고 이해했다.
그래서 일단 BaseHUD를 설계했고, 공통된 부분만 묶어서 정의해주었다.
새로운 HUD가 추가될 때마다 이 BaseHUD를 상속받아서 적용할 수 있게 설계했고, 위젯도 비슷한 방식을 적용했다.
근데 위젯은 어떤걸 묶을지 아직 감이 안잡히는 것 같다.. Base가 굳이 필요한가 싶기도하고.. 주말에 좀 더 정리를 해봐야겠다.
Text 위젯과 타이머 연동
타이머 연동은
00:00 이런 식으로 하려고 설계했고, 하나의 Text를 사용해서 만들어주었다.

이렇게 중간에 시간을 넣어주었고 위젯을 상속받은 C++을 기반으로 만들어진 위젯블루프린트이기때문에, 특정 위젯을 가져야만 오류가 나지 않도록 처리해주었다.
베이스 UI를 이렇게 만들어주었고, 이제 이 베이스 UI를 상속받은 훈련장 UI를 만들어주었다.

이런 느낌으로 초를 입력하면 화면 중앙 상단의 타이머까지 연동할 수 있게 하고 싶었다.
시작 버튼을 누르면 설정한 초만큼 시작이되고, 리셋을 누르면 타이머가 0초로 돌아가게 설정해주려고 한다.
#pragma once
#include "CoreMinimal.h"
#include "Framework/UI/BaseInGameWidget.h"
#include "TrainingWidget.generated.h"
UCLASS()
class CCFF_API UTrainingWidget : public UBaseInGameWidget
{
GENERATED_BODY()
UFUNCTION()
void OnStartButtonClicked();
UFUNCTION()
void OnResetButtonClicked();
UFUNCTION()
void UpdateTimer();
protected:
virtual void NativeConstruct() override;
float CurrentTime = 0.0f;
FTimerHandle TimerHandle;
UPROPERTY(meta = (BindWidget))
class UTextBlock* TimerText;
UPROPERTY(meta = (BindWidget))
class UEditableTextBox* TimeInputBox;
UPROPERTY(meta = (BindWidget))
class UButton* StartButton;
UPROPERTY(meta = (BindWidget))
class UButton* ResetButton;
};
#include "Framework/UI/TrainingWidget.h"
#include "Components/Button.h"
#include "Components/EditableTextBox.h"
#include "Components/TextBlock.h"
#include "TimeManagementClasses.h"
void UTrainingWidget::NativeConstruct()
{
Super::NativeConstruct();
if (IsValid(StartButton))
{
StartButton->OnClicked.AddDynamic(this, &UTrainingWidget::OnStartButtonClicked);
}
if (IsValid(ResetButton))
{
ResetButton->OnClicked.AddDynamic(this, &UTrainingWidget::OnResetButtonClicked);
}
if (IsValid(TimerText))
{
TimerText->SetText(FText::FromString(TEXT("00:00")));
}
}
void UTrainingWidget::OnStartButtonClicked()
{
if (IsValid(TimeInputBox))
{
const FString InputStr = TimeInputBox->GetText().ToString();
float EnterTime = FCString::Atof(*InputStr);
if (EnterTime < 0.f)
EnterTime = 0.f;
CurrentTime = EnterTime;
int32 Minutes = FMath::FloorToInt(CurrentTime / 60.0f);
int32 Seconds = FMath::FloorToInt(CurrentTime) % 60;
FString FormattedTime = FString::Printf(TEXT("%02d:%02d"), Minutes, Seconds);
if (IsValid(TimerText))
{
TimerText->SetText(FText::FromString(FormattedTime));
}
// initialize timer
GetWorld()->GetTimerManager().ClearTimer(TimerHandle);
GetWorld()->GetTimerManager().SetTimer(TimerHandle, this, &UTrainingWidget::UpdateTimer, 1.0f, true);
}
}
void UTrainingWidget::OnResetButtonClicked()
{
if (IsValid(TimerText))
{
TimerText->SetText(FText::FromString(TEXT("00:00")));
}
GetWorld()->GetTimerManager().ClearTimer(TimerHandle);
}
void UTrainingWidget::UpdateTimer()
{
CurrentTime -= 1.0f;
if (CurrentTime <= 0.0f)
{
CurrentTime = 0.0f;
GetWorld()->GetTimerManager().ClearTimer(TimerHandle);
}
int32 Minutes = FMath::FloorToInt(CurrentTime / 60.0f);
int32 Seconds = FMath::FloorToInt(CurrentTime) % 60;
FString FormattedTime = FString::Printf(TEXT("%02d:%02d"), Minutes, Seconds);
if (IsValid(TimerText))
{
TimerText->SetText(FText::FromString(FormattedTime));
}
}
Editable Text Box에서 입력된 텍스트를 가져와서 형변환을 해주고, 분과 초를 나눠서 타이머에 적용시켜주었다.
블루프린트로도 버튼에 대한 온클릭 함수를 구현할 수 있었지만 이번에 진행하는 프로젝트는 UI가 많아지고 Base 위젯을 상속받아 사용하기 때문에 혼동될 수도 있어서 안전하게 코드로 온클릭 함수를 연결해주었다.
결과

이렇게 입력한 값은 항상 초로 인식하고 라운드 시계에서는 저렇게 분과 초로 나눠서 타이머를 구현해보았다.
마지막 말
초반에 역할 분리가 애매하게 된 상황에서 클래스를 구현하다보니 내 담당이 아닌 부분까지 함수를 만들어뒀었다.
다른 팀원의 혼동을 막기 위해 주말에 쓸데없는 함수는 다 과감하게 없애버리고, 중복되는 코드들을 줄여볼 생각이다.
훈련장 모드를 완성하고, 멀티 서버 공부를 더 해봐야겠다.
프로젝트에서 내가 담당한 인게임의 구조를 다시 한 번 틀을 잡아가는 단계를 거쳐야할 것 같다.
코드를 짜면서 구조 설계가 미흡해서 자꾸 딴 길로 새면서 기능 하나를 구현하는데 쓸데없이 많은 시간을 쓰고 있는 것 같다.
'Unreal Engine' 카테고리의 다른 글
[ UE ] TrainingMode 및 UI 구현 (0) | 2025.04.08 |
---|---|
[ UE ] 주말 작업동안의 트러블 슈팅 정리 (0) | 2025.04.07 |
[ UE ] 훈련장모드 베이스 설계 (0) | 2025.04.03 |
[ UE ] 데디케이트 서버 패키징을 해보자 (1) | 2025.03.31 |
[ UE ] 언리얼의 멀티네트워크 (0) | 2025.03.27 |