diff --git a/Config/DefaultGame.ini b/Config/DefaultGame.ini index 5107308..aeba32e 100644 --- a/Config/DefaultGame.ini +++ b/Config/DefaultGame.ini @@ -8,4 +8,5 @@ CopyrightNotice= [/Script/GenericUISystem.GUIS_GenericUISystemSettings] ; Set this in editor to a BP subclass of UPHYGameUIPolicy (recommended), e.g. ; /Game/AGame/UI/BP_PHYGameUIPolicy.BP_PHYGameUIPolicy_C -GameUIPolicyClass= +GameUIPolicyClass=/Game/AGame/UI/GameUIPolicy.GameUIPolicy_C + diff --git a/Content/AGame/UI/Menu/Overlap/WBP_Menu_Overlap.uasset b/Content/AGame/UI/Menu/Overlap/WBP_Menu_Overlap.uasset index ca7828b..689d1fa 100644 Binary files a/Content/AGame/UI/Menu/Overlap/WBP_Menu_Overlap.uasset and b/Content/AGame/UI/Menu/Overlap/WBP_Menu_Overlap.uasset differ diff --git a/Content/AGame/UI/Menu/Overlap/Widget/WBP_CharacterInfo.uasset b/Content/AGame/UI/Menu/Overlap/Widget/WBP_CharacterInfo.uasset new file mode 100644 index 0000000..62465c6 Binary files /dev/null and b/Content/AGame/UI/Menu/Overlap/Widget/WBP_CharacterInfo.uasset differ diff --git a/Content/AGame/UI/Synty/WBP_Synty_ProgressBarBase.uasset b/Content/AGame/UI/Synty/WBP_Synty_ProgressBarBase.uasset new file mode 100644 index 0000000..1ba9e6a Binary files /dev/null and b/Content/AGame/UI/Synty/WBP_Synty_ProgressBarBase.uasset differ diff --git a/Source/PHY/Private/Character/PHYPlayerCharacter.cpp b/Source/PHY/Private/Character/PHYPlayerCharacter.cpp index e2e4741..8bc6d97 100644 --- a/Source/PHY/Private/Character/PHYPlayerCharacter.cpp +++ b/Source/PHY/Private/Character/PHYPlayerCharacter.cpp @@ -90,11 +90,15 @@ void APHYPlayerCharacter::PossessedBy(AController* NewController) void APHYPlayerCharacter::InitializeHUD() { + APHYPlayerState* PS = GetPlayerState(); + if (!PS) return; + UAbilitySystemComponent* ASC = PS->GetAbilitySystemComponent(); + if (!ASC) return; if (const APlayerController* PC = Cast(GetController())) { if (APHYGameHUD* GameHUD = Cast(PC->GetHUD())) { - GameHUD->InitializeHUD(); + GameHUD->InitializeHUD(ASC, PS); } } } @@ -179,19 +183,7 @@ void APHYPlayerCharacter::InitializeGAS() GetWorldTimerManager().SetTimer(RegenTimerHandle, this, &APHYPlayerCharacter::RegenTick, RegenInterval, true); } } - - // Push MoveSpeed to CharacterMovement (both sides, will converge via replication) - if (UCharacterMovementComponent* MoveComp = GetCharacterMovement()) - { - if (const UPHYAttributeSet* AS = PS->GetAttributeSet()) - { - const float DesiredSpeed = AS->GetMoveSpeed(); - if (DesiredSpeed > 0.f) - { - MoveComp->MaxWalkSpeed = DesiredSpeed; - } - } - } + } void APHYPlayerCharacter::StopRegen() diff --git a/Source/PHY/Private/GameplayTags/UITags.cpp b/Source/PHY/Private/GameplayTags/UITags.cpp index ee6a4f6..5dbd924 100644 --- a/Source/PHY/Private/GameplayTags/UITags.cpp +++ b/Source/PHY/Private/GameplayTags/UITags.cpp @@ -10,4 +10,7 @@ namespace UITags UE_DEFINE_GAMEPLAY_TAG(Tag__UI_Layer_GameMenu, "UI.Layer.GameMenu"); UE_DEFINE_GAMEPLAY_TAG(Tag__UI_Layer_Menu, "UI.Layer.Menu"); UE_DEFINE_GAMEPLAY_TAG(Tag__UI_Layer_Modal, "UI.Layer.Modal"); + + // 拓展点 + UE_DEFINE_GAMEPLAY_TAG(Tag__UI_HUD_CharacterInfo, "UI.HUD.CharacterInfo"); } diff --git a/Source/PHY/Private/GameplayTags/UITags.h b/Source/PHY/Private/GameplayTags/UITags.h index c5198f3..a6ba153 100644 --- a/Source/PHY/Private/GameplayTags/UITags.h +++ b/Source/PHY/Private/GameplayTags/UITags.h @@ -14,4 +14,7 @@ namespace UITags UE_DECLARE_GAMEPLAY_TAG_EXTERN(Tag__UI_Layer_GameMenu); UE_DECLARE_GAMEPLAY_TAG_EXTERN(Tag__UI_Layer_Menu); UE_DECLARE_GAMEPLAY_TAG_EXTERN(Tag__UI_Layer_Modal); + + // 拓展点 + UE_DECLARE_GAMEPLAY_TAG_EXTERN(Tag__UI_HUD_CharacterInfo); }; diff --git a/Source/PHY/Private/UI/GameUIExtensionPointWidget.cpp b/Source/PHY/Private/UI/GameUIExtensionPointWidget.cpp new file mode 100644 index 0000000..eebc84b --- /dev/null +++ b/Source/PHY/Private/UI/GameUIExtensionPointWidget.cpp @@ -0,0 +1,14 @@ +// + + +#include "GameUIExtensionPointWidget.h" + +UUserWidget* UGameUIExtensionPointWidget::FindExtensionWidgetByHandle(const FGUIS_GameUIExtHandle& Handle) const +{ + return ExtensionMapping.FindRef(Handle); +} + +void UGameUIExtensionPointWidget::SetExtensionTag(const FGameplayTag& InExtensionPointTag) +{ + ExtensionPointTag = InExtensionPointTag; +} diff --git a/Source/PHY/Private/UI/GameUIExtensionPointWidget.h b/Source/PHY/Private/UI/GameUIExtensionPointWidget.h new file mode 100644 index 0000000..ed7350d --- /dev/null +++ b/Source/PHY/Private/UI/GameUIExtensionPointWidget.h @@ -0,0 +1,20 @@ +// + +#pragma once + +#include "CoreMinimal.h" +#include "UIExtension/GUIS_GameUIExtensionPointWidget.h" +#include "GameUIExtensionPointWidget.generated.h" + +/** + * + */ +UCLASS() +class PHY_API UGameUIExtensionPointWidget : public UGUIS_GameUIExtensionPointWidget +{ + GENERATED_BODY() + +public: + UUserWidget* FindExtensionWidgetByHandle(const FGUIS_GameUIExtHandle& Handle) const; + void SetExtensionTag(const FGameplayTag& InExtensionPointTag); +}; diff --git a/Source/PHY/Private/UI/HUD/PHYGameHUD.cpp b/Source/PHY/Private/UI/HUD/PHYGameHUD.cpp index 41b6694..1c7aaa5 100644 --- a/Source/PHY/Private/UI/HUD/PHYGameHUD.cpp +++ b/Source/PHY/Private/UI/HUD/PHYGameHUD.cpp @@ -3,8 +3,12 @@ #include "PHYGameHUD.h" +#include "AbilitySystemBlueprintLibrary.h" +#include "Gameplay/Player/PHYPlayerState.h" #include "GameplayTags/UITags.h" #include "UI/Actions/GUIS_AsyncAction_PushContentToUILayer.h" +#include "UI/Menu/Menu_Overlap.h" + UGUIS_GameUISubsystem* APHYGameHUD::GetUISubsystem() const { @@ -15,15 +19,7 @@ UGUIS_GameUISubsystem* APHYGameHUD::GetUISubsystem() const return nullptr; } -void APHYGameHUD::OnBeforePushOverlapWidget(UCommonActivatableWidget* UserWidget) -{ -} - -void APHYGameHUD::OnAfterPushOverlapWidget(UCommonActivatableWidget* UserWidget) -{ -} - -void APHYGameHUD::InitializeHUD() +void APHYGameHUD::InitializeHUD(UAbilitySystemComponent* InAbilitySystemComponent, APHYPlayerState* InPlayerState) { // 获取当前的player controller APlayerController* PC = GetOwningPlayerController(); @@ -44,8 +40,38 @@ void APHYGameHUD::InitializeHUD() PushAction->AfterPush.AddDynamic(this, &APHYGameHUD::OnAfterPushOverlapWidget); PushAction->Activate(); } + if (InAbilitySystemComponent) + { + BoundAbilitySystemComponent = InAbilitySystemComponent; + } else + { + BoundAbilitySystemComponent = UAbilitySystemBlueprintLibrary::GetAbilitySystemComponent(GetOwningPawn()); + } + if (InPlayerState) + { + BoundPlayerState = InPlayerState; + } else + { + BoundPlayerState = PC->GetPlayerState(); + + } } +void APHYGameHUD::OnBeforePushOverlapWidget(UCommonActivatableWidget* UserWidget) +{ + +} + +void APHYGameHUD::OnAfterPushOverlapWidget(UCommonActivatableWidget* UserWidget) +{ + if (UMenu_Overlap* OverlapWidget = Cast(UserWidget)) + { + OverlapWidgetInstance = OverlapWidget; + OverlapWidgetInstance->BindAscEvents(BoundAbilitySystemComponent); + } +} + + void APHYGameHUD::EndPlay(const EEndPlayReason::Type EndPlayReason) { if (const APlayerController* PC = GetOwningPlayerController()) diff --git a/Source/PHY/Private/UI/HUD/PHYGameHUD.h b/Source/PHY/Private/UI/HUD/PHYGameHUD.h index d544464..31dfe6c 100644 --- a/Source/PHY/Private/UI/HUD/PHYGameHUD.h +++ b/Source/PHY/Private/UI/HUD/PHYGameHUD.h @@ -8,6 +8,9 @@ #include "PHYGameHUD.generated.h" +class UMenu_Overlap; +class APHYPlayerState; +class UAbilitySystemComponent; class UCommonActivatableWidget; /** * @@ -22,7 +25,7 @@ class PHY_API APHYGameHUD : public AHUD public: - void InitializeHUD(); + void InitializeHUD(UAbilitySystemComponent* InAbilitySystemComponent, APHYPlayerState* InPlayerState); protected: virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override; @@ -35,4 +38,8 @@ private: void OnBeforePushOverlapWidget(UCommonActivatableWidget* UserWidget); UFUNCTION() void OnAfterPushOverlapWidget(UCommonActivatableWidget* UserWidget); + UPROPERTY() + UMenu_Overlap* OverlapWidgetInstance; + UAbilitySystemComponent* BoundAbilitySystemComponent; + APHYPlayerState* BoundPlayerState; }; diff --git a/Source/PHY/Private/UI/Menu/Menu_Overlap.cpp b/Source/PHY/Private/UI/Menu/Menu_Overlap.cpp index c85ed62..aeea063 100644 --- a/Source/PHY/Private/UI/Menu/Menu_Overlap.cpp +++ b/Source/PHY/Private/UI/Menu/Menu_Overlap.cpp @@ -2,3 +2,77 @@ #include "Menu_Overlap.h" + +#include "AbilitySystemComponent.h" +#include "AbilitySystem/Attributes/PHYAttributeSet.h" +#include "GameplayTags/UITags.h" +#include "UI/GameUIExtensionPointWidget.h" +#include "UI/Widget/Widget_CharacterInfo.h" + +void UMenu_Overlap::NativePreConstruct() +{ + Super::NativePreConstruct(); + +} + +void UMenu_Overlap::NativeOnActivated() +{ + Super::NativeOnActivated(); + +} + +void UMenu_Overlap::NativeOnInitialized() +{ + Super::NativeOnInitialized(); + // 推送拓展点 + if (UGUIS_ExtensionSubsystem* ExtensionSubsystem = GetWorld()->GetSubsystem()) + { + if (CharacterInfoWidgetClass) + { + CharacterInfoExtHandle = ExtensionSubsystem->RegisterExtensionAsWidget(UITags::Tag__UI_HUD_CharacterInfo,CharacterInfoWidgetClass, 0); + } + } +} + +void UMenu_Overlap::BindAscEvents(UAbilitySystemComponent* AbilitySystemComponent) +{ + if (AbilitySystemComponent == nullptr) return; + const UPHYAttributeSet* Attributes = AbilitySystemComponent->GetSet(); + if (Attributes == nullptr) return; + // 从拓展点获取widget + if (!UserInfoExtensionPoint) return; + UUserWidget* CurrentUserInfoWidget = UserInfoExtensionPoint->FindExtensionWidgetByHandle(CharacterInfoExtHandle); + UWidget_CharacterInfo* CharacterInfoWidget = Cast(CurrentUserInfoWidget); + if (CharacterInfoWidget == nullptr) return; + // 同步初始属性值 + CurrentHp = Attributes->GetHealth(); + MaxHp = Attributes->GetMaxHealth(); + CurrentMp = Attributes->GetInnerPower(); + MaxMp = Attributes->GetMaxInnerPower(); + AbilitySystemComponent->GetGameplayAttributeValueChangeDelegate(Attributes->GetHealthAttribute()).AddLambda( + [this, CharacterInfoWidget](const FOnAttributeChangeData& Data) + { + CurrentHp = Data.NewValue; + CharacterInfoWidget->SetHp(CurrentHp, MaxHp); + }); + AbilitySystemComponent->GetGameplayAttributeValueChangeDelegate(Attributes->GetMaxHealthAttribute()).AddLambda( + [this, CharacterInfoWidget](const FOnAttributeChangeData& Data) + { + MaxHp = Data.NewValue; + CharacterInfoWidget->SetHp(CurrentHp, MaxHp); + }); + AbilitySystemComponent->GetGameplayAttributeValueChangeDelegate(Attributes->GetInnerPowerAttribute()).AddLambda( + [this, CharacterInfoWidget](const FOnAttributeChangeData& Data) + { + CurrentMp = Data.NewValue; + CharacterInfoWidget->SetMp(CurrentMp, MaxMp); + }); + AbilitySystemComponent->GetGameplayAttributeValueChangeDelegate(Attributes->GetMaxInnerPowerAttribute()).AddLambda( + [this, CharacterInfoWidget](const FOnAttributeChangeData& Data) + { + MaxMp = Data.NewValue; + CharacterInfoWidget->SetMp(CurrentMp, MaxMp); + }); + CharacterInfoWidget->SetHp(CurrentHp, MaxHp); + CharacterInfoWidget->SetMp(CurrentMp, MaxMp); +} diff --git a/Source/PHY/Private/UI/Menu/Menu_Overlap.h b/Source/PHY/Private/UI/Menu/Menu_Overlap.h index 6874968..3656f9d 100644 --- a/Source/PHY/Private/UI/Menu/Menu_Overlap.h +++ b/Source/PHY/Private/UI/Menu/Menu_Overlap.h @@ -4,8 +4,11 @@ #include "CoreMinimal.h" #include "UI/GUIS_ActivatableWidget.h" +#include "UIExtension/GUIS_GameUIExtensionSubsystem.h" #include "Menu_Overlap.generated.h" +class UAbilitySystemComponent; +class UGameUIExtensionPointWidget; /** * */ @@ -13,4 +16,26 @@ UCLASS() class PHY_API UMenu_Overlap : public UGUIS_ActivatableWidget { GENERATED_BODY() + + UPROPERTY(meta=(BindWidget)) + UGameUIExtensionPointWidget* UserInfoExtensionPoint; + UPROPERTY(EditDefaultsOnly, Category="Sub Widget Class") + TSubclassOf CharacterInfoWidgetClass; + FGUIS_GameUIExtHandle CharacterInfoExtHandle; + +protected: + virtual void NativePreConstruct() override; + virtual void NativeOnActivated() override; + virtual void NativeOnInitialized() override; + +public: + void BindAscEvents(UAbilitySystemComponent* AbilitySystemComponent); + +private: + float CurrentHp = 0.f; + float MaxHp = 0.f; + float CurrentMp = 0.f; + float MaxMp = 0.f; + float CurrentXp = 0.f; + float MaxXp = 0.f; }; diff --git a/Source/PHY/Private/UI/PHYGameUILayout.cpp b/Source/PHY/Private/UI/PHYGameUILayout.cpp deleted file mode 100644 index 687fba6..0000000 --- a/Source/PHY/Private/UI/PHYGameUILayout.cpp +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright 2026 PHY. All Rights Reserved. - -#include "UI/PHYGameUILayout.h" - -#include "GameplayTags/UITags.h" -#include "Widgets/CommonActivatableWidgetContainer.h" - -#include UE_INLINE_GENERATED_CPP_BY_NAME(PHYGameUILayout) - -UPHYGameUILayout::UPHYGameUILayout(const FObjectInitializer& ObjectInitializer) - : Super(ObjectInitializer) -{ -} - -void UPHYGameUILayout::NativeOnInitialized() -{ - Super::NativeOnInitialized(); - - // Register available layers. Widget Blueprint can bind any subset. - if (Layer_Game) - { - RegisterLayer(UITags::Tag__UI_Layer_Game, Layer_Game); - } - if (Layer_GameMenu) - { - RegisterLayer(UITags::Tag__UI_Layer_GameMenu, Layer_GameMenu); - } - if (Layer_Menu) - { - RegisterLayer(UITags::Tag__UI_Layer_Menu, Layer_Menu); - } - if (Layer_Modal) - { - RegisterLayer(UITags::Tag__UI_Layer_Modal, Layer_Modal); - } -} - diff --git a/Source/PHY/Private/UI/PHYGameUILayout.h b/Source/PHY/Private/UI/PHYGameUILayout.h deleted file mode 100644 index ef14836..0000000 --- a/Source/PHY/Private/UI/PHYGameUILayout.h +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright 2026 PHY. All Rights Reserved. - -#pragma once - -#include "CoreMinimal.h" -#include "UI/GUIS_GameUILayout.h" -#include "PHYGameUILayout.generated.h" - -class UCommonActivatableWidgetStack; -class UCommonActivatableWidgetContainerBase; - -/** - * Root UI Layout for a single local player. - * - * In BP (Widget Blueprint derived from this), you should: - * - Create several UCommonActivatableWidgetStack (or other ContainerBase) widgets - * - Bind them to the properties below - * - In PreConstruct/Construct, call RegisterLayer for each stack with tags from UITags - */ -UCLASS(Abstract, BlueprintType) -class PHY_API UPHYGameUILayout : public UGUIS_GameUILayout -{ - GENERATED_BODY() - -public: - UPHYGameUILayout(const FObjectInitializer& ObjectInitializer); - -protected: - virtual void NativeOnInitialized() override; - - /** Game HUD layer stack. Tag: UI.Layer.Game */ - UPROPERTY(meta=(BindWidgetOptional), BlueprintReadOnly) - TObjectPtr Layer_Game = nullptr; - - /** In-game menu layer stack. Tag: UI.Layer.GameMenu */ - UPROPERTY(meta=(BindWidgetOptional), BlueprintReadOnly) - TObjectPtr Layer_GameMenu = nullptr; - - /** Main menu layer stack. Tag: UI.Layer.Menu */ - UPROPERTY(meta=(BindWidgetOptional), BlueprintReadOnly) - TObjectPtr Layer_Menu = nullptr; - - /** Modal layer stack. Tag: UI.Layer.Modal */ - UPROPERTY(meta=(BindWidgetOptional), BlueprintReadOnly) - TObjectPtr Layer_Modal = nullptr; -}; - diff --git a/Source/PHY/Private/UI/PHYGameUIPolicy.h b/Source/PHY/Private/UI/PHYGameUIPolicy.h deleted file mode 100644 index fca4a7d..0000000 --- a/Source/PHY/Private/UI/PHYGameUIPolicy.h +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright 2026 PHY. All Rights Reserved. - -#pragma once - -#include "CoreMinimal.h" -#include "UI/GUIS_GameUIPolicy.h" -#include "PHYGameUIPolicy.generated.h" - -/** - * Game-specific UI policy. - * - * This class mainly exists so we can set up a default LayoutClass in a Blueprint child, - * and keep all UI wiring inside the game module. - */ -UCLASS(BlueprintType) -class PHY_API UPHYGameUIPolicy : public UGUIS_GameUIPolicy -{ - GENERATED_BODY() -}; - diff --git a/Source/PHY/Private/UI/Widget/Widget_CharacterInfo.cpp b/Source/PHY/Private/UI/Widget/Widget_CharacterInfo.cpp new file mode 100644 index 0000000..7533df2 --- /dev/null +++ b/Source/PHY/Private/UI/Widget/Widget_CharacterInfo.cpp @@ -0,0 +1,63 @@ +// + + +#include "Widget_CharacterInfo.h" + +#include "Components/TextBlock.h" +#include "UI/Synty/Synty_ProgressBar.h" + +void UWidget_CharacterInfo::SetXp(const float InCurrentXp, const float InMaxXp) const +{ + if (InMaxXp == 0) + { + return; + } + if (XP) + { + XP->SetTargetFillAmount(InCurrentXp / InMaxXp); + } + if (XPText) + { + XPText->SetText(FText::FromString(FString::Printf(TEXT("%d / %d"), FMath::RoundToInt(InCurrentXp), FMath::RoundToInt(InMaxXp)))); + } +} + +void UWidget_CharacterInfo::SetHp(const float InCurrentHp, const float InMaxHp) const +{ + if (InMaxHp == 0) + { + return; + } + if (HP) + { + HP->SetTargetFillAmount(InCurrentHp / InMaxHp); + } + if (HPText) + { + HPText->SetText(FText::FromString(FString::Printf(TEXT("%d / %d"), FMath::RoundToInt(InCurrentHp), FMath::RoundToInt(InMaxHp)))); + } +} + +void UWidget_CharacterInfo::SetMp(const float InCurrentMp, const float InMaxMp) const +{ + if (InMaxMp == 0) + { + return; + } + if (MP) + { + MP->SetTargetFillAmount(InCurrentMp / InMaxMp); + } + if (MPText) + { + MPText->SetText(FText::FromString(FString::Printf(TEXT("%d / %d"), FMath::RoundToInt(InCurrentMp), FMath::RoundToInt(InMaxMp)))); + } +} + +void UWidget_CharacterInfo::SetLevel(const int32 InLevel) const +{ + if (LevelText) + { + LevelText->SetText(FText::FromString(FString::Printf(TEXT("%d"), InLevel))); + } +} diff --git a/Source/PHY/Private/UI/Widget/Widget_CharacterInfo.h b/Source/PHY/Private/UI/Widget/Widget_CharacterInfo.h new file mode 100644 index 0000000..3252745 --- /dev/null +++ b/Source/PHY/Private/UI/Widget/Widget_CharacterInfo.h @@ -0,0 +1,42 @@ +// + +#pragma once + +#include "CoreMinimal.h" +#include "Blueprint/UserWidget.h" +#include "Widget_CharacterInfo.generated.h" + +/** + * + */ +UCLASS() +class PHY_API UWidget_CharacterInfo : public UUserWidget +{ + GENERATED_BODY() + + // xp + UPROPERTY(meta=(BindWidget)) + class USynty_ProgressBar* XP; + // Hp + UPROPERTY(meta=(BindWidget)) + class USynty_ProgressBar* HP; + // Mp + UPROPERTY(meta=(BindWidget)) + class USynty_ProgressBar* MP; + + UPROPERTY(meta=(BindWidget)) + class UTextBlock* LevelText; + + UPROPERTY(meta=(BindWidget)) + class UTextBlock* XPText; + UPROPERTY(meta=(BindWidget)) + class UTextBlock* HPText; + UPROPERTY(meta=(BindWidget)) + class UTextBlock* MPText; + +public: + void SetXp(float InCurrentXp, float InMaxXp) const; + void SetHp(float InCurrentHp, float InMaxHp) const; + void SetMp(float InCurrentMp, float InMaxMp) const; + void SetLevel(int32 InLevel) const; +};