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,
|
│ ├── UI/ ← HUD, Login, CharacterSelect, Inventory,
|
||||||
│ │ │ Chat, Party, Guild, Trade, Quest, Map,
|
│ │ │ Chat, Party, Guild, Trade, Quest, Map,
|
||||||
│ │ │ Shared, Icons
|
│ │ │ 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,
|
│ │ ├── FrontEnd/ ← WBP_PrimaryGameLayout, DA_FrontEndScreenSet,
|
||||||
│ │ │ telas refinadas (Boot/Login/… vindas do Forge)
|
│ │ │ telas refinadas (Boot/Login/… vindas do Forge)
|
||||||
│ │ ├── Fonts/ ← Font_*/FF_* (Cinzel, Rajdhani)
|
│ │ ├── Fonts/ ← Font_*/FF_* (Cinzel, Rajdhani)
|
||||||
@@ -268,9 +269,11 @@ Source/ZMMO/
|
|||||||
│ ├── Network/ ← UZMMOWorldSubsystem
|
│ ├── Network/ ← UZMMOWorldSubsystem
|
||||||
│ └── UI/ ← runtime de UI (não é contrato de dados)
|
│ └── UI/ ← runtime de UI (não é contrato de dados)
|
||||||
│ ├── ZMMOThemeSubsystem.* ← resolução de tema
|
│ ├── ZMMOThemeSubsystem.* ← resolução de tema
|
||||||
│ ├── Widgets/ ← UUIButton_Base, UUIPanel_Base
|
│ ├── Widgets/ ← UUIButton_Base, UUIPanel_Base,
|
||||||
|
│ │ UUISpinner_Base
|
||||||
│ └── FrontEnd/ ← infra de navegação CommonUI:
|
│ └── FrontEnd/ ← infra de navegação CommonUI:
|
||||||
│ UUIActivatableScreen_Base,
|
│ UUIActivatableScreen_Base,
|
||||||
|
│ UUIBootScreen_Base (1ª tela),
|
||||||
│ UUIPrimaryGameLayout_Base,
|
│ UUIPrimaryGameLayout_Base,
|
||||||
│ UUIManagerSubsystem (LocalPlayer),
|
│ UUIManagerSubsystem (LocalPlayer),
|
||||||
│ UUIFrontEndFlowSubsystem (GameInstance),
|
│ 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
|
navegadas por um stack CommonUI **dentro** da tela do Lobby — não são estados
|
||||||
de topo do fluxo.
|
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
|
1. Prototipar a tela em HTML/CSS em `Tools/Templates/MMO_Widget/` (ou usar os
|
||||||
templates já existentes).
|
templates já existentes).
|
||||||
2. Rodar o Zeus UMG Forge (HTML→UMG). Saída crua: `Content/AutoCreated/<batch>/`.
|
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_HealthBar UMETA(DisplayName = "HUD - Health Bar"),
|
||||||
HUD_ManaBar UMETA(DisplayName = "HUD - Mana 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 / Character Select
|
||||||
Login_Background UMETA(DisplayName = "Login - Background"),
|
Login_Background UMETA(DisplayName = "Login - Background"),
|
||||||
Login_Logo UMETA(DisplayName = "Login - Logo"),
|
Login_Logo UMETA(DisplayName = "Login - Logo"),
|
||||||
|
|||||||
@@ -42,6 +42,17 @@ struct ZMMO_API FUIStyle
|
|||||||
|
|
||||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style")
|
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style")
|
||||||
FUIStyleTooltip Tooltip;
|
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")
|
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "UI Style|Tooltip")
|
||||||
FLinearColor TextColor = FLinearColor(FColor(244, 240, 230)); // Text
|
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 "ZMMOGameInstance.h"
|
||||||
#include "ZMMOThemeSubsystem.h"
|
#include "ZMMOThemeSubsystem.h"
|
||||||
#include "ZeusNetworkSubsystem.h"
|
#include "ZeusNetworkSubsystem.h"
|
||||||
|
#include "ZeusCharServerSubsystem.h"
|
||||||
#include "CommonActivatableWidget.h"
|
#include "CommonActivatableWidget.h"
|
||||||
#include "Engine/GameInstance.h"
|
#include "Engine/GameInstance.h"
|
||||||
#include "Engine/LocalPlayer.h"
|
#include "Engine/LocalPlayer.h"
|
||||||
@@ -54,22 +55,15 @@ void UUIFrontEndFlowSubsystem::StartFrontEnd()
|
|||||||
|
|
||||||
SetState(EZMMOFrontEndState::Boot);
|
SetState(EZMMOFrontEndState::Boot);
|
||||||
|
|
||||||
// Substitui o auto-connect do GameInstance: o fluxo dirige a conexão.
|
// Pré-login fala com o ZeusCharServer (WebSocket), NÃO com o world server
|
||||||
if (UZeusNetworkSubsystem* Zeus = GetZeusNetwork())
|
// 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");
|
Char->ConnectToDefaultCharServer(); // lê CharServerUrl de Project Settings
|
||||||
int32 Port = 27777;
|
|
||||||
if (const UZMMOGameInstance* GI = Cast<UZMMOGameInstance>(GetGameInstance()))
|
|
||||||
{
|
|
||||||
Host = GI->ZeusServerHost;
|
|
||||||
Port = GI->ZeusServerPort;
|
|
||||||
}
|
|
||||||
SetState(EZMMOFrontEndState::Connecting);
|
|
||||||
Zeus->ConnectToZeusServer(Host, Port);
|
|
||||||
}
|
}
|
||||||
else
|
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;
|
return GI ? GI->GetSubsystem<UZeusNetworkSubsystem>() : nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
UZeusCharServerSubsystem* UUIFrontEndFlowSubsystem::GetCharServer() const
|
||||||
|
{
|
||||||
|
const UGameInstance* GI = GetGameInstance();
|
||||||
|
return GI ? GI->GetSubsystem<UZeusCharServerSubsystem>() : nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
UUIManagerSubsystem* UUIFrontEndFlowSubsystem::GetUIManager() const
|
UUIManagerSubsystem* UUIFrontEndFlowSubsystem::GetUIManager() const
|
||||||
{
|
{
|
||||||
if (const UGameInstance* GI = GetGameInstance())
|
if (const UGameInstance* GI = GetGameInstance())
|
||||||
@@ -257,14 +257,19 @@ void UUIFrontEndFlowSubsystem::BindNetwork()
|
|||||||
{
|
{
|
||||||
return;
|
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())
|
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);
|
Zeus->OnServerTravelRequested.AddDynamic(this, &UUIFrontEndFlowSubsystem::HandleServerTravelRequested);
|
||||||
bNetBound = true;
|
|
||||||
}
|
}
|
||||||
|
bNetBound = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void UUIFrontEndFlowSubsystem::UnbindNetwork()
|
void UUIFrontEndFlowSubsystem::UnbindNetwork()
|
||||||
@@ -273,11 +278,14 @@ void UUIFrontEndFlowSubsystem::UnbindNetwork()
|
|||||||
{
|
{
|
||||||
return;
|
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())
|
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);
|
Zeus->OnServerTravelRequested.RemoveDynamic(this, &UUIFrontEndFlowSubsystem::HandleServerTravelRequested);
|
||||||
}
|
}
|
||||||
bNetBound = false;
|
bNetBound = false;
|
||||||
@@ -285,20 +293,38 @@ void UUIFrontEndFlowSubsystem::UnbindNetwork()
|
|||||||
|
|
||||||
void UUIFrontEndFlowSubsystem::HandleConnected()
|
void UUIFrontEndFlowSubsystem::HandleConnected()
|
||||||
{
|
{
|
||||||
UE_LOG(LogZMMO, Log, TEXT("FrontEndFlow: conectado → Login."));
|
// NÃO auto-avança: a tela Boot libera o botão "Iniciar"; o usuário clica
|
||||||
SetState(EZMMOFrontEndState::Login);
|
// (→ RequestEnterLogin). Mantém o estado Boot.
|
||||||
|
UE_LOG(LogZMMO, Log, TEXT("FrontEndFlow: CharServer conectado (estado %s)."),
|
||||||
|
*UEnum::GetValueAsString(CurrentState));
|
||||||
}
|
}
|
||||||
|
|
||||||
void UUIFrontEndFlowSubsystem::HandleConnectionFailed(FString Reason)
|
void UUIFrontEndFlowSubsystem::HandleConnectionFailed(FString Reason)
|
||||||
{
|
{
|
||||||
UE_LOG(LogZMMO, Warning, TEXT("FrontEndFlow: conexão falhou (%s) → volta a Login."), *Reason);
|
// Permanece em Boot; a tela mostra erro e o CharServer pode re-tentar.
|
||||||
SetState(EZMMOFrontEndState::Login);
|
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."));
|
UE_LOG(LogZMMO, Log, TEXT("FrontEndFlow: CharServer desconectou (code=%d, %s)."),
|
||||||
if (CurrentState != EZMMOFrontEndState::InWorld)
|
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);
|
SetState(EZMMOFrontEndState::Login);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
class UUIFrontEndScreenSet;
|
class UUIFrontEndScreenSet;
|
||||||
class UUIManagerSubsystem;
|
class UUIManagerSubsystem;
|
||||||
class UZeusNetworkSubsystem;
|
class UZeusNetworkSubsystem;
|
||||||
|
class UZeusCharServerSubsystem;
|
||||||
|
|
||||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnZMMOFrontEndStateChanged, EZMMOFrontEndState, NewState);
|
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnZMMOFrontEndStateChanged, EZMMOFrontEndState, NewState);
|
||||||
|
|
||||||
@@ -57,6 +58,10 @@ public:
|
|||||||
UFUNCTION(BlueprintCallable, Category = "FrontEnd")
|
UFUNCTION(BlueprintCallable, Category = "FrontEnd")
|
||||||
bool RequestBack();
|
bool RequestBack();
|
||||||
|
|
||||||
|
/** Chamado pela tela Boot quando o usuário clica "Iniciar" (já conectado). */
|
||||||
|
UFUNCTION(BlueprintCallable, Category = "FrontEnd")
|
||||||
|
void RequestEnterLogin();
|
||||||
|
|
||||||
UPROPERTY(BlueprintAssignable, Category = "FrontEnd")
|
UPROPERTY(BlueprintAssignable, Category = "FrontEnd")
|
||||||
FOnZMMOFrontEndStateChanged OnStateChanged;
|
FOnZMMOFrontEndStateChanged OnStateChanged;
|
||||||
|
|
||||||
@@ -69,6 +74,7 @@ private:
|
|||||||
void BindNetwork();
|
void BindNetwork();
|
||||||
void UnbindNetwork();
|
void UnbindNetwork();
|
||||||
UZeusNetworkSubsystem* GetZeusNetwork() const;
|
UZeusNetworkSubsystem* GetZeusNetwork() const;
|
||||||
|
UZeusCharServerSubsystem* GetCharServer() const;
|
||||||
UUIManagerSubsystem* GetUIManager() const;
|
UUIManagerSubsystem* GetUIManager() const;
|
||||||
UUIFrontEndScreenSet* GetScreenSet();
|
UUIFrontEndScreenSet* GetScreenSet();
|
||||||
|
|
||||||
@@ -82,7 +88,7 @@ private:
|
|||||||
void HandleConnectionFailed(FString Reason);
|
void HandleConnectionFailed(FString Reason);
|
||||||
|
|
||||||
UFUNCTION()
|
UFUNCTION()
|
||||||
void HandleDisconnected();
|
void HandleCharDisconnected(int32 StatusCode, FString Reason);
|
||||||
|
|
||||||
UFUNCTION()
|
UFUNCTION()
|
||||||
void HandleServerTravelRequested(const FString& MapName, const FString& MapPath);
|
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