From 349d603775d462d626dddc297bcb73f696714670 Mon Sep 17 00:00:00 2001 From: cit110 <840418418@qq.com> Date: Sun, 26 Apr 2026 01:44:04 +0800 Subject: [PATCH] Apply class attribute allocations --- Config/DefaultPHYClass.ini | 8 ++ .../PHY/Private/Characters/PHYAICharacter.cpp | 9 +- .../Private/Characters/PHYPlayerCharacter.cpp | 9 +- .../PHY/Private/Class/PHYClassComponent.cpp | 106 ++++++++++++++++++ Source/PHY/Private/Class/PHYClassSettings.cpp | 39 +++++++ Source/PHY/Public/Class/PHYClassComponent.h | 5 + Source/PHY/Public/Class/PHYClassSettings.h | 53 +++++++++ 7 files changed, 221 insertions(+), 8 deletions(-) diff --git a/Config/DefaultPHYClass.ini b/Config/DefaultPHYClass.ini index b23f27c..8db70f7 100644 --- a/Config/DefaultPHYClass.ini +++ b/Config/DefaultPHYClass.ini @@ -11,3 +11,11 @@ bApplyPlayerClassMeshToAI=False +PlayerClassMeshes=(ClassTag=(TagName="Class.Caster"),PlayerMesh=None) +PlayerClassMeshes=(ClassTag=(TagName="Class.Assassin"),PlayerMesh=None) +PlayerClassMeshes=(ClassTag=(TagName="Class.Berserker"),PlayerMesh=None) +!CoreAttributeAllocations=ClearArray ++CoreAttributeAllocations=(ClassTag=(TagName="Class.Saber"),Strength=12,Dexterity=10,Vitality=12,Intelligence=8,Spirit=8,Perception=10) ++CoreAttributeAllocations=(ClassTag=(TagName="Class.Lancer"),Strength=10,Dexterity=13,Vitality=10,Intelligence=7,Spirit=8,Perception=12) ++CoreAttributeAllocations=(ClassTag=(TagName="Class.Archer"),Strength=8,Dexterity=12,Vitality=8,Intelligence=8,Spirit=10,Perception=14) ++CoreAttributeAllocations=(ClassTag=(TagName="Class.Rider"),Strength=10,Dexterity=11,Vitality=11,Intelligence=8,Spirit=10,Perception=10) ++CoreAttributeAllocations=(ClassTag=(TagName="Class.Caster"),Strength=5,Dexterity=7,Vitality=8,Intelligence=16,Spirit=15,Perception=9) ++CoreAttributeAllocations=(ClassTag=(TagName="Class.Assassin"),Strength=7,Dexterity=15,Vitality=7,Intelligence=8,Spirit=8,Perception=15) ++CoreAttributeAllocations=(ClassTag=(TagName="Class.Berserker"),Strength=16,Dexterity=8,Vitality=15,Intelligence=4,Spirit=5,Perception=12) diff --git a/Source/PHY/Private/Characters/PHYAICharacter.cpp b/Source/PHY/Private/Characters/PHYAICharacter.cpp index 18a5fac..3b1f5b8 100644 --- a/Source/PHY/Private/Characters/PHYAICharacter.cpp +++ b/Source/PHY/Private/Characters/PHYAICharacter.cpp @@ -45,10 +45,11 @@ void APHYAICharacter::InitializeAIAbilitySystem() if (AbilitySystemComponent) { InitializeAbilitySystem(AbilitySystemComponent, this); - } - if (ClassComponent) - { - ClassComponent->ApplyConfiguredPlayerMeshIfAllowed(this); + if (ClassComponent) + { + ClassComponent->ApplyClassAttributesToAbilitySystem(AbilitySystemComponent); + ClassComponent->ApplyConfiguredPlayerMeshIfAllowed(this); + } } } diff --git a/Source/PHY/Private/Characters/PHYPlayerCharacter.cpp b/Source/PHY/Private/Characters/PHYPlayerCharacter.cpp index d6f393e..9c477b3 100644 --- a/Source/PHY/Private/Characters/PHYPlayerCharacter.cpp +++ b/Source/PHY/Private/Characters/PHYPlayerCharacter.cpp @@ -51,10 +51,11 @@ void APHYPlayerCharacter::InitializeAbilitySystemFromPlayerState() if (UAbilitySystemComponent* PlayerASC = PHYPlayerState->GetAbilitySystemComponent()) { InitializeAbilitySystem(PlayerASC, PHYPlayerState); - } - if (UPHYClassComponent* ClassComponent = PHYPlayerState->GetClassComponent()) - { - ClassComponent->ApplyConfiguredPlayerMeshIfAllowed(this); + if (UPHYClassComponent* ClassComponent = PHYPlayerState->GetClassComponent()) + { + ClassComponent->ApplyClassAttributesToAbilitySystem(PlayerASC); + ClassComponent->ApplyConfiguredPlayerMeshIfAllowed(this); + } } } diff --git a/Source/PHY/Private/Class/PHYClassComponent.cpp b/Source/PHY/Private/Class/PHYClassComponent.cpp index ea39200..0517f22 100644 --- a/Source/PHY/Private/Class/PHYClassComponent.cpp +++ b/Source/PHY/Private/Class/PHYClassComponent.cpp @@ -4,6 +4,11 @@ #include UE_INLINE_GENERATED_CPP_BY_NAME(PHYClassComponent) +#include "AbilitySystemComponent.h" +#include "AbilitySystem/Attributes/PHYAttributeCalculationLibrary.h" +#include "AbilitySystem/Attributes/PHYCombatAttributeSet.h" +#include "AbilitySystem/Attributes/PHYCoreAttributeSet.h" +#include "AbilitySystem/Attributes/PHYElementAttributeSet.h" #include "Characters/PHYAICharacter.h" #include "Characters/PHYPlayerCharacter.h" #include "Class/PHYClassSettings.h" @@ -110,3 +115,104 @@ bool UPHYClassComponent::ApplyConfiguredPlayerMeshIfAllowed(AActor* AvatarActor) Character->GetMesh()->SetSkeletalMesh(PlayerMesh); return true; } + +bool UPHYClassComponent::ApplyClassAttributesToAbilitySystem(UAbilitySystemComponent* AbilitySystemComponent) const +{ + if (!AbilitySystemComponent) + { + return false; + } + + const AActor* AuthorityActor = GetOwner() ? GetOwner() : AbilitySystemComponent->GetOwnerActor(); + if (!AuthorityActor || !AuthorityActor->HasAuthority()) + { + return false; + } + + const UPHYClassSettings* ClassSettings = GetDefault(); + if (!ClassSettings) + { + return false; + } + + constexpr int32 RequiredTotalPoints = 60; + const FPHYClassCoreAttributeAllocation* Allocation = ClassSettings->FindCoreAttributeAllocation(CurrentClassTag); + if (!Allocation || !Allocation->IsValidTotal(RequiredTotalPoints)) + { + UE_LOG(LogTemp, Warning, TEXT("PHYClassComponent: Class %s has no valid %d-point core attribute allocation. Falling back to DefaultPlayerClassTag."), + *CurrentClassTag.ToString(), RequiredTotalPoints); + Allocation = ClassSettings->FindCoreAttributeAllocation(ClassSettings->DefaultPlayerClassTag); + } + + if (!Allocation || !Allocation->IsValidTotal(RequiredTotalPoints)) + { + UE_LOG(LogTemp, Warning, TEXT("PHYClassComponent: DefaultPlayerClassTag allocation is missing or invalid. Falling back to Class.Saber.")); + Allocation = ClassSettings->FindCoreAttributeAllocation(PHYGameplayTags::Class_Saber); + } + + if (!Allocation || !Allocation->IsValidTotal(RequiredTotalPoints)) + { + UE_LOG(LogTemp, Warning, TEXT("PHYClassComponent: Unable to apply class attributes because no valid fallback allocation exists.")); + return false; + } + + FPHYCoreAttributeSnapshot Snapshot; + Snapshot.Strength = Allocation->Strength; + Snapshot.Dexterity = Allocation->Dexterity; + Snapshot.Vitality = Allocation->Vitality; + Snapshot.Intelligence = Allocation->Intelligence; + Snapshot.Spirit = Allocation->Spirit; + Snapshot.Perception = Allocation->Perception; + + const FPHYDerivedCombatAttributes CombatAttributes = UPHYAttributeCalculationLibrary::CalculateCombatAttributes(Snapshot); + const FPHYDerivedElementAttributes ElementAttributes = UPHYAttributeCalculationLibrary::CalculateElementAttributes(Snapshot); + + // 直接设置 ASC 属性基值,重复调用只会覆盖当前职业结果,不会叠加。 + AbilitySystemComponent->SetNumericAttributeBase(UPHYCoreAttributeSet::GetStrengthAttribute(), Snapshot.Strength); + AbilitySystemComponent->SetNumericAttributeBase(UPHYCoreAttributeSet::GetDexterityAttribute(), Snapshot.Dexterity); + AbilitySystemComponent->SetNumericAttributeBase(UPHYCoreAttributeSet::GetVitalityAttribute(), Snapshot.Vitality); + AbilitySystemComponent->SetNumericAttributeBase(UPHYCoreAttributeSet::GetIntelligenceAttribute(), Snapshot.Intelligence); + AbilitySystemComponent->SetNumericAttributeBase(UPHYCoreAttributeSet::GetSpiritAttribute(), Snapshot.Spirit); + AbilitySystemComponent->SetNumericAttributeBase(UPHYCoreAttributeSet::GetPerceptionAttribute(), Snapshot.Perception); + + AbilitySystemComponent->SetNumericAttributeBase(UPHYCombatAttributeSet::GetMaxHealthAttribute(), CombatAttributes.MaxHealth); + AbilitySystemComponent->SetNumericAttributeBase(UPHYCombatAttributeSet::GetHealthAttribute(), CombatAttributes.MaxHealth); + AbilitySystemComponent->SetNumericAttributeBase(UPHYCombatAttributeSet::GetMaxManaAttribute(), CombatAttributes.MaxMana); + AbilitySystemComponent->SetNumericAttributeBase(UPHYCombatAttributeSet::GetManaAttribute(), CombatAttributes.MaxMana); + AbilitySystemComponent->SetNumericAttributeBase(UPHYCombatAttributeSet::GetMaxStaminaAttribute(), CombatAttributes.MaxStamina); + AbilitySystemComponent->SetNumericAttributeBase(UPHYCombatAttributeSet::GetStaminaAttribute(), CombatAttributes.MaxStamina); + AbilitySystemComponent->SetNumericAttributeBase(UPHYCombatAttributeSet::GetPhysicalAttackPowerAttribute(), CombatAttributes.PhysicalAttackPower); + AbilitySystemComponent->SetNumericAttributeBase(UPHYCombatAttributeSet::GetSpellPowerAttribute(), CombatAttributes.SpellPower); + AbilitySystemComponent->SetNumericAttributeBase(UPHYCombatAttributeSet::GetArmorAttribute(), CombatAttributes.Armor); + AbilitySystemComponent->SetNumericAttributeBase(UPHYCombatAttributeSet::GetMagicResistanceAttribute(), CombatAttributes.MagicResistance); + AbilitySystemComponent->SetNumericAttributeBase(UPHYCombatAttributeSet::GetAccuracyAttribute(), CombatAttributes.Accuracy); + AbilitySystemComponent->SetNumericAttributeBase(UPHYCombatAttributeSet::GetEvasionAttribute(), CombatAttributes.Evasion); + AbilitySystemComponent->SetNumericAttributeBase(UPHYCombatAttributeSet::GetCriticalChanceAttribute(), CombatAttributes.CriticalChance); + AbilitySystemComponent->SetNumericAttributeBase(UPHYCombatAttributeSet::GetCriticalDamageAttribute(), CombatAttributes.CriticalDamage); + AbilitySystemComponent->SetNumericAttributeBase(UPHYCombatAttributeSet::GetAttackSpeedAttribute(), CombatAttributes.AttackSpeed); + AbilitySystemComponent->SetNumericAttributeBase(UPHYCombatAttributeSet::GetCooldownReductionAttribute(), CombatAttributes.CooldownReduction); + AbilitySystemComponent->SetNumericAttributeBase(UPHYCombatAttributeSet::GetBlockPowerAttribute(), CombatAttributes.BlockPower); + AbilitySystemComponent->SetNumericAttributeBase(UPHYCombatAttributeSet::GetGuardBreakPowerAttribute(), CombatAttributes.GuardBreakPower); + AbilitySystemComponent->SetNumericAttributeBase(UPHYCombatAttributeSet::GetPoiseAttribute(), CombatAttributes.Poise); + AbilitySystemComponent->SetNumericAttributeBase(UPHYCombatAttributeSet::GetPoiseDamageAttribute(), CombatAttributes.PoiseDamage); + + AbilitySystemComponent->SetNumericAttributeBase(UPHYElementAttributeSet::GetFireDamageBonusAttribute(), ElementAttributes.DamageBonus); + AbilitySystemComponent->SetNumericAttributeBase(UPHYElementAttributeSet::GetWaterDamageBonusAttribute(), ElementAttributes.DamageBonus); + AbilitySystemComponent->SetNumericAttributeBase(UPHYElementAttributeSet::GetIceDamageBonusAttribute(), ElementAttributes.DamageBonus); + AbilitySystemComponent->SetNumericAttributeBase(UPHYElementAttributeSet::GetLightningDamageBonusAttribute(), ElementAttributes.DamageBonus); + AbilitySystemComponent->SetNumericAttributeBase(UPHYElementAttributeSet::GetEarthDamageBonusAttribute(), ElementAttributes.DamageBonus); + AbilitySystemComponent->SetNumericAttributeBase(UPHYElementAttributeSet::GetWindDamageBonusAttribute(), ElementAttributes.DamageBonus); + AbilitySystemComponent->SetNumericAttributeBase(UPHYElementAttributeSet::GetLightDamageBonusAttribute(), ElementAttributes.DamageBonus); + AbilitySystemComponent->SetNumericAttributeBase(UPHYElementAttributeSet::GetDarkDamageBonusAttribute(), ElementAttributes.DamageBonus); + AbilitySystemComponent->SetNumericAttributeBase(UPHYElementAttributeSet::GetFireResistanceAttribute(), ElementAttributes.Resistance); + AbilitySystemComponent->SetNumericAttributeBase(UPHYElementAttributeSet::GetWaterResistanceAttribute(), ElementAttributes.Resistance); + AbilitySystemComponent->SetNumericAttributeBase(UPHYElementAttributeSet::GetIceResistanceAttribute(), ElementAttributes.Resistance); + AbilitySystemComponent->SetNumericAttributeBase(UPHYElementAttributeSet::GetLightningResistanceAttribute(), ElementAttributes.Resistance); + AbilitySystemComponent->SetNumericAttributeBase(UPHYElementAttributeSet::GetEarthResistanceAttribute(), ElementAttributes.Resistance); + AbilitySystemComponent->SetNumericAttributeBase(UPHYElementAttributeSet::GetWindResistanceAttribute(), ElementAttributes.Resistance); + AbilitySystemComponent->SetNumericAttributeBase(UPHYElementAttributeSet::GetLightResistanceAttribute(), ElementAttributes.Resistance); + AbilitySystemComponent->SetNumericAttributeBase(UPHYElementAttributeSet::GetDarkResistanceAttribute(), ElementAttributes.Resistance); + AbilitySystemComponent->SetNumericAttributeBase(UPHYElementAttributeSet::GetElementPenetrationAttribute(), ElementAttributes.Penetration); + + return true; +} diff --git a/Source/PHY/Private/Class/PHYClassSettings.cpp b/Source/PHY/Private/Class/PHYClassSettings.cpp index 1ba60d8..1ae577a 100644 --- a/Source/PHY/Private/Class/PHYClassSettings.cpp +++ b/Source/PHY/Private/Class/PHYClassSettings.cpp @@ -6,6 +6,16 @@ #include "PHYGameplayTags.h" +int32 FPHYClassCoreAttributeAllocation::GetTotalPoints() const +{ + return Strength + Dexterity + Vitality + Intelligence + Spirit + Perception; +} + +bool FPHYClassCoreAttributeAllocation::IsValidTotal(const int32 RequiredTotal) const +{ + return GetTotalPoints() == RequiredTotal; +} + UPHYClassSettings::UPHYClassSettings() { DefaultPlayerClassTag = PHYGameplayTags::Class_Saber; @@ -21,4 +31,33 @@ UPHYClassSettings::UPHYClassSettings() { PHYGameplayTags::Class_Assassin, nullptr }, { PHYGameplayTags::Class_Berserker, nullptr } }; + + CoreAttributeAllocations = + { + { PHYGameplayTags::Class_Saber, 12, 10, 12, 8, 8, 10 }, + { PHYGameplayTags::Class_Lancer, 10, 13, 10, 7, 8, 12 }, + { PHYGameplayTags::Class_Archer, 8, 12, 8, 8, 10, 14 }, + { PHYGameplayTags::Class_Rider, 10, 11, 11, 8, 10, 10 }, + { PHYGameplayTags::Class_Caster, 5, 7, 8, 16, 15, 9 }, + { PHYGameplayTags::Class_Assassin, 7, 15, 7, 8, 8, 15 }, + { PHYGameplayTags::Class_Berserker, 16, 8, 15, 4, 5, 12 } + }; +} + +const FPHYClassCoreAttributeAllocation* UPHYClassSettings::FindCoreAttributeAllocation(const FGameplayTag ClassTag) const +{ + if (!ClassTag.IsValid()) + { + return nullptr; + } + + for (const FPHYClassCoreAttributeAllocation& Allocation : CoreAttributeAllocations) + { + if (Allocation.ClassTag == ClassTag) + { + return &Allocation; + } + } + + return nullptr; } diff --git a/Source/PHY/Public/Class/PHYClassComponent.h b/Source/PHY/Public/Class/PHYClassComponent.h index 93ca450..b3137c3 100644 --- a/Source/PHY/Public/Class/PHYClassComponent.h +++ b/Source/PHY/Public/Class/PHYClassComponent.h @@ -8,6 +8,7 @@ #include "PHYClassComponent.generated.h" class USkeletalMesh; +class UAbilitySystemComponent; /** * @brief PHY 职业组件。 @@ -46,6 +47,10 @@ public: UFUNCTION(BlueprintCallable, Category="PHY|Class") bool ApplyConfiguredPlayerMeshIfAllowed(AActor* AvatarActor = nullptr) const; + /** @brief 在服务器上按当前职业把基础属性和派生属性写入 ASC。 */ + UFUNCTION(BlueprintCallable, Category="PHY|Class|Attributes") + bool ApplyClassAttributesToAbilitySystem(UAbilitySystemComponent* AbilitySystemComponent) const; + protected: /** @brief 当前职业 Tag,由服务器复制给客户端。 */ UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Replicated, Category="PHY|Class") diff --git a/Source/PHY/Public/Class/PHYClassSettings.h b/Source/PHY/Public/Class/PHYClassSettings.h index 45a87a1..b9d2753 100644 --- a/Source/PHY/Public/Class/PHYClassSettings.h +++ b/Source/PHY/Public/Class/PHYClassSettings.h @@ -10,6 +10,52 @@ class USkeletalMesh; +/** + * @brief 职业基础六维属性分配。 + * + * 每个职业首期固定总点数为 60,用于初始化 CoreAttributeSet,并继续推导战斗/元素属性。 + */ +USTRUCT(BlueprintType) +struct PHY_API FPHYClassCoreAttributeAllocation +{ + GENERATED_BODY() + +public: + /** @brief 职业 Tag,必须来自 Class.* 原生 Gameplay Tag。 */ + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="PHY|Class|Attributes") + FGameplayTag ClassTag; + + /** @brief 力量点数。 */ + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="PHY|Class|Attributes") + int32 Strength = 0; + + /** @brief 灵巧点数。 */ + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="PHY|Class|Attributes") + int32 Dexterity = 0; + + /** @brief 体魄点数。 */ + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="PHY|Class|Attributes") + int32 Vitality = 0; + + /** @brief 智力点数。 */ + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="PHY|Class|Attributes") + int32 Intelligence = 0; + + /** @brief 精神点数。 */ + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="PHY|Class|Attributes") + int32 Spirit = 0; + + /** @brief 感知点数。 */ + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="PHY|Class|Attributes") + int32 Perception = 0; + + /** @brief 返回六维属性总点数。 */ + int32 GetTotalPoints() const; + + /** @brief 检查六维属性总点数是否等于要求值。 */ + bool IsValidTotal(int32 RequiredTotal = 60) const; +}; + /** * @brief 玩家职业 Mesh 配置项。 */ @@ -54,6 +100,10 @@ public: UPROPERTY(Config, EditDefaultsOnly, Category="PHY|Class") TArray PlayerClassMeshes; + /** @brief 职业基础属性分配表,首期每个职业总点数必须为 60。 */ + UPROPERTY(Config, EditDefaultsOnly, Category="PHY|Class|Attributes") + TArray CoreAttributeAllocations; + /** @brief 是否允许玩家 Avatar 应用玩家职业 Mesh。 */ UPROPERTY(Config, EditDefaultsOnly, Category="PHY|Class") bool bApplyPlayerClassMeshToPlayers = true; @@ -61,4 +111,7 @@ public: /** @brief 是否允许 AI 应用玩家职业 Mesh,默认关闭以保持 AI 外观独立。 */ UPROPERTY(Config, EditDefaultsOnly, Category="PHY|Class") bool bApplyPlayerClassMeshToAI = false; + + /** @brief 按职业 Tag 查找基础属性分配,未配置时返回 nullptr。 */ + const FPHYClassCoreAttributeAllocation* FindCoreAttributeAllocation(FGameplayTag ClassTag) const; };