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.
|
||||
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:
|
||||
; - 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 "UIManagerSubsystem.h"
|
||||
#include "UIPrimaryGameLayout_Base.h"
|
||||
#include "UI/Common/UILoadingProfilesDataAsset.h"
|
||||
#include "UI/Common/UILoadingScreen_Base.h"
|
||||
#include "UI/UILayerTags.h"
|
||||
#include "ZMMOGameInstance.h"
|
||||
#include "ZMMOThemeSubsystem.h"
|
||||
@@ -212,13 +214,27 @@ void UUIFrontEndFlowSubsystem::ResolveAndPushScreen(EZMMOFrontEndState State)
|
||||
const FSoftObjectPath Path = Soft.ToSoftObjectPath();
|
||||
UAssetManager::GetStreamableManager().RequestAsyncLoad(
|
||||
Path,
|
||||
FStreamableDelegate::CreateWeakLambda(this, [this, Soft, Layer]()
|
||||
FStreamableDelegate::CreateWeakLambda(this, [this, Soft, Layer, State]()
|
||||
{
|
||||
if (UUIManagerSubsystem* M = GetUIManager())
|
||||
{
|
||||
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())
|
||||
{
|
||||
Zeus->OnServerTravelRequested.AddDynamic(this, &UUIFrontEndFlowSubsystem::HandleServerTravelRequested);
|
||||
Zeus->OnPlayerSpawned.AddDynamic(this, &UUIFrontEndFlowSubsystem::HandlePlayerSpawned);
|
||||
}
|
||||
bNetBound = true;
|
||||
}
|
||||
@@ -302,6 +319,7 @@ void UUIFrontEndFlowSubsystem::UnbindNetwork()
|
||||
if (UZeusNetworkSubsystem* Zeus = GetZeusNetwork())
|
||||
{
|
||||
Zeus->OnServerTravelRequested.RemoveDynamic(this, &UUIFrontEndFlowSubsystem::HandleServerTravelRequested);
|
||||
Zeus->OnPlayerSpawned.RemoveDynamic(this, &UUIFrontEndFlowSubsystem::HandlePlayerSpawned);
|
||||
}
|
||||
bNetBound = false;
|
||||
}
|
||||
@@ -390,11 +408,65 @@ void UUIFrontEndFlowSubsystem::HandleServerTravelRequested(const FString& MapNam
|
||||
|
||||
void UUIFrontEndFlowSubsystem::HandlePostLoadMap(UWorld* /*LoadedWorld*/)
|
||||
{
|
||||
if (bTravelingToWorld)
|
||||
if (!bTravelingToWorld)
|
||||
{
|
||||
bTravelingToWorld = false;
|
||||
SetState(EZMMOFrontEndState::InWorld);
|
||||
return;
|
||||
}
|
||||
|
||||
// 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)
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
|
||||
class UUIFrontEndScreenSet;
|
||||
class UUIManagerSubsystem;
|
||||
class UUILoadingScreen_Base;
|
||||
class UZMMOLoadingProfilesDataAsset;
|
||||
class UZeusNetworkSubsystem;
|
||||
class UZeusCharServerSubsystem;
|
||||
struct FZMMOMapDef;
|
||||
@@ -147,6 +149,24 @@ protected:
|
||||
UPROPERTY(Config, EditDefaultsOnly, Category = "FrontEnd|Maps")
|
||||
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:
|
||||
void BindNetwork();
|
||||
void UnbindNetwork();
|
||||
@@ -170,8 +190,26 @@ private:
|
||||
UFUNCTION()
|
||||
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);
|
||||
|
||||
/** 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
|
||||
* ainda não expõe um delegate/getter dedicado. Quando expuser, ligar aqui
|
||||
@@ -199,6 +237,18 @@ private:
|
||||
UPROPERTY(Transient)
|
||||
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 bTravelingToWorld = false;
|
||||
FDelegateHandle PostLoadMapHandle;
|
||||
|
||||
Reference in New Issue
Block a user