feat(ui-ingame): UI in-game system + PlayerState + Component Registry + HUD composite

Espelho simétrico do front-end pattern (UUIFrontEndFlowSubsystem +
DA_FrontEndScreenSet + UUIPrimaryGameLayout_Base) para a UI in-game.
Pavimenta caminho pra Inventory, Skills, StatusWindow, EscapeMenu, etc.

## UI in-game system (Source/ZMMO/Game/UI/InGame/)

- EZMMOInGameUIState enum (None, Playing, StatusWindow, Inventory,
  SkillTree, EscapeMenu, Loading) em Data/UI/InGameTypes.h
- UUIInGameScreenSet DataAsset: TMap<EZMMOInGameUIState, TSoftClassPtr<...>>
- UUIInGameFlowSubsystem (GameInstanceSubsystem) com SetState/ToggleScreen
- DA_InGameScreenSet em Content/ZMMO/UI/InGame/ mapeia Playing->WBP_HUD
- Layer routing: Playing -> UI.Layer.Game | menus -> GameMenu | loading -> Modal

## AHUD pattern (idiomatic UE5)

- AZMMOHUD : AHUD em Source/ZMMO/Game/Modes/
- AZMMOGameMode::HUDClass = AZMMOHUD::StaticClass()
- AZMMOHUD::BeginPlay chama UIInGameFlowSubsystem::StartInGame
- Pawn não cria mais HUD (separação de responsabilidades)

## PlayerState + Component Registry (Open-Closed)

- AZMMOPlayerState : APlayerState em Source/ZMMO/Game/Modes/
- AZMMOGameMode::PlayerStateClass = AZMMOPlayerState::StaticClass()
- UPROPERTY Config TArray<TSubclassOf<UActorComponent>> ComponentClasses
- DefaultGame.ini: +ComponentClasses=/Script/ZMMOAttributes.ZMMOAttributeComponent
- Ctor lê via GConfig (mais robusto que UPROPERTY Config + parsing) e
  instancia cada componente como CreateDefaultSubobject

Por que componentes no PlayerState e não no Pawn:
- Sobrevive ao despawn (morte/respawn)
- Spectator-friendly
- 1:1 com player (Pawn pode trocar: vehicle, mount, possession)
- Padrão MMO clássico (rathena, TrinityCore)

Adicionar novo módulo (Inventory, Skills, Guild) = 1 linha no
DefaultGame.ini, sem editar AZMMOPlayerState.cpp.

## HUD composite

- UZMMOHudWidget : UCommonActivatableWidget em ZMMO core (UI/InGame/)
- WBP_HUD em Content/ZMMO/UI/HUD/ herda UZMMOHudWidget
- HpSpBar (UZMMOHudHpSpWidget — sub-modulo ZMMOAttributes) embedded via
  BindWidgetOptional. Sub-widgets futuros: PlayerInfo, Minimap, QuickBar,
  Buffs, Chat, TargetInfo
- UZMMOHudHpSpWidget volta a ser UUserWidget puro (sub-widget, não root)
- UZMMOHudWidget::NativeOnActivated:
  - SetVisibility(HitTestInvisible) — mouse passa direto pro Pawn
  - BindToLocalPlayer: busca AttributeComponent no PC->PlayerState
  - Subscreve OnAttributesChanged/OnHpSpChanged/OnLevelUp
  - Propaga snapshots pra sub-widgets via HpSpBar->ApplySnapshot
- GetDesiredInputConfig override: ECommonInputMode::Game (sem cursor,
  movimento livre). Menus override pra GameAndMenu/Menu.

## NetworkHandler refatorado (AttributeComponent agora no PlayerState)

- ZMMOAttributeNetworkHandler::FindComponentForEntity: busca via
  GameState->PlayerArray (O(N_players)), não TActorIterator (O(N_actors))
- ZMMOAttributes.Build.cs += CommonUI, CommonInput

## EnsureRootLayout (fix crítico)

- OpenLevel invalida widgets no viewport ("InvalidateAllWidgets")
- RootLayout (criado pelo FrontEndFlow no boot) sobrevive como UObject
  (LocalPlayerSubsystem) mas fica órfão fora do viewport
- Push do WBP_HUD no Stack_Game funcionava mas widget pai (RootLayout)
  estava fora do viewport → invisível
- UUIFrontEndFlowSubsystem::EnsureRootLayout (público) recria via
  UIManagerSubsystem (idempotente)
- UUIInGameFlowSubsystem::StartInGame chama EnsureRootLayout antes do push

## PlayerCharacter simplificado

- Removido AttributeComponent (vai pro PlayerState via Registry)
- Removido HudHpSpWidgetClass + spawn manual de HUD
- HandleLocalSpawnReady só faz seed do EntityId via PlayerState
- UI lifecycle agora é responsabilidade do AZMMOHUD::BeginPlay

## Verificação (smoke test)

PIE: Login -> CharSelect -> Lobby -> EnterWorld
  ↓
LogZMMO: AZMMOPlayerState ctor: 1 componente(s) no registry
LogZMMO: ZMMOPlayerState: componente registrado [0] ZMMOAttributeComponent
LogZMMO: FrontEndFlow::EnsureRootLayout: RootLayout recriado
LogZMMO: InGameFlow: state None -> Playing
LogZMMO: InGameFlow: tela Playing pushed em UI.Layer.Game (widget=WBP_HUD_C_0)
LogZMMO: ZMMOHudWidget activated (HpSpBar=WBP_HUD_HpSpBar_C_0)
  ↓
