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>
166 lines
6.0 KiB
C++
166 lines
6.0 KiB
C++
#pragma once
|
|
|
|
#include "CoreMinimal.h"
|
|
#include "GameFramework/Character.h"
|
|
#include "Logging/LogMacros.h"
|
|
#include "ZMMOEntityInterface.h"
|
|
#include "ZMMOEntityTypes.h"
|
|
#include "ZMMOPlayerCharacter.generated.h"
|
|
|
|
class USpringArmComponent;
|
|
class UCameraComponent;
|
|
class UInputAction;
|
|
class UZeusNetworkSubsystem;
|
|
struct FInputActionValue;
|
|
|
|
DECLARE_LOG_CATEGORY_EXTERN(LogZMMOPlayer, Log, All);
|
|
|
|
/**
|
|
* AZMMOPlayerCharacter
|
|
*
|
|
* Personagem do jogador local. Diferente do template `ThirdPersonCharacter`
|
|
* e do antigo `AZMMOCharacter`, este ator e parte do pipeline de rede Zeus:
|
|
*
|
|
* - Continua a usar o `UCharacterMovementComponent` LIVRE para correr a
|
|
* fisica do landscape (ADR 0038): o cliente e autoritativo localmente
|
|
* sobre Z/colisao com terreno; o servidor apenas valida input/velocidade.
|
|
* - Envia `C_INPUT_AXIS` ao `UZeusNetworkSubsystem` na cadencia
|
|
* `InputSendRateHz` (com heartbeat 5 Hz quando parado para evitar
|
|
* starvation do `MovementSystem` no servidor).
|
|
* - Implementa `IZMMOEntityInterface` para participar do registry do
|
|
* `UZMMOWorldSubsystem`, mas `ApplyEntitySnapshot` e `SetEntityRelevant`
|
|
* sao no-op em V0 — o cliente local nao reconcilia (cliente solto).
|
|
*
|
|
* TODO V1: reconciliacao opcional XY quando colisao de objetos chegar; AOI
|
|
* debug spheres; visual smoothing apos correcao do servidor.
|
|
*/
|
|
UCLASS(Blueprintable, BlueprintType)
|
|
class ZMMO_API AZMMOPlayerCharacter : public ACharacter, public IZMMOEntityInterface
|
|
{
|
|
GENERATED_BODY()
|
|
|
|
/** Camera boom positioning the camera behind the character */
|
|
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Components", meta = (AllowPrivateAccess = "true"))
|
|
USpringArmComponent* CameraBoom;
|
|
|
|
/** Follow camera */
|
|
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Components", meta = (AllowPrivateAccess = "true"))
|
|
UCameraComponent* FollowCamera;
|
|
|
|
// AttributeComponent vive no PlayerState (AZMMOPlayerState + Component Registry).
|
|
// Acesso: GetPlayerState<AZMMOPlayerState>()->GetZMMOComponent<UZMMOAttributeComponent>().
|
|
|
|
protected:
|
|
UPROPERTY(EditAnywhere, Category = "Input")
|
|
UInputAction* JumpAction;
|
|
|
|
UPROPERTY(EditAnywhere, Category = "Input")
|
|
UInputAction* MoveAction;
|
|
|
|
UPROPERTY(EditAnywhere, Category = "Input")
|
|
UInputAction* LookAction;
|
|
|
|
UPROPERTY(EditAnywhere, Category = "Input")
|
|
UInputAction* MouseLookAction;
|
|
|
|
/** Cadencia de envio dos pacotes `C_INPUT_AXIS` (Hz). */
|
|
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "ZMMO|Networking", meta = (ClampMin = "1", ClampMax = "120"))
|
|
int32 InputSendRateHz = 30;
|
|
|
|
/**
|
|
* Intervalo maximo (s) entre envios de `C_INPUT_AXIS` quando os inputs sao
|
|
* vazios (forward=0, right=0, sem jump). Mantem ~5 Hz de heartbeat para
|
|
* evitar starvation do servidor caso pacotes sejam perdidos. Em movimento
|
|
* o envio segue `InputSendRateHz` (30 Hz).
|
|
*/
|
|
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "ZMMO|Networking", meta = (ClampMin = "0.05", ClampMax = "5.0"))
|
|
float HeartbeatIntervalSec = 0.2f;
|
|
|
|
public:
|
|
AZMMOPlayerCharacter();
|
|
|
|
virtual void Tick(float DeltaSeconds) override;
|
|
|
|
// --- IZMMOEntityInterface ---
|
|
virtual int64 GetZMMOEntityId() const override { return ZMMOEntityId; }
|
|
virtual EZMMOEntityType GetZMMOEntityType() const override { return EZMMOEntityType::Player; }
|
|
virtual void ApplyEntitySnapshot(const FZMMOEntitySnapshot& Snapshot) override;
|
|
virtual void SetEntityRelevant(bool bRelevant) override;
|
|
|
|
UFUNCTION(BlueprintCallable, Category = "ZMMO|Entity")
|
|
void SetZMMOEntityId(int64 InEntityId) { ZMMOEntityId = InEntityId; }
|
|
|
|
FORCEINLINE USpringArmComponent* GetCameraBoom() const { return CameraBoom; }
|
|
FORCEINLINE UCameraComponent* GetFollowCamera() const { return FollowCamera; }
|
|
|
|
protected:
|
|
virtual void BeginPlay() override;
|
|
virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override;
|
|
|
|
virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;
|
|
|
|
void Move(const FInputActionValue& Value);
|
|
void MoveCompleted(const FInputActionValue& Value);
|
|
void Look(const FInputActionValue& Value);
|
|
void OnJumpPressed();
|
|
void OnJumpReleased();
|
|
|
|
public:
|
|
/** Wrappers expostos a Blueprint (UI / mobile / automation). */
|
|
UFUNCTION(BlueprintCallable, Category = "Input")
|
|
virtual void DoMove(float Right, float Forward);
|
|
|
|
UFUNCTION(BlueprintCallable, Category = "Input")
|
|
virtual void DoLook(float Yaw, float Pitch);
|
|
|
|
UFUNCTION(BlueprintCallable, Category = "Input")
|
|
virtual void DoJumpStart();
|
|
|
|
UFUNCTION(BlueprintCallable, Category = "Input")
|
|
virtual void DoJumpEnd();
|
|
|
|
private:
|
|
void ResolveZeusNetworkSubsystem();
|
|
void FlushInputAxisToServer(float DeltaSeconds);
|
|
|
|
/** Liga `OnPlayerSpawned` para captar EntityId local apos o handshake. */
|
|
void BindZeusSpawnDelegate();
|
|
void UnbindZeusSpawnDelegate();
|
|
|
|
/** Caso o spawn ja tenha chegado antes deste pawn existir, aplica via cache. */
|
|
void TryRegisterLocalEntityFromCachedSpawn();
|
|
|
|
/**
|
|
* Memoriza `EntityId` autoritativo e regista o pawn no `UZMMOWorldSubsystem`
|
|
* para o registry partilhado por proxies remotos (filtra snapshots locais).
|
|
*/
|
|
void HandleLocalSpawnReady(int32 InEntityId, FVector PosCm, float YawDeg, int64 ServerTimeMs);
|
|
|
|
UFUNCTION()
|
|
void HandleZeusPlayerSpawned(int32 InEntityId, bool bIsLocal, FVector PosCm, float YawDeg, int64 ServerTimeMs);
|
|
|
|
UPROPERTY()
|
|
TObjectPtr<UZeusNetworkSubsystem> ZeusNetwork;
|
|
|
|
bool bSpawnDelegateBound = false;
|
|
|
|
/** Identidade autoritativa atribuida quando o servidor envia S_SPAWN_PLAYER local. */
|
|
UPROPERTY(VisibleInstanceOnly, BlueprintReadOnly, Category = "ZMMO|Entity", meta = (AllowPrivateAccess = "true"))
|
|
int64 ZMMOEntityId = 0;
|
|
|
|
float PendingMoveForward = 0.0f;
|
|
float PendingMoveRight = 0.0f;
|
|
|
|
bool bPendingJumpPressed = false;
|
|
bool bPendingJumpReleased = false;
|
|
bool bPreviousJumpHeld = false;
|
|
/** Último estado IsFalling enviado ao servidor. Usado para forçar um envio
|
|
* imediato em transições walking↔falling, evitando que o rate limit segure
|
|
* a transição até o próximo heartbeat. */
|
|
bool bPreviousFalling = false;
|
|
|
|
float SendAccumulatorSec = 0.0f;
|
|
float TimeSinceLastSendSec = 0.0f;
|
|
int32 InputSequence = 0;
|
|
};
|