feat(frontend): esqueleto CommonUI de MainMenu (boot→login→lobby)

Estrutura principal de front-end estilo Lyra portada para C++ proprio do
ZMMO (sem CommonGame), seguindo o padrao C++ Abstract -> WBP concreto:

- EZMMOFrontEndState / EZMMOLobbyPage e GameplayTags UI.Layer.* nativos
- UUIActivatableScreen_Base: base de tela (input config, hook de tema,
  voltar) espelhando o padrao de UUIButton_Base
- UUIPrimaryGameLayout_Base: switch principal (4 stacks CommonUI/camada)
- UUIManagerSubsystem (LocalPlayer): dono do root layout, idempotente
- UUIFrontEndFlowSubsystem (GameInstance): maquina de estados + driver
  de rede (Boot->Connecting->Login->ServerSelect->Lobby->EnteringWorld
  ->InWorld); Lobby e o hub logado, paginas internas = EZMMOLobbyPage
- UUIFrontEndScreenSet (DataAsset): mapa estado->tela, forge-ready
- AZMMOFrontEndGameMode + AZMMOFrontEndPlayerController
- GameInstance: bAutoConnectOnStart=false (conexao dirigida pelo fluxo)
- Build.cs: +GameplayTags; ARQUITETURA.md PR-first (s2.1/2.2/3.3/4.8/5)
- Assets: L_FrontEnd (GameMode via World Settings), WBP_PrimaryGameLayout,
  DA_FrontEndScreenSet; Config wiring

WBPs de pagina (Boot/Login/...) virao do Zeus UMG Forge depois (s4.8).
Smoke test PIE OK: root layout no viewport, fluxo Boot->Connecting,
no-op+log para telas nao configuradas, sem crash.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-19 01:56:16 -03:00
parent ca781eff83
commit bc07c47557
25 changed files with 1214 additions and 11 deletions

View File

