feat(frontend): primeira tela WBP_Boot (conecta ao ZeusCharServer)
Primeira tela concreta do front-end, cópia fiel do WBP_BootScreen gerado pelo Zeus UMG Forge, adaptada aos componentes do projeto: - UUIBootScreen_Base : UUIActivatableScreen_Base — observa o UZeusCharServerSubsystem; libera "Iniciar" no OnConnected; clique → UUIFrontEndFlowSubsystem::RequestEnterLogin → estado Login. - UUISpinner_Base (componente compartilhado temável) + FUIStyleSpinner. - FUIStyle.ScreenBackground (brush data-driven); fundo via DT_UI_Styles (T_Boot_Background em UI/Themes/Default/Boot), sem hard-ref no WBP (§5). - ThemeKeys: Boot_Background/Logo/Music. - Correção do Flow: pré-login agora conecta no UZeusCharServerSubsystem (WebSocket 7100), não mais no UDP do world server (que fica só p/ EnteringWorld/InWorld); RequestEnterLogin() adicionado. - WBP_Boot duplicado fiel + reparent UUIBootScreen_Base; swap manual no Designer (Btn_Start/Spinner_Connect/Text_Status); slots de layout (Container_Root/Stage/Content) ajustados p/ preencher/centralizar. - WBP_PrimaryGameLayout: Stack_Modal corrigido p/ Fill/Fill. - DA_FrontEndScreenSet: StateScreens[Boot] = WBP_Boot. - ARQUITETURA.md (§2.1/§2.2/§4.8 + nota CharServer). Validado em PIE: tela centralizada, fundo, conexão ao ZeusCharServer e liberação do botão. Crash do plugin tratado em commit separado do server. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -210,7 +210,8 @@ Content/
|
||||
│ ├── UI/ ← HUD, Login, CharacterSelect, Inventory,
|
||||
│ │ │ Chat, Party, Guild, Trade, Quest, Map,
|
||||
│ │ │ Shared, Icons
|
||||
│ │ ├── Shared/ ← WBPs reutilizáveis (UI_Button_Master…)
|
||||
│ │ ├── Shared/ ← WBPs reutilizáveis (UI_Button_Master,
|
||||
│ │ │ UI_Panel_Master, UI_Spinner_Master)
|
||||
│ │ ├── FrontEnd/ ← WBP_PrimaryGameLayout, DA_FrontEndScreenSet,
|
||||
│ │ │ telas refinadas (Boot/Login/… vindas do Forge)
|
||||
│ │ ├── Fonts/ ← Font_*/FF_* (Cinzel, Rajdhani)
|
||||
@@ -268,9 +269,11 @@ Source/ZMMO/
|
||||
│ ├── Network/ ← UZMMOWorldSubsystem
|
||||
│ └── UI/ ← runtime de UI (não é contrato de dados)
|
||||
│ ├── ZMMOThemeSubsystem.* ← resolução de tema
|
||||
│ ├── Widgets/ ← UUIButton_Base, UUIPanel_Base
|
||||
│ ├── Widgets/ ← UUIButton_Base, UUIPanel_Base,
|
||||
│ │ UUISpinner_Base
|
||||
│ └── FrontEnd/ ← infra de navegação CommonUI:
|
||||
│ UUIActivatableScreen_Base,
|
||||
│ UUIBootScreen_Base (1ª tela),
|
||||
│ UUIPrimaryGameLayout_Base,
|
||||
│ UUIManagerSubsystem (LocalPlayer),
|
||||
│ UUIFrontEndFlowSubsystem (GameInstance),
|
||||
@@ -622,6 +625,15 @@ Fluxo geral: **Boot → Connecting → Login → ServerSelect → Lobby → Ente
|
||||
navegadas por um stack CommonUI **dentro** da tela do Lobby — não são estados
|
||||
de topo do fluxo.
|
||||
|
||||
**Rede do pré-login:** o `UUIFrontEndFlowSubsystem` dirige a conexão ao
|
||||
**`UZeusCharServerSubsystem`** (WebSocket, `CharServerUrl` de Project Settings,
|
||||
default `ws://127.0.0.1:7100/zeus`) ao entrar em **Boot** — NÃO o
|
||||
`UZeusNetworkSubsystem` (UDP, world server, usado só no travel
|
||||
EnteringWorld→InWorld). A tela Boot (`UUIBootScreen_Base`) observa o
|
||||
CharServer: libera o botão "Iniciar" no `OnConnected`; o clique chama
|
||||
`UUIFrontEndFlowSubsystem::RequestEnterLogin()` → estado Login. Tela nunca
|
||||
segura hard-ref de asset de tema (cores via `FUIStyle`; §5).
|
||||
|
||||
1. Prototipar a tela em HTML/CSS em `Tools/Templates/MMO_Widget/` (ou usar os
|
||||
templates já existentes).
|
||||
2. Rodar o Zeus UMG Forge (HTML→UMG). Saída crua: `Content/AutoCreated/<batch>/`.
|
||||
|
||||
Binary file not shown.
Binary file not shown.
BIN
Content/ZMMO/UI/FrontEnd/WBP_Boot.uasset
Normal file
BIN
Content/ZMMO/UI/FrontEnd/WBP_Boot.uasset
Normal file
Binary file not shown.
Binary file not shown.
BIN
Content/ZMMO/UI/Shared/UI_Spinner_Master.uasset
Normal file
BIN
Content/ZMMO/UI/Shared/UI_Spinner_Master.uasset
Normal file
Binary file not shown.
BIN
Content/ZMMO/UI/Themes/Default/Boot/T_Boot_Background.uasset
Normal file
BIN
Content/ZMMO/UI/Themes/Default/Boot/T_Boot_Background.uasset
Normal file
Binary file not shown.
@@ -12,6 +12,11 @@ enum class EZMMOThemeKey : uint8
|
||||
HUD_HealthBar UMETA(DisplayName = "HUD - Health Bar"),
|
||||
HUD_ManaBar UMETA(DisplayName = "HUD - Mana Bar"),
|
||||
|
||||
// Boot
|
||||
Boot_Background UMETA(DisplayName = "Boot - Background"),
|
||||
Boot_Logo UMETA(DisplayName = "Boot - Logo"),
|
||||
Boot_Music UMETA(DisplayName = "Boot - Music"),
|
||||
|
||||
// Login / Character Select
|
||||
Login_Background UMETA(DisplayName = "Login - Background"),
|
||||
Login_Logo UMETA(DisplayName = "Login - Logo"),
|
||||
|
||||
@@ -42,6 +42,17 @@ struct ZMMO_API FUIStyle
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style")
|
||||
FUIStyleTooltip Tooltip;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style")
|
||||
FUIStyleSpinner Spinner;
|
||||
|
||||
/**
|
||||
* Fundo de tela cheia do front-end (ex.: Boot/Login). Brush data-driven:
|
||||
* a tela aplica no seu Border_Bg via RefreshUIStyle — nunca hard-ref no
|
||||
* WBP (ARQUITETURA.md §5). Preenchido em DT_UI_Styles.
|
||||
*/
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style")
|
||||
FSlateBrush ScreenBackground;
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -444,3 +444,34 @@ struct ZMMO_API FUIStyleTooltip
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|Tooltip")
|
||||
FLinearColor TextColor = FLinearColor(FColor(244, 240, 230)); // Text
|
||||
};
|
||||
|
||||
/** Spinner/throbber indeterminado (ex.: "Conectando..." no Boot). */
|
||||
USTRUCT(BlueprintType)
|
||||
struct ZMMO_API FUIStyleSpinner
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
/** Cor das peças do spinner. Default = Gold (Aurora Arcana). */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|Spinner")
|
||||
FLinearColor Color = FLinearColor(FColor(201, 167, 90)); // Gold
|
||||
|
||||
/** Cor do "trilho"/peças apagadas (UCircularThrobber não usa; reservado). */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|Spinner")
|
||||
FLinearColor TrackColor = FLinearColor(FColor(58, 75, 120, 140));
|
||||
|
||||
/** Raio do anel (px). */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|Spinner")
|
||||
float Radius = 28.f;
|
||||
|
||||
/** Espessura da peça (px). */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|Spinner")
|
||||
float Thickness = 3.f;
|
||||
|
||||
/** Número de peças do UCircularThrobber. */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|Spinner", meta = (ClampMin = "1"))
|
||||
int32 NumberOfPieces = 8;
|
||||
|
||||
/** Período de uma volta (s). */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|Spinner", meta = (ClampMin = "0.05"))
|
||||
float Period = 0.9f;
|
||||
};
|
||||
|
||||
175
Source/ZMMO/Game/UI/FrontEnd/UIBootScreen_Base.cpp
Normal file
175
Source/ZMMO/Game/UI/FrontEnd/UIBootScreen_Base.cpp
Normal file
@@ -0,0 +1,175 @@
|
||||
#include "UIBootScreen_Base.h"
|
||||
|
||||
#include "ZMMO.h"
|
||||
#include "UIButton_Base.h"
|
||||
#include "UISpinner_Base.h"
|
||||
#include "UIFrontEndFlowSubsystem.h"
|
||||
#include "ZMMOThemeSubsystem.h"
|
||||
#include "ZeusCharServerSubsystem.h"
|
||||
#include "CommonTextBlock.h"
|
||||
#include "Components/Border.h"
|
||||
#include "Engine/GameInstance.h"
|
||||
#include "Engine/DataTable.h"
|
||||
#include "UI/UIStyleRow.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
// Runtime usa o tema ativo; design-time carrega a row "Default" de
|
||||
// DT_UI_Styles para o Designer ver o fundo (mesmo padrão de UUIPanel_Base).
|
||||
const FUIStyle& ResolveBootStyle(const UUserWidget* Widget, const FUIStyle& Fallback)
|
||||
{
|
||||
if (Widget)
|
||||
{
|
||||
if (const UGameInstance* GI = Widget->GetGameInstance())
|
||||
{
|
||||
if (const UZMMOThemeSubsystem* Theme = GI->GetSubsystem<UZMMOThemeSubsystem>())
|
||||
{
|
||||
return Theme->GetActiveUIStyle();
|
||||
}
|
||||
}
|
||||
}
|
||||
static FUIStyle DesignStyle;
|
||||
static bool bLoaded = false;
|
||||
if (!bLoaded)
|
||||
{
|
||||
if (const UDataTable* DT = LoadObject<UDataTable>(nullptr,
|
||||
TEXT("/Game/ZMMO/Data/UI/DT_UI_Styles.DT_UI_Styles")))
|
||||
{
|
||||
if (const FUIStyleRow* Row = DT->FindRow<FUIStyleRow>(
|
||||
FName(TEXT("Default")), TEXT("UIBootDesign"), false))
|
||||
{
|
||||
DesignStyle = Row->Style;
|
||||
bLoaded = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return bLoaded ? DesignStyle : Fallback;
|
||||
}
|
||||
}
|
||||
|
||||
void UUIBootScreen_Base::NativePreConstruct()
|
||||
{
|
||||
Super::NativePreConstruct();
|
||||
RefreshUIStyle(); // preview do fundo no Designer (DT_UI_Styles "Default")
|
||||
}
|
||||
|
||||
void UUIBootScreen_Base::NativeOnActivated()
|
||||
{
|
||||
Super::NativeOnActivated(); // base: RefreshUIStyle + BP_OnScreenActivated
|
||||
|
||||
if (Btn_Start)
|
||||
{
|
||||
Btn_Start->OnClicked().AddUObject(this, &UUIBootScreen_Base::HandleStartClicked);
|
||||
}
|
||||
|
||||
UZeusCharServerSubsystem* Char = GetCharServer();
|
||||
const bool bConnected = Char && Char->IsConnected();
|
||||
SetReady(bConnected);
|
||||
|
||||
if (Char && !bCharBound)
|
||||
{
|
||||
Char->OnConnected.AddDynamic(this, &UUIBootScreen_Base::HandleCharConnected);
|
||||
Char->OnConnectionFailed.AddDynamic(this, &UUIBootScreen_Base::HandleCharConnectionFailed);
|
||||
bCharBound = true;
|
||||
}
|
||||
// A conexão em si é disparada pelo UUIFrontEndFlowSubsystem (estado Boot).
|
||||
}
|
||||
|
||||
void UUIBootScreen_Base::NativeOnDeactivated()
|
||||
{
|
||||
if (Btn_Start)
|
||||
{
|
||||
Btn_Start->OnClicked().RemoveAll(this);
|
||||
}
|
||||
if (bCharBound)
|
||||
{
|
||||
if (UZeusCharServerSubsystem* Char = GetCharServer())
|
||||
{
|
||||
Char->OnConnected.RemoveDynamic(this, &UUIBootScreen_Base::HandleCharConnected);
|
||||
Char->OnConnectionFailed.RemoveDynamic(this, &UUIBootScreen_Base::HandleCharConnectionFailed);
|
||||
}
|
||||
bCharBound = false;
|
||||
}
|
||||
Super::NativeOnDeactivated();
|
||||
}
|
||||
|
||||
void UUIBootScreen_Base::RefreshUIStyle_Implementation()
|
||||
{
|
||||
Super::RefreshUIStyle_Implementation();
|
||||
|
||||
const FUIStyle Fallback;
|
||||
const FUIStyle& AS = ResolveBootStyle(this, Fallback);
|
||||
|
||||
if (Border_Bg)
|
||||
{
|
||||
// Fundo data-driven do DT_UI_Styles (T_Boot_Background) — sem
|
||||
// hard-ref de textura no WBP (§5). Fallback p/ cor se vazio.
|
||||
if (AS.ScreenBackground.GetResourceObject() != nullptr)
|
||||
{
|
||||
Border_Bg->SetBrush(AS.ScreenBackground);
|
||||
}
|
||||
else
|
||||
{
|
||||
Border_Bg->SetBrushColor(AS.Palette.Bg0);
|
||||
}
|
||||
}
|
||||
if (Text_Status)
|
||||
{
|
||||
Text_Status->SetColorAndOpacity(FSlateColor(AS.Semantic.OnSurfaceMuted));
|
||||
}
|
||||
}
|
||||
|
||||
void UUIBootScreen_Base::SetReady(bool bReady)
|
||||
{
|
||||
if (Btn_Start)
|
||||
{
|
||||
Btn_Start->SetIsInteractionEnabled(bReady);
|
||||
}
|
||||
if (Spinner_Connect)
|
||||
{
|
||||
Spinner_Connect->SetVisibility(
|
||||
bReady ? ESlateVisibility::Collapsed : ESlateVisibility::HitTestInvisible);
|
||||
}
|
||||
if (Text_Status)
|
||||
{
|
||||
Text_Status->SetText(bReady ? ReadyText : ConnectingText);
|
||||
}
|
||||
}
|
||||
|
||||
UZeusCharServerSubsystem* UUIBootScreen_Base::GetCharServer() const
|
||||
{
|
||||
const UGameInstance* GI = GetGameInstance();
|
||||
return GI ? GI->GetSubsystem<UZeusCharServerSubsystem>() : nullptr;
|
||||
}
|
||||
|
||||
void UUIBootScreen_Base::HandleStartClicked()
|
||||
{
|
||||
UZeusCharServerSubsystem* Char = GetCharServer();
|
||||
if (!Char || !Char->IsConnected())
|
||||
{
|
||||
return; // botão só age quando conectado (defensivo)
|
||||
}
|
||||
if (const UGameInstance* GI = GetGameInstance())
|
||||
{
|
||||
if (UUIFrontEndFlowSubsystem* Flow = GI->GetSubsystem<UUIFrontEndFlowSubsystem>())
|
||||
{
|
||||
Flow->RequestEnterLogin();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UUIBootScreen_Base::HandleCharConnected()
|
||||
{
|
||||
UE_LOG(LogZMMO, Log, TEXT("BootScreen: CharServer conectado — botão liberado."));
|
||||
SetReady(true);
|
||||
}
|
||||
|
||||
void UUIBootScreen_Base::HandleCharConnectionFailed(FString Reason)
|
||||
{
|
||||
UE_LOG(LogZMMO, Warning, TEXT("BootScreen: falha ao conectar ao CharServer: %s"), *Reason);
|
||||
SetReady(false);
|
||||
if (Text_Status)
|
||||
{
|
||||
Text_Status->SetText(FailedText);
|
||||
}
|
||||
}
|
||||
73
Source/ZMMO/Game/UI/FrontEnd/UIBootScreen_Base.h
Normal file
73
Source/ZMMO/Game/UI/FrontEnd/UIBootScreen_Base.h
Normal file
@@ -0,0 +1,73 @@
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "UIActivatableScreen_Base.h"
|
||||
#include "UIBootScreen_Base.generated.h"
|
||||
|
||||
class UUIButton_Base;
|
||||
class UUISpinner_Base;
|
||||
class UCommonTextBlock;
|
||||
class UBorder;
|
||||
class UZeusCharServerSubsystem;
|
||||
|
||||
/**
|
||||
* Tela de Boot — primeira tela do front-end. Reconstruída a partir do mock
|
||||
* do Zeus UMG Forge com os widgets compartilhados (UI_Button_Master,
|
||||
* UI_Spinner_Master). Camada Abstract; o WBP concreto (WBP_Boot) herda
|
||||
* DIRETO desta classe (ARQUITETURA.md §3.3).
|
||||
*
|
||||
* O UUIFrontEndFlowSubsystem dispara a conexão ao ZeusCharServer (WebSocket)
|
||||
* ao entrar no estado Boot. Esta tela OBSERVA o UZeusCharServerSubsystem:
|
||||
* enquanto não conecta, mostra o spinner e o botão desabilitado; ao conectar,
|
||||
* libera o botão "Iniciar" (clique → Flow::RequestEnterLogin → Login).
|
||||
* Sem hard-ref de asset de tema (§5): cores vêm do FUIStyle.
|
||||
*/
|
||||
UCLASS(Abstract, Blueprintable)
|
||||
class ZMMO_API UUIBootScreen_Base : public UUIActivatableScreen_Base
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
protected:
|
||||
virtual void NativePreConstruct() override;
|
||||
virtual void NativeOnActivated() override;
|
||||
virtual void NativeOnDeactivated() override;
|
||||
virtual void RefreshUIStyle_Implementation() override;
|
||||
|
||||
/** Texto exibido enquanto conecta / quando pronto / em erro. */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Boot")
|
||||
FText ConnectingText = FText::FromString(TEXT("Conectando ao Gateway"));
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Boot")
|
||||
FText ReadyText = FText::FromString(TEXT("Pronto"));
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Boot")
|
||||
FText FailedText = FText::FromString(TEXT("Falha ao conectar — tentando novamente"));
|
||||
|
||||
// ---- Widgets do WBP (nomes esperados) ----
|
||||
UPROPERTY(meta = (BindWidgetOptional))
|
||||
TObjectPtr<UUIButton_Base> Btn_Start;
|
||||
|
||||
UPROPERTY(meta = (BindWidgetOptional))
|
||||
TObjectPtr<UUISpinner_Base> Spinner_Connect;
|
||||
|
||||
UPROPERTY(meta = (BindWidgetOptional))
|
||||
TObjectPtr<UCommonTextBlock> Text_Status;
|
||||
|
||||
UPROPERTY(meta = (BindWidgetOptional))
|
||||
TObjectPtr<UBorder> Border_Bg;
|
||||
|
||||
private:
|
||||
void SetReady(bool bReady);
|
||||
UZeusCharServerSubsystem* GetCharServer() const;
|
||||
|
||||
UFUNCTION()
|
||||
void HandleStartClicked();
|
||||
|
||||
UFUNCTION()
|
||||
void HandleCharConnected();
|
||||
|
||||
UFUNCTION()
|
||||
void HandleCharConnectionFailed(FString Reason);
|
||||
|
||||
bool bCharBound = false;
|
||||
};
|
||||
@@ -8,6 +8,7 @@
|
||||
#include "ZMMOGameInstance.h"
|
||||
#include "ZMMOThemeSubsystem.h"
|
||||
#include "ZeusNetworkSubsystem.h"
|
||||
#include "ZeusCharServerSubsystem.h"
|
||||
#include "CommonActivatableWidget.h"
|
||||
#include "Engine/GameInstance.h"
|
||||
#include "Engine/LocalPlayer.h"
|
||||
@@ -54,22 +55,15 @@ void UUIFrontEndFlowSubsystem::StartFrontEnd()
|
||||
|
||||
SetState(EZMMOFrontEndState::Boot);
|
||||
|
||||
// Substitui o auto-connect do GameInstance: o fluxo dirige a conexão.
|
||||
if (UZeusNetworkSubsystem* Zeus = GetZeusNetwork())
|
||||
// Pré-login fala com o ZeusCharServer (WebSocket), NÃO com o world server
|
||||
// UDP. A tela Boot observa o CharServer e libera o botão ao conectar.
|
||||
if (UZeusCharServerSubsystem* Char = GetCharServer())
|
||||
{
|
||||
FString Host = TEXT("127.0.0.1");
|
||||
int32 Port = 27777;
|
||||
if (const UZMMOGameInstance* GI = Cast<UZMMOGameInstance>(GetGameInstance()))
|
||||
{
|
||||
Host = GI->ZeusServerHost;
|
||||
Port = GI->ZeusServerPort;
|
||||
}
|
||||
SetState(EZMMOFrontEndState::Connecting);
|
||||
Zeus->ConnectToZeusServer(Host, Port);
|
||||
Char->ConnectToDefaultCharServer(); // lê CharServerUrl de Project Settings
|
||||
}
|
||||
else
|
||||
{
|
||||
UE_LOG(LogZMMO, Warning, TEXT("FrontEndFlow: ZeusNetworkSubsystem indisponível; sem conexão."));
|
||||
UE_LOG(LogZMMO, Warning, TEXT("FrontEndFlow: ZeusCharServerSubsystem indisponível; sem conexão de Boot."));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -221,6 +215,12 @@ UZeusNetworkSubsystem* UUIFrontEndFlowSubsystem::GetZeusNetwork() const
|
||||
return GI ? GI->GetSubsystem<UZeusNetworkSubsystem>() : nullptr;
|
||||
}
|
||||
|
||||
UZeusCharServerSubsystem* UUIFrontEndFlowSubsystem::GetCharServer() const
|
||||
{
|
||||
const UGameInstance* GI = GetGameInstance();
|
||||
return GI ? GI->GetSubsystem<UZeusCharServerSubsystem>() : nullptr;
|
||||
}
|
||||
|
||||
UUIManagerSubsystem* UUIFrontEndFlowSubsystem::GetUIManager() const
|
||||
{
|
||||
if (const UGameInstance* GI = GetGameInstance())
|
||||
@@ -257,14 +257,19 @@ void UUIFrontEndFlowSubsystem::BindNetwork()
|
||||
{
|
||||
return;
|
||||
}
|
||||
// CharServer (WebSocket) dirige o pré-login: Boot→Login.
|
||||
if (UZeusCharServerSubsystem* Char = GetCharServer())
|
||||
{
|
||||
Char->OnConnected.AddDynamic(this, &UUIFrontEndFlowSubsystem::HandleConnected);
|
||||
Char->OnConnectionFailed.AddDynamic(this, &UUIFrontEndFlowSubsystem::HandleConnectionFailed);
|
||||
Char->OnDisconnected.AddDynamic(this, &UUIFrontEndFlowSubsystem::HandleCharDisconnected);
|
||||
}
|
||||
// UDP (world server) só interessa para o handoff de travel.
|
||||
if (UZeusNetworkSubsystem* Zeus = GetZeusNetwork())
|
||||
{
|
||||
Zeus->OnConnected.AddDynamic(this, &UUIFrontEndFlowSubsystem::HandleConnected);
|
||||
Zeus->OnConnectionFailed.AddDynamic(this, &UUIFrontEndFlowSubsystem::HandleConnectionFailed);
|
||||
Zeus->OnDisconnected.AddDynamic(this, &UUIFrontEndFlowSubsystem::HandleDisconnected);
|
||||
Zeus->OnServerTravelRequested.AddDynamic(this, &UUIFrontEndFlowSubsystem::HandleServerTravelRequested);
|
||||
bNetBound = true;
|
||||
}
|
||||
bNetBound = true;
|
||||
}
|
||||
|
||||
void UUIFrontEndFlowSubsystem::UnbindNetwork()
|
||||
@@ -273,11 +278,14 @@ void UUIFrontEndFlowSubsystem::UnbindNetwork()
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (UZeusCharServerSubsystem* Char = GetCharServer())
|
||||
{
|
||||
Char->OnConnected.RemoveDynamic(this, &UUIFrontEndFlowSubsystem::HandleConnected);
|
||||
Char->OnConnectionFailed.RemoveDynamic(this, &UUIFrontEndFlowSubsystem::HandleConnectionFailed);
|
||||
Char->OnDisconnected.RemoveDynamic(this, &UUIFrontEndFlowSubsystem::HandleCharDisconnected);
|
||||
}
|
||||
if (UZeusNetworkSubsystem* Zeus = GetZeusNetwork())
|
||||
{
|
||||
Zeus->OnConnected.RemoveDynamic(this, &UUIFrontEndFlowSubsystem::HandleConnected);
|
||||
Zeus->OnConnectionFailed.RemoveDynamic(this, &UUIFrontEndFlowSubsystem::HandleConnectionFailed);
|
||||
Zeus->OnDisconnected.RemoveDynamic(this, &UUIFrontEndFlowSubsystem::HandleDisconnected);
|
||||
Zeus->OnServerTravelRequested.RemoveDynamic(this, &UUIFrontEndFlowSubsystem::HandleServerTravelRequested);
|
||||
}
|
||||
bNetBound = false;
|
||||
@@ -285,20 +293,38 @@ void UUIFrontEndFlowSubsystem::UnbindNetwork()
|
||||
|
||||
void UUIFrontEndFlowSubsystem::HandleConnected()
|
||||
{
|
||||
UE_LOG(LogZMMO, Log, TEXT("FrontEndFlow: conectado → Login."));
|
||||
SetState(EZMMOFrontEndState::Login);
|
||||
// NÃO auto-avança: a tela Boot libera o botão "Iniciar"; o usuário clica
|
||||
// (→ RequestEnterLogin). Mantém o estado Boot.
|
||||
UE_LOG(LogZMMO, Log, TEXT("FrontEndFlow: CharServer conectado (estado %s)."),
|
||||
*UEnum::GetValueAsString(CurrentState));
|
||||
}
|
||||
|
||||
void UUIFrontEndFlowSubsystem::HandleConnectionFailed(FString Reason)
|
||||
{
|
||||
UE_LOG(LogZMMO, Warning, TEXT("FrontEndFlow: conexão falhou (%s) → volta a Login."), *Reason);
|
||||
SetState(EZMMOFrontEndState::Login);
|
||||
// Permanece em Boot; a tela mostra erro e o CharServer pode re-tentar.
|
||||
UE_LOG(LogZMMO, Warning, TEXT("FrontEndFlow: conexão ao CharServer falhou (%s)."), *Reason);
|
||||
}
|
||||
|
||||
void UUIFrontEndFlowSubsystem::HandleDisconnected()
|
||||
void UUIFrontEndFlowSubsystem::HandleCharDisconnected(int32 StatusCode, FString Reason)
|
||||
{
|
||||
UE_LOG(LogZMMO, Log, TEXT("FrontEndFlow: desconectado."));
|
||||
if (CurrentState != EZMMOFrontEndState::InWorld)
|
||||
UE_LOG(LogZMMO, Log, TEXT("FrontEndFlow: CharServer desconectou (code=%d, %s)."),
|
||||
StatusCode, *Reason);
|
||||
// Se caiu antes de entrar no mundo, volta a Boot para reconectar.
|
||||
if (CurrentState != EZMMOFrontEndState::InWorld &&
|
||||
CurrentState != EZMMOFrontEndState::EnteringWorld &&
|
||||
CurrentState != EZMMOFrontEndState::Boot)
|
||||
{
|
||||
SetState(EZMMOFrontEndState::Boot);
|
||||
if (UZeusCharServerSubsystem* Char = GetCharServer())
|
||||
{
|
||||
Char->ConnectToDefaultCharServer();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UUIFrontEndFlowSubsystem::RequestEnterLogin()
|
||||
{
|
||||
if (CurrentState == EZMMOFrontEndState::Boot)
|
||||
{
|
||||
SetState(EZMMOFrontEndState::Login);
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
class UUIFrontEndScreenSet;
|
||||
class UUIManagerSubsystem;
|
||||
class UZeusNetworkSubsystem;
|
||||
class UZeusCharServerSubsystem;
|
||||
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnZMMOFrontEndStateChanged, EZMMOFrontEndState, NewState);
|
||||
|
||||
@@ -57,6 +58,10 @@ public:
|
||||
UFUNCTION(BlueprintCallable, Category = "FrontEnd")
|
||||
bool RequestBack();
|
||||
|
||||
/** Chamado pela tela Boot quando o usuário clica "Iniciar" (já conectado). */
|
||||
UFUNCTION(BlueprintCallable, Category = "FrontEnd")
|
||||
void RequestEnterLogin();
|
||||
|
||||
UPROPERTY(BlueprintAssignable, Category = "FrontEnd")
|
||||
FOnZMMOFrontEndStateChanged OnStateChanged;
|
||||
|
||||
@@ -69,6 +74,7 @@ private:
|
||||
void BindNetwork();
|
||||
void UnbindNetwork();
|
||||
UZeusNetworkSubsystem* GetZeusNetwork() const;
|
||||
UZeusCharServerSubsystem* GetCharServer() const;
|
||||
UUIManagerSubsystem* GetUIManager() const;
|
||||
UUIFrontEndScreenSet* GetScreenSet();
|
||||
|
||||
@@ -82,7 +88,7 @@ private:
|
||||
void HandleConnectionFailed(FString Reason);
|
||||
|
||||
UFUNCTION()
|
||||
void HandleDisconnected();
|
||||
void HandleCharDisconnected(int32 StatusCode, FString Reason);
|
||||
|
||||
UFUNCTION()
|
||||
void HandleServerTravelRequested(const FString& MapName, const FString& MapPath);
|
||||
|
||||
104
Source/ZMMO/Game/UI/Widgets/UISpinner_Base.cpp
Normal file
104
Source/ZMMO/Game/UI/Widgets/UISpinner_Base.cpp
Normal file
@@ -0,0 +1,104 @@
|
||||
#include "UISpinner_Base.h"
|
||||
|
||||
#include "ZMMOThemeSubsystem.h"
|
||||
#include "Engine/GameInstance.h"
|
||||
#include "Engine/DataTable.h"
|
||||
#include "Components/CircularThrobber.h"
|
||||
#include "UI/UIStyleRow.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
// Mesmo padrão de UUIPanel_Base: runtime usa o tema ativo; design-time
|
||||
// carrega a row "Default" de DT_UI_Styles para o Designer ver igual.
|
||||
const FUIStyle& ResolveSpinnerStyle(const UUserWidget* Widget, const FUIStyle& Fallback)
|
||||
{
|
||||
if (Widget)
|
||||
{
|
||||
if (const UGameInstance* GI = Widget->GetGameInstance())
|
||||
{
|
||||
if (const UZMMOThemeSubsystem* Theme = GI->GetSubsystem<UZMMOThemeSubsystem>())
|
||||
{
|
||||
return Theme->GetActiveUIStyle();
|
||||
}
|
||||
}
|
||||
}
|
||||
static FUIStyle DesignStyle;
|
||||
static bool bDesignLoaded = false;
|
||||
if (!bDesignLoaded)
|
||||
{
|
||||
if (const UDataTable* DT = LoadObject<UDataTable>(nullptr,
|
||||
TEXT("/Game/ZMMO/Data/UI/DT_UI_Styles.DT_UI_Styles")))
|
||||
{
|
||||
if (const FUIStyleRow* Row = DT->FindRow<FUIStyleRow>(
|
||||
FName(TEXT("Default")), TEXT("UISpinnerDesign"), false))
|
||||
{
|
||||
DesignStyle = Row->Style;
|
||||
bDesignLoaded = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return bDesignLoaded ? DesignStyle : Fallback;
|
||||
}
|
||||
}
|
||||
|
||||
void UUISpinner_Base::RefreshUIStyle()
|
||||
{
|
||||
const FUIStyle Fallback;
|
||||
const FUIStyle& AS = ResolveSpinnerStyle(this, Fallback);
|
||||
const FUIStyleSpinner& S = AS.Spinner;
|
||||
|
||||
if (Throbber)
|
||||
{
|
||||
Throbber->SetNumberOfPieces(FMath::Max(1, S.NumberOfPieces));
|
||||
Throbber->SetPeriod(FMath::Max(0.05f, S.Period));
|
||||
Throbber->SetRadius(S.Radius);
|
||||
}
|
||||
|
||||
// Cor/brush: o WBP aplica no throbber (mesma divisão de UUIPanel_Base).
|
||||
BP_ApplySpinnerStyle(S);
|
||||
}
|
||||
|
||||
void UUISpinner_Base::NativePreConstruct()
|
||||
{
|
||||
Super::NativePreConstruct();
|
||||
RefreshUIStyle();
|
||||
}
|
||||
|
||||
void UUISpinner_Base::NativeConstruct()
|
||||
{
|
||||
Super::NativeConstruct();
|
||||
|
||||
if (!bThemeBound)
|
||||
{
|
||||
if (const UGameInstance* GI = GetGameInstance())
|
||||
{
|
||||
if (UZMMOThemeSubsystem* Theme = GI->GetSubsystem<UZMMOThemeSubsystem>())
|
||||
{
|
||||
Theme->OnThemeChanged.AddDynamic(this, &UUISpinner_Base::HandleThemeChanged);
|
||||
bThemeBound = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
RefreshUIStyle();
|
||||
}
|
||||
|
||||
void UUISpinner_Base::NativeDestruct()
|
||||
{
|
||||
if (bThemeBound)
|
||||
{
|
||||
if (const UGameInstance* GI = GetGameInstance())
|
||||
{
|
||||
if (UZMMOThemeSubsystem* Theme = GI->GetSubsystem<UZMMOThemeSubsystem>())
|
||||
{
|
||||
Theme->OnThemeChanged.RemoveDynamic(this, &UUISpinner_Base::HandleThemeChanged);
|
||||
}
|
||||
}
|
||||
bThemeBound = false;
|
||||
}
|
||||
Super::NativeDestruct();
|
||||
}
|
||||
|
||||
void UUISpinner_Base::HandleThemeChanged(FName /*NewThemeId*/)
|
||||
{
|
||||
RefreshUIStyle();
|
||||
}
|
||||
49
Source/ZMMO/Game/UI/Widgets/UISpinner_Base.h
Normal file
49
Source/ZMMO/Game/UI/Widgets/UISpinner_Base.h
Normal file
@@ -0,0 +1,49 @@
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "CommonUserWidget.h"
|
||||
#include "UI/UIStyleTokens.h"
|
||||
#include "UISpinner_Base.generated.h"
|
||||
|
||||
class UCircularThrobber;
|
||||
|
||||
/**
|
||||
* Spinner/throbber indeterminado do ZMMO — mesmo padrão de UUIPanel_Base.
|
||||
* Fundação CommonUI (UCommonUserWidget). Camada Abstract; o WBP concreto
|
||||
* (UI_Spinner_Master) herda DIRETO desta classe C++ (UMG não encadeia
|
||||
* árvores — ARQUITETURA.md §3.3).
|
||||
*
|
||||
* Visual data-driven por FUIStyle.Spinner (UZMMOThemeSubsystem). C++ aplica
|
||||
* NumberOfPieces/Period/Radius no UCircularThrobber; cor/brush vão pelo hook
|
||||
* BP_ApplySpinnerStyle (o WBP tinge a imagem do throbber).
|
||||
*/
|
||||
UCLASS(Abstract, Blueprintable)
|
||||
class ZMMO_API UUISpinner_Base : public UCommonUserWidget
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
/** Re-resolve os tokens do tema ativo e reaplica no throbber. */
|
||||
UFUNCTION(BlueprintCallable, Category = "UI Style")
|
||||
void RefreshUIStyle();
|
||||
|
||||
protected:
|
||||
virtual void NativePreConstruct() override;
|
||||
virtual void NativeConstruct() override;
|
||||
virtual void NativeDestruct() override;
|
||||
|
||||
/** Hook opcional: o WBP aplica cor/brush do spinner (FUIStyleSpinner). */
|
||||
UFUNCTION(BlueprintImplementableEvent, Category = "UI Style",
|
||||
meta = (DisplayName = "Apply Spinner Style"))
|
||||
void BP_ApplySpinnerStyle(const FUIStyleSpinner& Spinner);
|
||||
|
||||
/** Throbber circular (nome esperado no WBP: "Throbber"). */
|
||||
UPROPERTY(meta = (BindWidgetOptional))
|
||||
TObjectPtr<UCircularThrobber> Throbber;
|
||||
|
||||
private:
|
||||
UFUNCTION()
|
||||
void HandleThemeChanged(FName NewThemeId);
|
||||
|
||||
bool bThemeBound = false;
|
||||
};
|
||||
Reference in New Issue
Block a user