Files
ZMMO/Source/ZMMO/Game/UI/Common/UILoadingScreen_Base.cpp
Mateus Rodrigues 079d05c117 feat(ui/loading): tela de loading dinamica data-driven (etapas + dicas)
- UUILoadingScreen_Base (Game/UI/Common/) generica: Configure(Context),
  MarkStepRunning/Done/Failed, OnLoadingComplete delegate, progress bar
  reativa e rotacao automatica de dicas via timer.
- Etapas configuradas por contexto em UZMMOLoadingProfilesDataAsset
  (DA_LoadingProfiles). Perfil inicial FrontEndEnteringWorld:
  Travel -> MapLoaded -> Spawn.
- Dicas em DT_LoadingTips (FText localizavel + filtro por contexto +
  GameplayTag de categoria). 4 dicas seed.
- UIFrontEndFlowSubsystem agora bloqueia a transicao InWorld ate o
  HandlePlayerSpawned (bIsLocal=true) — usuario nao ve mundo vazio.
- WBP_Loading em /UI/Shared/Loading/ (neutro, reusavel pelo InGame futuro);
  DA_FrontEndScreenSet[EnteringWorld] aponta pra ele.
- DefaultGame.ini wire LoadingProfilesAsset + LoadingTipsAsset.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 02:24:48 -03:00

236 lines
5.5 KiB
C++