@@ -203,10 +203,16 @@ Content/
│ │ ├── Environment/ ← Architecture/ Nature/ Props/ │ │ ├── Environment/ ← Architecture/ Nature/ Props/
│ │ └── Interactables/ ← baús, portas, warps, recursos │ │ └── Interactables/ ← baús, portas, warps, recursos
│ │ │ │
│ ├── Maps/ ← mapas que NÃO são de mundo
│ │ └── FrontEnd/ ← L_FrontEnd (boot/menu; GameMode via
│ │ World Settings = AZMMOFrontEndGameMode)
│ │
│ ├── 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…)
│ │ ├── FrontEnd/ ← WBP_PrimaryGameLayout, DA_FrontEndScreenSet,
│ │ │ telas refinadas (Boot/Login/… vindas do Forge)
│ │ ├── Fonts/ ← Font_*/FF_* (Cinzel, Rajdhani) │ │ ├── Fonts/ ← Font_*/FF_* (Cinzel, Rajdhani)
│ │ ├── CommonUI/Style/ ← estilos CommonUI (espelha o Hyper) │ │ ├── CommonUI/Style/ ← estilos CommonUI (espelha o Hyper)
│ │ │ ├── Button/ ← BSB_* (UCommonButtonStyle) │ │ │ ├── Button/ ← BSB_* (UCommonButtonStyle)
@@ -236,6 +242,9 @@ Content/
├── Developers/ ← rascunho por dev (não cooka) ├── Developers/ ← rascunho por dev (não cooka)
│ └── <nick-do-dev>/ │ └── <nick-do-dev>/
├── AutoCreated/ ← saída CRUA do Zeus UMG Forge (HTML→WBP).
│ └── <batch>/ Quarentena: refina-se e MOVE p/ ZMMO/UI/
│ FrontEnd/ antes de produção (não cooka cru)
├── ExternalContent/ ← Fab / Megascans / Marketplace ├── ExternalContent/ ← Fab / Megascans / Marketplace
│ ├── Fab/ Megascans/ Marketplace/ │ ├── Fab/ Megascans/ Marketplace/
├── Collections/ ├── Collections/
@@ -252,9 +261,20 @@ Source/ZMMO/
├── Game/ ← gameplay runtime (já existe) ├── Game/ ← gameplay runtime (já existe)
│ ├── Entity/ ← AZMMOEntity + derivados │ ├── Entity/ ← AZMMOEntity + derivados
│ ├── Controller/ ← AZMMOPlayerController │ ├── Controller/ ← AZMMOPlayerController,
├── Modes/ ← GameMode + GameInstance AZMMOFrontEndPlayerController
── Network/UZMMOWorldSubsystem ── Modes/ GameMode + GameInstance,
│ │ AZMMOFrontEndGameMode
│ ├── Network/ ← UZMMOWorldSubsystem
│ └── UI/ ← runtime de UI (não é contrato de dados)
│ ├── ZMMOThemeSubsystem.* ← resolução de tema
│ ├── Widgets/ ← UUIButton_Base, UUIPanel_Base
│ └── FrontEnd/ ← infra de navegação CommonUI:
│ UUIActivatableScreen_Base,
│ UUIPrimaryGameLayout_Base,
│ UUIManagerSubsystem (LocalPlayer),
│ UUIFrontEndFlowSubsystem (GameInstance),
│ UUIFrontEndScreenSet (UDataAsset)
└── Data/ ← contrato de dados (USTRUCT + UENUM) └── Data/ ← contrato de dados (USTRUCT + UENUM)
├── ZMMOData.h ← header agregador (opcional) ├── ZMMOData.h ← header agregador (opcional)
@@ -278,7 +298,9 @@ Source/ZMMO/
├── UI/ ├── UI/
│ ├── ThemeKeys.h ← EZMMOThemeKey (HUD_Frame, Login_BG…) │ ├── ThemeKeys.h ← EZMMOThemeKey (HUD_Frame, Login_BG…)
│ ├── ThemeRow.h ← FThemeEntry (Key → SoftAsset) │ ├── ThemeRow.h ← FThemeEntry (Key → SoftAsset)
── ThemeCalendarRow.h ← FThemeCalendarRow (data → ThemeId) ── ThemeCalendarRow.h ← FThemeCalendarRow (data → ThemeId)
│ ├── FrontEndTypes.h ← EZMMOFrontEndState, EZMMOLobbyPage
│ └── UILayerTags.h/.cpp ← GameplayTags nativos UI.Layer.*
└── World/ └── World/
└── ZoneRow.h └── ZoneRow.h
``` ```
@@ -365,6 +387,16 @@ base de widget = `UUI<Componente>_Base`, sempre `UCLASS(Abstract)` (não
instanciável direto; só os filhos derivam — padrão `_Abstract` do Hyper). instanciável direto; só os filhos derivam — padrão `_Abstract` do Hyper).
Ex.: `UUIButton_Base`, `UUIPanel_Base`, `UUIProgressBar_Base`. Ex.: `UUIButton_Base`, `UUIPanel_Base`, `UUIProgressBar_Base`.
**Sub-regra — infra de navegação de front-end:** as classes que orquestram a
navegação CommonUI seguem o mesmo prefixo `UUI` (sem `ZMMO`) por coerência com
a família de UI, **mesmo não sendo widgets**. Assim: o layout raiz é
`UUIPrimaryGameLayout_Base` (Abstract, widget), a base de tela é
`UUIActivatableScreen_Base` (Abstract, widget), e os subsistemas de UI são
`UUIManagerSubsystem` (`ULocalPlayerSubsystem`) e `UUIFrontEndFlowSubsystem`
(`UGameInstanceSubsystem`). Atores de gameplay do front-end (GameMode,
PlayerController) **mantêm** o prefixo `AZMMO` — não são UI, espelham
`AZMMOGameMode`: `AZMMOFrontEndGameMode`, `AZMMOFrontEndPlayerController`.
**Regra do UMG (importante):** um Widget Blueprint **não pode** herdar de outro **Regra do UMG (importante):** um Widget Blueprint **não pode** herdar de outro
Widget Blueprint que tenha árvore de widgets (UMG aborta a compilação: "apenas Widget Blueprint que tenha árvore de widgets (UMG aborta a compilação: "apenas
um deles deve ter árvore"). Portanto a camada abstrata é **só a classe C++** um deles deve ter árvore"). Portanto a camada abstrata é **só a classe C++**
@@ -577,6 +609,38 @@ o opcode de spawn. O cliente nunca instancia mob "por nome" — sempre via id.
O subsystem aplica fallback internamente: pergunta ao tema activo; se a O subsystem aplica fallback internamente: pergunta ao tema activo; se a
chave não estiver preenchida, devolve a do `Default`. chave não estiver preenchida, devolve a do `Default`.
### 4.8. Adicionar uma tela de front-end (ex: `Login`)
**Pré-requisito:** já existem `UUIActivatableScreen_Base` e
`UUIFrontEndFlowSubsystem` em `Source/ZMMO/Game/UI/FrontEnd/`, o
`WBP_PrimaryGameLayout` e o `DA_FrontEndScreenSet` em
`Content/ZMMO/UI/FrontEnd/`.
Fluxo geral: **Boot → Connecting → Login → ServerSelect → Lobby → EnteringWorld
→ InWorld**. O **Lobby** é o hub principal logado; suas páginas internas
(CharacterSelect, CharacterCreate, Shop, Settings…) são `EZMMOLobbyPage`,
navegadas por um stack CommonUI **dentro** da tela do Lobby — não são estados
de topo do fluxo.
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>/`.
3. Abrir o `WBP_*` gerado e **reparent** a classe pai para
`UUIActivatableScreen_Base` (a base é `UCommonActivatableWidget`; o WBP tem
árvore própria — respeita §3.3, nunca encadeia WBP→WBP).
4. **Reconectar tema:** o Forge gera hard ref de textura (proibido §5).
Substituir por consumo via `UZMMOThemeSubsystem` por `EZMMOThemeKey` no
override de `RefreshUIStyle`; remontar botões/painéis com os widgets
compartilhados (`UI_Button_Master`, `UI_Panel_Master`).
5. **Mover** o asset refinado de `Content/AutoCreated/…` para
`Content/ZMMO/UI/FrontEnd/` (o cru não cooka).
6. Registrar no `DA_FrontEndScreenSet`: `StateScreens[EZMMOFrontEndState::Login]`
= soft class do WBP refinado (páginas do Lobby vão no map de
`EZMMOLobbyPage` da tela de Lobby). Sem registro, o flow faz no-op + log.
**Regra:** uma tela nunca segura hard ref a asset de tema (textura/som/VFX) —
sempre via `UZMMOThemeSubsystem` por `EZMMOThemeKey` (igual §4.7 e §5).
--- ---
## 5. O que **NÃO** fazer ## 5. O que **NÃO** fazer
@@ -588,7 +652,9 @@ chave não estiver preenchida, devolve a do `Default`.
- **Não** misturar assets de uma entidade com assets de outra. Cada - **Não** misturar assets de uma entidade com assets de outra. Cada
Wolf_Gray, Sword_Iron, etc. é uma pasta isolada. Wolf_Gray, Sword_Iron, etc. é uma pasta isolada.
- **Não** colocar nada na raiz `Content/`. Tudo é `Content/ZMMO/...` ou - **Não** colocar nada na raiz `Content/`. Tudo é `Content/ZMMO/...` ou
`Content/{Developers,ExternalContent}/...`. `Content/{Developers,AutoCreated,ExternalContent}/...`. `AutoCreated/` é
quarentena do Zeus UMG Forge: refina-se e **move** para `ZMMO/UI/FrontEnd/`
antes de produção; o cru não cooka.
- **Não** importar conteúdo do Fab/Megascans directo para dentro de `ZMMO/`. - **Não** importar conteúdo do Fab/Megascans directo para dentro de `ZMMO/`.
Vai sempre para `ExternalContent/` e, se quiser usar, **referencia** dali. Vai sempre para `ExternalContent/` e, se quiser usar, **referencia** dali.
- **Não** colocar dados tabulares em Blueprint (lista hardcoded de itens, - **Não** colocar dados tabulares em Blueprint (lista hardcoded de itens,
@@ -598,7 +664,10 @@ chave não estiver preenchida, devolve a do `Default`.
- **Não** colocar hard ref de textura/som de tema dentro de um Widget - **Não** colocar hard ref de textura/som de tema dentro de um Widget
(`TObjectPtr<UTexture2D> ChristmasFrame` é proibido). Widgets pedem ao (`TObjectPtr<UTexture2D> ChristmasFrame` é proibido). Widgets pedem ao
`UZMMOThemeSubsystem` via `EZMMOThemeKey`. Hard ref a asset de tema `UZMMOThemeSubsystem` via `EZMMOThemeKey`. Hard ref a asset de tema
bloqueia a troca em runtime e quebra a política de fallback. bloqueia a troca em runtime e quebra a política de fallback. Vale também
para **telas de front-end** (Boot/Login/ServerSelect/Lobby/…): a saída crua
do Forge traz hard ref — tem de ser reconectada ao subsystem antes do merge
(ver §4.8).
- **Não** criar chave de tema "ad-hoc" como `FName("HUD.Frame")`. Toda chave - **Não** criar chave de tema "ad-hoc" como `FName("HUD.Frame")`. Toda chave
é entrada do enum C++ `EZMMOThemeKey`. String literal é proibida — o é entrada do enum C++ `EZMMOThemeKey`. String literal é proibida — o
compilador tem de quebrar quando alguém esquecer de adicionar a chave. compilador tem de quebrar quando alguém esquecer de adicionar a chave.

View File

@@ -1,6 +1,6 @@
[/Script/EngineSettings.GameMapsSettings] [/Script/EngineSettings.GameMapsSettings]
GameDefaultMap=/Game/ThirdPerson/Lvl_ThirdPerson.Lvl_ThirdPerson GameDefaultMap=/Game/ZMMO/Maps/FrontEnd/L_FrontEnd.L_FrontEnd
EditorStartupMap=/Game/ThirdPerson/Lvl_ThirdPerson.Lvl_ThirdPerson EditorStartupMap=/Game/ZMMO/Maps/FrontEnd/L_FrontEnd.L_FrontEnd
; GameMode/PlayerController/PlayerCharacter agora moram em C++ (Game/Modes/Entity/Controller). ; GameMode/PlayerController/PlayerCharacter agora moram em C++ (Game/Modes/Entity/Controller).
; O motor pode spawnar AZMMOGameMode directamente; um BP filho continua opcional. ; O motor pode spawnar AZMMOGameMode directamente; um BP filho continua opcional.
GlobalDefaultGameMode=/Script/ZMMO.ZMMOGameMode GlobalDefaultGameMode=/Script/ZMMO.ZMMOGameMode

View File

@@ -24,3 +24,8 @@ ProjectName=Third Person Game Template
;CalendarTable=/Game/ZMMO/UI/Themes/DT_ThemeCalendar.DT_ThemeCalendar ;CalendarTable=/Game/ZMMO/UI/Themes/DT_ThemeCalendar.DT_ThemeCalendar
UIStyleTable=/Game/ZMMO/Data/UI/DT_UI_Styles.DT_UI_Styles UIStyleTable=/Game/ZMMO/Data/UI/DT_UI_Styles.DT_UI_Styles
;DevThemeOverride=Christmas ;DevThemeOverride=Christmas
[/Script/ZMMO.UIFrontEndFlowSubsystem]
; Mapa estado->tela. O DA já existe (RootLayoutClass = WBP_PrimaryGameLayout);
; StateScreens fica vazio até os WBPs de página virem do Zeus UMG Forge (§4.8).
ScreenSetAsset=/Game/ZMMO/UI/FrontEnd/DA_FrontEndScreenSet.DA_FrontEndScreenSet

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,40 @@
#pragma once
#include "CoreMinimal.h"
#include "FrontEndTypes.generated.h"
/**
* Estados de topo do fluxo de front-end (MainMenu). Dirigidos pelo
* UUIFrontEndFlowSubsystem; cada estado resolve uma tela
* (UUIActivatableScreen_Base) via DA_FrontEndScreenSet.
*
* Fluxo: Boot → Connecting → Login → ServerSelect → Lobby → EnteringWorld
* → InWorld. O Lobby é o hub principal logado; suas páginas internas são
* EZMMOLobbyPage (não são estados de topo — ver ARQUITETURA.md §4.8).
*/
UENUM(BlueprintType)
enum class EZMMOFrontEndState : uint8
{
None,
Boot, // splash inicial enquanto subsistemas sobem
Connecting, // conectando ao servidor Zeus (UDP)
Login, // autenticação
ServerSelect, // escolha de servidor/realm
Lobby, // hub principal logado (host das EZMMOLobbyPage)
EnteringWorld, // loading/handoff: OpenLevel do mapa de mundo
InWorld // no mundo (HUD de gameplay assume)
};
/**
* Páginas internas do Lobby — navegadas por um stack CommonUI DENTRO da tela
* de Lobby ("Switch principal"), não pelo fluxo de topo.
*/
UENUM(BlueprintType)
enum class EZMMOLobbyPage : uint8
{
None,
CharacterSelect,
CharacterCreate,
Shop,
Settings
};

View File

@@ -0,0 +1,9 @@
#include "UI/UILayerTags.h"
namespace ZMMOUITags
{
UE_DEFINE_GAMEPLAY_TAG(UI_Layer_Game, "UI.Layer.Game");
UE_DEFINE_GAMEPLAY_TAG(UI_Layer_GameMenu, "UI.Layer.GameMenu");
UE_DEFINE_GAMEPLAY_TAG(UI_Layer_Menu, "UI.Layer.Menu");
UE_DEFINE_GAMEPLAY_TAG(UI_Layer_Modal, "UI.Layer.Modal");
}

View File

@@ -0,0 +1,21 @@
#pragma once
#include "NativeGameplayTags.h"
/**
* GameplayTags nativos das camadas do switch principal de UI. Declaração
* nativa (não depende de DefaultGameplayTags.ini) — registradas no startup
* do módulo. Mapeadas para os stacks do UUIPrimaryGameLayout_Base.
*
* UI.Layer.Game — HUD de gameplay (camada de fundo)
* UI.Layer.GameMenu — menus sobre o jogo (inventário, mapa…)
* UI.Layer.Menu — front-end (Boot/Login/ServerSelect/Lobby)
* UI.Layer.Modal — diálogos/loading por cima de tudo
*/
namespace ZMMOUITags
{
ZMMO_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(UI_Layer_Game);
ZMMO_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(UI_Layer_GameMenu);
ZMMO_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(UI_Layer_Menu);
ZMMO_API UE_DECLARE_GAMEPLAY_TAG_EXTERN(UI_Layer_Modal);
}

View File

@@ -0,0 +1,24 @@
#include "ZMMOFrontEndPlayerController.h"
#include "UIFrontEndFlowSubsystem.h"
#include "Engine/GameInstance.h"
AZMMOFrontEndPlayerController::AZMMOFrontEndPlayerController()
{
bShowMouseCursor = true;
}
void AZMMOFrontEndPlayerController::BeginPlay()
{
Super::BeginPlay();
SetInputMode(FInputModeUIOnly());
if (const UGameInstance* GI = GetGameInstance())
{
if (UUIFrontEndFlowSubsystem* Flow = GI->GetSubsystem<UUIFrontEndFlowSubsystem>())
{
Flow->StartFrontEnd();
}
}
}

View File

@@ -0,0 +1,26 @@
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/PlayerController.h"
#include "ZMMOFrontEndPlayerController.generated.h"
/**
* AZMMOFrontEndPlayerController
*
* Controller do mapa de front-end. Não herda de AZMMOPlayerController (sem
* Enhanced Input de gameplay/touch). Em BeginPlay arranca o fluxo de
* front-end (UUIFrontEndFlowSubsystem) — feito aqui, e não no
* GameInstance::Init, porque os subsistemas já existem neste ponto (ver
* aviso no header do ZeusNetworkSubsystem).
*/
UCLASS(Blueprintable, BlueprintType)
class ZMMO_API AZMMOFrontEndPlayerController : public APlayerController
{
GENERATED_BODY()
public:
AZMMOFrontEndPlayerController();
protected:
virtual void BeginPlay() override;
};

View File

@@ -0,0 +1,10 @@
#include "ZMMOFrontEndGameMode.h"
#include "ZMMOFrontEndPlayerController.h"
AZMMOFrontEndGameMode::AZMMOFrontEndGameMode()
{
// UI pura: sem pawn. O controller cria o root layout e arranca o fluxo.
DefaultPawnClass = nullptr;
PlayerControllerClass = AZMMOFrontEndPlayerController::StaticClass();
}

View File

@@ -0,0 +1,22 @@
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/GameModeBase.h"
#include "ZMMOFrontEndGameMode.generated.h"
/**
* AZMMOFrontEndGameMode
*
* GameMode do mapa de front-end (L_FrontEnd): só UI, sem pawn nem network
* play. NÃO é o GameMode global (esse continua AZMMOGameMode para mapas de
* mundo) — aplicado via override no World Settings de L_FrontEnd
* (ARQUITETURA.md §3.3 / decisão de mapa dedicado).
*/
UCLASS(Blueprintable, BlueprintType)
class ZMMO_API AZMMOFrontEndGameMode : public AGameModeBase
{
GENERATED_BODY()
public:
AZMMOFrontEndGameMode();
};

View File

@@ -32,11 +32,12 @@ public:
/** /**
* Se `true`, tenta ligar ao servidor Zeus em `Init()` usando * Se `true`, tenta ligar ao servidor Zeus em `Init()` usando
* `ZeusServerHost`/`ZeusServerPort`. Default `true` em V0 (smoke test); * `ZeusServerHost`/`ZeusServerPort`. Default `false`: a conexao agora e
* passar a `false` quando existir UI de login que controle a conexao. * dirigida pelo `UUIFrontEndFlowSubsystem` (fluxo Boot→Connecting→Login).
* Pôr `true` apenas para smoke test legado sem front-end.
*/ */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Config, Category = "ZMMO|Network") UPROPERTY(EditAnywhere, BlueprintReadWrite, Config, Category = "ZMMO|Network")
bool bAutoConnectOnStart = true; bool bAutoConnectOnStart = false;
/** Host (IPv4 ou hostname) do servidor Zeus. */ /** Host (IPv4 ou hostname) do servidor Zeus. */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Config, Category = "ZMMO|Network") UPROPERTY(EditAnywhere, BlueprintReadWrite, Config, Category = "ZMMO|Network")

View File

@@ -0,0 +1,99 @@
#include "UIActivatableScreen_Base.h"
#include "ZMMOThemeSubsystem.h"
#include "UIFrontEndFlowSubsystem.h"
#include "Engine/GameInstance.h"
#include "Input/UIActionBindingHandle.h"
#include "CommonInputModeTypes.h"
TOptional<FUIInputConfig> UUIActivatableScreen_Base::GetDesiredInputConfig() const
{
switch (InputConfig)
{
case EZMMOScreenInputMode::GameAndMenu:
return FUIInputConfig(ECommonInputMode::All, GameMouseCaptureMode);
case EZMMOScreenInputMode::Game:
return FUIInputConfig(ECommonInputMode::Game, GameMouseCaptureMode);
case EZMMOScreenInputMode::Menu:
return FUIInputConfig(ECommonInputMode::Menu, EMouseCaptureMode::NoCapture);
case EZMMOScreenInputMode::Default:
default:
return TOptional<FUIInputConfig>();
}
}
void UUIActivatableScreen_Base::RefreshUIStyle_Implementation()
{
// Base no-op: cada WBP/filho sobrescreve para reaplicar tokens do tema
// ativo (via UZMMOThemeSubsystem). Mantém o contrato igual a
// UUIButton_Base::RefreshUIStyle / UUIPanel_Base::RefreshUIStyle.
}
void UUIActivatableScreen_Base::NativeConstruct()
{
Super::NativeConstruct();
if (!bThemeBound)
{
if (const UGameInstance* GI = GetGameInstance())
{
if (UZMMOThemeSubsystem* Theme = GI->GetSubsystem<UZMMOThemeSubsystem>())
{
Theme->OnThemeChanged.AddDynamic(this, &UUIActivatableScreen_Base::HandleThemeChanged);
bThemeBound = true;
}
}
}
RefreshUIStyle();
}
void UUIActivatableScreen_Base::NativeDestruct()
{
if (bThemeBound)
{
if (const UGameInstance* GI = GetGameInstance())
{
if (UZMMOThemeSubsystem* Theme = GI->GetSubsystem<UZMMOThemeSubsystem>())
{
Theme->OnThemeChanged.RemoveDynamic(this, &UUIActivatableScreen_Base::HandleThemeChanged);
}
}
bThemeBound = false;
}
Super::NativeDestruct();
}
void UUIActivatableScreen_Base::NativeOnActivated()
{
Super::NativeOnActivated();
RefreshUIStyle();
BP_OnScreenActivated();
}
void UUIActivatableScreen_Base::NativeOnDeactivated()
{
Super::NativeOnDeactivated();
BP_OnScreenDeactivated();
}
bool UUIActivatableScreen_Base::NativeOnHandleBackAction()
{
if (const UGameInstance* GI = GetGameInstance())
{
if (UUIFrontEndFlowSubsystem* Flow = GI->GetSubsystem<UUIFrontEndFlowSubsystem>())
{
if (Flow->RequestBack())
{
return true; // consumido pelo fluxo
}
}
}
return Super::NativeOnHandleBackAction();
}
void UUIActivatableScreen_Base::HandleThemeChanged(FName /*NewThemeId*/)
{
RefreshUIStyle();
}

View File

@@ -0,0 +1,77 @@
#pragma once
#include "CoreMinimal.h"
#include "CommonActivatableWidget.h"
#include "Engine/EngineBaseTypes.h"
#include "UIActivatableScreen_Base.generated.h"
/**
* Modo de input desejado pela tela (espelha o padrão do Lyra). O
* UUIActivatableScreen_Base converte isto num FUIInputConfig em
* GetDesiredInputConfig.
*/
UENUM(BlueprintType)
enum class EZMMOScreenInputMode : uint8
{
Default, // não força nada (herda do CommonUI)
GameAndMenu, // jogo + UI (ex.: HUD com cursor)
Game, // só jogo
Menu // só UI (telas de front-end — default)
};
/**
* Base de TODAS as telas de front-end (Boot/Login/ServerSelect/Lobby/…).
*
* Fundação CommonUI (UCommonActivatableWidget) cuida de
* ativação/foco/voltar/stack. Camada Abstract; o WBP concreto herda DIRETO
* desta classe C++ (UMG não encadeia árvores — ARQUITETURA.md §3.3). Os WBPs
* gerados pelo Zeus UMG Forge são reparented para cá (§4.8).
*
* Reage à troca de tema via UZMMOThemeSubsystem (mesmo padrão de
* UUIButton_Base) e encaminha o "voltar" ao UUIFrontEndFlowSubsystem.
*/
UCLASS(Abstract, Blueprintable)
class ZMMO_API UUIActivatableScreen_Base : public UCommonActivatableWidget
{
GENERATED_BODY()
public:
/** Re-resolve os tokens do tema ativo e reaplica. WBP/filho sobrescreve. */
UFUNCTION(BlueprintNativeEvent, BlueprintCallable, Category = "UI Style")
void RefreshUIStyle();
virtual void RefreshUIStyle_Implementation();
protected:
// ---- Input config (desktop/gamepad) ----
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Input")
EZMMOScreenInputMode InputConfig = EZMMOScreenInputMode::Menu;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Input")
EMouseCaptureMode GameMouseCaptureMode = EMouseCaptureMode::CapturePermanently;
virtual TOptional<FUIInputConfig> GetDesiredInputConfig() const override;
// ---- Lifecycle (espelha o hook de tema de UUIButton_Base) ----
virtual void NativeConstruct() override;
virtual void NativeDestruct() override;
virtual void NativeOnActivated() override;
virtual void NativeOnDeactivated() override;
/** "Voltar" (Esc/B): encaminha ao UUIFrontEndFlowSubsystem::RequestBack. */
virtual bool NativeOnHandleBackAction() override;
/** Hooks Blueprint para o WBP refinado. */
UFUNCTION(BlueprintImplementableEvent, Category = "UI",
meta = (DisplayName = "On Screen Activated"))
void BP_OnScreenActivated();
UFUNCTION(BlueprintImplementableEvent, Category = "UI",
meta = (DisplayName = "On Screen Deactivated"))
void BP_OnScreenDeactivated();
private:
UFUNCTION()
void HandleThemeChanged(FName NewThemeId);
bool bThemeBound = false;
};

View File

@@ -0,0 +1,339 @@
#include "UIFrontEndFlowSubsystem.h"
#include "ZMMO.h"
#include "UIFrontEndScreenSet.h"
#include "UIManagerSubsystem.h"
#include "UIPrimaryGameLayout_Base.h"
#include "UI/UILayerTags.h"
#include "ZMMOGameInstance.h"
#include "ZMMOThemeSubsystem.h"
#include "ZeusNetworkSubsystem.h"
#include "CommonActivatableWidget.h"
#include "Engine/GameInstance.h"
#include "Engine/LocalPlayer.h"
#include "UObject/UObjectGlobals.h"
#include "Engine/AssetManager.h"
#include "Engine/StreamableManager.h"
#include "Kismet/GameplayStatics.h"
void UUIFrontEndFlowSubsystem::Initialize(FSubsystemCollectionBase& Collection)
{
Super::Initialize(Collection);
// Detecta chegada ao mundo após o OpenLevel disparado por travel.
PostLoadMapHandle = FCoreUObjectDelegates::PostLoadMapWithWorld.AddUObject(
this, &UUIFrontEndFlowSubsystem::HandlePostLoadMap);
}
void UUIFrontEndFlowSubsystem::Deinitialize()
{
if (PostLoadMapHandle.IsValid())
{
FCoreUObjectDelegates::PostLoadMapWithWorld.Remove(PostLoadMapHandle);
PostLoadMapHandle.Reset();
}
UnbindNetwork();
Super::Deinitialize();
}
void UUIFrontEndFlowSubsystem::StartFrontEnd()
{
UUIFrontEndScreenSet* SS = GetScreenSet();
if (UUIManagerSubsystem* Mgr = GetUIManager())
{
TSubclassOf<UUIPrimaryGameLayout_Base> LayoutClass;
if (SS && !SS->RootLayoutClass.IsNull())
{
LayoutClass = SS->RootLayoutClass.LoadSynchronous();
}
Mgr->CreateAndAddRootLayout(LayoutClass);
}
BindNetwork();
SetState(EZMMOFrontEndState::Boot);
// Substitui o auto-connect do GameInstance: o fluxo dirige a conexão.
if (UZeusNetworkSubsystem* Zeus = GetZeusNetwork())
{
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);
}
else
{
UE_LOG(LogZMMO, Warning, TEXT("FrontEndFlow: ZeusNetworkSubsystem indisponível; sem conexão."));
}
}
void UUIFrontEndFlowSubsystem::SetState(EZMMOFrontEndState NewState)
{
if (NewState == CurrentState)
{
return;
}
CurrentState = NewState;
UE_LOG(LogZMMO, Log, TEXT("FrontEndFlow: estado → %s"),
*UEnum::GetValueAsString(NewState));
OnStateChanged.Broadcast(NewState);
const FGameplayTag MenuLayer = ZMMOUITags::UI_Layer_Menu.GetTag();
const FGameplayTag ModalLayer = ZMMOUITags::UI_Layer_Modal.GetTag();
switch (NewState)
{
case EZMMOFrontEndState::Boot:
case EZMMOFrontEndState::Connecting:
case EZMMOFrontEndState::Login:
case EZMMOFrontEndState::ServerSelect:
case EZMMOFrontEndState::Lobby:
// Telas do front-end vivem na camada Menu (uma por vez).
if (UUIManagerSubsystem* Mgr = GetUIManager())
{
Mgr->ClearLayer(MenuLayer);
}
ResolveAndPushScreen(NewState);
break;
case EZMMOFrontEndState::EnteringWorld:
// Loading/handoff por cima de tudo (camada Modal).
ResolveAndPushScreen(NewState);
break;
case EZMMOFrontEndState::InWorld:
// HUD de gameplay assume — limpa front-end e loading.
if (UUIManagerSubsystem* Mgr = GetUIManager())
{
Mgr->ClearLayer(ModalLayer);
Mgr->ClearLayer(MenuLayer);
}
break;
case EZMMOFrontEndState::None:
default:
break;
}
}
void UUIFrontEndFlowSubsystem::ShowLobbyPage(EZMMOLobbyPage Page)
{
UUIFrontEndScreenSet* SS = GetScreenSet();
UUIManagerSubsystem* Mgr = GetUIManager();
if (!SS || !Mgr)
{
return;
}
const TSoftClassPtr<UCommonActivatableWidget> Soft = SS->GetLobbyPage(Page);
if (Soft.IsNull())
{
UE_LOG(LogZMMO, Warning, TEXT("FrontEndFlow: página de Lobby %s não configurada (DA vazio)."),
*UEnum::GetValueAsString(Page));
return;
}
const FGameplayTag MenuLayer = ZMMOUITags::UI_Layer_Menu.GetTag();
const FSoftObjectPath Path = Soft.ToSoftObjectPath();
UAssetManager::GetStreamableManager().RequestAsyncLoad(
Path,
FStreamableDelegate::CreateWeakLambda(this, [this, Soft, MenuLayer]()
{
if (UUIManagerSubsystem* M = GetUIManager())
{
if (UClass* Cls = Soft.Get())
{
M->PushScreenToLayer(MenuLayer, Cls);
}
}
}));
}
bool UUIFrontEndFlowSubsystem::RequestBack()
{
switch (CurrentState)
{
case EZMMOFrontEndState::ServerSelect:
SetState(EZMMOFrontEndState::Login);
return true;
case EZMMOFrontEndState::Lobby:
SetState(EZMMOFrontEndState::ServerSelect);
return true;
default:
return false;
}
}
void UUIFrontEndFlowSubsystem::ResolveAndPushScreen(EZMMOFrontEndState State)
{
UUIFrontEndScreenSet* SS = GetScreenSet();
if (!SS)
{
UE_LOG(LogZMMO, Warning,
TEXT("FrontEndFlow: ScreenSet não configurado; tela de %s ignorada."),
*UEnum::GetValueAsString(State));
return;
}
const TSoftClassPtr<UCommonActivatableWidget> Soft = SS->GetScreenForState(State);
if (Soft.IsNull())
{
UE_LOG(LogZMMO, Warning, TEXT("FrontEndFlow: Screen for state %s not configured."),
*UEnum::GetValueAsString(State));
return;
}
UUIManagerSubsystem* Mgr = GetUIManager();
if (!Mgr)
{
return;
}
const FGameplayTag Layer = (State == EZMMOFrontEndState::EnteringWorld)
? ZMMOUITags::UI_Layer_Modal.GetTag()
: ZMMOUITags::UI_Layer_Menu.GetTag();
const FSoftObjectPath Path = Soft.ToSoftObjectPath();
UAssetManager::GetStreamableManager().RequestAsyncLoad(
Path,
FStreamableDelegate::CreateWeakLambda(this, [this, Soft, Layer]()
{
if (UUIManagerSubsystem* M = GetUIManager())
{
if (UClass* Cls = Soft.Get())
{
M->PushScreenToLayer(Layer, Cls);
}
}
}));
}
UZeusNetworkSubsystem* UUIFrontEndFlowSubsystem::GetZeusNetwork() const
{
const UGameInstance* GI = GetGameInstance();
return GI ? GI->GetSubsystem<UZeusNetworkSubsystem>() : nullptr;
}
UUIManagerSubsystem* UUIFrontEndFlowSubsystem::GetUIManager() const
{
if (const UGameInstance* GI = GetGameInstance())
{
if (ULocalPlayer* LP = GI->GetFirstGamePlayer())
{
return LP->GetSubsystem<UUIManagerSubsystem>();
}
}
return nullptr;
}
UUIFrontEndScreenSet* UUIFrontEndFlowSubsystem::GetScreenSet()
{
if (ScreenSet)
{
return ScreenSet;
}
if (!ScreenSetAsset.IsNull())
{
ScreenSet = ScreenSetAsset.LoadSynchronous();
}
else
{
UE_LOG(LogZMMO, Warning,
TEXT("FrontEndFlow: ScreenSetAsset não setado em DefaultGame.ini; telas farão no-op."));
}
return ScreenSet;
}
void UUIFrontEndFlowSubsystem::BindNetwork()
{
if (bNetBound)
{
return;
}
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;
}
}
void UUIFrontEndFlowSubsystem::UnbindNetwork()
{
if (!bNetBound)
{
return;
}
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;
}
void UUIFrontEndFlowSubsystem::HandleConnected()
{
UE_LOG(LogZMMO, Log, TEXT("FrontEndFlow: conectado → Login."));
SetState(EZMMOFrontEndState::Login);
}
void UUIFrontEndFlowSubsystem::HandleConnectionFailed(FString Reason)
{
UE_LOG(LogZMMO, Warning, TEXT("FrontEndFlow: conexão falhou (%s) → volta a Login."), *Reason);
SetState(EZMMOFrontEndState::Login);
}
void UUIFrontEndFlowSubsystem::HandleDisconnected()
{
UE_LOG(LogZMMO, Log, TEXT("FrontEndFlow: desconectado."));
if (CurrentState != EZMMOFrontEndState::InWorld)
{
SetState(EZMMOFrontEndState::Login);
}
}
void UUIFrontEndFlowSubsystem::HandleServerTravelRequested(const FString& MapName, const FString& MapPath)
{
UE_LOG(LogZMMO, Log, TEXT("FrontEndFlow: travel pedido pelo servidor → %s (%s)."), *MapName, *MapPath);
bTravelingToWorld = true;
SetState(EZMMOFrontEndState::EnteringWorld);
if (!MapPath.IsEmpty())
{
UGameplayStatics::OpenLevel(GetGameInstance(), FName(*MapPath));
}
}
void UUIFrontEndFlowSubsystem::HandlePostLoadMap(UWorld* /*LoadedWorld*/)
{
if (bTravelingToWorld)
{
bTravelingToWorld = false;
SetState(EZMMOFrontEndState::InWorld);
}
}
void UUIFrontEndFlowSubsystem::HandleServerHelloTheme(FName ThemeId)
{
// Dívida técnica (D5): ainda não há gancho no ZeusNetworkSubsystem para o
// ThemeId do ServerHello. Mantido pronto para quando o plugin expuser.
if (const UGameInstance* GI = GetGameInstance())
{
if (UZMMOThemeSubsystem* Theme = GI->GetSubsystem<UZMMOThemeSubsystem>())
{
Theme->ApplyServerTheme(ThemeId);
}
}
}

View File

@@ -0,0 +1,109 @@
#pragma once
#include "CoreMinimal.h"
#include "Subsystems/GameInstanceSubsystem.h"
#include "UI/FrontEndTypes.h"
#include "UIFrontEndFlowSubsystem.generated.h"
class UUIFrontEndScreenSet;
class UUIManagerSubsystem;
class UZeusNetworkSubsystem;
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnZMMOFrontEndStateChanged, EZMMOFrontEndState, NewState);
/**
* Orquestrador do fluxo de front-end. UGameInstanceSubsystem porque o
* GameInstance persiste o travel boot→mundo (a máquina de estados não pode
* morrer no OpenLevel).
*
* Responsável por: (a) dirigir a conexão via UZeusNetworkSubsystem;
* (b) manter EZMMOFrontEndState; (c) resolver estado→tela pelo
* DA_FrontEndScreenSet e pedir ao UUIManagerSubsystem para empurrar na
* camada certa; (d) detectar a chegada ao mundo.
*
* As telas (WBP) ainda não existem — virão do Zeus UMG Forge (§4.8).
* Enquanto não registradas no DA, cada transição faz no-op + log.
*/
UCLASS(Config = Game)
class ZMMO_API UUIFrontEndFlowSubsystem : public UGameInstanceSubsystem
{
GENERATED_BODY()
public:
// USubsystem
virtual void Initialize(FSubsystemCollectionBase& Collection) override;
virtual void Deinitialize() override;
/**
* Ponto de entrada chamado pelo AZMMOFrontEndPlayerController::BeginPlay
* (garante que os subsistemas já existem — ver aviso no header do
* ZeusNetworkSubsystem sobre GameInstance::Init). Cria o root layout,
* liga os delegates de rede e dispara Boot→Connecting.
*/
UFUNCTION(BlueprintCallable, Category = "FrontEnd")
void StartFrontEnd();
UFUNCTION(BlueprintCallable, Category = "FrontEnd")
void SetState(EZMMOFrontEndState NewState);
UFUNCTION(BlueprintPure, Category = "FrontEnd")
EZMMOFrontEndState GetCurrentState() const { return CurrentState; }
/** Mostra uma página interna do Lobby ("switch principal"). */
UFUNCTION(BlueprintCallable, Category = "FrontEnd")
void ShowLobbyPage(EZMMOLobbyPage Page);
/** "Voltar". Retorna true se o fluxo consumiu (tela base encaminha aqui). */
UFUNCTION(BlueprintCallable, Category = "FrontEnd")
bool RequestBack();
UPROPERTY(BlueprintAssignable, Category = "FrontEnd")
FOnZMMOFrontEndStateChanged OnStateChanged;
protected:
/** DA com o mapa estado→tela. Config em DefaultGame.ini. */
UPROPERTY(Config, EditDefaultsOnly, Category = "FrontEnd")
TSoftObjectPtr<UUIFrontEndScreenSet> ScreenSetAsset;
private:
void BindNetwork();
void UnbindNetwork();
UZeusNetworkSubsystem* GetZeusNetwork() const;
UUIManagerSubsystem* GetUIManager() const;
UUIFrontEndScreenSet* GetScreenSet();
/** Resolve a soft class do estado e empurra na camada Menu (no-op+log se vazio). */
void ResolveAndPushScreen(EZMMOFrontEndState State);
UFUNCTION()
void HandleConnected();
UFUNCTION()
void HandleConnectionFailed(FString Reason);
UFUNCTION()
void HandleDisconnected();
UFUNCTION()
void HandleServerTravelRequested(const FString& MapName, const FString& MapPath);
void HandlePostLoadMap(UWorld* LoadedWorld);
/**
* Dívida técnica (D5): ServerHello traz ThemeId, mas o UZeusNetworkSubsystem
* ainda não expõe um delegate/getter dedicado. Quando expuser, ligar aqui
* → UZMMOThemeSubsystem::ApplyServerTheme. Sem isto, o tema resolve por
* calendário/Default (ARQUITETURA.md §1.10).
*/
void HandleServerHelloTheme(FName ThemeId);
UPROPERTY(Transient)
EZMMOFrontEndState CurrentState = EZMMOFrontEndState::None;
UPROPERTY(Transient)
TObjectPtr<UUIFrontEndScreenSet> ScreenSet;
bool bNetBound = false;
bool bTravelingToWorld = false;
FDelegateHandle PostLoadMapHandle;
};

View File

@@ -0,0 +1,19 @@
#include "UIFrontEndScreenSet.h"
TSoftClassPtr<UCommonActivatableWidget> UUIFrontEndScreenSet::GetScreenForState(EZMMOFrontEndState State) const
{
if (const TSoftClassPtr<UCommonActivatableWidget>* Found = StateScreens.Find(State))
{
return *Found;
}
return TSoftClassPtr<UCommonActivatableWidget>();
}
TSoftClassPtr<UCommonActivatableWidget> UUIFrontEndScreenSet::GetLobbyPage(EZMMOLobbyPage Page) const
{
if (const TSoftClassPtr<UCommonActivatableWidget>* Found = LobbyPages.Find(Page))
{
return *Found;
}
return TSoftClassPtr<UCommonActivatableWidget>();
}

View File

@@ -0,0 +1,45 @@
#pragma once
#include "CoreMinimal.h"
#include "Engine/DataAsset.h"
#include "Templates/SubclassOf.h"
#include "UI/FrontEndTypes.h"
#include "UIFrontEndScreenSet.generated.h"
class UCommonActivatableWidget;
class UUIPrimaryGameLayout_Base;
/**
* Mapa data-driven estado → tela. Desacopla o UUIFrontEndFlowSubsystem das
* classes concretas: as telas (WBP) ainda não existem — serão geradas pelo
* Zeus UMG Forge, refinadas e registradas aqui depois (ARQUITETURA.md §4.8).
* Enquanto o registro estiver vazio, o flow faz no-op + log.
*
* Asset concreto: DA_FrontEndScreenSet em Content/ZMMO/UI/FrontEnd/.
*/
UCLASS(BlueprintType)
class ZMMO_API UUIFrontEndScreenSet : public UDataAsset
{
GENERATED_BODY()
public:
/** WBP de layout raiz (herda UUIPrimaryGameLayout_Base). */
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "FrontEnd")
TSoftClassPtr<UUIPrimaryGameLayout_Base> RootLayoutClass;
/** Tela por estado de topo do fluxo. */
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "FrontEnd")
TMap<EZMMOFrontEndState, TSoftClassPtr<UCommonActivatableWidget>> StateScreens;
/** Páginas internas do Lobby (host = a tela de Lobby; "switch principal"). */
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "FrontEnd")
TMap<EZMMOLobbyPage, TSoftClassPtr<UCommonActivatableWidget>> LobbyPages;
/** Soft class da tela do estado (vazio se não configurado). */
UFUNCTION(BlueprintCallable, Category = "FrontEnd")
TSoftClassPtr<UCommonActivatableWidget> GetScreenForState(EZMMOFrontEndState State) const;
/** Soft class da página de Lobby (vazio se não configurado). */
UFUNCTION(BlueprintCallable, Category = "FrontEnd")
TSoftClassPtr<UCommonActivatableWidget> GetLobbyPage(EZMMOLobbyPage Page) const;
};

View File

@@ -0,0 +1,95 @@
#include "UIManagerSubsystem.h"
#include "ZMMO.h"
#include "UIPrimaryGameLayout_Base.h"
#include "CommonActivatableWidget.h"
#include "Engine/LocalPlayer.h"
#include "GameFramework/PlayerController.h"
#include "Blueprint/UserWidget.h"
namespace
{
constexpr int32 GRootLayoutZOrder = 1000;
}
void UUIManagerSubsystem::CreateAndAddRootLayout(TSubclassOf<UUIPrimaryGameLayout_Base> LayoutClass)
{
// Idempotente: sobrevive ao travel boot→mundo sem recriar.
if (RootLayout && RootLayout->IsInViewport())
{
return;
}
if (!LayoutClass)
{
UE_LOG(LogZMMO, Warning,
TEXT("UIManagerSubsystem: RootLayoutClass não configurado (DA_FrontEndScreenSet vazio?). Sem root layout."));
return;
}
ULocalPlayer* LP = GetLocalPlayer();
if (!LP)
{
return;
}
APlayerController* PC = LP->GetPlayerController(LP->GetWorld());
if (!PC)
{
UE_LOG(LogZMMO, Warning, TEXT("UIManagerSubsystem: PlayerController ausente; root layout adiado."));
return;
}
RootLayout = CreateWidget<UUIPrimaryGameLayout_Base>(PC, LayoutClass);
if (!RootLayout)
{
UE_LOG(LogZMMO, Warning, TEXT("UIManagerSubsystem: falha ao criar o root layout."));
return;
}
RootLayout->AddToViewport(GRootLayoutZOrder);
UE_LOG(LogZMMO, Log, TEXT("UIManagerSubsystem: root layout criado e adicionado ao viewport."));
}
bool UUIManagerSubsystem::IsRootLayoutReady() const
{
return RootLayout != nullptr && RootLayout->IsInViewport();
}
UCommonActivatableWidget* UUIManagerSubsystem::PushScreenToLayer(
FGameplayTag LayerTag,
TSubclassOf<UCommonActivatableWidget> WidgetClass)
{
if (!RootLayout)
{
UE_LOG(LogZMMO, Warning, TEXT("UIManagerSubsystem: PushScreenToLayer sem root layout."));
return nullptr;
}
return RootLayout->PushWidgetToLayer(LayerTag, WidgetClass);
}
void UUIManagerSubsystem::PopScreenFromLayer(FGameplayTag LayerTag, UCommonActivatableWidget* Widget)
{
if (RootLayout)
{
RootLayout->PopWidgetFromLayer(LayerTag, Widget);
}
}
void UUIManagerSubsystem::ClearLayer(FGameplayTag LayerTag)
{
if (RootLayout)
{
RootLayout->ClearLayer(LayerTag);
}
}
void UUIManagerSubsystem::Deinitialize()
{
if (RootLayout)
{
RootLayout->RemoveFromParent();
RootLayout = nullptr;
}
Super::Deinitialize();
}

View File

@@ -0,0 +1,55 @@
#pragma once
#include "CoreMinimal.h"
#include "Subsystems/LocalPlayerSubsystem.h"
#include "GameplayTagContainer.h"
#include "Templates/SubclassOf.h"
#include "UIManagerSubsystem.generated.h"
class UCommonActivatableWidget;
class UUIPrimaryGameLayout_Base;
/**
* Dono do layout raiz para o LocalPlayer. ULocalPlayerSubsystem porque o
* LocalPlayer persiste entre mapas (non-seamless do mesmo GameInstance) e
* troca de PlayerController — o root layout sobrevive ao travel
* boot→mundo sem ser recriado (idempotente).
*
* É "burro": recebe classes já resolvidas. Quem resolve estado→tela e dirige
* a navegação é o UUIFrontEndFlowSubsystem.
*/
UCLASS()
class ZMMO_API UUIManagerSubsystem : public ULocalPlayerSubsystem
{
GENERATED_BODY()
public:
/** Cria o root layout e adiciona ao viewport. Idempotente. */
UFUNCTION(BlueprintCallable, Category = "UI|Manager")
void CreateAndAddRootLayout(TSubclassOf<UUIPrimaryGameLayout_Base> LayoutClass);
UFUNCTION(BlueprintPure, Category = "UI|Manager")
bool IsRootLayoutReady() const;
UFUNCTION(BlueprintPure, Category = "UI|Manager")
UUIPrimaryGameLayout_Base* GetRootLayout() const { return RootLayout; }
/** Empurra a widget no topo do stack da camada. */
UFUNCTION(BlueprintCallable, Category = "UI|Manager")
UCommonActivatableWidget* PushScreenToLayer(
FGameplayTag LayerTag,
TSubclassOf<UCommonActivatableWidget> WidgetClass);
UFUNCTION(BlueprintCallable, Category = "UI|Manager")
void PopScreenFromLayer(FGameplayTag LayerTag, UCommonActivatableWidget* Widget);
UFUNCTION(BlueprintCallable, Category = "UI|Manager")
void ClearLayer(FGameplayTag LayerTag);
// USubsystem
virtual void Deinitialize() override;
private:
UPROPERTY(Transient)
TObjectPtr<UUIPrimaryGameLayout_Base> RootLayout;
};

View File

@@ -0,0 +1,64 @@
#include "UIPrimaryGameLayout_Base.h"
#include "CommonActivatableWidget.h"
#include "Widgets/CommonActivatableWidgetContainer.h"
#include "UI/UILayerTags.h"
void UUIPrimaryGameLayout_Base::NativeOnInitialized()
{
Super::NativeOnInitialized();
RegisterLayer(ZMMOUITags::UI_Layer_Game, Stack_Game);
RegisterLayer(ZMMOUITags::UI_Layer_GameMenu, Stack_GameMenu);
RegisterLayer(ZMMOUITags::UI_Layer_Menu, Stack_Menu);
RegisterLayer(ZMMOUITags::UI_Layer_Modal, Stack_Modal);
}
void UUIPrimaryGameLayout_Base::RegisterLayer(FGameplayTag LayerTag, UCommonActivatableWidgetStack* Stack)
{
if (Stack)
{
Layers.Add(LayerTag, Stack);
}
}
UCommonActivatableWidgetStack* UUIPrimaryGameLayout_Base::GetLayerStack(FGameplayTag LayerTag) const
{
const TObjectPtr<UCommonActivatableWidgetStack>* Found = Layers.Find(LayerTag);
return Found ? *Found : nullptr;
}
UCommonActivatableWidget* UUIPrimaryGameLayout_Base::PushWidgetToLayer(
FGameplayTag LayerTag,
TSubclassOf<UCommonActivatableWidget> WidgetClass)
{
if (!WidgetClass)
{
return nullptr;
}
if (UCommonActivatableWidgetStack* Stack = GetLayerStack(LayerTag))
{
return Stack->AddWidget(WidgetClass);
}
return nullptr;
}
void UUIPrimaryGameLayout_Base::PopWidgetFromLayer(FGameplayTag LayerTag, UCommonActivatableWidget* Widget)
{
if (!Widget)
{
return;
}
if (UCommonActivatableWidgetStack* Stack = GetLayerStack(LayerTag))
{
Stack->RemoveWidget(*Widget);
}
}
void UUIPrimaryGameLayout_Base::ClearLayer(FGameplayTag LayerTag)
{
if (UCommonActivatableWidgetStack* Stack = GetLayerStack(LayerTag))
{
Stack->ClearWidgets();
}
}

View File

@@ -0,0 +1,72 @@
#pragma once
#include "CoreMinimal.h"
#include "CommonUserWidget.h"
#include "GameplayTagContainer.h"
#include "Templates/SubclassOf.h"
#include "UIPrimaryGameLayout_Base.generated.h"
class UCommonActivatableWidget;
class UCommonActivatableWidgetStack;
/**
* Layout raiz ("switch principal") do cliente. Mantém 4 stacks CommonUI,
* um por camada (GameplayTag UI.Layer.* — ver UI/UILayerTags.h):
*
* Stack_Game ← UI.Layer.Game (HUD de gameplay)
* Stack_GameMenu ← UI.Layer.GameMenu (menus sobre o jogo)
* Stack_Menu ← UI.Layer.Menu (front-end: Boot/Login/Lobby/…)
* Stack_Modal ← UI.Layer.Modal (diálogos/loading por cima)
*
* Camada Abstract; o WBP concreto (WBP_PrimaryGameLayout) herda DIRETO desta
* classe C++ e nomeia os 4 stacks exatamente como os BindWidget abaixo
* (ARQUITETURA.md §3.3). O UUIManagerSubsystem instancia o WBP e o adiciona
* ao viewport; o UUIFrontEndFlowSubsystem empurra telas por camada.
*/
UCLASS(Abstract, Blueprintable)
class ZMMO_API UUIPrimaryGameLayout_Base : public UCommonUserWidget
{
GENERATED_BODY()
public:
/** Stack da camada (nullptr se a tag não for uma UI.Layer.* conhecida). */
UFUNCTION(BlueprintCallable, Category = "UI|Layout")
UCommonActivatableWidgetStack* GetLayerStack(FGameplayTag LayerTag) const;
/** Instancia e empurra a widget no topo do stack da camada. */
UFUNCTION(BlueprintCallable, Category = "UI|Layout")
UCommonActivatableWidget* PushWidgetToLayer(
FGameplayTag LayerTag,
TSubclassOf<UCommonActivatableWidget> WidgetClass);
/** Remove uma widget específica do stack da camada. */
UFUNCTION(BlueprintCallable, Category = "UI|Layout")
void PopWidgetFromLayer(FGameplayTag LayerTag, UCommonActivatableWidget* Widget);
/** Esvazia o stack da camada. */
UFUNCTION(BlueprintCallable, Category = "UI|Layout")
void ClearLayer(FGameplayTag LayerTag);
protected:
virtual void NativeOnInitialized() override;
/** Registra um stack numa camada (chamado em NativeOnInitialized). */
void RegisterLayer(FGameplayTag LayerTag, UCommonActivatableWidgetStack* Stack);
// ---- Stacks por camada (o WBP concreto deve nomear assim) ----
UPROPERTY(BlueprintReadOnly, Category = "UI|Layout", meta = (BindWidget))
TObjectPtr<UCommonActivatableWidgetStack> Stack_Game;
UPROPERTY(BlueprintReadOnly, Category = "UI|Layout", meta = (BindWidget))
TObjectPtr<UCommonActivatableWidgetStack> Stack_GameMenu;
UPROPERTY(BlueprintReadOnly, Category = "UI|Layout", meta = (BindWidget))
TObjectPtr<UCommonActivatableWidgetStack> Stack_Menu;
UPROPERTY(BlueprintReadOnly, Category = "UI|Layout", meta = (BindWidget))
TObjectPtr<UCommonActivatableWidgetStack> Stack_Modal;
private:
UPROPERTY(Transient)
TMap<FGameplayTag, TObjectPtr<UCommonActivatableWidgetStack>> Layers;
};

View File

@@ -17,6 +17,7 @@ public class ZMMO : ModuleRules
"Niagara", "Niagara",
"CommonUI", "CommonUI",
"CommonInput", "CommonInput",
"GameplayTags",
"ZeusNetwork" "ZeusNetwork"
}); });
@@ -33,6 +34,7 @@ public class ZMMO : ModuleRules
"ZMMO/Game/Network", "ZMMO/Game/Network",
"ZMMO/Game/UI", "ZMMO/Game/UI",
"ZMMO/Game/UI/Widgets", "ZMMO/Game/UI/Widgets",
"ZMMO/Game/UI/FrontEnd",
"ZMMO/Data", "ZMMO/Data",
"ZMMO/Data/Items", "ZMMO/Data/Items",
"ZMMO/Data/Mobs", "ZMMO/Data/Mobs",