Apply class attribute allocations

This commit is contained in:
2026-04-26 01:44:04 +08:00
parent 408a764560
commit 349d603775
7 changed files with 221 additions and 8 deletions

View File

@@ -11,3 +11,11 @@ bApplyPlayerClassMeshToAI=False
+PlayerClassMeshes=(ClassTag=(TagName="Class.Caster"),PlayerMesh=None) +PlayerClassMeshes=(ClassTag=(TagName="Class.Caster"),PlayerMesh=None)
+PlayerClassMeshes=(ClassTag=(TagName="Class.Assassin"),PlayerMesh=None) +PlayerClassMeshes=(ClassTag=(TagName="Class.Assassin"),PlayerMesh=None)
+PlayerClassMeshes=(ClassTag=(TagName="Class.Berserker"),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)

View File

@@ -45,10 +45,11 @@ void APHYAICharacter::InitializeAIAbilitySystem()
if (AbilitySystemComponent) if (AbilitySystemComponent)
{ {
InitializeAbilitySystem(AbilitySystemComponent, this); InitializeAbilitySystem(AbilitySystemComponent, this);
}
if (ClassComponent) if (ClassComponent)
{ {
ClassComponent->ApplyClassAttributesToAbilitySystem(AbilitySystemComponent);
ClassComponent->ApplyConfiguredPlayerMeshIfAllowed(this); ClassComponent->ApplyConfiguredPlayerMeshIfAllowed(this);
} }
} }
}

View File

@@ -51,10 +51,11 @@ void APHYPlayerCharacter::InitializeAbilitySystemFromPlayerState()
if (UAbilitySystemComponent* PlayerASC = PHYPlayerState->GetAbilitySystemComponent()) if (UAbilitySystemComponent* PlayerASC = PHYPlayerState->GetAbilitySystemComponent())
{ {
InitializeAbilitySystem(PlayerASC, PHYPlayerState); InitializeAbilitySystem(PlayerASC, PHYPlayerState);
}
if (UPHYClassComponent* ClassComponent = PHYPlayerState->GetClassComponent()) if (UPHYClassComponent* ClassComponent = PHYPlayerState->GetClassComponent())
{ {
ClassComponent->ApplyClassAttributesToAbilitySystem(PlayerASC);
ClassComponent->ApplyConfiguredPlayerMeshIfAllowed(this); ClassComponent->ApplyConfiguredPlayerMeshIfAllowed(this);
} }
} }
}

View File

@@ -4,6 +4,11 @@
#include UE_INLINE_GENERATED_CPP_BY_NAME(PHYClassComponent) #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/PHYAICharacter.h"
#include "Characters/PHYPlayerCharacter.h" #include "Characters/PHYPlayerCharacter.h"
#include "Class/PHYClassSettings.h" #include "Class/PHYClassSettings.h"
@@ -110,3 +115,104 @@ bool UPHYClassComponent::ApplyConfiguredPlayerMeshIfAllowed(AActor* AvatarActor)
Character->GetMesh()->SetSkeletalMesh(PlayerMesh); Character->GetMesh()->SetSkeletalMesh(PlayerMesh);
return true; 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<UPHYClassSettings>();
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;
}

View File

@@ -6,6 +6,16 @@
#include "PHYGameplayTags.h" #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() UPHYClassSettings::UPHYClassSettings()
{ {
DefaultPlayerClassTag = PHYGameplayTags::Class_Saber; DefaultPlayerClassTag = PHYGameplayTags::Class_Saber;
@@ -21,4 +31,33 @@ UPHYClassSettings::UPHYClassSettings()
{ PHYGameplayTags::Class_Assassin, nullptr }, { PHYGameplayTags::Class_Assassin, nullptr },
{ PHYGameplayTags::Class_Berserker, 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;
} }

View File

@@ -8,6 +8,7 @@
#include "PHYClassComponent.generated.h" #include "PHYClassComponent.generated.h"
class USkeletalMesh; class USkeletalMesh;
class UAbilitySystemComponent;
/** /**
* @brief PHY 职业组件。 * @brief PHY 职业组件。
@@ -46,6 +47,10 @@ public:
UFUNCTION(BlueprintCallable, Category="PHY|Class") UFUNCTION(BlueprintCallable, Category="PHY|Class")
bool ApplyConfiguredPlayerMeshIfAllowed(AActor* AvatarActor = nullptr) const; bool ApplyConfiguredPlayerMeshIfAllowed(AActor* AvatarActor = nullptr) const;
/** @brief 在服务器上按当前职业把基础属性和派生属性写入 ASC。 */
UFUNCTION(BlueprintCallable, Category="PHY|Class|Attributes")
bool ApplyClassAttributesToAbilitySystem(UAbilitySystemComponent* AbilitySystemComponent) const;
protected: protected:
/** @brief 当前职业 Tag由服务器复制给客户端。 */ /** @brief 当前职业 Tag由服务器复制给客户端。 */
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Replicated, Category="PHY|Class") UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Replicated, Category="PHY|Class")

View File

@@ -10,6 +10,52 @@
class USkeletalMesh; 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 配置项。 * @brief 玩家职业 Mesh 配置项。
*/ */
@@ -54,6 +100,10 @@ public:
UPROPERTY(Config, EditDefaultsOnly, Category="PHY|Class") UPROPERTY(Config, EditDefaultsOnly, Category="PHY|Class")
TArray<FPHYPlayerClassMeshConfig> PlayerClassMeshes; TArray<FPHYPlayerClassMeshConfig> PlayerClassMeshes;
/** @brief 职业基础属性分配表,首期每个职业总点数必须为 60。 */
UPROPERTY(Config, EditDefaultsOnly, Category="PHY|Class|Attributes")
TArray<FPHYClassCoreAttributeAllocation> CoreAttributeAllocations;
/** @brief 是否允许玩家 Avatar 应用玩家职业 Mesh。 */ /** @brief 是否允许玩家 Avatar 应用玩家职业 Mesh。 */
UPROPERTY(Config, EditDefaultsOnly, Category="PHY|Class") UPROPERTY(Config, EditDefaultsOnly, Category="PHY|Class")
bool bApplyPlayerClassMeshToPlayers = true; bool bApplyPlayerClassMeshToPlayers = true;
@@ -61,4 +111,7 @@ public:
/** @brief 是否允许 AI 应用玩家职业 Mesh默认关闭以保持 AI 外观独立。 */ /** @brief 是否允许 AI 应用玩家职业 Mesh默认关闭以保持 AI 外观独立。 */
UPROPERTY(Config, EditDefaultsOnly, Category="PHY|Class") UPROPERTY(Config, EditDefaultsOnly, Category="PHY|Class")
bool bApplyPlayerClassMeshToAI = false; bool bApplyPlayerClassMeshToAI = false;
/** @brief 按职业 Tag 查找基础属性分配,未配置时返回 nullptr。 */
const FPHYClassCoreAttributeAllocation* FindCoreAttributeAllocation(FGameplayTag ClassTag) const;
}; };