HUD aparece com Lv 5 / HP 10/200 / SP 5/20 (valores do DB)
Regen funciona (HP/SP enchem a cada 6s/8s)
Movimento normal (input flui pro Pawn)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-23 05:27:41 -03:00
parent 7d83dd800f
commit 396223e2a8
25 changed files with 1029 additions and 242 deletions

View File

@@ -7,21 +7,27 @@
class UProgressBar;
class UTextBlock;
class UZMMOAttributeComponent;
/**
* Widget base do HUD de atributos (HP/SP/level). Spawnada por
* `AZMMOPlayerCharacter::BeginPlay` quando o pawn local virou autoritativo.
* Sub-widget do HUD: barras de HP/SP + label de level. Sem auto-bind no
* AttributeComponent — o parent (`UZMMOHudWidget`) propaga snapshots via
* `ApplySnapshot` / `ApplyHpSpDelta` quando o player local recebe updates.
*
* Layout (cor, textos, ancoras) vem do WBP filho via UMG.
* Comportamento (binding com AttributeComponent + refresh on delegate)
* vive aqui em C++ — permite que outros modulos consumam esta API
* sem precisar abrir o WBP. Sub-classe via Blueprint apenas para skin
* visual e disposicao dos sub-widgets.
* Composicao do HUD (ver `UZMMOHudWidget`):
* WBP_HUD (UZMMOHudWidget root)
* |-- HpSpBar (UZMMOHudHpSpWidget — este)
* |-- PlayerInfo (futuro — nome/job/level)
* |-- Minimap (futuro)
* |-- QuickBar (futuro — hotkeys)
* |-- Buffs (futuro — icones SC)
* |-- Chat (futuro)
* |-- TargetInfo (futuro — mob/player targetado)
*
* Convencao `meta=(BindWidget)`: o WBP filho DEVE ter widgets com os
* mesmos nomes (HpBar, SpBar). `BindWidgetOptional` permite ao WBP
* omitir o widget (ex.: HUD compacto sem texto numerico).
* Cada sub-widget tem responsabilidade unica (SRP). O parent (UZMMOHudWidget)
* faz binding com AttributeComponent e propaga deltas.
*
* Convencao `meta=(BindWidget)`: o WBP filho deve ter widgets com nomes
* exatos (HpBar, SpBar). `BindWidgetOptional` permite omitir.
*/
UCLASS(Abstract, Blueprintable, BlueprintType)
class ZMMOATTRIBUTES_API UZMMOHudHpSpWidget : public UUserWidget
@@ -29,34 +35,24 @@ class ZMMOATTRIBUTES_API UZMMOHudHpSpWidget : public UUserWidget
GENERATED_BODY()
public:
/**
* Liga o widget a um `UZMMOAttributeComponent`. Subscreve aos delegates
* e faz um refresh imediato com o snapshot corrente. Idempotente —
* chamar com outro componente desliga do anterior.
* Se `InComponent` for nullptr, desliga sem religar.
*/
/** Atualiza todos os campos a partir do snapshot. Chamado pelo UZMMOHudWidget. */
UFUNCTION(BlueprintCallable, Category = "Zeus|HUD")
void BindToAttributeComponent(UZMMOAttributeComponent* InComponent);
void ApplySnapshot(const FZMMOAttributesSnapshot& Snapshot);
UFUNCTION(BlueprintPure, Category = "Zeus|HUD")
UZMMOAttributeComponent* GetBoundComponent() const { return BoundComponent.Get(); }
/** Atualiza apenas HP/SP (sem mexer no level/text). Otimizacao do tick. */
UFUNCTION(BlueprintCallable, Category = "Zeus|HUD")
void ApplyHpSp(int32 Hp, int32 MaxHp, int32 Sp, int32 MaxSp);
protected:
virtual void NativeDestruct() override;
/**
* Hook BlueprintNativeEvent para skin visual reagir a um snapshot novo
* (cores, animacao de level up, etc.). Implementacao default em C++
* apenas atualiza progress bars + textos via `BindWidget`/`BindWidgetOptional`.
* (cores, animacao de level up, etc.). Impl default em C++ apenas
* atualiza progress bars + textos via BindWidget/BindWidgetOptional.
*/
UFUNCTION(BlueprintNativeEvent, Category = "Zeus|HUD")
void OnSnapshotApplied(const FZMMOAttributesSnapshot& Snapshot);
virtual void OnSnapshotApplied_Implementation(const FZMMOAttributesSnapshot& Snapshot);
UFUNCTION(BlueprintNativeEvent, Category = "Zeus|HUD")
void OnHpSpDelta(int32 Hp, int32 Sp);
virtual void OnHpSpDelta_Implementation(int32 Hp, int32 Sp);
// === BindWidget — devem existir no WBP com EXATAMENTE estes nomes ===
UPROPERTY(BlueprintReadOnly, meta = (BindWidget), Category = "Zeus|HUD")
UProgressBar* HpBar = nullptr;
@@ -74,12 +70,7 @@ protected:
UTextBlock* LevelText = nullptr;
private:
UFUNCTION()
void HandleAttributesChanged(const FZMMOAttributesSnapshot& Snapshot);
UFUNCTION()
void HandleHpSpChanged(int32 Hp, int32 Sp);
UPROPERTY()
TWeakObjectPtr<UZMMOAttributeComponent> BoundComponent;
/// Ultimo snapshot recebido (cache pra ApplyHpSp manter MaxHp/MaxSp). */
UPROPERTY(Transient)
FZMMOAttributesSnapshot LastSnapshot;
};