#include "UILoadingScreen_Base.h"
#include "Components/ProgressBar.h"
#include "CommonTextBlock.h"
#include "Engine/DataTable.h"
#include "Engine/World.h"
#include "TimerManager.h"
#include "UILoadingProfilesDataAsset.h"
#include "UILoadingTipRow.h"
void UUILoadingScreen_Base::Configure(EZMMOLoadingContext InContext,
UZMMOLoadingProfilesDataAsset* Profiles,
UDataTable* TipsTable)
{
Context_ = InContext;
bCompleteFired_ = false;
// Perfil (etapas + título).
Profile_ = (Profiles != nullptr)
? Profiles->GetProfile(InContext)
: FZMMOLoadingProfile();
StepStatus_.Reset();
for (const FZMMOLoadingStepDef& Step : Profile_.Steps)
{
StepStatus_.Add(Step.StepId, EZMMOLoadingStepStatus::Pending);
}
// Pool de dicas filtrada por contexto (None = qualquer).
TipPool_.Reset();
if (TipsTable != nullptr)
{
TipsTable->ForeachRow<FZMMOLoadingTipRow>(TEXT("UUILoadingScreen_Base::Configure"),
[this](const FName& /*Key*/, const FZMMOLoadingTipRow& Row)
{
if (Row.Context == EZMMOLoadingContext::None || Row.Context == Context_)
{
if (!Row.Tip.IsEmpty())
{
TipPool_.Add(Row.Tip);
}
}
});
}
NextTipIndex_ = 0;
RefreshUI();
if (IsActivated())
{
StartTipTimer();
}
}
void UUILoadingScreen_Base::NativeOnActivated()
{
Super::NativeOnActivated();
RefreshUI();
StartTipTimer();
}
void UUILoadingScreen_Base::NativeOnDeactivated()
{
StopTipTimer();
Super::NativeOnDeactivated();
}
void UUILoadingScreen_Base::MarkStepRunning(FName StepId)
{
if (EZMMOLoadingStepStatus* Status = StepStatus_.Find(StepId))
{
// Já Done não regride.
if (*Status != EZMMOLoadingStepStatus::Done && *Status != EZMMOLoadingStepStatus::Failed)
{
*Status = EZMMOLoadingStepStatus::Running;
RefreshUI();
}
}
}
void UUILoadingScreen_Base::MarkStepDone(FName StepId)
{
if (EZMMOLoadingStepStatus* Status = StepStatus_.Find(StepId))
{
*Status = EZMMOLoadingStepStatus::Done;
RefreshUI();
if (!bCompleteFired_ && AreAllStepsDone())
{
bCompleteFired_ = true;
OnLoadingComplete.Broadcast();
}
}
}
void UUILoadingScreen_Base::MarkStepFailed(FName StepId, const FText& Reason)
{
if (EZMMOLoadingStepStatus* Status = StepStatus_.Find(StepId))
{
*Status = EZMMOLoadingStepStatus::Failed;
if (Text_Status && !Reason.IsEmpty())
{
Text_Status->SetText(Reason);
}
RefreshUI();
}
}
bool UUILoadingScreen_Base::AreAllStepsDone() const
{
if (Profile_.Steps.Num() == 0)
{
return false;
}
for (const FZMMOLoadingStepDef& Step : Profile_.Steps)
{
const EZMMOLoadingStepStatus* Status = StepStatus_.Find(Step.StepId);
if (Status == nullptr || *Status != EZMMOLoadingStepStatus::Done)
{
return false;
}
}
return true;
}
int32 UUILoadingScreen_Base::FindStepIndex(FName StepId) const
{
for (int32 i = 0; i < Profile_.Steps.Num(); ++i)
{
if (Profile_.Steps[i].StepId == StepId)
{
return i;
}
}
return INDEX_NONE;
}
EZMMOLoadingStepStatus UUILoadingScreen_Base::GetStepStatus(int32 Index) const
{
if (!Profile_.Steps.IsValidIndex(Index)) return EZMMOLoadingStepStatus::Pending;
const EZMMOLoadingStepStatus* Status = StepStatus_.Find(Profile_.Steps[Index].StepId);
return Status ? *Status : EZMMOLoadingStepStatus::Pending;
}
FText UUILoadingScreen_Base::ComputeCurrentStatusText() const
{
// Prioridade: Running > último Done > primeiro Pending > Status default.
int32 LastDone = INDEX_NONE;
for (int32 i = 0; i < Profile_.Steps.Num(); ++i)
{
const EZMMOLoadingStepStatus St = GetStepStatus(i);
if (St == EZMMOLoadingStepStatus::Running)
{
return Profile_.Steps[i].Label;
}
if (St == EZMMOLoadingStepStatus::Done)
{
LastDone = i;
}
}
if (LastDone != INDEX_NONE)
{
return Profile_.Steps[LastDone].Label;
}
if (Profile_.Steps.Num() > 0)
{
return Profile_.Steps[0].Label;
}
return StatusDefault;
}
float UUILoadingScreen_Base::ComputeProgress01() const
{
const int32 N = Profile_.Steps.Num();
if (N == 0) return 0.f;
float Acc = 0.f;
for (int32 i = 0; i < N; ++i)
{
const EZMMOLoadingStepStatus St = GetStepStatus(i);
if (St == EZMMOLoadingStepStatus::Done) Acc += 1.f;
else if (St == EZMMOLoadingStepStatus::Running) Acc += 0.5f;
}
return FMath::Clamp(Acc / static_cast<float>(N), 0.f, 1.f);
}
void UUILoadingScreen_Base::RefreshUI()
{
if (Text_Title)
{
const FText& Title = Profile_.TitleOverride.IsEmpty() ? TitleDefault : Profile_.TitleOverride;
Text_Title->SetText(Title);
}
if (Text_Status)
{
Text_Status->SetText(ComputeCurrentStatusText());
}
if (ProgressBar_Progress)
{
ProgressBar_Progress->SetPercent(ComputeProgress01());
}
if (Text_Tip && TipPool_.Num() > 0 && Text_Tip->GetText().IsEmpty())
{
// Primeira dica na inicialização (timer cuida das próximas).
Text_Tip->SetText(TipPool_[0]);
NextTipIndex_ = TipPool_.Num() > 1 ? 1 : 0;
}
}
void UUILoadingScreen_Base::RotateTip()
{
if (!Text_Tip || TipPool_.Num() == 0) return;
Text_Tip->SetText(TipPool_[NextTipIndex_]);
NextTipIndex_ = (NextTipIndex_ + 1) % TipPool_.Num();
}
void UUILoadingScreen_Base::StartTipTimer()
{
StopTipTimer();
if (TipRotationIntervalSeconds <= 0.f || TipPool_.Num() <= 1) return;
if (UWorld* W = GetWorld())
{
W->GetTimerManager().SetTimer(TipTimerHandle_, this,
&UUILoadingScreen_Base::RotateTip,
TipRotationIntervalSeconds, /*bLoop=*/true);
}
}
void UUILoadingScreen_Base::StopTipTimer()
{
if (UWorld* W = GetWorld())
{
W->GetTimerManager().ClearTimer(TipTimerHandle_);
}
TipTimerHandle_.Invalidate();
}