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>
This commit is contained in:
@@ -35,6 +35,12 @@ ScreenSetAsset=/Game/ZMMO/UI/FrontEnd/DA_FrontEndScreenSet.DA_FrontEndScreenSet
|
|||||||
; cliente resolve `mapId -> ClientLevel/spawns` via FZMMOMapDef rows.
|
; cliente resolve `mapId -> ClientLevel/spawns` via FZMMOMapDef rows.
|
||||||
MapsTableAsset=/Game/ZMMO/Data/World/DT_Maps.DT_Maps
|
MapsTableAsset=/Game/ZMMO/Data/World/DT_Maps.DT_Maps
|
||||||
|
|
||||||
|
; Loading dinâmico (etapas + dicas). DA_LoadingProfiles tem 1 perfil por
|
||||||
|
; contexto (EZMMOLoadingContext); DT_LoadingTips rotaciona durante o load.
|
||||||
|
; Cliente local marca etapas via eventos (sem opcode novo).
|
||||||
|
LoadingProfilesAsset=/Game/ZMMO/Data/UI/Loading/DA_LoadingProfiles.DA_LoadingProfiles
|
||||||
|
LoadingTipsAsset=/Game/ZMMO/Data/UI/Loading/DT_LoadingTips.DT_LoadingTips
|
||||||
|
|
||||||
; -----------------------------------------------------------------------------
|
; -----------------------------------------------------------------------------
|
||||||
; UI in-game (PR 19+). Espelho do front-end pattern:
|
; UI in-game (PR 19+). Espelho do front-end pattern:
|
||||||
; - ScreenSetAsset mapeia EZMMOInGameUIState -> WBP (Playing -> WBP_HUD,
|
; - ScreenSetAsset mapeia EZMMOInGameUIState -> WBP (Playing -> WBP_HUD,
|
||||||
|
|||||||
BIN
Content/ZMMO/Data/UI/Loading/DA_LoadingProfiles.uasset
Normal file
BIN
Content/ZMMO/Data/UI/Loading/DA_LoadingProfiles.uasset
Normal file
Binary file not shown.
BIN
Content/ZMMO/Data/UI/Loading/DT_LoadingTips.uasset
Normal file
BIN
Content/ZMMO/Data/UI/Loading/DT_LoadingTips.uasset
Normal file
Binary file not shown.
Binary file not shown.
BIN
Content/ZMMO/UI/Shared/Loading/WBP_Loading.uasset
Normal file
BIN
Content/ZMMO/UI/Shared/Loading/WBP_Loading.uasset
Normal file
Binary file not shown.
66
Source/ZMMO/Data/UI/LoadingTypes.h
Normal file
66
Source/ZMMO/Data/UI/LoadingTypes.h
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "LoadingTypes.generated.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Contexto do loading screen genérico. Cada contexto resolve um perfil
|
||||||
|
* (lista de etapas) no UZMMOLoadingProfilesDataAsset.
|
||||||
|
*
|
||||||
|
* Cliente local rastreia o progresso assinando eventos próprios
|
||||||
|
* (HandlePostLoadMap, OnPlayerSpawned, etc) — server não envia opcode de
|
||||||
|
* progresso, ver decisão em [[project_ui_loading_dynamic]].
|
||||||
|
*/
|
||||||
|
UENUM(BlueprintType)
|
||||||
|
enum class EZMMOLoadingContext : uint8
|
||||||
|
{
|
||||||
|
None,
|
||||||
|
FrontEndEnteringWorld, ///< FrontEnd → mundo (handoff CharServer→WorldServer + spawn).
|
||||||
|
InGameEnteringInstance, ///< Mundo → dungeon/instance (futuro).
|
||||||
|
InGameRespawn, ///< Pós-morte, retorno ao mundo (futuro).
|
||||||
|
};
|
||||||
|
|
||||||
|
UENUM(BlueprintType)
|
||||||
|
enum class EZMMOLoadingStepStatus : uint8
|
||||||
|
{
|
||||||
|
Pending, ///< Ainda não começou.
|
||||||
|
Running, ///< Em andamento (o "barber pole").
|
||||||
|
Done, ///< Concluída.
|
||||||
|
Failed, ///< Falhou (com reason em Status).
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Uma etapa do loading. StepId é o identificador estável usado por código
|
||||||
|
* pra chamar MarkStepDone(StepId); Label é o texto localizado mostrado.
|
||||||
|
*/
|
||||||
|
USTRUCT(BlueprintType)
|
||||||
|
struct FZMMOLoadingStepDef
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
/** Id estável (ex.: "Travel", "MapLoaded", "Connect", "Spawn"). */
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Loading")
|
||||||
|
FName StepId;
|
||||||
|
|
||||||
|
/** Texto localizado ("Carregando mapa...", "Conectando..."). */
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Loading")
|
||||||
|
FText Label;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perfil de loading por contexto. Designer edita no DA — código só
|
||||||
|
* consulta StepId pra avançar etapa.
|
||||||
|
*/
|
||||||
|
USTRUCT(BlueprintType)
|
||||||
|
struct FZMMOLoadingProfile
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
/** Etapas em ordem. ProgressBar = #Done / #Steps. */
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Loading")
|
||||||
|
TArray<FZMMOLoadingStepDef> Steps;
|
||||||
|
|
||||||
|
/** Título da tela (vazio = usa default da classe). */
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Loading")
|
||||||
|
FText TitleOverride;
|
||||||
|
};
|
||||||
10
Source/ZMMO/Game/UI/Common/UILoadingProfilesDataAsset.cpp
Normal file
10
Source/ZMMO/Game/UI/Common/UILoadingProfilesDataAsset.cpp
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
#include "UILoadingProfilesDataAsset.h"
|
||||||
|
|
||||||
|
FZMMOLoadingProfile UZMMOLoadingProfilesDataAsset::GetProfile(EZMMOLoadingContext Context) const
|
||||||
|
{
|
||||||
|
if (const FZMMOLoadingProfile* Found = Profiles.Find(Context))
|
||||||
|
{
|
||||||
|
return *Found;
|
||||||
|
}
|
||||||
|
return FZMMOLoadingProfile();
|
||||||
|
}
|
||||||
29
Source/ZMMO/Game/UI/Common/UILoadingProfilesDataAsset.h
Normal file
29
Source/ZMMO/Game/UI/Common/UILoadingProfilesDataAsset.h
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "Engine/DataAsset.h"
|
||||||
|
#include "UI/LoadingTypes.h"
|
||||||
|
#include "UILoadingProfilesDataAsset.generated.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mapa data-driven contexto → perfil (etapas + título). Designer edita o
|
||||||
|
* asset; código consulta por EZMMOLoadingContext na hora de configurar a
|
||||||
|
* tela. Asset canônico: DA_LoadingProfiles em /Game/ZMMO/Data/UI/Loading/.
|
||||||
|
*
|
||||||
|
* Configurado em DefaultGame.ini:
|
||||||
|
* [/Script/ZMMO.UIFrontEndFlowSubsystem]
|
||||||
|
* LoadingProfilesAsset=/Game/ZMMO/Data/UI/Loading/DA_LoadingProfiles.DA_LoadingProfiles
|
||||||
|
*/
|
||||||
|
UCLASS(BlueprintType)
|
||||||
|
class ZMMO_API UZMMOLoadingProfilesDataAsset : public UDataAsset
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
public:
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Loading")
|
||||||
|
TMap<EZMMOLoadingContext, FZMMOLoadingProfile> Profiles;
|
||||||
|
|
||||||
|
/** Retorna o perfil do contexto ou um perfil vazio (sem etapas). */
|
||||||
|
UFUNCTION(BlueprintCallable, Category = "Loading")
|
||||||
|
FZMMOLoadingProfile GetProfile(EZMMOLoadingContext Context) const;
|
||||||
|
};
|
||||||
235
Source/ZMMO/Game/UI/Common/UILoadingScreen_Base.cpp
Normal file
235
Source/ZMMO/Game/UI/Common/UILoadingScreen_Base.cpp
Normal file
@@ -0,0 +1,235 @@
|
|||||||
|
#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();
|
||||||
|
}
|
||||||
124
Source/ZMMO/Game/UI/Common/UILoadingScreen_Base.h
Normal file
124
Source/ZMMO/Game/UI/Common/UILoadingScreen_Base.h
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "UIActivatableScreen_Base.h"
|
||||||
|
#include "UI/LoadingTypes.h"
|
||||||
|
#include "UILoadingScreen_Base.generated.h"
|
||||||
|
|
||||||
|
class UCommonTextBlock;
|
||||||
|
class UProgressBar;
|
||||||
|
class UDataTable;
|
||||||
|
class UZMMOLoadingProfilesDataAsset;
|
||||||
|
|
||||||
|
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FZMMOOnLoadingComplete);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tela de loading genérica. Reusada pelo FrontEnd (handoff CharServer →
|
||||||
|
* WorldServer) e pelo InGame (entrada em instance/dungeon, respawn). Cada
|
||||||
|
* uso passa um EZMMOLoadingContext em Configure(), que resolve o perfil
|
||||||
|
* no UZMMOLoadingProfilesDataAsset (lista de etapas + título).
|
||||||
|
*
|
||||||
|
* Quem orquestra (UIFrontEndFlowSubsystem, futuro UIInGameFlowSubsystem)
|
||||||
|
* assina os próprios eventos (HandlePostLoadMap, OnPlayerSpawned, etc) e
|
||||||
|
* chama MarkStepDone(StepId) — server NÃO envia opcode de progresso (ver
|
||||||
|
* [[project_ui_loading_dynamic]]).
|
||||||
|
*
|
||||||
|
* Dismiss: a tela emite OnLoadingComplete quando todas as etapas estão
|
||||||
|
* Done. Caller decide quando popar (geralmente após pequeno fade-out).
|
||||||
|
*
|
||||||
|
* WBP concreto herda DIRETO desta classe. Subclasses C++ específicas só
|
||||||
|
* surgem quando houver necessidade real de estilo divergente.
|
||||||
|
*/
|
||||||
|
UCLASS(Abstract, Blueprintable)
|
||||||
|
class ZMMO_API UUILoadingScreen_Base : public UUIActivatableScreen_Base
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* Configura a tela: carrega o perfil do contexto, popula a pool de
|
||||||
|
* dicas filtrada e reseta estado das etapas. Pode ser chamado depois
|
||||||
|
* de NativeOnActivated (estado é reaplicado em RefreshUI).
|
||||||
|
*/
|
||||||
|
UFUNCTION(BlueprintCallable, Category = "Loading")
|
||||||
|
void Configure(EZMMOLoadingContext InContext,
|
||||||
|
UZMMOLoadingProfilesDataAsset* Profiles,
|
||||||
|
UDataTable* TipsTable);
|
||||||
|
|
||||||
|
UFUNCTION(BlueprintCallable, Category = "Loading")
|
||||||
|
void MarkStepRunning(FName StepId);
|
||||||
|
|
||||||
|
UFUNCTION(BlueprintCallable, Category = "Loading")
|
||||||
|
void MarkStepDone(FName StepId);
|
||||||
|
|
||||||
|
UFUNCTION(BlueprintCallable, Category = "Loading")
|
||||||
|
void MarkStepFailed(FName StepId, const FText& Reason);
|
||||||
|
|
||||||
|
UFUNCTION(BlueprintPure, Category = "Loading")
|
||||||
|
bool AreAllStepsDone() const;
|
||||||
|
|
||||||
|
/** Disparado UMA vez quando todas as etapas viram Done. */
|
||||||
|
UPROPERTY(BlueprintAssignable, Category = "Loading")
|
||||||
|
FZMMOOnLoadingComplete OnLoadingComplete;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
virtual void NativeOnActivated() override;
|
||||||
|
virtual void NativeOnDeactivated() override;
|
||||||
|
|
||||||
|
/** Reaplica todo o estado aos widgets bindados (texto, progress, tip). */
|
||||||
|
void RefreshUI();
|
||||||
|
void RotateTip();
|
||||||
|
void StartTipTimer();
|
||||||
|
void StopTipTimer();
|
||||||
|
|
||||||
|
/** Acha índice da etapa por StepId; -1 se não existir. */
|
||||||
|
int32 FindStepIndex(FName StepId) const;
|
||||||
|
|
||||||
|
/** Status atual da etapa em [Steps_]; Pending se não rastreada. */
|
||||||
|
EZMMOLoadingStepStatus GetStepStatus(int32 Index) const;
|
||||||
|
|
||||||
|
/** Texto exibido em Text_Status — etapa Running atual ou última Done. */
|
||||||
|
FText ComputeCurrentStatusText() const;
|
||||||
|
|
||||||
|
/** Progresso 0..1: #Done / max(1,#Steps). Running conta como meio passo. */
|
||||||
|
float ComputeProgress01() const;
|
||||||
|
|
||||||
|
// ---- Bind widgets (BindWidgetOptional — WBP pode omitir qualquer) ----
|
||||||
|
UPROPERTY(meta = (BindWidgetOptional))
|
||||||
|
TObjectPtr<UCommonTextBlock> Text_Title;
|
||||||
|
|
||||||
|
UPROPERTY(meta = (BindWidgetOptional))
|
||||||
|
TObjectPtr<UCommonTextBlock> Text_Status;
|
||||||
|
|
||||||
|
UPROPERTY(meta = (BindWidgetOptional))
|
||||||
|
TObjectPtr<UProgressBar> ProgressBar_Progress;
|
||||||
|
|
||||||
|
UPROPERTY(meta = (BindWidgetOptional))
|
||||||
|
TObjectPtr<UCommonTextBlock> Text_Tip;
|
||||||
|
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Loading")
|
||||||
|
FText TitleDefault = FText::FromString(TEXT("Carregando"));
|
||||||
|
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Loading")
|
||||||
|
FText StatusDefault = FText::FromString(TEXT("Entrando no mundo..."));
|
||||||
|
|
||||||
|
/** Intervalo entre dicas. 0 = sem rotação (mostra a primeira dica fixa). */
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Loading")
|
||||||
|
float TipRotationIntervalSeconds = 6.f;
|
||||||
|
|
||||||
|
private:
|
||||||
|
EZMMOLoadingContext Context_ = EZMMOLoadingContext::None;
|
||||||
|
|
||||||
|
UPROPERTY(Transient)
|
||||||
|
FZMMOLoadingProfile Profile_;
|
||||||
|
|
||||||
|
UPROPERTY(Transient)
|
||||||
|
TMap<FName, EZMMOLoadingStepStatus> StepStatus_;
|
||||||
|
|
||||||
|
UPROPERTY(Transient)
|
||||||
|
TArray<FText> TipPool_;
|
||||||
|
|
||||||
|
int32 NextTipIndex_ = 0;
|
||||||
|
bool bCompleteFired_ = false;
|
||||||
|
FTimerHandle TipTimerHandle_;
|
||||||
|
};
|
||||||
30
Source/ZMMO/Game/UI/Common/UILoadingTipRow.h
Normal file
30
Source/ZMMO/Game/UI/Common/UILoadingTipRow.h
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "Engine/DataTable.h"
|
||||||
|
#include "GameplayTagContainer.h"
|
||||||
|
#include "UI/LoadingTypes.h"
|
||||||
|
#include "UILoadingTipRow.generated.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Row do DT_LoadingTips. Cada row = uma dica mostrada (rotaciona durante o
|
||||||
|
* loading). FText é localizável (basta extrair pelo gather de tradução do
|
||||||
|
* UE). Filtra por contexto: Context=None significa "qualquer loading".
|
||||||
|
*/
|
||||||
|
USTRUCT(BlueprintType)
|
||||||
|
struct FZMMOLoadingTipRow : public FTableRowBase
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
/** Texto da dica (localizável). */
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Loading")
|
||||||
|
FText Tip;
|
||||||
|
|
||||||
|
/** Restringe a dica a um contexto. None = todos. */
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Loading")
|
||||||
|
EZMMOLoadingContext Context = EZMMOLoadingContext::None;
|
||||||
|
|
||||||
|
/** Tag opcional pra categorizar (PvE/PvP/Crafting...) — futuro filtro. */
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Loading")
|
||||||
|
FGameplayTag Category;
|
||||||
|
};
|
||||||
@@ -5,6 +5,8 @@
|
|||||||
#include "UIFrontEndScreenSet.h"
|
#include "UIFrontEndScreenSet.h"
|
||||||
#include "UIManagerSubsystem.h"
|
#include "UIManagerSubsystem.h"
|
||||||
#include "UIPrimaryGameLayout_Base.h"
|
#include "UIPrimaryGameLayout_Base.h"
|
||||||
|
#include "UI/Common/UILoadingProfilesDataAsset.h"
|
||||||
|
#include "UI/Common/UILoadingScreen_Base.h"
|
||||||
#include "UI/UILayerTags.h"
|
#include "UI/UILayerTags.h"
|
||||||
#include "ZMMOGameInstance.h"
|
#include "ZMMOGameInstance.h"
|
||||||
#include "ZMMOThemeSubsystem.h"
|
#include "ZMMOThemeSubsystem.h"
|
||||||
@@ -212,13 +214,27 @@ void UUIFrontEndFlowSubsystem::ResolveAndPushScreen(EZMMOFrontEndState State)
|
|||||||
const FSoftObjectPath Path = Soft.ToSoftObjectPath();
|
const FSoftObjectPath Path = Soft.ToSoftObjectPath();
|
||||||
UAssetManager::GetStreamableManager().RequestAsyncLoad(
|
UAssetManager::GetStreamableManager().RequestAsyncLoad(
|
||||||
Path,
|
Path,
|
||||||
FStreamableDelegate::CreateWeakLambda(this, [this, Soft, Layer]()
|
FStreamableDelegate::CreateWeakLambda(this, [this, Soft, Layer, State]()
|
||||||
{
|
{
|
||||||
if (UUIManagerSubsystem* M = GetUIManager())
|
if (UUIManagerSubsystem* M = GetUIManager())
|
||||||
{
|
{
|
||||||
if (UClass* Cls = Soft.Get())
|
if (UClass* Cls = Soft.Get())
|
||||||
{
|
{
|
||||||
M->PushScreenToLayer(Layer, Cls);
|
UCommonActivatableWidget* Pushed = M->PushScreenToLayer(Layer, Cls);
|
||||||
|
|
||||||
|
// Loading: configura logo após o push e marca etapa "Travel"
|
||||||
|
// (chegamos aqui via HandleServerTravelRequested → SetState).
|
||||||
|
if (State == EZMMOFrontEndState::EnteringWorld)
|
||||||
|
{
|
||||||
|
if (UUILoadingScreen_Base* Loading = Cast<UUILoadingScreen_Base>(Pushed))
|
||||||
|
{
|
||||||
|
ActiveLoadingScreen = Loading;
|
||||||
|
Loading->Configure(EZMMOLoadingContext::FrontEndEnteringWorld,
|
||||||
|
GetLoadingProfiles(), GetLoadingTips());
|
||||||
|
Loading->OnLoadingComplete.AddDynamic(this, &UUIFrontEndFlowSubsystem::HandleLoadingComplete);
|
||||||
|
Loading->MarkStepDone(TEXT("Travel"));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
@@ -283,6 +299,7 @@ void UUIFrontEndFlowSubsystem::BindNetwork()
|
|||||||
if (UZeusNetworkSubsystem* Zeus = GetZeusNetwork())
|
if (UZeusNetworkSubsystem* Zeus = GetZeusNetwork())
|
||||||
{
|
{
|
||||||
Zeus->OnServerTravelRequested.AddDynamic(this, &UUIFrontEndFlowSubsystem::HandleServerTravelRequested);
|
Zeus->OnServerTravelRequested.AddDynamic(this, &UUIFrontEndFlowSubsystem::HandleServerTravelRequested);
|
||||||
|
Zeus->OnPlayerSpawned.AddDynamic(this, &UUIFrontEndFlowSubsystem::HandlePlayerSpawned);
|
||||||
}
|
}
|
||||||
bNetBound = true;
|
bNetBound = true;
|
||||||
}
|
}
|
||||||
@@ -302,6 +319,7 @@ void UUIFrontEndFlowSubsystem::UnbindNetwork()
|
|||||||
if (UZeusNetworkSubsystem* Zeus = GetZeusNetwork())
|
if (UZeusNetworkSubsystem* Zeus = GetZeusNetwork())
|
||||||
{
|
{
|
||||||
Zeus->OnServerTravelRequested.RemoveDynamic(this, &UUIFrontEndFlowSubsystem::HandleServerTravelRequested);
|
Zeus->OnServerTravelRequested.RemoveDynamic(this, &UUIFrontEndFlowSubsystem::HandleServerTravelRequested);
|
||||||
|
Zeus->OnPlayerSpawned.RemoveDynamic(this, &UUIFrontEndFlowSubsystem::HandlePlayerSpawned);
|
||||||
}
|
}
|
||||||
bNetBound = false;
|
bNetBound = false;
|
||||||
}
|
}
|
||||||
@@ -390,11 +408,65 @@ void UUIFrontEndFlowSubsystem::HandleServerTravelRequested(const FString& MapNam
|
|||||||
|
|
||||||
void UUIFrontEndFlowSubsystem::HandlePostLoadMap(UWorld* /*LoadedWorld*/)
|
void UUIFrontEndFlowSubsystem::HandlePostLoadMap(UWorld* /*LoadedWorld*/)
|
||||||
{
|
{
|
||||||
if (bTravelingToWorld)
|
if (!bTravelingToWorld)
|
||||||
{
|
{
|
||||||
bTravelingToWorld = false;
|
return;
|
||||||
SetState(EZMMOFrontEndState::InWorld);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Loading dinâmico: marca a etapa "MapLoaded" e espera o spawn antes de
|
||||||
|
// liberar o InWorld (a tela limpa via OnLoadingComplete). Fallback: se
|
||||||
|
// não há screen ativa (DA não configurado), comportamento legado —
|
||||||
|
// transita já pra InWorld pra não travar.
|
||||||
|
if (UUILoadingScreen_Base* Loading = ActiveLoadingScreen.Get())
|
||||||
|
{
|
||||||
|
Loading->MarkStepDone(TEXT("MapLoaded"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
bTravelingToWorld = false;
|
||||||
|
SetState(EZMMOFrontEndState::InWorld);
|
||||||
|
}
|
||||||
|
|
||||||
|
void UUIFrontEndFlowSubsystem::HandlePlayerSpawned(int32 /*EntityId*/, bool bIsLocal,
|
||||||
|
FVector /*PosCm*/, float /*YawDeg*/,
|
||||||
|
int64 /*ServerTimeMs*/)
|
||||||
|
{
|
||||||
|
if (!bIsLocal) return;
|
||||||
|
if (UUILoadingScreen_Base* Loading = ActiveLoadingScreen.Get())
|
||||||
|
{
|
||||||
|
Loading->MarkStepDone(TEXT("Spawn"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void UUIFrontEndFlowSubsystem::HandleLoadingComplete()
|
||||||
|
{
|
||||||
|
if (UUILoadingScreen_Base* Loading = ActiveLoadingScreen.Get())
|
||||||
|
{
|
||||||
|
Loading->OnLoadingComplete.RemoveDynamic(this, &UUIFrontEndFlowSubsystem::HandleLoadingComplete);
|
||||||
|
}
|
||||||
|
ActiveLoadingScreen.Reset();
|
||||||
|
bTravelingToWorld = false;
|
||||||
|
SetState(EZMMOFrontEndState::InWorld);
|
||||||
|
}
|
||||||
|
|
||||||
|
UZMMOLoadingProfilesDataAsset* UUIFrontEndFlowSubsystem::GetLoadingProfiles()
|
||||||
|
{
|
||||||
|
if (LoadingProfiles) return LoadingProfiles;
|
||||||
|
if (!LoadingProfilesAsset.IsNull())
|
||||||
|
{
|
||||||
|
LoadingProfiles = LoadingProfilesAsset.LoadSynchronous();
|
||||||
|
}
|
||||||
|
return LoadingProfiles;
|
||||||
|
}
|
||||||
|
|
||||||
|
UDataTable* UUIFrontEndFlowSubsystem::GetLoadingTips()
|
||||||
|
{
|
||||||
|
if (LoadingTips) return LoadingTips;
|
||||||
|
if (!LoadingTipsAsset.IsNull())
|
||||||
|
{
|
||||||
|
LoadingTips = LoadingTipsAsset.LoadSynchronous();
|
||||||
|
}
|
||||||
|
return LoadingTips;
|
||||||
}
|
}
|
||||||
|
|
||||||
void UUIFrontEndFlowSubsystem::HandleServerHelloTheme(FName ThemeId)
|
void UUIFrontEndFlowSubsystem::HandleServerHelloTheme(FName ThemeId)
|
||||||
|
|||||||
@@ -8,6 +8,8 @@
|
|||||||
|
|
||||||
class UUIFrontEndScreenSet;
|
class UUIFrontEndScreenSet;
|
||||||
class UUIManagerSubsystem;
|
class UUIManagerSubsystem;
|
||||||
|
class UUILoadingScreen_Base;
|
||||||
|
class UZMMOLoadingProfilesDataAsset;
|
||||||
class UZeusNetworkSubsystem;
|
class UZeusNetworkSubsystem;
|
||||||
class UZeusCharServerSubsystem;
|
class UZeusCharServerSubsystem;
|
||||||
struct FZMMOMapDef;
|
struct FZMMOMapDef;
|
||||||
@@ -147,6 +149,24 @@ protected:
|
|||||||
UPROPERTY(Config, EditDefaultsOnly, Category = "FrontEnd|Maps")
|
UPROPERTY(Config, EditDefaultsOnly, Category = "FrontEnd|Maps")
|
||||||
TSoftObjectPtr<UDataTable> MapsTableAsset;
|
TSoftObjectPtr<UDataTable> MapsTableAsset;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DataAsset com os perfis de loading (etapas por contexto). Configure em
|
||||||
|
* DefaultGame.ini:
|
||||||
|
* [/Script/ZMMO.UIFrontEndFlowSubsystem]
|
||||||
|
* LoadingProfilesAsset=/Game/ZMMO/Data/UI/Loading/DA_LoadingProfiles.DA_LoadingProfiles
|
||||||
|
*/
|
||||||
|
UPROPERTY(Config, EditDefaultsOnly, Category = "FrontEnd|Loading")
|
||||||
|
TSoftObjectPtr<UZMMOLoadingProfilesDataAsset> LoadingProfilesAsset;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DataTable com dicas (rows = FZMMOLoadingTipRow). Opcional — vazio
|
||||||
|
* significa "sem dicas". Configure em DefaultGame.ini:
|
||||||
|
* [/Script/ZMMO.UIFrontEndFlowSubsystem]
|
||||||
|
* LoadingTipsAsset=/Game/ZMMO/Data/UI/Loading/DT_LoadingTips.DT_LoadingTips
|
||||||
|
*/
|
||||||
|
UPROPERTY(Config, EditDefaultsOnly, Category = "FrontEnd|Loading")
|
||||||
|
TSoftObjectPtr<UDataTable> LoadingTipsAsset;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void BindNetwork();
|
void BindNetwork();
|
||||||
void UnbindNetwork();
|
void UnbindNetwork();
|
||||||
@@ -170,8 +190,26 @@ private:
|
|||||||
UFUNCTION()
|
UFUNCTION()
|
||||||
void HandleServerTravelRequested(const FString& MapName, const FString& MapPath);
|
void HandleServerTravelRequested(const FString& MapName, const FString& MapPath);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Spawn do player local — fim da última etapa do loading EnteringWorld.
|
||||||
|
* Assinatura espelha FZeusOnPlayerSpawned (FiveParams).
|
||||||
|
*/
|
||||||
|
UFUNCTION()
|
||||||
|
void HandlePlayerSpawned(int32 EntityId, bool bIsLocal, FVector PosCm,
|
||||||
|
float YawDeg, int64 ServerTimeMs);
|
||||||
|
|
||||||
|
/** Disparado pela tela de loading quando todas as etapas viram Done. */
|
||||||
|
UFUNCTION()
|
||||||
|
void HandleLoadingComplete();
|
||||||
|
|
||||||
void HandlePostLoadMap(UWorld* LoadedWorld);
|
void HandlePostLoadMap(UWorld* LoadedWorld);
|
||||||
|
|
||||||
|
/** Resolve+carrega o DA de perfis (sync). */
|
||||||
|
UZMMOLoadingProfilesDataAsset* GetLoadingProfiles();
|
||||||
|
|
||||||
|
/** Resolve+carrega o DT de dicas (sync). */
|
||||||
|
UDataTable* GetLoadingTips();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Dívida técnica (D5): ServerHello traz ThemeId, mas o UZeusNetworkSubsystem
|
* Dívida técnica (D5): ServerHello traz ThemeId, mas o UZeusNetworkSubsystem
|
||||||
* ainda não expõe um delegate/getter dedicado. Quando expuser, ligar aqui
|
* ainda não expõe um delegate/getter dedicado. Quando expuser, ligar aqui
|
||||||
@@ -199,6 +237,18 @@ private:
|
|||||||
UPROPERTY(Transient)
|
UPROPERTY(Transient)
|
||||||
TObjectPtr<UUIFrontEndScreenSet> ScreenSet;
|
TObjectPtr<UUIFrontEndScreenSet> ScreenSet;
|
||||||
|
|
||||||
|
UPROPERTY(Transient)
|
||||||
|
TObjectPtr<UZMMOLoadingProfilesDataAsset> LoadingProfiles;
|
||||||
|
|
||||||
|
UPROPERTY(Transient)
|
||||||
|
TObjectPtr<UDataTable> LoadingTips;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tela de loading atualmente em Modal. Weak porque o stack do CommonUI
|
||||||
|
* é dono — se popar por outro caminho não queremos dangling.
|
||||||
|
*/
|
||||||
|
TWeakObjectPtr<UUILoadingScreen_Base> ActiveLoadingScreen;
|
||||||
|
|
||||||
bool bNetBound = false;
|
bool bNetBound = false;
|
||||||
bool bTravelingToWorld = false;
|
bool bTravelingToWorld = false;
|
||||||
FDelegateHandle PostLoadMapHandle;
|
FDelegateHandle PostLoadMapHandle;
|
||||||
|
|||||||
Reference in New Issue
Block a user