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:
@@ -34,3 +34,26 @@ ScreenSetAsset=/Game/ZMMO/UI/FrontEnd/DA_FrontEndScreenSet.DA_FrontEndScreenSet
|
|||||||
; server (Server/ZeusServerEngine/Config/DataTables/maps_config.json) — o
|
; server (Server/ZeusServerEngine/Config/DataTables/maps_config.json) — o
|
||||||
; cliente resolve `mapId -> ClientLevel/spawns` via FZMMOMapDef rows.
|
; cliente resolve `mapId -> ClientLevel/spawns` via FZMMOMapDef rows.
|
||||||
MapsTableAsset=/Game/ZMMO/Data/World/DT_Maps.DT_Maps
|
MapsTableAsset=/Game/ZMMO/Data/World/DT_Maps.DT_Maps
|
||||||
|
|
||||||
|
; -----------------------------------------------------------------------------
|
||||||
|
; UI in-game (PR 19+). Espelho do front-end pattern:
|
||||||
|
; - ScreenSetAsset mapeia EZMMOInGameUIState -> WBP (Playing -> WBP_HUD,
|
||||||
|
; StatusWindow -> WBP_StatusWindow, etc.)
|
||||||
|
; - Subsystem orquestra: AZMMOHUD::BeginPlay chama StartInGame que vai pra
|
||||||
|
; Playing.
|
||||||
|
; Asset criado via MCP em /Game/ZMMO/UI/InGame/DA_InGameScreenSet.
|
||||||
|
; -----------------------------------------------------------------------------
|
||||||
|
[/Script/ZMMO.UIInGameFlowSubsystem]
|
||||||
|
ScreenSetAsset=/Game/ZMMO/UI/InGame/DA_InGameScreenSet.DA_InGameScreenSet
|
||||||
|
|
||||||
|
; -----------------------------------------------------------------------------
|
||||||
|
; PlayerState Component Registry (PR 19+). Cada módulo MMO registra seus
|
||||||
|
; UActorComponent aqui. O AZMMOPlayerState lê esta lista no construtor e
|
||||||
|
; instancia cada classe como subobject — pattern Open-Closed: adicionar
|
||||||
|
; Inventory/Skills/Guild = mais uma linha aqui, sem editar AZMMOPlayerState.
|
||||||
|
;
|
||||||
|
; Convenção: usa o path /Script/<Module>.<ClassName> (sem o "U" do prefixo
|
||||||
|
; C++; UClass resolve pelo nome curto).
|
||||||
|
; -----------------------------------------------------------------------------
|
||||||
|
[/Script/ZMMO.ZMMOPlayerState]
|
||||||
|
+ComponentClasses=/Script/ZMMOAttributes.ZMMOAttributeComponent
|
||||||
|
|||||||
BIN
Content/ZMMO/UI/HUD/WBP_HUD.uasset
Normal file
BIN
Content/ZMMO/UI/HUD/WBP_HUD.uasset
Normal file
Binary file not shown.
Binary file not shown.
BIN
Content/ZMMO/UI/InGame/DA_InGameScreenSet.uasset
Normal file
BIN
Content/ZMMO/UI/InGame/DA_InGameScreenSet.uasset
Normal file
Binary file not shown.
34
Source/ZMMO/Data/UI/InGameTypes.h
Normal file
34
Source/ZMMO/Data/UI/InGameTypes.h
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "InGameTypes.generated.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Estados da UI in-game. Dirigidos pelo UUIInGameFlowSubsystem; cada estado
|
||||||
|
* resolve uma tela (UCommonActivatableWidget) via DA_InGameScreenSet.
|
||||||
|
*
|
||||||
|
* Espelha simetricamente EZMMOFrontEndState (Data/UI/FrontEndTypes.h):
|
||||||
|
* - Front-end usa estados pra Login/ServerSelect/Lobby (UI.Layer.Menu)
|
||||||
|
* - In-game usa estados pra HUD/Status/Inventory/Skills (UI.Layer.Game + GameMenu)
|
||||||
|
*
|
||||||
|
* Convencao de camadas (resolvida pelo subsystem):
|
||||||
|
* - Playing -> UI.Layer.Game (HUD principal)
|
||||||
|
* - StatusWindow / Inventory /
|
||||||
|
* SkillTree / EscapeMenu -> UI.Layer.GameMenu (sobreposicoes)
|
||||||
|
* - Loading -> UI.Layer.Modal (overlay critico)
|
||||||
|
*
|
||||||
|
* `Playing` e' o estado base — quando in-game, sempre ha um HUD no
|
||||||
|
* Layer.Game. Abrir Status/Inventory NAO substitui o HUD; empilha em
|
||||||
|
* Layer.GameMenu por cima dele.
|
||||||
|
*/
|
||||||
|
UENUM(BlueprintType)
|
||||||
|
enum class EZMMOInGameUIState : uint8
|
||||||
|
{
|
||||||
|
None, ///< Antes do player local spawnar; UI in-game inativa.
|
||||||
|
Playing, ///< HUD principal visivel (HP/SP/level/etc).
|
||||||
|
StatusWindow, ///< Janela de atributos + botões de alocacao (Fase 4).
|
||||||
|
Inventory, ///< (futuro) Bag/equipamentos.
|
||||||
|
SkillTree, ///< (futuro) Arvore de skills.
|
||||||
|
EscapeMenu, ///< (futuro) Pausa, settings, logout.
|
||||||
|
Loading ///< Overlay de loading generico (transitorio).
|
||||||
|
};
|
||||||
@@ -13,12 +13,11 @@
|
|||||||
#include "GameFramework/SpringArmComponent.h"
|
#include "GameFramework/SpringArmComponent.h"
|
||||||
#include "InputAction.h"
|
#include "InputAction.h"
|
||||||
#include "InputActionValue.h"
|
#include "InputActionValue.h"
|
||||||
#include "Blueprint/UserWidget.h"
|
#include "GameFramework/PlayerState.h"
|
||||||
#include "Subsystems/WorldSubsystem.h"
|
#include "Subsystems/WorldSubsystem.h"
|
||||||
#include "UObject/ConstructorHelpers.h"
|
#include "UObject/ConstructorHelpers.h"
|
||||||
#include "ZMMO.h"
|
#include "ZMMO.h"
|
||||||
#include "ZMMOAttributeComponent.h"
|
#include "ZMMOAttributeComponent.h"
|
||||||
#include "ZMMOHudHpSpWidget.h"
|
|
||||||
#include "ZMMOWorldSubsystem.h"
|
#include "ZMMOWorldSubsystem.h"
|
||||||
#include "ZeusNetworkSubsystem.h"
|
#include "ZeusNetworkSubsystem.h"
|
||||||
#include "UI/FrontEnd/UIFrontEndFlowSubsystem.h"
|
#include "UI/FrontEnd/UIFrontEndFlowSubsystem.h"
|
||||||
@@ -55,12 +54,8 @@ AZMMOPlayerCharacter::AZMMOPlayerCharacter()
|
|||||||
FollowCamera->SetupAttachment(CameraBoom, USpringArmComponent::SocketName);
|
FollowCamera->SetupAttachment(CameraBoom, USpringArmComponent::SocketName);
|
||||||
FollowCamera->bUsePawnControlRotation = false;
|
FollowCamera->bUsePawnControlRotation = false;
|
||||||
|
|
||||||
// AttributeSystem cliente (sub-modulo ZMMOAttributes). Estado authoritative
|
// AttributeComponent migrou pro PlayerState (AZMMOPlayerState + Component
|
||||||
// vive no servidor (Game/MMO/Modules/AttributeSystem); este componente
|
// Registry config-driven). Pawn fica leve — so' movement/input/camera.
|
||||||
// apenas armazena o ultimo snapshot recebido e expoe delegates pro HUD.
|
|
||||||
// Roteamento server -> componente via UZMMOAttributeNetworkHandler
|
|
||||||
// (UWorldSubsystem em ZMMOAttributes/Private/).
|
|
||||||
AttributeComponent = CreateDefaultSubobject<UZMMOAttributeComponent>(TEXT("AttributeComponent"));
|
|
||||||
|
|
||||||
// Defaults visuais (mesh + AnimBP) — alinhados ao ZClientMMO. Permitem ao
|
// Defaults visuais (mesh + AnimBP) — alinhados ao ZClientMMO. Permitem ao
|
||||||
// motor spawnar AZMMOPlayerCharacter directamente como DefaultPawnClass do
|
// motor spawnar AZMMOPlayerCharacter directamente como DefaultPawnClass do
|
||||||
@@ -111,20 +106,6 @@ AZMMOPlayerCharacter::AZMMOPlayerCharacter()
|
|||||||
if (LookActionAsset.Succeeded()) { LookAction = LookActionAsset.Object; }
|
if (LookActionAsset.Succeeded()) { LookAction = LookActionAsset.Object; }
|
||||||
if (MouseLookActionAsset.Succeeded()) { MouseLookAction = MouseLookActionAsset.Object; }
|
if (MouseLookActionAsset.Succeeded()) { MouseLookAction = MouseLookActionAsset.Object; }
|
||||||
if (JumpActionAsset.Succeeded()) { JumpAction = JumpActionAsset.Object; }
|
if (JumpActionAsset.Succeeded()) { JumpAction = JumpActionAsset.Object; }
|
||||||
|
|
||||||
// HUD default — WBP_HUD_HpSpBar herda de UZMMOHudHpSpWidget. BP filho do
|
|
||||||
// player character pode sobrescrever via EditAnywhere.
|
|
||||||
static ConstructorHelpers::FClassFinder<UZMMOHudHpSpWidget> HudClassFinder(
|
|
||||||
TEXT("/Game/ZMMO/UI/HUD/WBP_HUD_HpSpBar"));
|
|
||||||
if (HudClassFinder.Succeeded())
|
|
||||||
{
|
|
||||||
HudHpSpWidgetClass = HudClassFinder.Class;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
UE_LOG(LogZMMOPlayer, Warning,
|
|
||||||
TEXT("WBP_HUD_HpSpBar not found at /Game/ZMMO/UI/HUD/ — HUD desactivado"));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void AZMMOPlayerCharacter::BeginPlay()
|
void AZMMOPlayerCharacter::BeginPlay()
|
||||||
@@ -164,11 +145,6 @@ void AZMMOPlayerCharacter::EndPlay(const EEndPlayReason::Type EndPlayReason)
|
|||||||
{
|
{
|
||||||
UnbindZeusSpawnDelegate();
|
UnbindZeusSpawnDelegate();
|
||||||
ZeusNetwork = nullptr;
|
ZeusNetwork = nullptr;
|
||||||
if (HudHpSpWidget)
|
|
||||||
{
|
|
||||||
HudHpSpWidget->RemoveFromParent();
|
|
||||||
HudHpSpWidget = nullptr;
|
|
||||||
}
|
|
||||||
Super::EndPlay(EndPlayReason);
|
Super::EndPlay(EndPlayReason);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -373,49 +349,23 @@ void AZMMOPlayerCharacter::HandleLocalSpawnReady(const int32 InEntityId, const F
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Seed do EntityId no AttributeComponent: o S_ATTRIBUTE_SNAPSHOT_FULL ja
|
// Seed do EntityId no AttributeComponent do PlayerState. O componente
|
||||||
// pode estar em transito (server envia logo apos S_SPAWN_PLAYER) — sem
|
// vive no AZMMOPlayerState via Component Registry — busca via PlayerState
|
||||||
// este seed o NetworkHandler nao consegue achar o componente certo via
|
// (sobrevive ao despawn do Pawn). S_ATTRIBUTE_SNAPSHOT_FULL chega via
|
||||||
// lookup por EntityId.
|
// UDP e e' rouado pelo ZMMOAttributeNetworkHandler via lookup por
|
||||||
if (AttributeComponent)
|
// EntityId no PlayerArray do GameState.
|
||||||
|
if (APlayerState* PS = GetPlayerState())
|
||||||
{
|
{
|
||||||
AttributeComponent->SeedEntityId(InEntityId);
|
if (UZMMOAttributeComponent* AttrComp = PS->FindComponentByClass<UZMMOAttributeComponent>())
|
||||||
}
|
|
||||||
|
|
||||||
// Spawn do HUD de atributos (HP/SP/level). Apenas player local (este
|
|
||||||
// metodo so' roda quando bIsLocal=true em HandleZeusPlayerSpawned).
|
|
||||||
UE_LOG(LogZMMOPlayer, Warning,
|
|
||||||
TEXT("HUD spawn check: HudClass=%s AttrComp=%s HudPrev=%s Controller=%s"),
|
|
||||||
HudHpSpWidgetClass ? *HudHpSpWidgetClass->GetName() : TEXT("NULL"),
|
|
||||||
AttributeComponent ? TEXT("OK") : TEXT("NULL"),
|
|
||||||
HudHpSpWidget ? TEXT("ALREADY_EXISTS") : TEXT("none"),
|
|
||||||
GetController() ? *GetController()->GetName() : TEXT("NULL"));
|
|
||||||
|
|
||||||
if (HudHpSpWidgetClass && AttributeComponent && !HudHpSpWidget)
|
|
||||||
{
|
|
||||||
APlayerController* PC = Cast<APlayerController>(GetController());
|
|
||||||
if (!PC)
|
|
||||||
{
|
{
|
||||||
UE_LOG(LogZMMOPlayer, Warning, TEXT("HUD spawn: GetController() retornou null/nao-PC. Adiando."));
|
AttrComp->SeedEntityId(InEntityId);
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
HudHpSpWidget = CreateWidget<UZMMOHudHpSpWidget>(PC, HudHpSpWidgetClass);
|
|
||||||
if (!HudHpSpWidget)
|
|
||||||
{
|
|
||||||
UE_LOG(LogZMMOPlayer, Error, TEXT("HUD spawn: CreateWidget retornou NULL para classe %s"),
|
|
||||||
*HudHpSpWidgetClass->GetName());
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
HudHpSpWidget->AddToViewport(/*ZOrder=*/10);
|
|
||||||
HudHpSpWidget->BindToAttributeComponent(AttributeComponent);
|
|
||||||
UE_LOG(LogZMMOPlayer, Warning,
|
|
||||||
TEXT("HUD spawn: widget criado e adicionado ao viewport (%s)"),
|
|
||||||
*HudHpSpWidget->GetName());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UI in-game e' responsabilidade do AZMMOHUD (GameMode.HUDClass), nao do
|
||||||
|
// Pawn. AHUD::BeginPlay chama UUIInGameFlowSubsystem::StartInGame, que
|
||||||
|
// pushea WBP_HUD em UI.Layer.Game. UZMMOHudWidget::NativeOnActivated auto-binda
|
||||||
|
// no UZMMOAttributeComponent via PlayerState.
|
||||||
}
|
}
|
||||||
|
|
||||||
void AZMMOPlayerCharacter::FlushInputAxisToServer(const float DeltaSeconds)
|
void AZMMOPlayerCharacter::FlushInputAxisToServer(const float DeltaSeconds)
|
||||||
|
|||||||
@@ -11,8 +11,6 @@ class USpringArmComponent;
|
|||||||
class UCameraComponent;
|
class UCameraComponent;
|
||||||
class UInputAction;
|
class UInputAction;
|
||||||
class UZeusNetworkSubsystem;
|
class UZeusNetworkSubsystem;
|
||||||
class UZMMOAttributeComponent;
|
|
||||||
class UZMMOHudHpSpWidget;
|
|
||||||
struct FInputActionValue;
|
struct FInputActionValue;
|
||||||
|
|
||||||
DECLARE_LOG_CATEGORY_EXTERN(LogZMMOPlayer, Log, All);
|
DECLARE_LOG_CATEGORY_EXTERN(LogZMMOPlayer, Log, All);
|
||||||
@@ -49,10 +47,8 @@ class ZMMO_API AZMMOPlayerCharacter : public ACharacter, public IZMMOEntityInter
|
|||||||
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Components", meta = (AllowPrivateAccess = "true"))
|
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Components", meta = (AllowPrivateAccess = "true"))
|
||||||
UCameraComponent* FollowCamera;
|
UCameraComponent* FollowCamera;
|
||||||
|
|
||||||
/** Atributos do MMO (HP/SP/STR/etc). Estado authoritative no server;
|
// AttributeComponent vive no PlayerState (AZMMOPlayerState + Component Registry).
|
||||||
* cliente apenas exibe via UI bindando em `OnAttributesChanged`. */
|
// Acesso: GetPlayerState<AZMMOPlayerState>()->GetZMMOComponent<UZMMOAttributeComponent>().
|
||||||
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Components", meta = (AllowPrivateAccess = "true"))
|
|
||||||
UZMMOAttributeComponent* AttributeComponent;
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
UPROPERTY(EditAnywhere, Category = "Input")
|
UPROPERTY(EditAnywhere, Category = "Input")
|
||||||
@@ -80,12 +76,6 @@ protected:
|
|||||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "ZMMO|Networking", meta = (ClampMin = "0.05", ClampMax = "5.0"))
|
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "ZMMO|Networking", meta = (ClampMin = "0.05", ClampMax = "5.0"))
|
||||||
float HeartbeatIntervalSec = 0.2f;
|
float HeartbeatIntervalSec = 0.2f;
|
||||||
|
|
||||||
/** Classe do HUD de HP/SP. Spawnada quando o pawn local recebe S_SPAWN_PLAYER.
|
|
||||||
* Default = WBP_HUD_HpSpBar que herda de UZMMOHudHpSpWidget (criado em Fase 1
|
|
||||||
* via MCP em /Game/ZMMO/UI/HUD/). Deixar null desativa o HUD. */
|
|
||||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "ZMMO|UI")
|
|
||||||
TSubclassOf<UZMMOHudHpSpWidget> HudHpSpWidgetClass;
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
AZMMOPlayerCharacter();
|
AZMMOPlayerCharacter();
|
||||||
|
|
||||||
@@ -102,7 +92,6 @@ public:
|
|||||||
|
|
||||||
FORCEINLINE USpringArmComponent* GetCameraBoom() const { return CameraBoom; }
|
FORCEINLINE USpringArmComponent* GetCameraBoom() const { return CameraBoom; }
|
||||||
FORCEINLINE UCameraComponent* GetFollowCamera() const { return FollowCamera; }
|
FORCEINLINE UCameraComponent* GetFollowCamera() const { return FollowCamera; }
|
||||||
FORCEINLINE UZMMOAttributeComponent* GetAttributeComponent() const { return AttributeComponent; }
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual void BeginPlay() override;
|
virtual void BeginPlay() override;
|
||||||
@@ -173,9 +162,4 @@ private:
|
|||||||
float SendAccumulatorSec = 0.0f;
|
float SendAccumulatorSec = 0.0f;
|
||||||
float TimeSinceLastSendSec = 0.0f;
|
float TimeSinceLastSendSec = 0.0f;
|
||||||
int32 InputSequence = 0;
|
int32 InputSequence = 0;
|
||||||
|
|
||||||
/** Instancia do HUD spawnada no spawn local. nullptr ate o spawn ou se
|
|
||||||
* HudHpSpWidgetClass nao foi setado. */
|
|
||||||
UPROPERTY(Transient)
|
|
||||||
TObjectPtr<UZMMOHudHpSpWidget> HudHpSpWidget;
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,10 +1,14 @@
|
|||||||
#include "ZMMOGameMode.h"
|
#include "ZMMOGameMode.h"
|
||||||
|
|
||||||
|
#include "ZMMOHUD.h"
|
||||||
#include "ZMMOPlayerCharacter.h"
|
#include "ZMMOPlayerCharacter.h"
|
||||||
#include "ZMMOPlayerController.h"
|
#include "ZMMOPlayerController.h"
|
||||||
|
#include "ZMMOPlayerState.h"
|
||||||
|
|
||||||
AZMMOGameMode::AZMMOGameMode()
|
AZMMOGameMode::AZMMOGameMode()
|
||||||
{
|
{
|
||||||
DefaultPawnClass = AZMMOPlayerCharacter::StaticClass();
|
DefaultPawnClass = AZMMOPlayerCharacter::StaticClass();
|
||||||
PlayerControllerClass = AZMMOPlayerController::StaticClass();
|
PlayerControllerClass = AZMMOPlayerController::StaticClass();
|
||||||
|
PlayerStateClass = AZMMOPlayerState::StaticClass();
|
||||||
|
HUDClass = AZMMOHUD::StaticClass();
|
||||||
}
|
}
|
||||||
|
|||||||
40
Source/ZMMO/Game/Modes/ZMMOHUD.cpp
Normal file
40
Source/ZMMO/Game/Modes/ZMMOHUD.cpp
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
#include "ZMMOHUD.h"
|
||||||
|
|
||||||
|
#include "Engine/GameInstance.h"
|
||||||
|
#include "ZMMO.h"
|
||||||
|
#include "UI/InGame/UIInGameFlowSubsystem.h"
|
||||||
|
|
||||||
|
AZMMOHUD::AZMMOHUD()
|
||||||
|
{
|
||||||
|
// AHUD nao precisa tick — toda atualizacao vem via UMG (delegates +
|
||||||
|
// CommonUI). PrimaryActorTick default e' off; mantemos.
|
||||||
|
}
|
||||||
|
|
||||||
|
void AZMMOHUD::BeginPlay()
|
||||||
|
{
|
||||||
|
Super::BeginPlay();
|
||||||
|
|
||||||
|
// UI in-game assume responsabilidade aqui. Subsystem orquestra:
|
||||||
|
// resolve DA_InGameScreenSet[Playing] -> WBP_HUD, pushea em UI.Layer.Game.
|
||||||
|
if (const UGameInstance* GI = GetGameInstance())
|
||||||
|
{
|
||||||
|
if (UUIInGameFlowSubsystem* Flow = GI->GetSubsystem<UUIInGameFlowSubsystem>())
|
||||||
|
{
|
||||||
|
Flow->StartInGame();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
UE_LOG(LogZMMO, Warning,
|
||||||
|
TEXT("AZMMOHUD: UUIInGameFlowSubsystem nao encontrado — HUD nao sera spawnado"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AZMMOHUD::EndPlay(const EEndPlayReason::Type EndPlayReason)
|
||||||
|
{
|
||||||
|
// UUIInGameFlowSubsystem persiste no GameInstance entre OpenLevel — nao
|
||||||
|
// limpamos UI in-game aqui (o front-end flow vai tomar conta na proxima
|
||||||
|
// transicao de estado). Cleanup do widget atual acontece no
|
||||||
|
// NativeOnDeactivated quando o stack for esvaziado pelo flow.
|
||||||
|
Super::EndPlay(EndPlayReason);
|
||||||
|
}
|
||||||
36
Source/ZMMO/Game/Modes/ZMMOHUD.h
Normal file
36
Source/ZMMO/Game/Modes/ZMMOHUD.h
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "GameFramework/HUD.h"
|
||||||
|
#include "ZMMOHUD.generated.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HUD do MMO. Pattern UE5 idiomatico: o GameMode atribui `HUDClass`, o
|
||||||
|
* engine cria 1 instancia por PlayerController automaticamente.
|
||||||
|
*
|
||||||
|
* Responsabilidade unica: orquestrar o lifecycle da UI in-game. Em
|
||||||
|
* BeginPlay, pede ao `UUIInGameFlowSubsystem` pra entrar em `Playing`
|
||||||
|
* (que pushea WBP_HUD em UI.Layer.Game). Em EndPlay/destrucao, o flow
|
||||||
|
* subsystem cuida do cleanup.
|
||||||
|
*
|
||||||
|
* Separacao de responsabilidades:
|
||||||
|
* - AZMMOPlayerCharacter (Pawn) — movimento, input, AttributeComponent
|
||||||
|
* - AZMMOPlayerController — input mapping, mouse
|
||||||
|
* - AZMMOHUD — UI lifecycle
|
||||||
|
* - AZMMOGameMode — wires tudo via class slots
|
||||||
|
*
|
||||||
|
* NAO desenha nada via canvas (AHUD::DrawHUD). UI inteira vive em UMG +
|
||||||
|
* CommonUI (UI.Layer.Game / GameMenu / Modal).
|
||||||
|
*/
|
||||||
|
UCLASS()
|
||||||
|
class ZMMO_API AZMMOHUD : public AHUD
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
public:
|
||||||
|
AZMMOHUD();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
virtual void BeginPlay() override;
|
||||||
|
virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override;
|
||||||
|
};
|
||||||
82
Source/ZMMO/Game/Modes/ZMMOPlayerState.cpp
Normal file
82
Source/ZMMO/Game/Modes/ZMMOPlayerState.cpp
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
#include "ZMMOPlayerState.h"
|
||||||
|
|
||||||
|
#include "Components/ActorComponent.h"
|
||||||
|
#include "Misc/ConfigCacheIni.h"
|
||||||
|
#include "ZMMO.h"
|
||||||
|
|
||||||
|
AZMMOPlayerState::AZMMOPlayerState()
|
||||||
|
{
|
||||||
|
// Component Registry: instancia cada classe declarada em
|
||||||
|
// DefaultGame.ini::[/Script/ZMMO.ZMMOPlayerState]+ComponentClasses=...
|
||||||
|
//
|
||||||
|
// Le manualmente via GConfig para evitar timing do UPROPERTY(Config),
|
||||||
|
// que requer LoadConfig() ja ter rodado no CDO antes da instance ser
|
||||||
|
// construida. Pra propriedades em arrays config com `+` syntax, lendo
|
||||||
|
// direto e mais previsivel.
|
||||||
|
if (ComponentClasses.Num() == 0 && GConfig)
|
||||||
|
{
|
||||||
|
TArray<FString> ClassPaths;
|
||||||
|
GConfig->GetArray(TEXT("/Script/ZMMO.ZMMOPlayerState"),
|
||||||
|
TEXT("ComponentClasses"),
|
||||||
|
ClassPaths,
|
||||||
|
GGameIni);
|
||||||
|
for (const FString& Path : ClassPaths)
|
||||||
|
{
|
||||||
|
if (UClass* C = LoadClass<UActorComponent>(nullptr, *Path))
|
||||||
|
{
|
||||||
|
ComponentClasses.Add(C);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
UE_LOG(LogZMMO, Warning,
|
||||||
|
TEXT("ZMMOPlayerState: ComponentClasses path nao resolveu: %s"), *Path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
UE_LOG(LogZMMO, Log,
|
||||||
|
TEXT("AZMMOPlayerState ctor: %d componente(s) no registry (instance=%s)"),
|
||||||
|
ComponentClasses.Num(), *GetName());
|
||||||
|
|
||||||
|
int32 SubobjectIndex = 0;
|
||||||
|
for (const TSubclassOf<UActorComponent>& CompClassRef : ComponentClasses)
|
||||||
|
{
|
||||||
|
UClass* CompClass = CompClassRef.Get();
|
||||||
|
if (!CompClass)
|
||||||
|
{
|
||||||
|
UE_LOG(LogZMMO, Warning,
|
||||||
|
TEXT("ZMMOPlayerState: ComponentClass null em ComponentClasses[%d]"),
|
||||||
|
SubobjectIndex);
|
||||||
|
++SubobjectIndex;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const FName CompName = MakeUniqueObjectName(this, CompClass,
|
||||||
|
FName(*FString::Printf(TEXT("ZMMOComp_%d_%s"), SubobjectIndex, *CompClass->GetName())));
|
||||||
|
// Overload runtime de CreateDefaultSubobject retorna UObject* (sem template) —
|
||||||
|
// precisa cast para UActorComponent.
|
||||||
|
UActorComponent* Comp = Cast<UActorComponent>(CreateDefaultSubobject(CompName, CompClass, CompClass,
|
||||||
|
/*bIsRequired*/ false, /*bIsTransient*/ false));
|
||||||
|
if (Comp)
|
||||||
|
{
|
||||||
|
UE_LOG(LogZMMO, Log,
|
||||||
|
TEXT("ZMMOPlayerState: componente registrado [%d] %s"),
|
||||||
|
SubobjectIndex, *CompClass->GetName());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
UE_LOG(LogZMMO, Warning,
|
||||||
|
TEXT("ZMMOPlayerState: CreateDefaultSubobject falhou para %s"),
|
||||||
|
*CompClass->GetName());
|
||||||
|
}
|
||||||
|
++SubobjectIndex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AZMMOPlayerState::SetPublicIdentity(int64 InEntityId, const FString& InCharId,
|
||||||
|
int32 InBaseLevel, int32 InClassId)
|
||||||
|
{
|
||||||
|
ZMMOEntityId = InEntityId;
|
||||||
|
CharId = InCharId;
|
||||||
|
BaseLevel = InBaseLevel;
|
||||||
|
ClassId = InClassId;
|
||||||
|
}
|
||||||
104
Source/ZMMO/Game/Modes/ZMMOPlayerState.h
Normal file
104
Source/ZMMO/Game/Modes/ZMMOPlayerState.h
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "GameFramework/PlayerState.h"
|
||||||
|
#include "Templates/SubclassOf.h"
|
||||||
|
#include "ZMMOPlayerState.generated.h"
|
||||||
|
|
||||||
|
class UActorComponent;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PlayerState do MMO. Pattern UE5 idiomatico: o GameMode atribui
|
||||||
|
* `PlayerStateClass`, engine cria 1 instancia por PlayerController.
|
||||||
|
*
|
||||||
|
* Responsabilidade — estado **publico** do jogador (visivel pra outros):
|
||||||
|
* - Identidade: charId, entityId, name, guild
|
||||||
|
* - Progressao publica: baseLevel, classId
|
||||||
|
* - Estado: alive/dead, in-combat (futuro)
|
||||||
|
*
|
||||||
|
* Dados PRIVADOS (HP/SP atuais, stats brutos, inventory, hotbar) vivem
|
||||||
|
* em componentes anexados via Component Registry (vide abaixo).
|
||||||
|
*
|
||||||
|
* Por que componentes no PlayerState e nao no Pawn:
|
||||||
|
* - **Sobrevive ao despawn** (morte/respawn — Pawn destrui, PlayerState
|
||||||
|
* mantem). Padrao MMO classico (rathena, TrinityCore).
|
||||||
|
* - **Spectator-friendly**: dados acessiveis sem ter pawn.
|
||||||
|
* - **1:1 com player** (Pawn pode trocar — vehicle, mount, possession).
|
||||||
|
*
|
||||||
|
* ## Component Registry (Open-Closed)
|
||||||
|
*
|
||||||
|
* Cada modulo (ZMMOAttributes, ZMMOInventory, ZMMOSkills futuros) declara
|
||||||
|
* sua component class em DefaultGame.ini:
|
||||||
|
*
|
||||||
|
* [/Script/ZMMO.ZMMOPlayerState]
|
||||||
|
* +ComponentClasses=/Script/ZMMOAttributes.ZMMOAttributeComponent
|
||||||
|
* +ComponentClasses=/Script/ZMMOInventory.ZMMOInventoryComponent
|
||||||
|
* +ComponentClasses=/Script/ZMMOSkills.ZMMOSkillComponent
|
||||||
|
*
|
||||||
|
* O construtor de AZMMOPlayerState le essa lista (Config) e instancia
|
||||||
|
* cada classe como subobject. PlayerState fica fechado pra modificacao,
|
||||||
|
* modulos abertos pra extensao — adicionar Inventory NAO requer
|
||||||
|
* recompilar/editar AZMMOPlayerState.
|
||||||
|
*
|
||||||
|
* Acesso: `PlayerState->FindComponentByClass<UZMMOAttributeComponent>()`
|
||||||
|
* ou o helper `GetZMMOComponent<T>()`.
|
||||||
|
*/
|
||||||
|
UCLASS(Config = Game, BlueprintType)
|
||||||
|
class ZMMO_API AZMMOPlayerState : public APlayerState
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
public:
|
||||||
|
AZMMOPlayerState();
|
||||||
|
|
||||||
|
// === Identidade publica ===
|
||||||
|
|
||||||
|
/** EntityId autoritativo do server. 0 ate o spawn local confirmar. */
|
||||||
|
UPROPERTY(VisibleInstanceOnly, BlueprintReadOnly, Category = "ZMMO|Identity")
|
||||||
|
int64 ZMMOEntityId = 0;
|
||||||
|
|
||||||
|
/** CharId (BIGINT no DB; FString pra preservar 64 bits em Blueprint). */
|
||||||
|
UPROPERTY(VisibleInstanceOnly, BlueprintReadOnly, Category = "ZMMO|Identity")
|
||||||
|
FString CharId;
|
||||||
|
|
||||||
|
// === Progressao publica ===
|
||||||
|
|
||||||
|
UPROPERTY(VisibleInstanceOnly, BlueprintReadOnly, Category = "ZMMO|Progress")
|
||||||
|
int32 BaseLevel = 1;
|
||||||
|
|
||||||
|
UPROPERTY(VisibleInstanceOnly, BlueprintReadOnly, Category = "ZMMO|Progress")
|
||||||
|
int32 ClassId = 0;
|
||||||
|
|
||||||
|
// === Guild (futuro) ===
|
||||||
|
|
||||||
|
UPROPERTY(VisibleInstanceOnly, BlueprintReadOnly, Category = "ZMMO|Guild")
|
||||||
|
FString GuildName;
|
||||||
|
|
||||||
|
// === Helpers ===
|
||||||
|
|
||||||
|
/// Atualizado quando S_ATTRIBUTE_SNAPSHOT_FULL chega — UZMMOHudWidget chama
|
||||||
|
/// isso pra refletir os dados publicos do snapshot no PlayerState.
|
||||||
|
UFUNCTION(BlueprintCallable, Category = "ZMMO|Identity")
|
||||||
|
void SetPublicIdentity(int64 InEntityId, const FString& InCharId,
|
||||||
|
int32 InBaseLevel, int32 InClassId);
|
||||||
|
|
||||||
|
/// Template helper: PS->GetZMMOComponent<UZMMOAttributeComponent>()
|
||||||
|
template <typename T>
|
||||||
|
T* GetZMMOComponent() const
|
||||||
|
{
|
||||||
|
return Cast<T>(FindComponentByClass(T::StaticClass()));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
/**
|
||||||
|
* Component Registry — config-driven. Modulos registram suas classes
|
||||||
|
* via DefaultGame.ini com prefix `+`. Construtor instancia cada uma
|
||||||
|
* como CreateDefaultSubobject.
|
||||||
|
*
|
||||||
|
* TSubclassOf (nao TSoftClassPtr): config UE resolve classes nativas
|
||||||
|
* diretamente. Modulos C++ precisam estar carregados antes do CDO ser
|
||||||
|
* instanciado — garantido por LoadingPhase=PreDefault no .uproject.
|
||||||
|
*/
|
||||||
|
UPROPERTY(Config, EditDefaultsOnly, Category = "ZMMO|Components")
|
||||||
|
TArray<TSubclassOf<UActorComponent>> ComponentClasses;
|
||||||
|
};
|
||||||
@@ -41,18 +41,7 @@ void UUIFrontEndFlowSubsystem::Deinitialize()
|
|||||||
|
|
||||||
void UUIFrontEndFlowSubsystem::StartFrontEnd()
|
void UUIFrontEndFlowSubsystem::StartFrontEnd()
|
||||||
{
|
{
|
||||||
UUIFrontEndScreenSet* SS = GetScreenSet();
|
EnsureRootLayout();
|
||||||
|
|
||||||
if (UUIManagerSubsystem* Mgr = GetUIManager())
|
|
||||||
{
|
|
||||||
TSubclassOf<UUIPrimaryGameLayout_Base> LayoutClass;
|
|
||||||
if (SS && !SS->RootLayoutClass.IsNull())
|
|
||||||
{
|
|
||||||
LayoutClass = SS->RootLayoutClass.LoadSynchronous();
|
|
||||||
}
|
|
||||||
Mgr->CreateAndAddRootLayout(LayoutClass);
|
|
||||||
}
|
|
||||||
|
|
||||||
BindNetwork();
|
BindNetwork();
|
||||||
|
|
||||||
SetState(EZMMOFrontEndState::Boot);
|
SetState(EZMMOFrontEndState::Boot);
|
||||||
@@ -69,6 +58,26 @@ void UUIFrontEndFlowSubsystem::StartFrontEnd()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void UUIFrontEndFlowSubsystem::EnsureRootLayout()
|
||||||
|
{
|
||||||
|
UUIManagerSubsystem* Mgr = GetUIManager();
|
||||||
|
if (!Mgr) { return; }
|
||||||
|
if (Mgr->IsRootLayoutReady())
|
||||||
|
{
|
||||||
|
return; // ja' no viewport
|
||||||
|
}
|
||||||
|
UUIFrontEndScreenSet* SS = GetScreenSet();
|
||||||
|
if (!SS || SS->RootLayoutClass.IsNull())
|
||||||
|
{
|
||||||
|
UE_LOG(LogZMMO, Warning,
|
||||||
|
TEXT("FrontEndFlow::EnsureRootLayout: RootLayoutClass ausente em ScreenSet"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
TSubclassOf<UUIPrimaryGameLayout_Base> LayoutClass = SS->RootLayoutClass.LoadSynchronous();
|
||||||
|
Mgr->CreateAndAddRootLayout(LayoutClass);
|
||||||
|
UE_LOG(LogZMMO, Log, TEXT("FrontEndFlow::EnsureRootLayout: RootLayout recriado"));
|
||||||
|
}
|
||||||
|
|
||||||
void UUIFrontEndFlowSubsystem::SetState(EZMMOFrontEndState NewState)
|
void UUIFrontEndFlowSubsystem::SetState(EZMMOFrontEndState NewState)
|
||||||
{
|
{
|
||||||
if (NewState == CurrentState)
|
if (NewState == CurrentState)
|
||||||
|
|||||||
@@ -46,6 +46,18 @@ public:
|
|||||||
UFUNCTION(BlueprintCallable, Category = "FrontEnd")
|
UFUNCTION(BlueprintCallable, Category = "FrontEnd")
|
||||||
void StartFrontEnd();
|
void StartFrontEnd();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Garante que o root layout (UUIPrimaryGameLayout_Base) esta no viewport.
|
||||||
|
* Chamado pelo UIInGameFlowSubsystem::StartInGame apos OpenLevel — o
|
||||||
|
* `OpenLevel` invalida widgets no viewport ("InvalidateAllWidgets") e o
|
||||||
|
* RootLayout fica orfao. Idempotente: CreateAndAddRootLayout do
|
||||||
|
* UIManagerSubsystem checa `IsInViewport` antes de recriar.
|
||||||
|
*
|
||||||
|
* C++ puro (sem UFUNCTION) — Live Coding nao registra UFUNCTIONs novas;
|
||||||
|
* consumidores externos chamam direto via C++ (UIInGameFlowSubsystem).
|
||||||
|
*/
|
||||||
|
void EnsureRootLayout();
|
||||||
|
|
||||||
UFUNCTION(BlueprintCallable, Category = "FrontEnd")
|
UFUNCTION(BlueprintCallable, Category = "FrontEnd")
|
||||||
void SetState(EZMMOFrontEndState NewState);
|
void SetState(EZMMOFrontEndState NewState);
|
||||||
|
|
||||||
|
|||||||
210
Source/ZMMO/Game/UI/InGame/UIInGameFlowSubsystem.cpp
Normal file
210
Source/ZMMO/Game/UI/InGame/UIInGameFlowSubsystem.cpp
Normal file
@@ -0,0 +1,210 @@
|
|||||||
|
#include "UIInGameFlowSubsystem.h"
|
||||||
|
|
||||||
|
#include "CommonActivatableWidget.h" // Necessario pra TSoftClassPtr<UCommonActivatableWidget>::Get()
|
||||||
|
#include "Engine/AssetManager.h"
|
||||||
|
#include "Engine/GameInstance.h"
|
||||||
|
#include "Engine/StreamableManager.h"
|
||||||
|
#include "GameFramework/PlayerController.h"
|
||||||
|
#include "GameplayTagContainer.h"
|
||||||
|
#include "ZMMO.h"
|
||||||
|
#include "Data/UI/UILayerTags.h"
|
||||||
|
#include "FrontEnd/UIFrontEndFlowSubsystem.h"
|
||||||
|
#include "FrontEnd/UIManagerSubsystem.h"
|
||||||
|
#include "InGame/UIInGameScreenSet.h"
|
||||||
|
|
||||||
|
void UUIInGameFlowSubsystem::Initialize(FSubsystemCollectionBase& Collection)
|
||||||
|
{
|
||||||
|
Super::Initialize(Collection);
|
||||||
|
UE_LOG(LogZMMO, Log, TEXT("UIInGameFlowSubsystem initialized"));
|
||||||
|
}
|
||||||
|
|
||||||
|
void UUIInGameFlowSubsystem::Deinitialize()
|
||||||
|
{
|
||||||
|
ScreenSet = nullptr;
|
||||||
|
CurrentState = EZMMOInGameUIState::None;
|
||||||
|
Super::Deinitialize();
|
||||||
|
}
|
||||||
|
|
||||||
|
void UUIInGameFlowSubsystem::StartInGame()
|
||||||
|
{
|
||||||
|
if (CurrentState != EZMMOInGameUIState::None)
|
||||||
|
{
|
||||||
|
// Idempotente: chamadas extras (ex.: respawn) sao no-op se ja in-world.
|
||||||
|
UE_LOG(LogZMMO, Verbose,
|
||||||
|
TEXT("InGameFlow: StartInGame ignorado (ja em estado %s)"),
|
||||||
|
*UEnum::GetValueAsString(CurrentState));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// CRITICAL: OpenLevel invalida widgets no viewport ("InvalidateAllWidgets"),
|
||||||
|
// orfanando o RootLayout (UUIPrimaryGameLayout_Base) que foi criado pelo
|
||||||
|
// FrontEndFlow no boot. Sem RootLayout no viewport, PushScreenToLayer
|
||||||
|
// silenciosamente adiciona widgets ao stack do RootLayout — mas o stack
|
||||||
|
// nao e' visivel porque o parent (RootLayout) nao esta no viewport.
|
||||||
|
//
|
||||||
|
// Re-cria via EnsureRootLayout (idempotente) ANTES de qualquer push.
|
||||||
|
if (UGameInstance* GI = GetGameInstance())
|
||||||
|
{
|
||||||
|
if (UUIFrontEndFlowSubsystem* FE = GI->GetSubsystem<UUIFrontEndFlowSubsystem>())
|
||||||
|
{
|
||||||
|
FE->EnsureRootLayout();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Limpa qualquer residuo do front-end (Menu/Modal podem ter "Loading" ou
|
||||||
|
// dialogs orfaos do handoff). Layer.Game vai receber o HUD novo.
|
||||||
|
if (UUIManagerSubsystem* Mgr = GetUIManager())
|
||||||
|
{
|
||||||
|
Mgr->ClearLayer(ZMMOUITags::UI_Layer_Menu.GetTag());
|
||||||
|
Mgr->ClearLayer(ZMMOUITags::UI_Layer_Modal.GetTag());
|
||||||
|
Mgr->ClearLayer(ZMMOUITags::UI_Layer_GameMenu.GetTag());
|
||||||
|
}
|
||||||
|
|
||||||
|
SetState(EZMMOInGameUIState::Playing);
|
||||||
|
}
|
||||||
|
|
||||||
|
void UUIInGameFlowSubsystem::SetState(EZMMOInGameUIState NewState)
|
||||||
|
{
|
||||||
|
if (NewState == CurrentState)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const EZMMOInGameUIState OldState = CurrentState;
|
||||||
|
CurrentState = NewState;
|
||||||
|
|
||||||
|
UE_LOG(LogZMMO, Log, TEXT("InGameFlow: state %s -> %s"),
|
||||||
|
*UEnum::GetValueAsString(OldState),
|
||||||
|
*UEnum::GetValueAsString(NewState));
|
||||||
|
|
||||||
|
// Decisao de stack: ao voltar pra `Playing`, fechamos quaisquer overlays
|
||||||
|
// (Status/Inventory/Menu) limpando o Layer.GameMenu — HUD em Layer.Game
|
||||||
|
// permanece. Para qualquer outro estado, mantemos HUD e empilhamos no
|
||||||
|
// layer correspondente.
|
||||||
|
if (UUIManagerSubsystem* Mgr = GetUIManager())
|
||||||
|
{
|
||||||
|
const bool bGoingToPlaying = (NewState == EZMMOInGameUIState::Playing);
|
||||||
|
if (bGoingToPlaying)
|
||||||
|
{
|
||||||
|
Mgr->ClearLayer(ZMMOUITags::UI_Layer_GameMenu.GetTag());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ResolveAndPushScreen(NewState);
|
||||||
|
OnStateChanged.Broadcast(NewState);
|
||||||
|
}
|
||||||
|
|
||||||
|
void UUIInGameFlowSubsystem::ToggleScreen(EZMMOInGameUIState Screen)
|
||||||
|
{
|
||||||
|
if (Screen == EZMMOInGameUIState::Playing || Screen == EZMMOInGameUIState::None)
|
||||||
|
{
|
||||||
|
// Toggle para Playing nao faz sentido (estado base). Use SetState direto.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (CurrentState == Screen)
|
||||||
|
{
|
||||||
|
SetState(EZMMOInGameUIState::Playing);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
SetState(Screen);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
UUIInGameScreenSet* UUIInGameFlowSubsystem::GetScreenSet()
|
||||||
|
{
|
||||||
|
if (ScreenSet) { return ScreenSet; }
|
||||||
|
if (ScreenSetAsset.IsNull())
|
||||||
|
{
|
||||||
|
UE_LOG(LogZMMO, Warning,
|
||||||
|
TEXT("InGameFlow: ScreenSetAsset nao configurado em DefaultGame.ini "
|
||||||
|
"[/Script/ZMMO.UIInGameFlowSubsystem] ScreenSetAsset=..."));
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
// Sync load — DA e' pequeno (so' soft refs); PIE/dev OK.
|
||||||
|
ScreenSet = ScreenSetAsset.LoadSynchronous();
|
||||||
|
return ScreenSet;
|
||||||
|
}
|
||||||
|
|
||||||
|
UUIManagerSubsystem* UUIInGameFlowSubsystem::GetUIManager() const
|
||||||
|
{
|
||||||
|
if (const UGameInstance* GI = GetGameInstance())
|
||||||
|
{
|
||||||
|
if (ULocalPlayer* LP = GI->GetFirstGamePlayer())
|
||||||
|
{
|
||||||
|
return LP->GetSubsystem<UUIManagerSubsystem>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
FGameplayTag UUIInGameFlowSubsystem::LayerForState(EZMMOInGameUIState State) const
|
||||||
|
{
|
||||||
|
switch (State)
|
||||||
|
{
|
||||||
|
case EZMMOInGameUIState::Playing:
|
||||||
|
return ZMMOUITags::UI_Layer_Game.GetTag();
|
||||||
|
case EZMMOInGameUIState::Loading:
|
||||||
|
return ZMMOUITags::UI_Layer_Modal.GetTag();
|
||||||
|
case EZMMOInGameUIState::StatusWindow:
|
||||||
|
case EZMMOInGameUIState::Inventory:
|
||||||
|
case EZMMOInGameUIState::SkillTree:
|
||||||
|
case EZMMOInGameUIState::EscapeMenu:
|
||||||
|
default:
|
||||||
|
return ZMMOUITags::UI_Layer_GameMenu.GetTag();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void UUIInGameFlowSubsystem::ResolveAndPushScreen(EZMMOInGameUIState State)
|
||||||
|
{
|
||||||
|
if (State == EZMMOInGameUIState::None) { return; }
|
||||||
|
|
||||||
|
UUIInGameScreenSet* SS = GetScreenSet();
|
||||||
|
if (!SS)
|
||||||
|
{
|
||||||
|
UE_LOG(LogZMMO, Warning,
|
||||||
|
TEXT("InGameFlow: ScreenSet ausente; tela de %s ignorada"),
|
||||||
|
*UEnum::GetValueAsString(State));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const TSoftClassPtr<UCommonActivatableWidget> Soft = SS->GetScreenForState(State);
|
||||||
|
if (Soft.IsNull())
|
||||||
|
{
|
||||||
|
UE_LOG(LogZMMO, Warning,
|
||||||
|
TEXT("InGameFlow: Screen for state %s not configured em DA_InGameScreenSet (verificar StateScreens)"),
|
||||||
|
*UEnum::GetValueAsString(State));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
UUIManagerSubsystem* Mgr = GetUIManager();
|
||||||
|
if (!Mgr)
|
||||||
|
{
|
||||||
|
UE_LOG(LogZMMO, Warning, TEXT("InGameFlow: UIManagerSubsystem ausente"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const FGameplayTag Layer = LayerForState(State);
|
||||||
|
const FSoftObjectPath Path = Soft.ToSoftObjectPath();
|
||||||
|
|
||||||
|
UAssetManager::GetStreamableManager().RequestAsyncLoad(
|
||||||
|
Path,
|
||||||
|
FStreamableDelegate::CreateWeakLambda(this, [this, Soft, Layer, State]()
|
||||||
|
{
|
||||||
|
UUIManagerSubsystem* M = GetUIManager();
|
||||||
|
if (!M) { return; }
|
||||||
|
UClass* Cls = Soft.Get();
|
||||||
|
if (!Cls)
|
||||||
|
{
|
||||||
|
UE_LOG(LogZMMO, Warning,
|
||||||
|
TEXT("InGameFlow: async load falhou pra %s"),
|
||||||
|
*UEnum::GetValueAsString(State));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
UCommonActivatableWidget* W = M->PushScreenToLayer(Layer, Cls);
|
||||||
|
UE_LOG(LogZMMO, Log,
|
||||||
|
TEXT("InGameFlow: tela %s pushed em %s (widget=%s)"),
|
||||||
|
*UEnum::GetValueAsString(State), *Layer.ToString(),
|
||||||
|
W ? *W->GetName() : TEXT("null"));
|
||||||
|
}));
|
||||||
|
}
|
||||||
97
Source/ZMMO/Game/UI/InGame/UIInGameFlowSubsystem.h
Normal file
97
Source/ZMMO/Game/UI/InGame/UIInGameFlowSubsystem.h
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "Subsystems/GameInstanceSubsystem.h"
|
||||||
|
#include "UI/InGameTypes.h"
|
||||||
|
#include "UIInGameFlowSubsystem.generated.h"
|
||||||
|
|
||||||
|
class UUIInGameScreenSet;
|
||||||
|
class UUIManagerSubsystem;
|
||||||
|
class UCommonActivatableWidget;
|
||||||
|
struct FGameplayTag;
|
||||||
|
|
||||||
|
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnZMMOInGameStateChanged, EZMMOInGameUIState, NewState);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Orquestrador da UI in-game. Espelho simetrico do UUIFrontEndFlowSubsystem
|
||||||
|
* (Game/UI/FrontEnd/UIFrontEndFlowSubsystem.h) — o front-end gerencia
|
||||||
|
* Boot/Login/ServerSelect/Lobby; este subsystem gerencia
|
||||||
|
* HUD/StatusWindow/Inventory/SkillTree/EscapeMenu in-world.
|
||||||
|
*
|
||||||
|
* UGameInstanceSubsystem pra persistir entre OpenLevel (futuro: trocar
|
||||||
|
* de mapa in-game preserva estado da UI). Inicializa em modo `None`;
|
||||||
|
* caller (`AZMMOPlayerCharacter::HandleLocalSpawnReady`) chama
|
||||||
|
* `StartInGame()` ao confirmar spawn local, que vai pra `Playing`.
|
||||||
|
*
|
||||||
|
* Convencao de layer (resolvida internamente):
|
||||||
|
* - Playing -> UI.Layer.Game
|
||||||
|
* - StatusWindow/Inventory/
|
||||||
|
* SkillTree/EscapeMenu -> UI.Layer.GameMenu
|
||||||
|
* - Loading -> UI.Layer.Modal
|
||||||
|
*
|
||||||
|
* Adicionar nova tela in-game = 3 passos:
|
||||||
|
* 1. Adicionar entry em `EZMMOInGameUIState`
|
||||||
|
* 2. Adicionar entry no DA_InGameScreenSet (estado -> WBP)
|
||||||
|
* 3. (se for em layer custom) adicionar caso em `LayerForState`
|
||||||
|
* Subsystem nao precisa de mudancas.
|
||||||
|
*/
|
||||||
|
UCLASS(Config = Game)
|
||||||
|
class ZMMO_API UUIInGameFlowSubsystem : public UGameInstanceSubsystem
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
public:
|
||||||
|
virtual void Initialize(FSubsystemCollectionBase& Collection) override;
|
||||||
|
virtual void Deinitialize() override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ativa a UI in-game. Chamado pelo `AZMMOPlayerCharacter::HandleLocalSpawnReady`
|
||||||
|
* quando o servidor confirma o spawn local. Garante root layout, limpa
|
||||||
|
* layers do front-end (Menu/Modal residuais), e vai pra `Playing` (HUD).
|
||||||
|
*/
|
||||||
|
UFUNCTION(BlueprintCallable, Category = "InGame|UI")
|
||||||
|
void StartInGame();
|
||||||
|
|
||||||
|
/** Define o estado top-level. Resolve a tela do DA + push no layer correto. */
|
||||||
|
UFUNCTION(BlueprintCallable, Category = "InGame|UI")
|
||||||
|
void SetState(EZMMOInGameUIState NewState);
|
||||||
|
|
||||||
|
UFUNCTION(BlueprintPure, Category = "InGame|UI")
|
||||||
|
EZMMOInGameUIState GetCurrentState() const { return CurrentState; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Atalho UX: alterna entre `Playing` e a tela passada. Util pra teclas
|
||||||
|
* de toggle (Alt+A abre/fecha StatusWindow, I abre/fecha Inventory).
|
||||||
|
* Se ja' estiver em `Screen`, volta pra Playing; senao vai pra `Screen`.
|
||||||
|
*/
|
||||||
|
UFUNCTION(BlueprintCallable, Category = "InGame|UI")
|
||||||
|
void ToggleScreen(EZMMOInGameUIState Screen);
|
||||||
|
|
||||||
|
/** Disparado a cada SetState bem-sucedido. */
|
||||||
|
UPROPERTY(BlueprintAssignable, Category = "InGame|UI")
|
||||||
|
FOnZMMOInGameStateChanged OnStateChanged;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
/** DA com o mapa estado->tela. Config em DefaultGame.ini:
|
||||||
|
* [/Script/ZMMO.UIInGameFlowSubsystem]
|
||||||
|
* ScreenSetAsset=/Game/ZMMO/UI/InGame/DA_InGameScreenSet.DA_InGameScreenSet
|
||||||
|
*/
|
||||||
|
UPROPERTY(Config, EditDefaultsOnly, Category = "InGame|UI")
|
||||||
|
TSoftObjectPtr<UUIInGameScreenSet> ScreenSetAsset;
|
||||||
|
|
||||||
|
private:
|
||||||
|
UUIInGameScreenSet* GetScreenSet();
|
||||||
|
UUIManagerSubsystem* GetUIManager() const;
|
||||||
|
|
||||||
|
/** Retorna a layer tag onde a tela do estado deve ir. */
|
||||||
|
FGameplayTag LayerForState(EZMMOInGameUIState State) const;
|
||||||
|
|
||||||
|
/** Resolve a soft class + push no layer apropriado (async load). */
|
||||||
|
void ResolveAndPushScreen(EZMMOInGameUIState State);
|
||||||
|
|
||||||
|
UPROPERTY(Transient)
|
||||||
|
EZMMOInGameUIState CurrentState = EZMMOInGameUIState::None;
|
||||||
|
|
||||||
|
UPROPERTY(Transient)
|
||||||
|
TObjectPtr<UUIInGameScreenSet> ScreenSet;
|
||||||
|
};
|
||||||
7
Source/ZMMO/Game/UI/InGame/UIInGameScreenSet.cpp
Normal file
7
Source/ZMMO/Game/UI/InGame/UIInGameScreenSet.cpp
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
#include "UIInGameScreenSet.h"
|
||||||
|
|
||||||
|
TSoftClassPtr<UCommonActivatableWidget> UUIInGameScreenSet::GetScreenForState(EZMMOInGameUIState State) const
|
||||||
|
{
|
||||||
|
const TSoftClassPtr<UCommonActivatableWidget>* Found = StateScreens.Find(State);
|
||||||
|
return Found ? *Found : TSoftClassPtr<UCommonActivatableWidget>();
|
||||||
|
}
|
||||||
35
Source/ZMMO/Game/UI/InGame/UIInGameScreenSet.h
Normal file
35
Source/ZMMO/Game/UI/InGame/UIInGameScreenSet.h
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "Engine/DataAsset.h"
|
||||||
|
#include "Templates/SubclassOf.h"
|
||||||
|
#include "UI/InGameTypes.h"
|
||||||
|
#include "UIInGameScreenSet.generated.h"
|
||||||
|
|
||||||
|
class UCommonActivatableWidget;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mapa data-driven estado -> tela in-game. Espelho de UUIFrontEndScreenSet
|
||||||
|
* (Game/UI/FrontEnd/UIFrontEndScreenSet.h) — mesma estrategia: as classes
|
||||||
|
* concretas das telas (WBP) vivem no DA, nao em codigo. Subsystem orquestra.
|
||||||
|
*
|
||||||
|
* Asset concreto: DA_InGameScreenSet em Content/ZMMO/UI/InGame/
|
||||||
|
* (criado via MCP no editor).
|
||||||
|
*
|
||||||
|
* Adicionar nova tela = adicionar entry em EZMMOInGameUIState + entry no DA
|
||||||
|
* + WBP herdando UCommonActivatableWidget. Subsystem nao muda.
|
||||||
|
*/
|
||||||
|
UCLASS(BlueprintType)
|
||||||
|
class ZMMO_API UUIInGameScreenSet : public UDataAsset
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
public:
|
||||||
|
/** Tela por estado in-game. */
|
||||||
|
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "InGame")
|
||||||
|
TMap<EZMMOInGameUIState, TSoftClassPtr<UCommonActivatableWidget>> StateScreens;
|
||||||
|
|
||||||
|
/** Soft class da tela do estado (vazio se nao configurado). */
|
||||||
|
UFUNCTION(BlueprintCallable, Category = "InGame")
|
||||||
|
TSoftClassPtr<UCommonActivatableWidget> GetScreenForState(EZMMOInGameUIState State) const;
|
||||||
|
};
|
||||||
122
Source/ZMMO/Game/UI/InGame/ZMMOHudWidget.cpp
Normal file
122
Source/ZMMO/Game/UI/InGame/ZMMOHudWidget.cpp
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
#include "ZMMOHudWidget.h"
|
||||||
|
|
||||||
|
#include "CommonInputTypeEnum.h" // ECommonInputMode
|
||||||
|
#include "Engine/World.h"
|
||||||
|
#include "GameFramework/PlayerController.h"
|
||||||
|
#include "GameFramework/PlayerState.h"
|
||||||
|
#include "ZMMO.h"
|
||||||
|
#include "ZMMOAttributeComponent.h"
|
||||||
|
#include "ZMMOHudHpSpWidget.h"
|
||||||
|
|
||||||
|
UZMMOHudWidget::UZMMOHudWidget(const FObjectInitializer& ObjectInitializer)
|
||||||
|
: Super(ObjectInitializer)
|
||||||
|
{
|
||||||
|
// HUD nao bloqueia "back" (Esc / B no controller). Menus reais (StatusWindow,
|
||||||
|
// EscapeMenu) sim — eles setam bIsBackHandler=true.
|
||||||
|
bIsBackHandler = false;
|
||||||
|
// HUD nao reativa stack ao virar topo (e' sempre fundo). Garante que o
|
||||||
|
// UCommonActivatableWidgetStack do Layer.Game nao force activate/deactivate.
|
||||||
|
bAutoActivate = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
TOptional<FUIInputConfig> UZMMOHudWidget::GetDesiredInputConfig() const
|
||||||
|
{
|
||||||
|
// Modo Game: input flui pro PlayerController/Pawn, mouse capturado (3a pessoa).
|
||||||
|
// Sem isso, CommonUI assume Menu config (mostra cursor + bloqueia movimento).
|
||||||
|
return FUIInputConfig(ECommonInputMode::Game, EMouseCaptureMode::CapturePermanently);
|
||||||
|
}
|
||||||
|
|
||||||
|
void UZMMOHudWidget::NativeOnActivated()
|
||||||
|
{
|
||||||
|
Super::NativeOnActivated();
|
||||||
|
// HUD nao intercepta cliques — mouse passa direto pro Pawn (camera, etc.).
|
||||||
|
// Sub-widgets que precisam de click (futuro: hotkeys) podem override.
|
||||||
|
SetVisibility(ESlateVisibility::HitTestInvisible);
|
||||||
|
BindToLocalPlayer();
|
||||||
|
UE_LOG(LogZMMO, Log, TEXT("ZMMOHudWidget activated (HpSpBar=%s)"),
|
||||||
|
HpSpBar ? *HpSpBar->GetName() : TEXT("none"));
|
||||||
|
}
|
||||||
|
|
||||||
|
void UZMMOHudWidget::NativeOnDeactivated()
|
||||||
|
{
|
||||||
|
UnbindFromComponent();
|
||||||
|
Super::NativeOnDeactivated();
|
||||||
|
}
|
||||||
|
|
||||||
|
void UZMMOHudWidget::NativeDestruct()
|
||||||
|
{
|
||||||
|
UnbindFromComponent();
|
||||||
|
Super::NativeDestruct();
|
||||||
|
}
|
||||||
|
|
||||||
|
void UZMMOHudWidget::BindToLocalPlayer()
|
||||||
|
{
|
||||||
|
UWorld* World = GetWorld();
|
||||||
|
if (!World) { return; }
|
||||||
|
|
||||||
|
APlayerController* PC = World->GetFirstPlayerController();
|
||||||
|
if (!PC) { return; }
|
||||||
|
APlayerState* PS = PC->PlayerState;
|
||||||
|
if (!PS)
|
||||||
|
{
|
||||||
|
// PlayerState pode nao existir ainda — race com GameMode setup.
|
||||||
|
// AZMMOPlayerCharacter::HandleLocalSpawnReady chama BindToAttributeComponent
|
||||||
|
// manualmente como fallback (seed do EntityId).
|
||||||
|
UE_LOG(LogZMMO, Log, TEXT("ZMMOHudWidget: PlayerState ausente no activate (race fix via HandleLocalSpawnReady esperado)"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
UZMMOAttributeComponent* Comp = PS->FindComponentByClass<UZMMOAttributeComponent>();
|
||||||
|
if (!Comp) { return; }
|
||||||
|
if (Comp == BoundComponent.Get()) { return; } // idempotente
|
||||||
|
|
||||||
|
UnbindFromComponent();
|
||||||
|
BoundComponent = Comp;
|
||||||
|
|
||||||
|
Comp->OnAttributesChanged.AddDynamic(this, &UZMMOHudWidget::HandleAttributesChanged);
|
||||||
|
Comp->OnHpSpChanged.AddDynamic(this, &UZMMOHudWidget::HandleHpSpChanged);
|
||||||
|
Comp->OnLevelUp.AddDynamic(this, &UZMMOHudWidget::HandleLevelUp);
|
||||||
|
|
||||||
|
// Refresh imediato — caso o snapshot ja' tenha chegado.
|
||||||
|
HandleAttributesChanged(Comp->GetSnapshot());
|
||||||
|
}
|
||||||
|
|
||||||
|
void UZMMOHudWidget::UnbindFromComponent()
|
||||||
|
{
|
||||||
|
if (UZMMOAttributeComponent* Old = BoundComponent.Get())
|
||||||
|
{
|
||||||
|
Old->OnAttributesChanged.RemoveDynamic(this, &UZMMOHudWidget::HandleAttributesChanged);
|
||||||
|
Old->OnHpSpChanged.RemoveDynamic(this, &UZMMOHudWidget::HandleHpSpChanged);
|
||||||
|
Old->OnLevelUp.RemoveDynamic(this, &UZMMOHudWidget::HandleLevelUp);
|
||||||
|
}
|
||||||
|
BoundComponent.Reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
void UZMMOHudWidget::HandleAttributesChanged(const FZMMOAttributesSnapshot& Snapshot)
|
||||||
|
{
|
||||||
|
// Propaga snapshot aos sub-widgets que ja existem. Em V1 so' HpSpBar.
|
||||||
|
if (HpSpBar)
|
||||||
|
{
|
||||||
|
HpSpBar->ApplySnapshot(Snapshot);
|
||||||
|
}
|
||||||
|
// Futuros: PlayerInfo->ApplySnapshot, Buffs->RefreshIcons, etc.
|
||||||
|
}
|
||||||
|
|
||||||
|
void UZMMOHudWidget::HandleHpSpChanged(int32 Hp, int32 Sp)
|
||||||
|
{
|
||||||
|
if (UZMMOAttributeComponent* Comp = BoundComponent.Get())
|
||||||
|
{
|
||||||
|
const FZMMOAttributesSnapshot& Snap = Comp->GetSnapshot();
|
||||||
|
if (HpSpBar)
|
||||||
|
{
|
||||||
|
HpSpBar->ApplyHpSp(Hp, Snap.MaxHp, Sp, Snap.MaxSp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void UZMMOHudWidget::HandleLevelUp(int32 NewBaseLevel, int32 StatusPointDelta)
|
||||||
|
{
|
||||||
|
// Snapshot subsequente do AttributeSystem vai disparar HandleAttributesChanged
|
||||||
|
// e atualizar HpSpBar->LevelText. Aqui e' o ponto pra efeitos visuais
|
||||||
|
// instantaneos (toast "LEVEL UP", confetti, sound). V1 noop — UI pode
|
||||||
|
// hook via Blueprint depois.
|
||||||
|
}
|
||||||
102
Source/ZMMO/Game/UI/InGame/ZMMOHudWidget.h
Normal file
102
Source/ZMMO/Game/UI/InGame/ZMMOHudWidget.h
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CommonActivatableWidget.h"
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "ZMMOAttributeTypes.h"
|
||||||
|
#include "ZMMOHudWidget.generated.h"
|
||||||
|
|
||||||
|
class UZMMOAttributeComponent;
|
||||||
|
class UZMMOHudHpSpWidget;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Root da HUD de gameplay. UCommonActivatableWidget pushed pelo
|
||||||
|
* UUIInGameFlowSubsystem em UI.Layer.Game quando o estado vira `Playing`.
|
||||||
|
*
|
||||||
|
* **Vive em ZMMO core** (UI/InGame/) porque o HUD root e' generico pra
|
||||||
|
* qualquer GameType — sub-widgets vem dos modulos especializados
|
||||||
|
* (HpSpBar do sub-modulo ZMMOAttributes, Minimap de ZMMOMap futuro,
|
||||||
|
* Chat de ZMMOChat futuro, etc.).
|
||||||
|
*
|
||||||
|
* Padrao **composite**: o WBP_HUD (filho) declara as PECAS visuais como
|
||||||
|
* sub-widgets BindWidgetOptional, e este C++ orquestra:
|
||||||
|
* 1. Em NativeOnActivated: auto-binda no UZMMOAttributeComponent do
|
||||||
|
* player local (vive no PlayerState via Component Registry)
|
||||||
|
* 2. Subscreve aos delegates do componente (OnAttributesChanged,
|
||||||
|
* OnHpSpChanged, OnLevelUp)
|
||||||
|
* 3. Propaga os deltas pros sub-widgets via metodos publicos
|
||||||
|
* (HpSpBar->ApplySnapshot, HpSpBar->ApplyHpSp, etc.)
|
||||||
|
*
|
||||||
|
* Composicao prevista (sub-widgets — todos `BindWidgetOptional` pra
|
||||||
|
* permitir GameTypes sem modulos opcionais — ex.: Survival sem stats RO):
|
||||||
|
* - HpSpBar (UZMMOHudHpSpWidget — sub-modulo ZMMOAttributes) — impl
|
||||||
|
* - PlayerInfo (futuro — nome/job/level/exp bar)
|
||||||
|
* - Minimap (futuro)
|
||||||
|
* - QuickBar (futuro — hotkeys de skills)
|
||||||
|
* - Buffs (futuro — icones SC ativos)
|
||||||
|
* - Chat (futuro)
|
||||||
|
* - TargetInfo (futuro — HP/SP/nome do mob/player targetado)
|
||||||
|
*
|
||||||
|
* Adicionar novo sub-widget = adicionar campo BindWidgetOptional aqui +
|
||||||
|
* setup no NativeOnActivated/handlers + adicionar no WBP_HUD.
|
||||||
|
*
|
||||||
|
* Pra mudar o LAYOUT visual ou skin do HUD: edita o WBP_HUD (filho)
|
||||||
|
* no Designer — sem tocar este C++.
|
||||||
|
*/
|
||||||
|
UCLASS(Abstract, Blueprintable, BlueprintType)
|
||||||
|
class ZMMO_API UZMMOHudWidget : public UCommonActivatableWidget
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
public:
|
||||||
|
UZMMOHudWidget(const FObjectInitializer& ObjectInitializer);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
virtual void NativeOnActivated() override;
|
||||||
|
virtual void NativeOnDeactivated() override;
|
||||||
|
virtual void NativeDestruct() override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HUD passa input direto pro Pawn — sem capturar mouse/teclado. Override
|
||||||
|
* retorna `TOptional<FUIInputConfig>(unset)`. Sem isso, UCommonActivatableWidget
|
||||||
|
* default usa Menu config (`SetInputMode_GameAndUI` + mouse cursor), o que
|
||||||
|
* desativa input de gameplay.
|
||||||
|
*
|
||||||
|
* Menus (StatusWindow, Inventory, etc.) override e' diferente — eles
|
||||||
|
* retornam GameAndMenu/Menu pra mostrar cursor + bloquear movement.
|
||||||
|
*/
|
||||||
|
virtual TOptional<FUIInputConfig> GetDesiredInputConfig() const override;
|
||||||
|
|
||||||
|
/// Sub-widget HP/SP/level (sub-modulo ZMMOAttributes). BindWidgetOptional
|
||||||
|
/// — WBP_HUD pode omitir se o GameType nao usa Attributes (Survival, etc.).
|
||||||
|
UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional), Category = "Zeus|HUD")
|
||||||
|
UZMMOHudHpSpWidget* HpSpBar = nullptr;
|
||||||
|
|
||||||
|
// === Futuros sub-widgets ===
|
||||||
|
// UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional)) UZMMOHudPlayerInfoWidget* PlayerInfo = nullptr;
|
||||||
|
// UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional)) UZMMOHudMinimapWidget* Minimap = nullptr;
|
||||||
|
// UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional)) UZMMOHudQuickBarWidget* QuickBar = nullptr;
|
||||||
|
// UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional)) UZMMOHudBuffsWidget* Buffs = nullptr;
|
||||||
|
// UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional)) UZMMOHudChatWidget* Chat = nullptr;
|
||||||
|
// UPROPERTY(BlueprintReadOnly, meta = (BindWidgetOptional)) UZMMOHudTargetInfoWidget* TargetInfo = nullptr;
|
||||||
|
|
||||||
|
UFUNCTION(BlueprintPure, Category = "Zeus|HUD")
|
||||||
|
UZMMOAttributeComponent* GetBoundComponent() const { return BoundComponent.Get(); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
/// Tenta achar o UZMMOAttributeComponent do player local + subscrever
|
||||||
|
/// nos delegates. Idempotente.
|
||||||
|
void BindToLocalPlayer();
|
||||||
|
void UnbindFromComponent();
|
||||||
|
|
||||||
|
UFUNCTION()
|
||||||
|
void HandleAttributesChanged(const FZMMOAttributesSnapshot& Snapshot);
|
||||||
|
|
||||||
|
UFUNCTION()
|
||||||
|
void HandleHpSpChanged(int32 Hp, int32 Sp);
|
||||||
|
|
||||||
|
UFUNCTION()
|
||||||
|
void HandleLevelUp(int32 NewBaseLevel, int32 StatusPointDelta);
|
||||||
|
|
||||||
|
UPROPERTY(Transient)
|
||||||
|
TWeakObjectPtr<UZMMOAttributeComponent> BoundComponent;
|
||||||
|
};
|
||||||
@@ -2,8 +2,8 @@
|
|||||||
|
|
||||||
#include "Engine/GameInstance.h"
|
#include "Engine/GameInstance.h"
|
||||||
#include "Engine/World.h"
|
#include "Engine/World.h"
|
||||||
#include "EngineUtils.h" // TActorIterator
|
#include "GameFramework/GameStateBase.h"
|
||||||
#include "GameFramework/Actor.h"
|
#include "GameFramework/PlayerState.h"
|
||||||
#include "ZMMOAttributeComponent.h"
|
#include "ZMMOAttributeComponent.h"
|
||||||
#include "ZMMOAttributeTypes.h"
|
#include "ZMMOAttributeTypes.h"
|
||||||
#include "ZeusNetworkSubsystem.h"
|
#include "ZeusNetworkSubsystem.h"
|
||||||
@@ -45,25 +45,22 @@ namespace
|
|||||||
return S;
|
return S;
|
||||||
}
|
}
|
||||||
|
|
||||||
// V1 — resolve por EntityId varrendo atores do mundo procurando um que
|
// Resolve `EntityId -> UZMMOAttributeComponent` via PlayerArray do GameState.
|
||||||
// tenha `UZMMOAttributeComponent`. Funciona para PIE com 1-2 players +
|
// AttributeComponent agora vive no PlayerState (vide AZMMOPlayerState +
|
||||||
// proxies. Otimizacao futura: usar `UZMMOWorldSubsystem::GetActorByEntityId`
|
// Component Registry em DefaultGame.ini). Itera apenas players (1 por
|
||||||
// (precisa expor) ou cache local de `EntityId -> UAttributeComponent*`.
|
// conexao), nao 1000+ atores — O(N_players).
|
||||||
//
|
//
|
||||||
// Convencao: `IZMMOEntityInterface::GetZMMOEntityId()` retorna o EntityId
|
// Cada UZMMOAttributeComponent carrega seu proprio EntityId (seed em
|
||||||
// autoritativo do servidor. Nao depender da interface aqui para manter
|
// AZMMOPlayerCharacter::HandleLocalSpawnReady ou no primeiro snapshot).
|
||||||
// ZMMOAttributes sem `#include` do modulo ZMMO core — varremos `Tags`
|
|
||||||
// alternativamente, mas o caminho mais simples e' a propria component
|
|
||||||
// keys de `EntityId` no proprio componente (TODO Fase 2: armazenar
|
|
||||||
// EntityId no componente quando snapshot chega).
|
|
||||||
UZMMOAttributeComponent* FindComponentForEntity(UWorld* World, int32 EntityId)
|
UZMMOAttributeComponent* FindComponentForEntity(UWorld* World, int32 EntityId)
|
||||||
{
|
{
|
||||||
if (!World || EntityId == 0) { return nullptr; }
|
if (!World || EntityId == 0) { return nullptr; }
|
||||||
for (TActorIterator<AActor> It(World); It; ++It)
|
const AGameStateBase* GS = World->GetGameState();
|
||||||
|
if (!GS) { return nullptr; }
|
||||||
|
for (APlayerState* PS : GS->PlayerArray)
|
||||||
{
|
{
|
||||||
AActor* Actor = *It;
|
if (!PS) { continue; }
|
||||||
if (!Actor) { continue; }
|
UZMMOAttributeComponent* Comp = PS->FindComponentByClass<UZMMOAttributeComponent>();
|
||||||
UZMMOAttributeComponent* Comp = Actor->FindComponentByClass<UZMMOAttributeComponent>();
|
|
||||||
if (!Comp) { continue; }
|
if (!Comp) { continue; }
|
||||||
if (Comp->GetSnapshot().EntityId == EntityId)
|
if (Comp->GetSnapshot().EntityId == EntityId)
|
||||||
{
|
{
|
||||||
@@ -136,21 +133,23 @@ void UZMMOAttributeNetworkHandler::HandleAttributeSnapshotFull(const FZeusAttrib
|
|||||||
UZMMOAttributeComponent* Comp = FindComponentForEntity(World, Payload.EntityId);
|
UZMMOAttributeComponent* Comp = FindComponentForEntity(World, Payload.EntityId);
|
||||||
if (!Comp)
|
if (!Comp)
|
||||||
{
|
{
|
||||||
// V1 fallback: aplica ao primeiro UZMMOAttributeComponent encontrado
|
// Fallback chicken-and-egg: primeiro snapshot chega antes do EntityId
|
||||||
// (player local). Isso resolve o caso do primeiro snapshot chegar
|
// estar seeded no componente. Aplica ao primeiro componente do
|
||||||
// antes do `EntityId` estar registrado no componente (que so' acontece
|
// PlayerArray que ainda tem EntityId=0. Apos o primeiro apply, o
|
||||||
// apos o primeiro snapshot — chicken-and-egg). Apos o primeiro apply,
|
// caminho normal por lookup funciona.
|
||||||
// `Current.EntityId` ja' bate e o caminho normal funciona.
|
const AGameStateBase* GS = World->GetGameState();
|
||||||
for (TActorIterator<AActor> It(World); It; ++It)
|
if (GS)
|
||||||
{
|
{
|
||||||
AActor* Actor = *It;
|
for (APlayerState* PS : GS->PlayerArray)
|
||||||
if (!Actor) { continue; }
|
|
||||||
Comp = Actor->FindComponentByClass<UZMMOAttributeComponent>();
|
|
||||||
if (Comp && Comp->GetSnapshot().EntityId == 0)
|
|
||||||
{
|
{
|
||||||
break;
|
if (!PS) { continue; }
|
||||||
|
UZMMOAttributeComponent* Candidate = PS->FindComponentByClass<UZMMOAttributeComponent>();
|
||||||
|
if (Candidate && Candidate->GetSnapshot().EntityId == 0)
|
||||||
|
{
|
||||||
|
Comp = Candidate;
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Comp = nullptr;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (Comp)
|
if (Comp)
|
||||||
|
|||||||
@@ -3,99 +3,43 @@
|
|||||||
#include "Components/ProgressBar.h"
|
#include "Components/ProgressBar.h"
|
||||||
#include "Components/TextBlock.h"
|
#include "Components/TextBlock.h"
|
||||||
#include "Internationalization/Text.h"
|
#include "Internationalization/Text.h"
|
||||||
#include "ZMMOAttributeComponent.h"
|
|
||||||
|
|
||||||
void UZMMOHudHpSpWidget::BindToAttributeComponent(UZMMOAttributeComponent* InComponent)
|
void UZMMOHudHpSpWidget::ApplySnapshot(const FZMMOAttributesSnapshot& Snapshot)
|
||||||
{
|
|
||||||
if (UZMMOAttributeComponent* Old = BoundComponent.Get())
|
|
||||||
{
|
|
||||||
Old->OnAttributesChanged.RemoveDynamic(this, &UZMMOHudHpSpWidget::HandleAttributesChanged);
|
|
||||||
Old->OnHpSpChanged.RemoveDynamic(this, &UZMMOHudHpSpWidget::HandleHpSpChanged);
|
|
||||||
}
|
|
||||||
|
|
||||||
BoundComponent = InComponent;
|
|
||||||
|
|
||||||
if (InComponent)
|
|
||||||
{
|
|
||||||
InComponent->OnAttributesChanged.AddDynamic(this, &UZMMOHudHpSpWidget::HandleAttributesChanged);
|
|
||||||
InComponent->OnHpSpChanged.AddDynamic(this, &UZMMOHudHpSpWidget::HandleHpSpChanged);
|
|
||||||
// Refresh imediato — caso o primeiro snapshot tenha chegado antes
|
|
||||||
// do widget existir.
|
|
||||||
HandleAttributesChanged(InComponent->GetSnapshot());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void UZMMOHudHpSpWidget::NativeDestruct()
|
|
||||||
{
|
|
||||||
if (UZMMOAttributeComponent* Old = BoundComponent.Get())
|
|
||||||
{
|
|
||||||
Old->OnAttributesChanged.RemoveDynamic(this, &UZMMOHudHpSpWidget::HandleAttributesChanged);
|
|
||||||
Old->OnHpSpChanged.RemoveDynamic(this, &UZMMOHudHpSpWidget::HandleHpSpChanged);
|
|
||||||
}
|
|
||||||
BoundComponent.Reset();
|
|
||||||
Super::NativeDestruct();
|
|
||||||
}
|
|
||||||
|
|
||||||
void UZMMOHudHpSpWidget::HandleAttributesChanged(const FZMMOAttributesSnapshot& Snapshot)
|
|
||||||
{
|
{
|
||||||
|
LastSnapshot = Snapshot;
|
||||||
OnSnapshotApplied(Snapshot);
|
OnSnapshotApplied(Snapshot);
|
||||||
}
|
}
|
||||||
|
|
||||||
void UZMMOHudHpSpWidget::HandleHpSpChanged(int32 Hp, int32 Sp)
|
void UZMMOHudHpSpWidget::ApplyHpSp(int32 Hp, int32 MaxHp, int32 Sp, int32 MaxSp)
|
||||||
{
|
{
|
||||||
OnHpSpDelta(Hp, Sp);
|
LastSnapshot.Hp = Hp;
|
||||||
|
LastSnapshot.MaxHp = MaxHp;
|
||||||
|
LastSnapshot.Sp = Sp;
|
||||||
|
LastSnapshot.MaxSp = MaxSp;
|
||||||
|
|
||||||
|
if (HpBar)
|
||||||
|
{
|
||||||
|
HpBar->SetPercent(MaxHp > 0 ? static_cast<float>(Hp) / static_cast<float>(MaxHp) : 0.f);
|
||||||
|
}
|
||||||
|
if (SpBar)
|
||||||
|
{
|
||||||
|
SpBar->SetPercent(MaxSp > 0 ? static_cast<float>(Sp) / static_cast<float>(MaxSp) : 0.f);
|
||||||
|
}
|
||||||
|
if (HpText)
|
||||||
|
{
|
||||||
|
HpText->SetText(FText::FromString(FString::Printf(TEXT("%d / %d"), Hp, MaxHp)));
|
||||||
|
}
|
||||||
|
if (SpText)
|
||||||
|
{
|
||||||
|
SpText->SetText(FText::FromString(FString::Printf(TEXT("%d / %d"), Sp, MaxSp)));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void UZMMOHudHpSpWidget::OnSnapshotApplied_Implementation(const FZMMOAttributesSnapshot& Snapshot)
|
void UZMMOHudHpSpWidget::OnSnapshotApplied_Implementation(const FZMMOAttributesSnapshot& Snapshot)
|
||||||
{
|
{
|
||||||
if (HpBar)
|
ApplyHpSp(Snapshot.Hp, Snapshot.MaxHp, Snapshot.Sp, Snapshot.MaxSp);
|
||||||
{
|
|
||||||
HpBar->SetPercent(Snapshot.MaxHp > 0
|
|
||||||
? static_cast<float>(Snapshot.Hp) / static_cast<float>(Snapshot.MaxHp) : 0.f);
|
|
||||||
}
|
|
||||||
if (SpBar)
|
|
||||||
{
|
|
||||||
SpBar->SetPercent(Snapshot.MaxSp > 0
|
|
||||||
? static_cast<float>(Snapshot.Sp) / static_cast<float>(Snapshot.MaxSp) : 0.f);
|
|
||||||
}
|
|
||||||
if (HpText)
|
|
||||||
{
|
|
||||||
HpText->SetText(FText::FromString(FString::Printf(TEXT("%d / %d"), Snapshot.Hp, Snapshot.MaxHp)));
|
|
||||||
}
|
|
||||||
if (SpText)
|
|
||||||
{
|
|
||||||
SpText->SetText(FText::FromString(FString::Printf(TEXT("%d / %d"), Snapshot.Sp, Snapshot.MaxSp)));
|
|
||||||
}
|
|
||||||
if (LevelText)
|
if (LevelText)
|
||||||
{
|
{
|
||||||
LevelText->SetText(FText::FromString(FString::Printf(TEXT("Lv %d"), Snapshot.BaseLevel)));
|
LevelText->SetText(FText::FromString(FString::Printf(TEXT("Lv %d"), Snapshot.BaseLevel)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void UZMMOHudHpSpWidget::OnHpSpDelta_Implementation(int32 Hp, int32 Sp)
|
|
||||||
{
|
|
||||||
// Atualiza apenas os campos vitais (evita refresh redundante de
|
|
||||||
// stats/level que nao mudaram).
|
|
||||||
if (UZMMOAttributeComponent* Comp = BoundComponent.Get())
|
|
||||||
{
|
|
||||||
const FZMMOAttributesSnapshot& Snap = Comp->GetSnapshot();
|
|
||||||
if (HpBar)
|
|
||||||
{
|
|
||||||
HpBar->SetPercent(Snap.MaxHp > 0
|
|
||||||
? static_cast<float>(Hp) / static_cast<float>(Snap.MaxHp) : 0.f);
|
|
||||||
}
|
|
||||||
if (SpBar)
|
|
||||||
{
|
|
||||||
SpBar->SetPercent(Snap.MaxSp > 0
|
|
||||||
? static_cast<float>(Sp) / static_cast<float>(Snap.MaxSp) : 0.f);
|
|
||||||
}
|
|
||||||
if (HpText)
|
|
||||||
{
|
|
||||||
HpText->SetText(FText::FromString(FString::Printf(TEXT("%d / %d"), Hp, Snap.MaxHp)));
|
|
||||||
}
|
|
||||||
if (SpText)
|
|
||||||
{
|
|
||||||
SpText->SetText(FText::FromString(FString::Printf(TEXT("%d / %d"), Sp, Snap.MaxSp)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -7,21 +7,27 @@
|
|||||||
|
|
||||||
class UProgressBar;
|
class UProgressBar;
|
||||||
class UTextBlock;
|
class UTextBlock;
|
||||||
class UZMMOAttributeComponent;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Widget base do HUD de atributos (HP/SP/level). Spawnada por
|
* Sub-widget do HUD: barras de HP/SP + label de level. Sem auto-bind no
|
||||||
* `AZMMOPlayerCharacter::BeginPlay` quando o pawn local virou autoritativo.
|
* 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.
|
* Composicao do HUD (ver `UZMMOHudWidget`):
|
||||||
* Comportamento (binding com AttributeComponent + refresh on delegate)
|
* WBP_HUD (UZMMOHudWidget root)
|
||||||
* vive aqui em C++ — permite que outros modulos consumam esta API
|
* |-- HpSpBar (UZMMOHudHpSpWidget — este)
|
||||||
* sem precisar abrir o WBP. Sub-classe via Blueprint apenas para skin
|
* |-- PlayerInfo (futuro — nome/job/level)
|
||||||
* visual e disposicao dos sub-widgets.
|
* |-- 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
|
* Cada sub-widget tem responsabilidade unica (SRP). O parent (UZMMOHudWidget)
|
||||||
* mesmos nomes (HpBar, SpBar). `BindWidgetOptional` permite ao WBP
|
* faz binding com AttributeComponent e propaga deltas.
|
||||||
* omitir o widget (ex.: HUD compacto sem texto numerico).
|
*
|
||||||
|
* Convencao `meta=(BindWidget)`: o WBP filho deve ter widgets com nomes
|
||||||
|
* exatos (HpBar, SpBar). `BindWidgetOptional` permite omitir.
|
||||||
*/
|
*/
|
||||||
UCLASS(Abstract, Blueprintable, BlueprintType)
|
UCLASS(Abstract, Blueprintable, BlueprintType)
|
||||||
class ZMMOATTRIBUTES_API UZMMOHudHpSpWidget : public UUserWidget
|
class ZMMOATTRIBUTES_API UZMMOHudHpSpWidget : public UUserWidget
|
||||||
@@ -29,34 +35,24 @@ class ZMMOATTRIBUTES_API UZMMOHudHpSpWidget : public UUserWidget
|
|||||||
GENERATED_BODY()
|
GENERATED_BODY()
|
||||||
|
|
||||||
public:
|
public:
|
||||||
/**
|
/** Atualiza todos os campos a partir do snapshot. Chamado pelo UZMMOHudWidget. */
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
UFUNCTION(BlueprintCallable, Category = "Zeus|HUD")
|
UFUNCTION(BlueprintCallable, Category = "Zeus|HUD")
|
||||||
void BindToAttributeComponent(UZMMOAttributeComponent* InComponent);
|
void ApplySnapshot(const FZMMOAttributesSnapshot& Snapshot);
|
||||||
|
|
||||||
UFUNCTION(BlueprintPure, Category = "Zeus|HUD")
|
/** Atualiza apenas HP/SP (sem mexer no level/text). Otimizacao do tick. */
|
||||||
UZMMOAttributeComponent* GetBoundComponent() const { return BoundComponent.Get(); }
|
UFUNCTION(BlueprintCallable, Category = "Zeus|HUD")
|
||||||
|
void ApplyHpSp(int32 Hp, int32 MaxHp, int32 Sp, int32 MaxSp);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual void NativeDestruct() override;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hook BlueprintNativeEvent para skin visual reagir a um snapshot novo
|
* Hook BlueprintNativeEvent para skin visual reagir a um snapshot novo
|
||||||
* (cores, animacao de level up, etc.). Implementacao default em C++
|
* (cores, animacao de level up, etc.). Impl default em C++ apenas
|
||||||
* apenas atualiza progress bars + textos via `BindWidget`/`BindWidgetOptional`.
|
* atualiza progress bars + textos via BindWidget/BindWidgetOptional.
|
||||||
*/
|
*/
|
||||||
UFUNCTION(BlueprintNativeEvent, Category = "Zeus|HUD")
|
UFUNCTION(BlueprintNativeEvent, Category = "Zeus|HUD")
|
||||||
void OnSnapshotApplied(const FZMMOAttributesSnapshot& Snapshot);
|
void OnSnapshotApplied(const FZMMOAttributesSnapshot& Snapshot);
|
||||||
virtual void OnSnapshotApplied_Implementation(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 ===
|
// === BindWidget — devem existir no WBP com EXATAMENTE estes nomes ===
|
||||||
UPROPERTY(BlueprintReadOnly, meta = (BindWidget), Category = "Zeus|HUD")
|
UPROPERTY(BlueprintReadOnly, meta = (BindWidget), Category = "Zeus|HUD")
|
||||||
UProgressBar* HpBar = nullptr;
|
UProgressBar* HpBar = nullptr;
|
||||||
@@ -74,12 +70,7 @@ protected:
|
|||||||
UTextBlock* LevelText = nullptr;
|
UTextBlock* LevelText = nullptr;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
UFUNCTION()
|
/// Ultimo snapshot recebido (cache pra ApplyHpSp manter MaxHp/MaxSp). */
|
||||||
void HandleAttributesChanged(const FZMMOAttributesSnapshot& Snapshot);
|
UPROPERTY(Transient)
|
||||||
|
FZMMOAttributesSnapshot LastSnapshot;
|
||||||
UFUNCTION()
|
|
||||||
void HandleHpSpChanged(int32 Hp, int32 Sp);
|
|
||||||
|
|
||||||
UPROPERTY()
|
|
||||||
TWeakObjectPtr<UZMMOAttributeComponent> BoundComponent;
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ public class ZMMOAttributes : ModuleRules
|
|||||||
"Engine",
|
"Engine",
|
||||||
"UMG",
|
"UMG",
|
||||||
"Slate",
|
"Slate",
|
||||||
|
"CommonUI",
|
||||||
|
"CommonInput",
|
||||||
"ZeusNetwork"
|
"ZeusNetwork"
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"side": "client",
|
"side": "client",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"engine": ["Core", "CoreUObject", "Engine", "UMG", "Slate"],
|
"engine": ["Core", "CoreUObject", "Engine", "UMG", "Slate", "CommonUI", "CommonInput"],
|
||||||
"plugins": ["ZeusNetwork"],
|
"plugins": ["ZeusNetwork"],
|
||||||
"modules": []
|
"modules": []
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user