diff --git a/Config/DefaultPHYCharacter.ini b/Config/DefaultPHYCharacter.ini new file mode 100644 index 0000000..26a729e --- /dev/null +++ b/Config/DefaultPHYCharacter.ini @@ -0,0 +1,7 @@ +[/Script/PHY.PHYCharacterSettings] +bEnableGenericGameInteraction=True +bEnableContextEffects=True +DefaultMaxWalkSpeed=500.000000 +DefaultSprintSpeed=650.000000 +DefaultMaxAcceleration=2048.000000 +DefaultInteractionOption=0 diff --git a/PHY.uproject b/PHY.uproject index cf5edc7..6e1609d 100644 --- a/PHY.uproject +++ b/PHY.uproject @@ -18,6 +18,14 @@ "Editor" ] }, + { + "Name": "GameplayAbilities", + "Enabled": true + }, + { + "Name": "ModularGameplay", + "Enabled": true + }, { "Name": "GenericCombatSystem", "Enabled": true, @@ -47,4 +55,4 @@ ] } ] -} \ No newline at end of file +} diff --git a/Source/PHY/PHY.Build.cs b/Source/PHY/PHY.Build.cs index 9e00e9e..38aded8 100644 --- a/Source/PHY/PHY.Build.cs +++ b/Source/PHY/PHY.Build.cs @@ -8,7 +8,25 @@ public class PHY : ModuleRules { PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs; - PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore", "EnhancedInput", "GameplayTags" }); + PublicDependencyModuleNames.AddRange(new string[] + { + "Core", + "CoreUObject", + "Engine", + "InputCore", + "EnhancedInput", + "GameplayTags", + "GameplayAbilities", + "GameplayTasks", + "ModularGameplay", + "GenericGameplayAbilities", + "GenericCombatSystem", + "GenericInputSystem", + "GenericGameSystem", + "GenericEffectsSystem", + "AuroraDevs_UGC", + "AIModule" + }); PrivateDependencyModuleNames.AddRange(new string[] { }); diff --git a/Source/PHY/Private/AI/PHYAIController.cpp b/Source/PHY/Private/AI/PHYAIController.cpp new file mode 100644 index 0000000..0e7662a --- /dev/null +++ b/Source/PHY/Private/AI/PHYAIController.cpp @@ -0,0 +1,41 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "AI/PHYAIController.h" + +#include UE_INLINE_GENERATED_CPP_BY_NAME(PHYAIController) + +#include "AbilitySystemComponent.h" +#include "Characters/PHYAICharacter.h" + +APHYAICharacter* APHYAIController::GetPHYAICharacter() const +{ + return Cast(GetPawn()); +} + +bool APHYAIController::RequestInteraction(const int32 OptionIndex) +{ + APHYAICharacter* PHYCharacter = GetPHYAICharacter(); + return PHYCharacter ? PHYCharacter->RequestInteraction(OptionIndex) : false; +} + +void APHYAIController::SetCombatTarget(AActor* TargetActor) +{ + if (APHYAICharacter* PHYCharacter = GetPHYAICharacter()) + { + PHYCharacter->SetCombatTargetActor(TargetActor); + } +} + +bool APHYAIController::ActivateAbilityByTag(const FGameplayTag AbilityTag) +{ + APHYAICharacter* PHYCharacter = GetPHYAICharacter(); + UAbilitySystemComponent* AbilitySystemComponent = PHYCharacter ? PHYCharacter->GetAbilitySystemComponent() : nullptr; + if (!AbilitySystemComponent || !AbilityTag.IsValid()) + { + return false; + } + + FGameplayTagContainer AbilityTags; + AbilityTags.AddTag(AbilityTag); + return AbilitySystemComponent->TryActivateAbilitiesByTag(AbilityTags); +} diff --git a/Source/PHY/Private/Camera/PHYUGCSpringArmComponent.cpp b/Source/PHY/Private/Camera/PHYUGCSpringArmComponent.cpp new file mode 100644 index 0000000..dda7db2 --- /dev/null +++ b/Source/PHY/Private/Camera/PHYUGCSpringArmComponent.cpp @@ -0,0 +1,7 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "Camera/PHYUGCSpringArmComponent.h" + +#include UE_INLINE_GENERATED_CPP_BY_NAME(PHYUGCSpringArmComponent) + +// 当前包装层只解除抽象限制,后续相机专家可在这里集中扩展 UGC 项目逻辑。 diff --git a/Source/PHY/Private/Characters/PHYAICharacter.cpp b/Source/PHY/Private/Characters/PHYAICharacter.cpp new file mode 100644 index 0000000..f2601d0 --- /dev/null +++ b/Source/PHY/Private/Characters/PHYAICharacter.cpp @@ -0,0 +1,37 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "Characters/PHYAICharacter.h" + +#include UE_INLINE_GENERATED_CPP_BY_NAME(PHYAICharacter) + +#include "GGA_AbilitySystemComponent.h" + +APHYAICharacter::APHYAICharacter(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + AbilitySystemComponent = CreateDefaultSubobject(TEXT("AbilitySystemComponent")); + AbilitySystemComponent->SetIsReplicated(true); + AbilitySystemComponent->SetReplicationMode(EGameplayEffectReplicationMode::Mixed); +} + +void APHYAICharacter::BeginPlay() +{ + Super::BeginPlay(); + + InitializeAIAbilitySystem(); +} + +void APHYAICharacter::PossessedBy(AController* NewController) +{ + Super::PossessedBy(NewController); + + InitializeAIAbilitySystem(); +} + +void APHYAICharacter::InitializeAIAbilitySystem() +{ + if (AbilitySystemComponent) + { + InitializeAbilitySystem(AbilitySystemComponent, this); + } +} diff --git a/Source/PHY/Private/Characters/PHYCharacterBase.cpp b/Source/PHY/Private/Characters/PHYCharacterBase.cpp new file mode 100644 index 0000000..a1aedd9 --- /dev/null +++ b/Source/PHY/Private/Characters/PHYCharacterBase.cpp @@ -0,0 +1,580 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "Characters/PHYCharacterBase.h" + +#include UE_INLINE_GENERATED_CPP_BY_NAME(PHYCharacterBase) + +#include "AbilitySystemComponent.h" +#include "Camera/CameraComponent.h" +#include "Characters/PHYCharacterSettings.h" +#include "Characters/PHYCharacterStateComponent.h" +#include "Components/CapsuleComponent.h" +#include "Components/GameFrameworkComponentManager.h" +#include "Components/SkeletalMeshComponent.h" +#include "Feedback/GES_ContextEffectComponent.h" +#include "GCS_CombatSystemComponent.h" +#include "GGA_AbilitySystemComponent.h" +#include "GameFramework/CharacterMovementComponent.h" +#include "Interaction/GGS_InteractionSystemComponent.h" +#include "Net/UnrealNetwork.h" +#include "PHYGameplayTags.h" +#include "Ragdoll/GGS_RagdollComponent.h" +#include "Targeting/GCS_TargetingSystemComponent.h" +#include "Team/GCS_CombatTeamAgentComponent.h" + +APHYCharacterBase::APHYCharacterBase(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + bReplicates = true; + + CharacterStateComponent = CreateDefaultSubobject(TEXT("CharacterStateComponent")); + CombatSystemComponent = CreateDefaultSubobject(TEXT("CombatSystemComponent")); + TargetingSystemComponent = CreateDefaultSubobject(TEXT("TargetingSystemComponent")); + CombatTeamAgentComponent = CreateDefaultSubobject(TEXT("CombatTeamAgentComponent")); + InteractionSystemComponent = CreateDefaultSubobject(TEXT("InteractionSystemComponent")); + RagdollComponent = CreateDefaultSubobject(TEXT("RagdollComponent")); + ContextEffectComponent = CreateDefaultSubobject(TEXT("ContextEffectComponent")); +} + +void APHYCharacterBase::PreInitializeComponents() +{ + Super::PreInitializeComponents(); + + UGameFrameworkComponentManager::AddGameFrameworkComponentReceiver(this); +} + +void APHYCharacterBase::BeginPlay() +{ + Super::BeginPlay(); + + ApplyCharacterSettings(); + + if (RagdollComponent) + { + RagdollComponent->SetMeshComponent(GetMesh()); + } + + if (ContextEffectComponent) + { + ContextEffectComponent->SetGameplayTagsProvider(this); + } + + UGameFrameworkComponentManager::SendGameFrameworkComponentExtensionEvent(this, UGameFrameworkComponentManager::NAME_GameActorReady); +} + +void APHYCharacterBase::EndPlay(const EEndPlayReason::Type EndPlayReason) +{ + UGameFrameworkComponentManager::RemoveGameFrameworkComponentReceiver(this); + + Super::EndPlay(EndPlayReason); +} + +void APHYCharacterBase::PossessedBy(AController* NewController) +{ + Super::PossessedBy(NewController); + + if (CharacterStateComponent) + { + CharacterStateComponent->SetMovementIntent(FVector::ZeroVector); + } +} + +void APHYCharacterBase::OnRep_Controller() +{ + Super::OnRep_Controller(); + + if (CharacterStateComponent) + { + CharacterStateComponent->SetMovementIntent(FVector::ZeroVector); + } +} + +UAbilitySystemComponent* APHYCharacterBase::GetAbilitySystemComponent() const +{ + return CachedAbilitySystemComponent; +} + +void APHYCharacterBase::GetOwnedGameplayTags(FGameplayTagContainer& TagContainer) const +{ + if (CachedAbilitySystemComponent) + { + CachedAbilitySystemComponent->GetOwnedGameplayTags(TagContainer); + } + + if (CharacterStateComponent) + { + CharacterStateComponent->GetOwnedStateTags(TagContainer); + } +} + +void APHYCharacterBase::InitializeAbilitySystem(UAbilitySystemComponent* NewAbilitySystemComponent, AActor* OwnerActor) +{ + if (!NewAbilitySystemComponent || !OwnerActor) + { + return; + } + + if (CachedAbilitySystemComponent == NewAbilitySystemComponent && bAbilitySystemReady) + { + return; + } + + CachedAbilitySystemComponent = NewAbilitySystemComponent; + + if (UGGA_AbilitySystemComponent* GGAAbilitySystemComponent = Cast(NewAbilitySystemComponent)) + { + GGAAbilitySystemComponent->InitializeAbilitySystem(OwnerActor, this); + } + else + { + NewAbilitySystemComponent->InitAbilityActorInfo(OwnerActor, this); + } + + bAbilitySystemReady = true; +} + +void APHYCharacterBase::ClearAbilitySystem() +{ + if (UGGA_AbilitySystemComponent* GGAAbilitySystemComponent = Cast(CachedAbilitySystemComponent)) + { + GGAAbilitySystemComponent->UninitializeAbilitySystem(); + } + + CachedAbilitySystemComponent = nullptr; + bAbilitySystemReady = false; +} + +bool APHYCharacterBase::HandleInputTag(const FGameplayTag InputTag, const ETriggerEvent TriggerEvent) +{ + if (!InputTag.IsValid() || IGCS_CombatEntityInterface::Execute_IsDead(this)) + { + return false; + } + + const bool bPressed = TriggerEvent == ETriggerEvent::Started || TriggerEvent == ETriggerEvent::Triggered; + const bool bReleased = TriggerEvent == ETriggerEvent::Completed || TriggerEvent == ETriggerEvent::Canceled; + + if (InputTag == PHYGameplayTags::Input_Jump) + { + if (bPressed) + { + Jump(); + } + else if (bReleased) + { + StopJumping(); + } + + return true; + } + + if (InputTag == PHYGameplayTags::Input_Sprint) + { + if (CharacterStateComponent) + { + CharacterStateComponent->SetMovementState(bPressed ? PHYGameplayTags::State_Movement_Sprint : PHYGameplayTags::State_Movement_Run); + } + + if (UCharacterMovementComponent* MoveComponent = GetCharacterMovement()) + { + const UPHYCharacterSettings* Settings = GetDefault(); + MoveComponent->MaxWalkSpeed = bPressed ? Settings->DefaultSprintSpeed : Settings->DefaultMaxWalkSpeed; + } + + return true; + } + + if (InputTag == PHYGameplayTags::Input_Aim) + { + if (CharacterStateComponent) + { + CharacterStateComponent->SetMovementSet(bPressed ? PHYGameplayTags::State_MovementSet_Aiming : PHYGameplayTags::State_MovementSet_Default); + CharacterStateComponent->SetRotationMode(bPressed ? PHYGameplayTags::State_Rotation_Strafe : PHYGameplayTags::State_Rotation_OrientToMovement); + } + + return true; + } + + if (InputTag == PHYGameplayTags::Input_LockOn && bPressed) + { + if (TargetingSystemComponent) + { + if (TargetingSystemComponent->GetTargetedActor()) + { + SetCombatTargetActor(nullptr); + } + else + { + TargetingSystemComponent->SearchForActorToTarget(); + SetCombatTargetActor(TargetingSystemComponent->GetTargetedActor()); + } + } + + return true; + } + + if (InputTag == PHYGameplayTags::Input_Interact && bPressed) + { + const UPHYCharacterSettings* Settings = GetDefault(); + return RequestInteraction(Settings->DefaultInteractionOption); + } + + const FGameplayTag AbilityTag = GetAbilityTagForInputTag(InputTag); + if (AbilityTag.IsValid() && bPressed && CachedAbilitySystemComponent) + { + FGameplayTagContainer AbilityTags; + AbilityTags.AddTag(AbilityTag); + return CachedAbilitySystemComponent->TryActivateAbilitiesByTag(AbilityTags); + } + + return false; +} + +void APHYCharacterBase::SetMovementIntent(const FVector NewMovementIntent) +{ + const FVector ClampedIntent = NewMovementIntent.GetClampedToMaxSize(1.0f); + + if (CharacterStateComponent) + { + CharacterStateComponent->SetMovementIntent(ClampedIntent); + } + + if (!HasAuthority()) + { + ServerSetMovementIntent(ClampedIntent); + } +} + +void APHYCharacterBase::SetCombatTargetActor(AActor* NewCombatTargetActor) +{ + if (CharacterStateComponent) + { + CharacterStateComponent->SetCombatTargetActor(NewCombatTargetActor); + } + + if (TargetingSystemComponent) + { + TargetingSystemComponent->SetTargetedActor(NewCombatTargetActor); + } + + if (!HasAuthority()) + { + ServerSetCombatTargetActor(NewCombatTargetActor); + } +} + +bool APHYCharacterBase::RequestInteraction(const int32 OptionIndex) +{ + const UPHYCharacterSettings* Settings = GetDefault(); + if (!Settings->bEnableGenericGameInteraction || !InteractionSystemComponent) + { + return false; + } + + if (!HasAuthority()) + { + ServerRequestInteraction(OptionIndex); + return true; + } + + InteractionSystemComponent->SearchInteractableActors(); + InteractionSystemComponent->StartInteraction(OptionIndex); + return true; +} + +bool APHYCharacterBase::RequestInstantInteraction(const int32 OptionIndex) +{ + const UPHYCharacterSettings* Settings = GetDefault(); + if (!Settings->bEnableGenericGameInteraction || !InteractionSystemComponent) + { + return false; + } + + if (!HasAuthority()) + { + ServerRequestInstantInteraction(OptionIndex); + return true; + } + + InteractionSystemComponent->SearchInteractableActors(); + InteractionSystemComponent->InstantInteraction(OptionIndex); + return true; +} + +void APHYCharacterBase::EndInteraction() +{ + if (!InteractionSystemComponent) + { + return; + } + + if (!HasAuthority()) + { + ServerEndInteraction(); + return; + } + + InteractionSystemComponent->EndInteraction(); +} + +void APHYCharacterBase::CycleInteractionTarget(const bool bNext) +{ + if (!InteractionSystemComponent) + { + return; + } + + if (!HasAuthority()) + { + ServerCycleInteractionTarget(bNext); + return; + } + + InteractionSystemComponent->CycleInteractableActors(bNext); +} + +AActor* APHYCharacterBase::GetCombatTargetActor_Implementation() const +{ + if (CharacterStateComponent && CharacterStateComponent->GetCombatTargetActor()) + { + return CharacterStateComponent->GetCombatTargetActor(); + } + + return TargetingSystemComponent ? TargetingSystemComponent->GetTargetedActor() : nullptr; +} + +const UDataTable* APHYCharacterBase::GetComboDefinitionTable_Implementation() const +{ + return nullptr; +} + +USceneComponent* APHYCharacterBase::GetCombatTargetObject_Implementation() const +{ + const AActor* TargetActor = IGCS_CombatEntityInterface::Execute_GetCombatTargetActor(this); + return TargetActor ? TargetActor->GetRootComponent() : nullptr; +} + +bool APHYCharacterBase::QueryAbilityActions_Implementation(FGameplayTagContainer AbilityTags, FGameplayTagContainer SourceTags, FGameplayTagContainer TargetTags, TArray& AbilityActions) +{ + (void)AbilityTags; + (void)SourceTags; + (void)TargetTags; + AbilityActions.Reset(); + return false; +} + +bool APHYCharacterBase::QueryAbilityActionsByContext_Implementation(UObject* Context, FGameplayTagContainer AbilityTags, FGameplayTagContainer SourceTags, FGameplayTagContainer TargetTags, TArray& AbilityActions) +{ + (void)Context; + (void)AbilityTags; + (void)SourceTags; + (void)TargetTags; + AbilityActions.Reset(); + return false; +} + +UObject* APHYCharacterBase::QueryWeapon_Implementation(const FGameplayTagQuery& Query) const +{ + UObject* Weapon = CharacterStateComponent ? CharacterStateComponent->GetCurrentWeapon() : nullptr; + if (!Weapon || Query.IsEmpty()) + { + return Weapon; + } + + if (const IGameplayTagAssetInterface* WeaponTagsProvider = Cast(Weapon)) + { + FGameplayTagContainer WeaponTags; + WeaponTagsProvider->GetOwnedGameplayTags(WeaponTags); + return Query.Matches(WeaponTags) ? Weapon : nullptr; + } + + return nullptr; +} + +void APHYCharacterBase::SetRotationMode_Implementation(const FGameplayTag NewRotationMode) +{ + if (CharacterStateComponent) + { + CharacterStateComponent->SetRotationMode(NewRotationMode); + } +} + +FGameplayTag APHYCharacterBase::GetRotationMode_Implementation() const +{ + return CharacterStateComponent ? CharacterStateComponent->GetRotationMode() : FGameplayTag(); +} + +void APHYCharacterBase::SetMovementSet_Implementation(const FGameplayTag NewMovementSet) +{ + if (CharacterStateComponent) + { + CharacterStateComponent->SetMovementSet(NewMovementSet); + } +} + +FGameplayTag APHYCharacterBase::GetMovementSet_Implementation() const +{ + return CharacterStateComponent ? CharacterStateComponent->GetMovementSet() : FGameplayTag(); +} + +void APHYCharacterBase::SetMovementState_Implementation(const FGameplayTag NewMovementState) +{ + if (CharacterStateComponent) + { + CharacterStateComponent->SetMovementState(NewMovementState); + } +} + +FGameplayTag APHYCharacterBase::GetMovementState_Implementation() const +{ + return CharacterStateComponent ? CharacterStateComponent->GetMovementState() : FGameplayTag(); +} + +void APHYCharacterBase::StartDeath_Implementation() +{ + if (CharacterStateComponent && CharacterStateComponent->IsDead()) + { + return; + } + + if (CharacterStateComponent) + { + CharacterStateComponent->SetDead(true); + } + + if (CachedAbilitySystemComponent) + { + CachedAbilitySystemComponent->CancelAllAbilities(); + } + + if (UCharacterMovementComponent* MoveComponent = GetCharacterMovement()) + { + MoveComponent->DisableMovement(); + } + + if (UCapsuleComponent* Capsule = GetCapsuleComponent()) + { + Capsule->SetCollisionEnabled(ECollisionEnabled::NoCollision); + } +} + +void APHYCharacterBase::FinishDeath_Implementation() +{ + if (RagdollComponent && !RagdollComponent->IsRagdolling()) + { + RagdollComponent->StartRagdoll(); + } +} + +bool APHYCharacterBase::IsDead_Implementation() const +{ + return CharacterStateComponent && CharacterStateComponent->IsDead(); +} + +FVector APHYCharacterBase::GetMovementIntent_Implementation() const +{ + return CharacterStateComponent ? CharacterStateComponent->GetMovementIntent() : FVector::ZeroVector; +} + +UObject* APHYCharacterBase::GetCurrentWeapon_Implementation(UObject* Context) const +{ + (void)Context; + return CharacterStateComponent ? CharacterStateComponent->GetCurrentWeapon() : nullptr; +} + +void APHYCharacterBase::SetCurrentWeapon_Implementation(UObject* Weapon) +{ + if (CharacterStateComponent) + { + CharacterStateComponent->SetCurrentWeapon(Weapon); + } +} + +bool APHYCharacterBase::GetRelativeTransformToSocket_Implementation(const USkeletalMeshComponent* InSkeletalMeshComponent, const UStaticMesh* StaticMesh, const USkeletalMesh* SkeletalMesh, const FName SocketName, FTransform& OutTransform) const +{ + (void)StaticMesh; + (void)SkeletalMesh; + + if (!InSkeletalMeshComponent || SocketName.IsNone() || !InSkeletalMeshComponent->DoesSocketExist(SocketName)) + { + OutTransform = FTransform::Identity; + return false; + } + + OutTransform = InSkeletalMeshComponent->GetSocketTransform(SocketName, RTS_Component); + return true; +} + +FGameplayTag APHYCharacterBase::GetAbilityTagForInputTag(const FGameplayTag InputTag) const +{ + if (InputTag == PHYGameplayTags::Input_Attack_Primary) + { + return PHYGameplayTags::Ability_Attack_Primary; + } + + if (InputTag == PHYGameplayTags::Input_Attack_Secondary) + { + return PHYGameplayTags::Ability_Attack_Secondary; + } + + if (InputTag == PHYGameplayTags::Input_Jump) + { + return PHYGameplayTags::Ability_Jump; + } + + if (InputTag == PHYGameplayTags::Input_Interact) + { + return PHYGameplayTags::Ability_Interact; + } + + return FGameplayTag(); +} + +void APHYCharacterBase::ApplyCharacterSettings() +{ + const UPHYCharacterSettings* Settings = GetDefault(); + + if (UCharacterMovementComponent* MoveComponent = GetCharacterMovement()) + { + MoveComponent->MaxWalkSpeed = Settings->DefaultMaxWalkSpeed; + MoveComponent->MaxAcceleration = Settings->DefaultMaxAcceleration; + } + + if (CharacterStateComponent && GetCharacterMovement() && GetCharacterMovement()->IsFalling()) + { + CharacterStateComponent->SetMovementState(PHYGameplayTags::State_Movement_Falling); + } +} + +void APHYCharacterBase::ServerSetMovementIntent_Implementation(const FVector NewMovementIntent) +{ + if (CharacterStateComponent) + { + CharacterStateComponent->SetMovementIntent(NewMovementIntent); + } +} + +void APHYCharacterBase::ServerSetCombatTargetActor_Implementation(AActor* NewCombatTargetActor) +{ + SetCombatTargetActor(NewCombatTargetActor); +} + +void APHYCharacterBase::ServerRequestInteraction_Implementation(const int32 OptionIndex) +{ + RequestInteraction(OptionIndex); +} + +void APHYCharacterBase::ServerRequestInstantInteraction_Implementation(const int32 OptionIndex) +{ + RequestInstantInteraction(OptionIndex); +} + +void APHYCharacterBase::ServerEndInteraction_Implementation() +{ + EndInteraction(); +} + +void APHYCharacterBase::ServerCycleInteractionTarget_Implementation(const bool bNext) +{ + CycleInteractionTarget(bNext); +} diff --git a/Source/PHY/Private/Characters/PHYCharacterSettings.cpp b/Source/PHY/Private/Characters/PHYCharacterSettings.cpp new file mode 100644 index 0000000..8e1dd41 --- /dev/null +++ b/Source/PHY/Private/Characters/PHYCharacterSettings.cpp @@ -0,0 +1,7 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "Characters/PHYCharacterSettings.h" + +#include UE_INLINE_GENERATED_CPP_BY_NAME(PHYCharacterSettings) + +// 角色配置只承载默认值,实际初始化由角色基类按需读取。 diff --git a/Source/PHY/Private/Characters/PHYCharacterStateComponent.cpp b/Source/PHY/Private/Characters/PHYCharacterStateComponent.cpp new file mode 100644 index 0000000..11e8669 --- /dev/null +++ b/Source/PHY/Private/Characters/PHYCharacterStateComponent.cpp @@ -0,0 +1,89 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "Characters/PHYCharacterStateComponent.h" + +#include UE_INLINE_GENERATED_CPP_BY_NAME(PHYCharacterStateComponent) + +#include "Net/UnrealNetwork.h" +#include "PHYGameplayTags.h" + +UPHYCharacterStateComponent::UPHYCharacterStateComponent(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + SetIsReplicatedByDefault(true); + + MovementState = PHYGameplayTags::State_Movement_Walk; + MovementSet = PHYGameplayTags::State_MovementSet_Default; + RotationMode = PHYGameplayTags::State_Rotation_OrientToMovement; +} + +void UPHYCharacterStateComponent::GetLifetimeReplicatedProps(TArray& OutLifetimeProps) const +{ + Super::GetLifetimeReplicatedProps(OutLifetimeProps); + + DOREPLIFETIME(ThisClass, bDead); + DOREPLIFETIME(ThisClass, CombatTargetActor); + DOREPLIFETIME(ThisClass, CurrentWeapon); + DOREPLIFETIME(ThisClass, MovementIntent); + DOREPLIFETIME(ThisClass, MovementState); + DOREPLIFETIME(ThisClass, MovementSet); + DOREPLIFETIME(ThisClass, RotationMode); +} + +void UPHYCharacterStateComponent::SetDead(const bool bNewDead) +{ + bDead = bNewDead; +} + +void UPHYCharacterStateComponent::SetCombatTargetActor(AActor* NewCombatTargetActor) +{ + CombatTargetActor = NewCombatTargetActor; +} + +void UPHYCharacterStateComponent::SetCurrentWeapon(UObject* NewCurrentWeapon) +{ + CurrentWeapon = NewCurrentWeapon; +} + +void UPHYCharacterStateComponent::SetMovementIntent(const FVector NewMovementIntent) +{ + MovementIntent = NewMovementIntent.GetClampedToMaxSize(1.0f); +} + +void UPHYCharacterStateComponent::SetMovementState(const FGameplayTag NewMovementState) +{ + MovementState = NewMovementState; +} + +void UPHYCharacterStateComponent::SetMovementSet(const FGameplayTag NewMovementSet) +{ + MovementSet = NewMovementSet; +} + +void UPHYCharacterStateComponent::SetRotationMode(const FGameplayTag NewRotationMode) +{ + RotationMode = NewRotationMode; +} + +void UPHYCharacterStateComponent::GetOwnedStateTags(FGameplayTagContainer& OutTags) const +{ + if (bDead) + { + OutTags.AddTag(PHYGameplayTags::State_Dead); + } + + if (MovementState.IsValid()) + { + OutTags.AddTag(MovementState); + } + + if (MovementSet.IsValid()) + { + OutTags.AddTag(MovementSet); + } + + if (RotationMode.IsValid()) + { + OutTags.AddTag(RotationMode); + } +} diff --git a/Source/PHY/Private/Characters/PHYPlayerCharacter.cpp b/Source/PHY/Private/Characters/PHYPlayerCharacter.cpp new file mode 100644 index 0000000..e8e1cc2 --- /dev/null +++ b/Source/PHY/Private/Characters/PHYPlayerCharacter.cpp @@ -0,0 +1,51 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "Characters/PHYPlayerCharacter.h" + +#include UE_INLINE_GENERATED_CPP_BY_NAME(PHYPlayerCharacter) + +#include "Camera/CameraComponent.h" +#include "Camera/PHYUGCSpringArmComponent.h" +#include "GameFramework/SpringArmComponent.h" +#include "Player/PHYPlayerState.h" + +APHYPlayerCharacter::APHYPlayerCharacter(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + CameraBoom = CreateDefaultSubobject(TEXT("CameraBoom")); + CameraBoom->SetupAttachment(GetRootComponent()); + CameraBoom->TargetArmLength = 320.0f; + CameraBoom->bUsePawnControlRotation = true; + + FollowCamera = CreateDefaultSubobject(TEXT("FollowCamera")); + FollowCamera->SetupAttachment(CameraBoom, USpringArmComponent::SocketName); + FollowCamera->bUsePawnControlRotation = false; +} + +void APHYPlayerCharacter::PossessedBy(AController* NewController) +{ + Super::PossessedBy(NewController); + + InitializeAbilitySystemFromPlayerState(); +} + +void APHYPlayerCharacter::OnRep_PlayerState() +{ + Super::OnRep_PlayerState(); + + InitializeAbilitySystemFromPlayerState(); +} + +void APHYPlayerCharacter::InitializeAbilitySystemFromPlayerState() +{ + APHYPlayerState* PHYPlayerState = GetPlayerState(); + if (!PHYPlayerState) + { + return; + } + + if (UAbilitySystemComponent* PlayerASC = PHYPlayerState->GetAbilitySystemComponent()) + { + InitializeAbilitySystem(PlayerASC, PHYPlayerState); + } +} diff --git a/Source/PHY/Private/GameplayTags/PHYGameplayTags_State.cpp b/Source/PHY/Private/GameplayTags/PHYGameplayTags_State.cpp index f5cf014..045f843 100644 --- a/Source/PHY/Private/GameplayTags/PHYGameplayTags_State.cpp +++ b/Source/PHY/Private/GameplayTags/PHYGameplayTags_State.cpp @@ -10,4 +10,13 @@ namespace PHYGameplayTags UE_DEFINE_GAMEPLAY_TAG(State_LockedOn, "State.LockedOn"); UE_DEFINE_GAMEPLAY_TAG(State_Interacting, "State.Interacting"); UE_DEFINE_GAMEPLAY_TAG(State_UI_Open, "State.UI.Open"); + UE_DEFINE_GAMEPLAY_TAG(State_Dead, "State.Dead"); + UE_DEFINE_GAMEPLAY_TAG(State_Movement_Walk, "State.Movement.Walk"); + UE_DEFINE_GAMEPLAY_TAG(State_Movement_Run, "State.Movement.Run"); + UE_DEFINE_GAMEPLAY_TAG(State_Movement_Sprint, "State.Movement.Sprint"); + UE_DEFINE_GAMEPLAY_TAG(State_Movement_Falling, "State.Movement.Falling"); + UE_DEFINE_GAMEPLAY_TAG(State_MovementSet_Default, "State.MovementSet.Default"); + UE_DEFINE_GAMEPLAY_TAG(State_MovementSet_Aiming, "State.MovementSet.Aiming"); + UE_DEFINE_GAMEPLAY_TAG(State_Rotation_OrientToMovement, "State.Rotation.OrientToMovement"); + UE_DEFINE_GAMEPLAY_TAG(State_Rotation_Strafe, "State.Rotation.Strafe"); } diff --git a/Source/PHY/Private/Player/PHYPlayerCameraManager.cpp b/Source/PHY/Private/Player/PHYPlayerCameraManager.cpp new file mode 100644 index 0000000..d300ca3 --- /dev/null +++ b/Source/PHY/Private/Player/PHYPlayerCameraManager.cpp @@ -0,0 +1,19 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "Player/PHYPlayerCameraManager.h" + +#include UE_INLINE_GENERATED_CPP_BY_NAME(PHYPlayerCameraManager) + +#include "Player/PHYPlayerController.h" + +FRotator APHYPlayerCameraManager::GetRotationInput_Implementation() const +{ + const APHYPlayerController* PHYController = Cast(PCOwner); + return PHYController ? PHYController->GetCachedRotationInput() : FRotator::ZeroRotator; +} + +FVector APHYPlayerCameraManager::GetMovementControlInput_Implementation() const +{ + const APHYPlayerController* PHYController = Cast(PCOwner); + return PHYController ? PHYController->GetCachedMovementControlInput() : FVector::ZeroVector; +} diff --git a/Source/PHY/Private/Player/PHYPlayerController.cpp b/Source/PHY/Private/Player/PHYPlayerController.cpp new file mode 100644 index 0000000..ad22258 --- /dev/null +++ b/Source/PHY/Private/Player/PHYPlayerController.cpp @@ -0,0 +1,138 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "Player/PHYPlayerController.h" + +#include UE_INLINE_GENERATED_CPP_BY_NAME(PHYPlayerController) + +#include "Characters/PHYPlayerCharacter.h" +#include "GIPS_InputSystemComponent.h" +#include "InputActionValue.h" +#include "Player/PHYPlayerCameraManager.h" +#include "PHYGameplayTags.h" + +namespace +{ + // 将 EnhancedInput 的多种值类型统一成二维输入,便于移动和视角路径共用。 + FVector2D ExtractAxis2D(const FInputActionValue& Value) + { + switch (Value.GetValueType()) + { + case EInputActionValueType::Axis1D: + return FVector2D(Value.Get(), 0.0f); + case EInputActionValueType::Axis2D: + return Value.Get(); + case EInputActionValueType::Axis3D: + { + const FVector Axis3D = Value.Get(); + return FVector2D(Axis3D.X, Axis3D.Y); + } + default: + return FVector2D::ZeroVector; + } + } + + bool IsInputReleased(const ETriggerEvent TriggerEvent) + { + return TriggerEvent == ETriggerEvent::Completed || TriggerEvent == ETriggerEvent::Canceled; + } +} + +APHYPlayerController::APHYPlayerController(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + PlayerCameraManagerClass = APHYPlayerCameraManager::StaticClass(); + + InputSystemComponent = CreateDefaultSubobject(TEXT("InputSystemComponent")); +} + +void APHYPlayerController::BeginPlay() +{ + Super::BeginPlay(); + + BindInputEvents(); +} + +void APHYPlayerController::OnPossess(APawn* InPawn) +{ + Super::OnPossess(InPawn); + + BindInputEvents(); +} + +APHYPlayerCharacter* APHYPlayerController::GetPHYPlayerCharacter() const +{ + return Cast(GetPawn()); +} + +void APHYPlayerController::BindInputEvents() +{ + if (!InputSystemComponent) + { + return; + } + + InputSystemComponent->OnReceivedInput.RemoveDynamic(this, &ThisClass::OnReceivedInput); + InputSystemComponent->OnReceivedInput.AddDynamic(this, &ThisClass::OnReceivedInput); +} + +void APHYPlayerController::OnReceivedInput(const FInputActionInstance& ActionData, const FGameplayTag& InputTag, const ETriggerEvent TriggerEvent) +{ + if (InputTag == PHYGameplayTags::Input_Move) + { + HandleMoveInput(ActionData, TriggerEvent); + return; + } + + if (InputTag == PHYGameplayTags::Input_Look) + { + HandleLookInput(ActionData, TriggerEvent); + return; + } + + if (APHYPlayerCharacter* PHYCharacter = GetPHYPlayerCharacter()) + { + PHYCharacter->HandleInputTag(InputTag, TriggerEvent); + } +} + +bool APHYPlayerController::HandleMoveInput(const FInputActionInstance& ActionData, const ETriggerEvent TriggerEvent) +{ + APHYPlayerCharacter* PHYCharacter = GetPHYPlayerCharacter(); + if (!PHYCharacter) + { + CachedMovementInput = FVector2D::ZeroVector; + return false; + } + + CachedMovementInput = IsInputReleased(TriggerEvent) ? FVector2D::ZeroVector : ExtractAxis2D(ActionData.GetValue()); + + const FRotator CurrentControlRotation = GetControlRotation(); + const FRotator YawRotation(0.0f, CurrentControlRotation.Yaw, 0.0f); + const FVector ForwardDirection = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::X); + const FVector RightDirection = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::Y); + + const FVector WorldIntent = (ForwardDirection * CachedMovementInput.Y) + (RightDirection * CachedMovementInput.X); + PHYCharacter->SetMovementIntent(WorldIntent); + + if (!CachedMovementInput.IsNearlyZero()) + { + PHYCharacter->AddMovementInput(ForwardDirection, CachedMovementInput.Y); + PHYCharacter->AddMovementInput(RightDirection, CachedMovementInput.X); + } + + return true; +} + +bool APHYPlayerController::HandleLookInput(const FInputActionInstance& ActionData, const ETriggerEvent TriggerEvent) +{ + CachedLookInput = IsInputReleased(TriggerEvent) ? FVector2D::ZeroVector : ExtractAxis2D(ActionData.GetValue()); + CachedRotationInput = FRotator(CachedLookInput.Y, CachedLookInput.X, 0.0f); + + if (!CachedLookInput.IsNearlyZero()) + { + AddYawInput(CachedLookInput.X); + AddPitchInput(CachedLookInput.Y); + } + + return true; +} diff --git a/Source/PHY/Private/Player/PHYPlayerState.cpp b/Source/PHY/Private/Player/PHYPlayerState.cpp new file mode 100644 index 0000000..54c18a8 --- /dev/null +++ b/Source/PHY/Private/Player/PHYPlayerState.cpp @@ -0,0 +1,22 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "Player/PHYPlayerState.h" + +#include UE_INLINE_GENERATED_CPP_BY_NAME(PHYPlayerState) + +#include "GGA_AbilitySystemComponent.h" + +APHYPlayerState::APHYPlayerState(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + SetNetUpdateFrequency(100.0f); + + AbilitySystemComponent = CreateDefaultSubobject(TEXT("AbilitySystemComponent")); + AbilitySystemComponent->SetIsReplicated(true); + AbilitySystemComponent->SetReplicationMode(EGameplayEffectReplicationMode::Mixed); +} + +UAbilitySystemComponent* APHYPlayerState::GetAbilitySystemComponent() const +{ + return AbilitySystemComponent; +} diff --git a/Source/PHY/Public/AI/PHYAIController.h b/Source/PHY/Public/AI/PHYAIController.h new file mode 100644 index 0000000..5919afe --- /dev/null +++ b/Source/PHY/Public/AI/PHYAIController.h @@ -0,0 +1,38 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "AIController.h" +#include "GameplayTagContainer.h" +#include "PHYAIController.generated.h" + +class APHYAICharacter; + +/** + * @brief PHY AI 控制器。 + * + * AI 通过角色请求接口触发移动、目标、交互和 Ability,不模拟玩家输入。 + */ +UCLASS(BlueprintType, Blueprintable) +class PHY_API APHYAIController : public AAIController +{ + GENERATED_BODY() + +public: + /** @brief 获取当前控制的 PHY AI 角色。 */ + UFUNCTION(BlueprintCallable, BlueprintPure, Category="PHY|AI") + APHYAICharacter* GetPHYAICharacter() const; + + /** @brief 请求 AI 角色开始交互。 */ + UFUNCTION(BlueprintCallable, Category="PHY|AI") + bool RequestInteraction(int32 OptionIndex = 0); + + /** @brief 设置 AI 角色的战斗目标。 */ + UFUNCTION(BlueprintCallable, Category="PHY|AI") + void SetCombatTarget(AActor* TargetActor); + + /** @brief 通过 Ability Tag 激活 AI 角色能力。 */ + UFUNCTION(BlueprintCallable, Category="PHY|AI") + bool ActivateAbilityByTag(FGameplayTag AbilityTag); +}; diff --git a/Source/PHY/Public/Camera/PHYUGCSpringArmComponent.h b/Source/PHY/Public/Camera/PHYUGCSpringArmComponent.h new file mode 100644 index 0000000..d6cd990 --- /dev/null +++ b/Source/PHY/Public/Camera/PHYUGCSpringArmComponent.h @@ -0,0 +1,18 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Camera/Components/UGC_SpringArmComponentBase.h" +#include "PHYUGCSpringArmComponent.generated.h" + +/** + * @brief PHY 对 UGC SpringArm 的项目级可实例化包装。 + * + * UGC 基类是抽象组件,项目通过该薄包装在 C++ 构造玩家相机臂。 + */ +UCLASS(BlueprintType, Blueprintable, meta=(BlueprintSpawnableComponent)) +class PHY_API UPHYUGCSpringArmComponent : public UUGC_SpringArmComponentBase +{ + GENERATED_BODY() +}; diff --git a/Source/PHY/Public/Characters/PHYAICharacter.h b/Source/PHY/Public/Characters/PHYAICharacter.h new file mode 100644 index 0000000..46270a3 --- /dev/null +++ b/Source/PHY/Public/Characters/PHYAICharacter.h @@ -0,0 +1,42 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Characters/PHYCharacterBase.h" +#include "PHYAICharacter.generated.h" + +class UGGA_AbilitySystemComponent; + +/** + * @brief PHY AI 角色。 + * + * AI ASC 创建在 AI Character 自身,不创建玩家相机,也不绑定玩家输入。 + */ +UCLASS(BlueprintType, Blueprintable) +class PHY_API APHYAICharacter : public APHYCharacterBase +{ + GENERATED_BODY() + +public: + /** @brief 构造 AI 角色自有 ASC。 */ + APHYAICharacter(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get()); + + /** @brief 开始游戏时初始化 AI 自身 ASC。 */ + virtual void BeginPlay() override; + + /** @brief AI 被控制器接管时初始化自身 ASC。 */ + virtual void PossessedBy(AController* NewController) override; + + /** @brief 获取 AI 自有 GGA ASC。 */ + UFUNCTION(BlueprintCallable, BlueprintPure, Category="PHY|AI") + UGGA_AbilitySystemComponent* GetAIAbilitySystemComponent() const { return AbilitySystemComponent; } + +protected: + /** @brief 初始化 AI 自身作为 Owner 和 Avatar 的 ASC。 */ + virtual void InitializeAIAbilitySystem(); + + /** @brief AI 自身 ASC。 */ + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="PHY|AI") + TObjectPtr AbilitySystemComponent; +}; diff --git a/Source/PHY/Public/Characters/PHYCharacterBase.h b/Source/PHY/Public/Characters/PHYCharacterBase.h new file mode 100644 index 0000000..3c73ccb --- /dev/null +++ b/Source/PHY/Public/Characters/PHYCharacterBase.h @@ -0,0 +1,244 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "AbilitySystemInterface.h" +#include "GCS_CombatEntityInterface.h" +#include "GameplayTagAssetInterface.h" +#include "GameFramework/Character.h" +#include "InputTriggers.h" +#include "PHYCharacterBase.generated.h" + +class UAbilitySystemComponent; +class UGCS_CombatSystemComponent; +class UGCS_CombatTeamAgentComponent; +class UGCS_TargetingSystemComponent; +class UGES_ContextEffectComponent; +class UGGS_InteractionSystemComponent; +class UGGS_RagdollComponent; +class UPHYCharacterStateComponent; + +/** + * @brief PHY 玩家和 AI 共用角色基类。 + * + * 基类直接继承 ACharacter,并通过项目级组件和接口适配 GAS、GCS、GGS、UGC 与后续运动系统。 + */ +UCLASS(Abstract, BlueprintType, Blueprintable) +class PHY_API APHYCharacterBase : public ACharacter, public IAbilitySystemInterface, public IGameplayTagAssetInterface, public IGCS_CombatEntityInterface +{ + GENERATED_BODY() + +public: + /** @brief 构造基础角色组件。 */ + APHYCharacterBase(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get()); + + /** @brief 注册 ModularGameplay 组件接收者。 */ + virtual void PreInitializeComponents() override; + + /** @brief 初始化角色运行时组件。 */ + virtual void BeginPlay() override; + + /** @brief 移除 ModularGameplay 组件接收者。 */ + virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override; + + /** @brief 控制器接管时刷新项目状态。 */ + virtual void PossessedBy(AController* NewController) override; + + /** @brief 控制器复制到客户端后刷新项目状态。 */ + virtual void OnRep_Controller() override; + + /** @brief 获取当前角色使用的 AbilitySystemComponent。 */ + virtual UAbilitySystemComponent* GetAbilitySystemComponent() const override; + + /** @brief 收集 ASC 与角色状态组件提供的 GameplayTag。 */ + virtual void GetOwnedGameplayTags(FGameplayTagContainer& TagContainer) const override; + + /** @brief 初始化当前 Avatar 对应的 ASC。 */ + UFUNCTION(BlueprintCallable, Category="PHY|Character|Ability") + virtual void InitializeAbilitySystem(UAbilitySystemComponent* NewAbilitySystemComponent, AActor* OwnerActor); + + /** @brief 清理角色缓存的 ASC 引用。 */ + UFUNCTION(BlueprintCallable, Category="PHY|Character|Ability") + virtual void ClearAbilitySystem(); + + /** @brief 查询 ASC 是否已经完成项目级初始化。 */ + UFUNCTION(BlueprintCallable, BlueprintPure, Category="PHY|Character|Ability") + bool IsAbilitySystemReady() const { return bAbilitySystemReady; } + + /** @brief 接收输入 Tag 并路由到移动、交互或 GAS Ability。 */ + UFUNCTION(BlueprintCallable, Category="PHY|Character|Input") + virtual bool HandleInputTag(FGameplayTag InputTag, ETriggerEvent TriggerEvent); + + /** @brief 设置移动意图,并在客户端请求服务端同步。 */ + UFUNCTION(BlueprintCallable, Category="PHY|Character|Movement") + virtual void SetMovementIntent(FVector NewMovementIntent); + + /** @brief 设置项目级战斗目标。 */ + UFUNCTION(BlueprintCallable, Category="PHY|Character|Combat") + virtual void SetCombatTargetActor(AActor* NewCombatTargetActor); + + /** @brief 请求开始交互。 */ + UFUNCTION(BlueprintCallable, Category="PHY|Character|Interaction") + virtual bool RequestInteraction(int32 OptionIndex = 0); + + /** @brief 请求一次性即时交互。 */ + UFUNCTION(BlueprintCallable, Category="PHY|Character|Interaction") + virtual bool RequestInstantInteraction(int32 OptionIndex = 0); + + /** @brief 请求结束当前交互。 */ + UFUNCTION(BlueprintCallable, Category="PHY|Character|Interaction") + virtual void EndInteraction(); + + /** @brief 请求切换当前可交互对象。 */ + UFUNCTION(BlueprintCallable, Category="PHY|Character|Interaction") + virtual void CycleInteractionTarget(bool bNext); + + /** @brief 获取项目角色状态组件。 */ + UFUNCTION(BlueprintCallable, BlueprintPure, Category="PHY|Character") + UPHYCharacterStateComponent* GetCharacterStateComponent() const { return CharacterStateComponent; } + + /** @brief 获取 GCS 战斗组件。 */ + UFUNCTION(BlueprintCallable, BlueprintPure, Category="PHY|Character") + UGCS_CombatSystemComponent* GetCombatSystemComponent() const { return CombatSystemComponent; } + + /** @brief 获取 GCS 目标组件。 */ + UFUNCTION(BlueprintCallable, BlueprintPure, Category="PHY|Character") + UGCS_TargetingSystemComponent* GetTargetingSystemComponent() const { return TargetingSystemComponent; } + + /** @brief 获取 GGS 交互组件。 */ + UFUNCTION(BlueprintCallable, BlueprintPure, Category="PHY|Character") + UGGS_InteractionSystemComponent* GetInteractionSystemComponent() const { return InteractionSystemComponent; } + + /** @brief 获取 GGS 布娃娃组件。 */ + UFUNCTION(BlueprintCallable, BlueprintPure, Category="PHY|Character") + UGGS_RagdollComponent* GetRagdollComponent() const { return RagdollComponent; } + + /** @brief 获取上下文效果组件。 */ + UFUNCTION(BlueprintCallable, BlueprintPure, Category="PHY|Character") + UGES_ContextEffectComponent* GetContextEffectComponent() const { return ContextEffectComponent; } + + /** @brief 获取当前战斗目标 Actor。 */ + virtual AActor* GetCombatTargetActor_Implementation() const override; + + /** @brief 获取连击定义表,首期骨架默认不绑定表。 */ + virtual const UDataTable* GetComboDefinitionTable_Implementation() const override; + + /** @brief 获取当前战斗目标场景对象。 */ + virtual USceneComponent* GetCombatTargetObject_Implementation() const override; + + /** @brief 查询能力动作,首期骨架默认返回空。 */ + virtual bool QueryAbilityActions_Implementation(FGameplayTagContainer AbilityTags, FGameplayTagContainer SourceTags, FGameplayTagContainer TargetTags, TArray& AbilityActions) override; + + /** @brief 按上下文查询能力动作,首期骨架默认返回空。 */ + virtual bool QueryAbilityActionsByContext_Implementation(UObject* Context, FGameplayTagContainer AbilityTags, FGameplayTagContainer SourceTags, FGameplayTagContainer TargetTags, TArray& AbilityActions) override; + + /** @brief 按 Tag 查询当前武器。 */ + virtual UObject* QueryWeapon_Implementation(const FGameplayTagQuery& Query) const override; + + /** @brief 设置旋转模式。 */ + virtual void SetRotationMode_Implementation(FGameplayTag NewRotationMode) override; + + /** @brief 获取旋转模式。 */ + virtual FGameplayTag GetRotationMode_Implementation() const override; + + /** @brief 设置移动集合。 */ + virtual void SetMovementSet_Implementation(FGameplayTag NewMovementSet) override; + + /** @brief 获取移动集合。 */ + virtual FGameplayTag GetMovementSet_Implementation() const override; + + /** @brief 设置移动状态。 */ + virtual void SetMovementState_Implementation(FGameplayTag NewMovementState) override; + + /** @brief 获取移动状态。 */ + virtual FGameplayTag GetMovementState_Implementation() const override; + + /** @brief 开始死亡流程。 */ + virtual void StartDeath_Implementation() override; + + /** @brief 完成死亡流程。 */ + virtual void FinishDeath_Implementation() override; + + /** @brief 查询角色是否死亡。 */ + virtual bool IsDead_Implementation() const override; + + /** @brief 获取移动意图。 */ + virtual FVector GetMovementIntent_Implementation() const override; + + /** @brief 获取当前武器。 */ + virtual UObject* GetCurrentWeapon_Implementation(UObject* Context = nullptr) const override; + + /** @brief 设置当前武器。 */ + virtual void SetCurrentWeapon_Implementation(UObject* Weapon) override; + + /** @brief 获取指定 Socket 的相对变换。 */ + virtual bool GetRelativeTransformToSocket_Implementation(const USkeletalMeshComponent* InSkeletalMeshComponent, const UStaticMesh* StaticMesh, const USkeletalMesh* SkeletalMesh, FName SocketName, FTransform& OutTransform) const override; + +protected: + /** @brief 将输入 Tag 映射为 Ability Tag。 */ + virtual FGameplayTag GetAbilityTagForInputTag(FGameplayTag InputTag) const; + + /** @brief 应用 Character 配置默认值。 */ + virtual void ApplyCharacterSettings(); + + /** @brief 服务端同步移动意图。 */ + UFUNCTION(Server, Unreliable) + void ServerSetMovementIntent(FVector NewMovementIntent); + + /** @brief 服务端设置战斗目标。 */ + UFUNCTION(Server, Reliable) + void ServerSetCombatTargetActor(AActor* NewCombatTargetActor); + + /** @brief 服务端执行开始交互。 */ + UFUNCTION(Server, Reliable) + void ServerRequestInteraction(int32 OptionIndex); + + /** @brief 服务端执行即时交互。 */ + UFUNCTION(Server, Reliable) + void ServerRequestInstantInteraction(int32 OptionIndex); + + /** @brief 服务端结束交互。 */ + UFUNCTION(Server, Reliable) + void ServerEndInteraction(); + + /** @brief 服务端切换交互目标。 */ + UFUNCTION(Server, Reliable) + void ServerCycleInteractionTarget(bool bNext); + + /** @brief 角色共享状态组件。 */ + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="PHY|Character") + TObjectPtr CharacterStateComponent; + + /** @brief GCS 战斗系统组件。 */ + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="PHY|Character") + TObjectPtr CombatSystemComponent; + + /** @brief GCS 目标系统组件。 */ + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="PHY|Character") + TObjectPtr TargetingSystemComponent; + + /** @brief GCS 战斗队伍代理组件。 */ + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="PHY|Character") + TObjectPtr CombatTeamAgentComponent; + + /** @brief GGS 交互系统组件。 */ + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="PHY|Character") + TObjectPtr InteractionSystemComponent; + + /** @brief GGS 布娃娃组件。 */ + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="PHY|Character") + TObjectPtr RagdollComponent; + + /** @brief GES 上下文效果组件。 */ + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="PHY|Character") + TObjectPtr ContextEffectComponent; + + /** @brief 当前 Avatar 缓存的 ASC,玩家来自 PlayerState,AI 来自自身。 */ + UPROPERTY(Transient) + TObjectPtr CachedAbilitySystemComponent; + + /** @brief ASC 是否已经初始化为当前 Avatar。 */ + UPROPERTY(Transient) + bool bAbilitySystemReady = false; +}; diff --git a/Source/PHY/Public/Characters/PHYCharacterSettings.h b/Source/PHY/Public/Characters/PHYCharacterSettings.h new file mode 100644 index 0000000..2ea2077 --- /dev/null +++ b/Source/PHY/Public/Characters/PHYCharacterSettings.h @@ -0,0 +1,75 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "UObject/Object.h" +#include "UObject/SoftObjectPtr.h" +#include "PHYCharacterSettings.generated.h" + +class APHYAICharacter; +class APHYAIController; +class APHYPlayerCameraManager; +class APHYPlayerCharacter; +class APHYPlayerController; +class UUGC_CameraDataAssetBase; + +/** + * @brief PHY 角色系统配置。 + * + * 角色默认参数独立放在 PHYCharacter 配置域,避免把玩法参数混入 Engine 或 Input 标准配置。 + */ +UCLASS(Config=PHYCharacter, DefaultConfig) +class PHY_API UPHYCharacterSettings : public UObject +{ + GENERATED_BODY() + +public: + /** @brief 是否启用 GenericGameSystem 交互组件路径。 */ + UPROPERTY(Config, EditDefaultsOnly, Category="PHY|Character") + bool bEnableGenericGameInteraction = true; + + /** @brief 是否启用 GenericEffectsSystem 上下文效果组件。 */ + UPROPERTY(Config, EditDefaultsOnly, Category="PHY|Character") + bool bEnableContextEffects = true; + + /** @brief 默认最大行走速度。 */ + UPROPERTY(Config, EditDefaultsOnly, Category="PHY|Character") + float DefaultMaxWalkSpeed = 500.0f; + + /** @brief 默认冲刺速度。 */ + UPROPERTY(Config, EditDefaultsOnly, Category="PHY|Character") + float DefaultSprintSpeed = 650.0f; + + /** @brief 默认最大加速度。 */ + UPROPERTY(Config, EditDefaultsOnly, Category="PHY|Character") + float DefaultMaxAcceleration = 2048.0f; + + /** @brief 默认交互选项索引。 */ + UPROPERTY(Config, EditDefaultsOnly, Category="PHY|Character") + int32 DefaultInteractionOption = 0; + + /** @brief 玩家角色默认软类引用。 */ + UPROPERTY(Config, EditDefaultsOnly, Category="PHY|Character") + TSoftClassPtr DefaultPlayerCharacterClass; + + /** @brief AI 角色默认软类引用。 */ + UPROPERTY(Config, EditDefaultsOnly, Category="PHY|Character") + TSoftClassPtr DefaultAICharacterClass; + + /** @brief 玩家控制器默认软类引用。 */ + UPROPERTY(Config, EditDefaultsOnly, Category="PHY|Character") + TSoftClassPtr DefaultPlayerControllerClass; + + /** @brief AI 控制器默认软类引用。 */ + UPROPERTY(Config, EditDefaultsOnly, Category="PHY|Character") + TSoftClassPtr DefaultAIControllerClass; + + /** @brief 玩家相机管理器默认软类引用。 */ + UPROPERTY(Config, EditDefaultsOnly, Category="PHY|Character") + TSoftClassPtr DefaultPlayerCameraManagerClass; + + /** @brief UGC 默认 CameraData 软引用,首期不强制制作资产。 */ + UPROPERTY(Config, EditDefaultsOnly, Category="PHY|Character") + TSoftObjectPtr DefaultUGCCameraDataAsset; +}; diff --git a/Source/PHY/Public/Characters/PHYCharacterStateComponent.h b/Source/PHY/Public/Characters/PHYCharacterStateComponent.h new file mode 100644 index 0000000..0a72416 --- /dev/null +++ b/Source/PHY/Public/Characters/PHYCharacterStateComponent.h @@ -0,0 +1,114 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Components/ActorComponent.h" +#include "GameplayTagContainer.h" +#include "PHYCharacterStateComponent.generated.h" + +/** + * @brief PHY 角色复制状态组件。 + * + * 该组件集中保存 GCS、相机、输入和运动层共享的轻量状态,避免玩法层直接散落插件状态字段。 + */ +UCLASS(BlueprintType, Blueprintable, meta=(BlueprintSpawnableComponent)) +class PHY_API UPHYCharacterStateComponent : public UActorComponent +{ + GENERATED_BODY() + +public: + /** @brief 构造默认复制状态。 */ + UPHYCharacterStateComponent(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get()); + + /** @brief 注册需要网络复制的角色状态字段。 */ + virtual void GetLifetimeReplicatedProps(TArray& OutLifetimeProps) const override; + + /** @brief 设置死亡状态。 */ + UFUNCTION(BlueprintCallable, Category="PHY|Character|State") + void SetDead(bool bNewDead); + + /** @brief 查询角色是否已经死亡。 */ + UFUNCTION(BlueprintCallable, BlueprintPure, Category="PHY|Character|State") + bool IsDead() const { return bDead; } + + /** @brief 设置当前战斗目标 Actor。 */ + UFUNCTION(BlueprintCallable, Category="PHY|Character|State") + void SetCombatTargetActor(AActor* NewCombatTargetActor); + + /** @brief 获取当前战斗目标 Actor。 */ + UFUNCTION(BlueprintCallable, BlueprintPure, Category="PHY|Character|State") + AActor* GetCombatTargetActor() const { return CombatTargetActor; } + + /** @brief 设置当前武器对象。 */ + UFUNCTION(BlueprintCallable, Category="PHY|Character|State") + void SetCurrentWeapon(UObject* NewCurrentWeapon); + + /** @brief 获取当前武器对象。 */ + UFUNCTION(BlueprintCallable, BlueprintPure, Category="PHY|Character|State") + UObject* GetCurrentWeapon() const { return CurrentWeapon; } + + /** @brief 设置移动输入意图,通常由玩家控制器或 AI 控制器写入。 */ + UFUNCTION(BlueprintCallable, Category="PHY|Character|State") + void SetMovementIntent(FVector NewMovementIntent); + + /** @brief 获取当前移动输入意图。 */ + UFUNCTION(BlueprintCallable, BlueprintPure, Category="PHY|Character|State") + FVector GetMovementIntent() const { return MovementIntent; } + + /** @brief 设置当前移动状态 Tag。 */ + UFUNCTION(BlueprintCallable, Category="PHY|Character|State") + void SetMovementState(FGameplayTag NewMovementState); + + /** @brief 获取当前移动状态 Tag。 */ + UFUNCTION(BlueprintCallable, BlueprintPure, Category="PHY|Character|State") + FGameplayTag GetMovementState() const { return MovementState; } + + /** @brief 设置当前移动集合 Tag。 */ + UFUNCTION(BlueprintCallable, Category="PHY|Character|State") + void SetMovementSet(FGameplayTag NewMovementSet); + + /** @brief 获取当前移动集合 Tag。 */ + UFUNCTION(BlueprintCallable, BlueprintPure, Category="PHY|Character|State") + FGameplayTag GetMovementSet() const { return MovementSet; } + + /** @brief 设置当前旋转模式 Tag。 */ + UFUNCTION(BlueprintCallable, Category="PHY|Character|State") + void SetRotationMode(FGameplayTag NewRotationMode); + + /** @brief 获取当前旋转模式 Tag。 */ + UFUNCTION(BlueprintCallable, BlueprintPure, Category="PHY|Character|State") + FGameplayTag GetRotationMode() const { return RotationMode; } + + /** @brief 将状态组件拥有的 GameplayTag 写入输出容器。 */ + void GetOwnedStateTags(FGameplayTagContainer& OutTags) const; + +protected: + /** @brief 死亡状态,由服务端驱动复制。 */ + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Replicated, Category="PHY|Character|State") + bool bDead = false; + + /** @brief 当前战斗目标 Actor。 */ + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Replicated, Category="PHY|Character|State") + TObjectPtr CombatTargetActor; + + /** @brief 当前武器对象。 */ + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Replicated, Category="PHY|Character|State") + TObjectPtr CurrentWeapon; + + /** @brief 移动输入意图。 */ + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Replicated, Category="PHY|Character|State") + FVector MovementIntent = FVector::ZeroVector; + + /** @brief 当前移动状态。 */ + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Replicated, Category="PHY|Character|State") + FGameplayTag MovementState; + + /** @brief 当前移动集合。 */ + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Replicated, Category="PHY|Character|State") + FGameplayTag MovementSet; + + /** @brief 当前旋转模式。 */ + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Replicated, Category="PHY|Character|State") + FGameplayTag RotationMode; +}; diff --git a/Source/PHY/Public/Characters/PHYPlayerCharacter.h b/Source/PHY/Public/Characters/PHYPlayerCharacter.h new file mode 100644 index 0000000..645f210 --- /dev/null +++ b/Source/PHY/Public/Characters/PHYPlayerCharacter.h @@ -0,0 +1,51 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Characters/PHYCharacterBase.h" +#include "PHYPlayerCharacter.generated.h" + +class UCameraComponent; +class UPHYUGCSpringArmComponent; + +/** + * @brief PHY 玩家角色。 + * + * 玩家角色从 PlayerState 获取 ASC,并只持有相机组件,不直接持有输入组件。 + */ +UCLASS(BlueprintType, Blueprintable) +class PHY_API APHYPlayerCharacter : public APHYCharacterBase +{ + GENERATED_BODY() + +public: + /** @brief 构造玩家角色相机组件。 */ + APHYPlayerCharacter(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get()); + + /** @brief 服务端 Possess 后从 PlayerState 初始化 ASC。 */ + virtual void PossessedBy(AController* NewController) override; + + /** @brief 客户端 PlayerState 复制后初始化 ASC。 */ + virtual void OnRep_PlayerState() override; + + /** @brief 获取 UGC 相机臂。 */ + UFUNCTION(BlueprintCallable, BlueprintPure, Category="PHY|Camera") + UPHYUGCSpringArmComponent* GetCameraBoom() const { return CameraBoom; } + + /** @brief 获取跟随相机。 */ + UFUNCTION(BlueprintCallable, BlueprintPure, Category="PHY|Camera") + UCameraComponent* GetFollowCamera() const { return FollowCamera; } + +protected: + /** @brief 根据当前 PlayerState 初始化 ASC。 */ + virtual void InitializeAbilitySystemFromPlayerState(); + + /** @brief UGC 项目相机臂。 */ + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="PHY|Camera") + TObjectPtr CameraBoom; + + /** @brief 玩家跟随相机。 */ + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="PHY|Camera") + TObjectPtr FollowCamera; +}; diff --git a/Source/PHY/Public/GameplayTags/PHYGameplayTags_State.h b/Source/PHY/Public/GameplayTags/PHYGameplayTags_State.h index 8d41e14..8417c2a 100644 --- a/Source/PHY/Public/GameplayTags/PHYGameplayTags_State.h +++ b/Source/PHY/Public/GameplayTags/PHYGameplayTags_State.h @@ -23,4 +23,31 @@ namespace PHYGameplayTags /** @brief UI 占用主要输入。 */ UE_DECLARE_GAMEPLAY_TAG_EXTERN(State_UI_Open); + + /** @brief 角色已经进入死亡流程。 */ + UE_DECLARE_GAMEPLAY_TAG_EXTERN(State_Dead); + + /** @brief 角色处于行走移动状态。 */ + UE_DECLARE_GAMEPLAY_TAG_EXTERN(State_Movement_Walk); + + /** @brief 角色处于常规跑动移动状态。 */ + UE_DECLARE_GAMEPLAY_TAG_EXTERN(State_Movement_Run); + + /** @brief 角色处于冲刺移动状态。 */ + UE_DECLARE_GAMEPLAY_TAG_EXTERN(State_Movement_Sprint); + + /** @brief 角色处于下落移动状态。 */ + UE_DECLARE_GAMEPLAY_TAG_EXTERN(State_Movement_Falling); + + /** @brief 角色使用默认移动集合。 */ + UE_DECLARE_GAMEPLAY_TAG_EXTERN(State_MovementSet_Default); + + /** @brief 角色使用瞄准移动集合。 */ + UE_DECLARE_GAMEPLAY_TAG_EXTERN(State_MovementSet_Aiming); + + /** @brief 角色旋转朝向移动方向。 */ + UE_DECLARE_GAMEPLAY_TAG_EXTERN(State_Rotation_OrientToMovement); + + /** @brief 角色旋转使用战斗横移模式。 */ + UE_DECLARE_GAMEPLAY_TAG_EXTERN(State_Rotation_Strafe); } diff --git a/Source/PHY/Public/Player/PHYPlayerCameraManager.h b/Source/PHY/Public/Player/PHYPlayerCameraManager.h new file mode 100644 index 0000000..9d220ae --- /dev/null +++ b/Source/PHY/Public/Player/PHYPlayerCameraManager.h @@ -0,0 +1,25 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Camera/UGC_PlayerCameraManager.h" +#include "PHYPlayerCameraManager.generated.h" + +/** + * @brief PHY 玩家相机管理器。 + * + * 该类型从 PlayerController 的 C++ 缓存读取输入,不把 UGC 蓝图 Pawn Interface 作为主路径。 + */ +UCLASS(BlueprintType, Blueprintable) +class PHY_API APHYPlayerCameraManager : public AUGC_PlayerCameraManager +{ + GENERATED_BODY() + +protected: + /** @brief 返回玩家视角输入。 */ + virtual FRotator GetRotationInput_Implementation() const override; + + /** @brief 返回玩家移动控制输入。 */ + virtual FVector GetMovementControlInput_Implementation() const override; +}; diff --git a/Source/PHY/Public/Player/PHYPlayerController.h b/Source/PHY/Public/Player/PHYPlayerController.h new file mode 100644 index 0000000..3c7c653 --- /dev/null +++ b/Source/PHY/Public/Player/PHYPlayerController.h @@ -0,0 +1,78 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "GameFramework/PlayerController.h" +#include "GIPS_InputTypes.h" +#include "PHYPlayerController.generated.h" + +class APHYPlayerCharacter; +class UGIPS_InputSystemComponent; + +/** + * @brief PHY 玩家控制器。 + * + * 控制器持有 GenericInputSystem 组件,并将 Input GameplayTag 路由到当前玩家角色、ASC、移动和相机输入缓存。 + */ +UCLASS(BlueprintType, Blueprintable) +class PHY_API APHYPlayerController : public APlayerController +{ + GENERATED_BODY() + +public: + /** @brief 构造输入组件并设置 UGC 相机管理器。 */ + APHYPlayerController(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get()); + + /** @brief 初始化输入委托。 */ + virtual void BeginPlay() override; + + /** @brief 接管 Pawn 时刷新输入委托。 */ + virtual void OnPossess(APawn* InPawn) override; + + /** @brief 获取 GenericInputSystem 组件。 */ + UFUNCTION(BlueprintCallable, BlueprintPure, Category="PHY|Input") + UGIPS_InputSystemComponent* GetInputSystemComponent() const { return InputSystemComponent; } + + /** @brief 获取当前 PHY 玩家角色。 */ + UFUNCTION(BlueprintCallable, BlueprintPure, Category="PHY|Input") + APHYPlayerCharacter* GetPHYPlayerCharacter() const; + + /** @brief 获取相机使用的旋转输入缓存。 */ + UFUNCTION(BlueprintCallable, BlueprintPure, Category="PHY|Input") + FRotator GetCachedRotationInput() const { return CachedRotationInput; } + + /** @brief 获取相机使用的移动控制输入缓存。 */ + UFUNCTION(BlueprintCallable, BlueprintPure, Category="PHY|Input") + FVector GetCachedMovementControlInput() const { return FVector(CachedMovementInput.X, CachedMovementInput.Y, 0.0f); } + +protected: + /** @brief 绑定 GenericInputSystem 输入委托。 */ + virtual void BindInputEvents(); + + /** @brief 处理 GenericInputSystem 发出的输入事件。 */ + UFUNCTION() + void OnReceivedInput(const FInputActionInstance& ActionData, const FGameplayTag& InputTag, ETriggerEvent TriggerEvent); + + /** @brief 处理移动输入。 */ + virtual bool HandleMoveInput(const FInputActionInstance& ActionData, ETriggerEvent TriggerEvent); + + /** @brief 处理视角输入。 */ + virtual bool HandleLookInput(const FInputActionInstance& ActionData, ETriggerEvent TriggerEvent); + + /** @brief GenericInputSystem 路由组件。 */ + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="PHY|Input") + TObjectPtr InputSystemComponent; + + /** @brief 本地移动输入缓存。 */ + UPROPERTY(Transient) + FVector2D CachedMovementInput = FVector2D::ZeroVector; + + /** @brief 本地视角输入缓存。 */ + UPROPERTY(Transient) + FVector2D CachedLookInput = FVector2D::ZeroVector; + + /** @brief UGC 相机管理器读取的旋转输入缓存。 */ + UPROPERTY(Transient) + FRotator CachedRotationInput = FRotator::ZeroRotator; +}; diff --git a/Source/PHY/Public/Player/PHYPlayerState.h b/Source/PHY/Public/Player/PHYPlayerState.h new file mode 100644 index 0000000..d951e3a --- /dev/null +++ b/Source/PHY/Public/Player/PHYPlayerState.h @@ -0,0 +1,37 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "AbilitySystemInterface.h" +#include "GameFramework/PlayerState.h" +#include "PHYPlayerState.generated.h" + +class UGGA_AbilitySystemComponent; + +/** + * @brief PHY 玩家状态。 + * + * 玩家 ASC 挂在 PlayerState 上,以支持重生、换 Pawn 和持久属性复制。 + */ +UCLASS(BlueprintType, Blueprintable) +class PHY_API APHYPlayerState : public APlayerState, public IAbilitySystemInterface +{ + GENERATED_BODY() + +public: + /** @brief 构造玩家状态与 ASC。 */ + APHYPlayerState(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get()); + + /** @brief 获取玩家持久 AbilitySystemComponent。 */ + virtual UAbilitySystemComponent* GetAbilitySystemComponent() const override; + + /** @brief 获取 GGA ASC 的强类型指针。 */ + UFUNCTION(BlueprintCallable, BlueprintPure, Category="PHY|PlayerState") + UGGA_AbilitySystemComponent* GetGGAAbilitySystemComponent() const { return AbilitySystemComponent; } + +protected: + /** @brief 玩家持久 ASC。 */ + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="PHY|PlayerState") + TObjectPtr AbilitySystemComponent; +};