> 技术文档 > UE5多人MOBA+GAS 28、创建资产类来管理GAS通用的资产、设置经验表来升级以及用MMC计算升级添加的属性值

UE5多人MOBA+GAS 28、创建资产类来管理GAS通用的资产、设置经验表来升级以及用MMC计算升级添加的属性值


文章目录

  • 创建资产类
  • 设置经验
  • 使用MMC来计算角色升级的属性
  • 调整生命值和法力值

创建资产类

UE5多人MOBA+GAS 28、创建资产类来管理GAS通用的资产、设置经验表来升级以及用MMC计算升级添加的属性值

// 幻雨喜欢小猫咪#pragma once#include \"CoreMinimal.h\"#include \"Abilities/GameplayAbility.h\"#include \"Engine/DataAsset.h\"#include \"PDA_AbilitySystemGenerics.generated.h\"/** * 通用能力系统数据资产类 * 存储游戏所有角色能力系统的公共配置数据 * 包含基础属性、游戏效果、被动技能等配置信息 */UCLASS()class CRUNCH_API UPDA_AbilitySystemGenerics : public UPrimaryDataAsset{GENERATED_BODY()public:// 获取完整属性效果类// 用于初始化角色基础属性值FORCEINLINE TSubclassOf<UGameplayEffect> GetFullStatEffect() const { return FullStatEffect; }// 获取死亡效果类// 角色死亡时应用的全局游戏效果FORCEINLINE TSubclassOf<UGameplayEffect> GetDeathEffect() const { return DeathEffect; }// 获取初始效果数组// 角色初始化时自动应用的游戏效果集合FORCEINLINE const TArray<TSubclassOf<UGameplayEffect>>& GetInitialEffects() const { return InitialEffects; }// 获取被动技能数组// 角色默认解锁的被动技能列表FORCEINLINE const TArray<TSubclassOf<UGameplayAbility>>& GetPassiveAbilities() const { return PassiveAbilities; }// 获取基础属性数据表// 存储角色基础属性(力量、敏捷等)的DataTable资源FORCEINLINE const UDataTable* GetBaseStatDataTable() const { return BaseStatDataTable; }// 获取经验曲线数据// 用于计算角色升级所需经验值的曲线const FRealCurve* GetExperienceCurve() const;private:// 全局属性效果UPROPERTY(EditDefaultsOnly, Category = \"Gameplay Effects\")TSubclassOf<UGameplayEffect> FullStatEffect;// 死亡惩罚效果UPROPERTY(EditDefaultsOnly, Category = \"Gameplay Effects\")TSubclassOf<UGameplayEffect> DeathEffect;// 初始效果列表UPROPERTY(EditDefaultsOnly, Category = \"Gameplay Effects\")TArray<TSubclassOf<UGameplayEffect>> InitialEffects;// 默认被动技能列表UPROPERTY(EditDefaultsOnly, Category = \"Gameplay Ability\")TArray<TSubclassOf<UGameplayAbility>> PassiveAbilities;// 基础属性数据表资源UPROPERTY(EditDefaultsOnly, Category = \"Base Stats\")TObjectPtr<UDataTable> BaseStatDataTable;// 经验等级曲线名称// 指定经验曲线表中使用的行名称UPROPERTY(EditDefaultsOnly, Category = \"Level\")FName ExperienceRowName = \"ExperienceNeededToReachLevel\";// 经验曲线资源// 存储等级-经验值对应关系的曲线表UPROPERTY(EditDefaultsOnly, Category = \"Level\")TObjectPtr<UCurveTable> ExperienceCurveTable;};
const FRealCurve* UPDA_AbilitySystemGenerics::GetExperienceCurve() const{return ExperienceCurveTable->FindCurve(ExperienceRowName, \"\");}

ASC中去掉被动和基础属性的
UE5多人MOBA+GAS 28、创建资产类来管理GAS通用的资产、设置经验表来升级以及用MMC计算升级添加的属性值

ASC.cpp中需要更改的部分

void UCAbilitySystemComponent::InitializeBaseAttributes(){if (!AbilitySystemGenerics || !AbilitySystemGenerics->GetBaseStatDataTable() || !GetOwner()){return;}// 获取基础属性数据表和角色对应的配置数据const UDataTable* BaseStatDataTable = AbilitySystemGenerics->GetBaseStatDataTable();const FTHeroBaseStats* BaseStats = nullptr;}void UCAbilitySystemComponent::ApplyInitialEffects(){// 检查当前组件是否拥有拥有者,并且拥有者是否具有网络权限(权威性) if (!GetOwner() || !GetOwner()->HasAuthority()) return;if (!AbilitySystemGenerics)return;for (const TSubclassOf<UGameplayEffect>& EffectClass : AbilitySystemGenerics->GetInitialEffects()){// 创建游戏效果规格句柄,用于描述要应用的效果及其上下文FGameplayEffectSpecHandle EffectSpecHandle = MakeOutgoingSpec(EffectClass, 1, MakeEffectContext());// 将游戏效果应用到自身ApplyGameplayEffectSpecToSelf(*EffectSpecHandle.Data.Get());}}void UCAbilitySystemComponent::ApplyFullStatEffect(){if (!AbilitySystemGenerics || !AbilitySystemGenerics->GetFullStatEffect())return;AuthApplyGameplayEffect(AbilitySystemGenerics->GetFullStatEffect());}void UCAbilitySystemComponent::GiveInitialAbilities(){// 检查当前组件是否拥有拥有者,并且拥有者是否具有网络权限(权威性) if (!GetOwner() || !GetOwner()->HasAuthority()) return;for (const TPair<ECAbilityInputID,TSubclassOf<UGameplayAbility>>& AbilityPair : BasicAbilities){// 赋予技能 等级为 1GiveAbility(FGameplayAbilitySpec(AbilityPair.Value, 1, static_cast<int32>(AbilityPair.Key), nullptr));}for (const TPair<ECAbilityInputID,TSubclassOf<UGameplayAbility>>& AbilityPair : Abilities){GiveAbility(FGameplayAbilitySpec(AbilityPair.Value, 0, static_cast<int32>(AbilityPair.Key), nullptr));}// 需要更改的被动技能地方if (!AbilitySystemGenerics)return;for (const TSubclassOf<UGameplayAbility>& PassiveAbility : AbilitySystemGenerics->GetPassiveAbilities()){GiveAbility(FGameplayAbilitySpec(PassiveAbility, 1, -1, nullptr));}}void UCAbilitySystemComponent::HealthUpdated(const FOnAttributeChangeData& ChangeData){if (!GetOwner() || !GetOwner()->HasAuthority()) return;// 获取当前最大生命值bool bFound = false;float MaxHealth = GetGameplayAttributeValue(UCAttributeSet::GetMaxHealthAttribute(), bFound); // 如果生命值达到最大值,添加生命值已满标签if (bFound && ChangeData.NewValue >= MaxHealth){if (!HasMatchingGameplayTag(TGameplayTags::Stats_Health_Full)){// 仅本地会添加标签AddLooseGameplayTag(TGameplayTags::Stats_Health_Full);}}else{// 移除生命值已满标签RemoveLooseGameplayTag(TGameplayTags::Stats_Health_Full);}if (ChangeData.NewValue <= 0.0f){if (!HasMatchingGameplayTag(TGameplayTags::Stats_Health_Empty)){// 本地添加生命值清零标签AddLooseGameplayTag(TGameplayTags::Stats_Health_Empty);// 角色死亡if(AbilitySystemGenerics && AbilitySystemGenerics->GetDeathEffect())AuthApplyGameplayEffect(AbilitySystemGenerics->GetDeathEffect());// TODO:这里是由GE直接扣血的时候触发这种的死亡,我使用的是GCC触发的方式是在属性这边发送事件// // 创建需要传给死亡被动技能的事件数据// FGameplayEventData DeadAbilityEventData;// if (ChangeData.GEModData)// {// DeadAbilityEventData.ContextHandle = ChangeData.GEModData->EffectSpec.GetContext();// }else// {// UE_LOG(LogTemp, Error, TEXT(\"ChangeData.GEModData is null\"))// }// UAbilitySystemBlueprintLibrary::SendGameplayEventToActor(GetOwner(), // TGameplayTags::Stats_Dead, // DeadAbilityEventData);}}else{RemoveLooseGameplayTag(TGameplayTags::Stats_Health_Empty);}}

创建一个给英雄一个给小兵
UE5多人MOBA+GAS 28、创建资产类来管理GAS通用的资产、设置经验表来升级以及用MMC计算升级添加的属性值
UE5多人MOBA+GAS 28、创建资产类来管理GAS通用的资产、设置经验表来升级以及用MMC计算升级添加的属性值
分别配置一下
UE5多人MOBA+GAS 28、创建资产类来管理GAS通用的资产、设置经验表来升级以及用MMC计算升级添加的属性值

UE5多人MOBA+GAS 28、创建资产类来管理GAS通用的资产、设置经验表来升级以及用MMC计算升级添加的属性值

设置经验

创建一个曲线表格
UE5多人MOBA+GAS 28、创建资产类来管理GAS通用的资产、设置经验表来升级以及用MMC计算升级添加的属性值
再把表格塞进去
UE5多人MOBA+GAS 28、创建资产类来管理GAS通用的资产、设置经验表来升级以及用MMC计算升级添加的属性值
创建经验值的回调

// 判断是否满级bool IsAtMaxLevel() const;void ExperienceUpdated(const FOnAttributeChangeData& ChangeData);
bool UCAbilitySystemComponent::IsAtMaxLevel() const{bool bFound;float CurrentLevel = GetGameplayAttributeValue(UCHeroAttributeSet::GetLevelAttribute(), bFound);float MaxLevel = GetGameplayAttributeValue(UCHeroAttributeSet::GetMaxLevelAttribute(), bFound);return CurrentLevel >= MaxLevel;}UCAbilitySystemComponent::UCAbilitySystemComponent(){GetGameplayAttributeValueChangeDelegate(UCAttributeSet::GetHealthAttribute()).AddUObject(this, &UCAbilitySystemComponent::HealthUpdated);GetGameplayAttributeValueChangeDelegate(UCAttributeSet::GetManaAttribute()).AddUObject(this, &UCAbilitySystemComponent::ManaUpdated);GetGameplayAttributeValueChangeDelegate(UCHeroAttributeSet::GetExperienceAttribute()).AddUObject(this, &UCAbilitySystemComponent::ExperienceUpdated);GenericConfirmInputID = static_cast<int32>(ECAbilityInputID::Confirm);GenericCancelInputID = static_cast<int32>(ECAbilityInputID::Cancel);}void UCAbilitySystemComponent::InitializeBaseAttributes(){if (!AbilitySystemGenerics || !AbilitySystemGenerics->GetBaseStatDataTable() || !GetOwner()){return;}// 处理经验系统配置const FRealCurve* ExperienceCurve = AbilitySystemGenerics->GetExperienceCurve();if (ExperienceCurve){int MaxLevel = ExperienceCurve->GetNumKeys(); // 经验曲线中的最大等级SetNumericAttributeBase(UCHeroAttributeSet::GetMaxLevelAttribute(), MaxLevel); // 设置角色最大等级限制float MaxExp = ExperienceCurve->GetKeyValue(ExperienceCurve->GetLastKeyHandle()); // 最高等级所需经验SetNumericAttributeBase(UCHeroAttributeSet::GetMaxLevelExperienceAttribute(), MaxExp); // 设置最高等级经验阈值// 输出调试信息UE_LOG(LogTemp, Warning, TEXT(\"最大等级为: %d, 最大经验值为: %f\"), MaxLevel, MaxExp);} ExperienceUpdated(FOnAttributeChangeData());}void UCAbilitySystemComponent::ExperienceUpdated(const FOnAttributeChangeData& ChangeData){// 仅在拥有者存在且为服务器时执行if (!GetOwner() || !GetOwner()->HasAuthority()) return;// 满级返回if (IsAtMaxLevel()) return;// 检查能力系统通用配置是否有效if (!AbilitySystemGenerics)return;// 获取当前经验值float CurrentExp = ChangeData.NewValue;// 从配置中获取经验曲线数据(等级->所需经验的映射)const FRealCurve* ExperienceCurve = AbilitySystemGenerics->GetExperienceCurve();if (!ExperienceCurve){UE_LOG(LogTemp, Warning, TEXT(\"无法找到经验数据!\"));return;}float PrevLevelExp = 0.f;// 当前等级的最低经验值float NextLevelExp = 0.f;// 下一级所需最低经验值float NewLevel = 1.f;// 新的等级for (auto Iter = ExperienceCurve->GetKeyHandleIterator(); Iter; ++Iter){// 获取当前等级(NewLevel)对应的升级经验阈值float ExperienceToReachLevel = ExperienceCurve->GetKeyValue(*Iter);if (CurrentExp < ExperienceToReachLevel){// 找到第一个大于当前经验的等级阈值NextLevelExp = ExperienceToReachLevel;break;}// 记录当前等级的最低经验值PrevLevelExp = ExperienceToReachLevel;NewLevel = Iter.GetIndex() + 1;// 等级加一}// 获取当前等级以及可用的升级点数float CurrentLevel = GetNumericAttributeBase(UCHeroAttributeSet::GetLevelAttribute());float CurrentUpgradePoint = GetNumericAttribute(UCHeroAttributeSet::GetUpgradePointAttribute());// 计算等级提升数float LevelUpgraded = NewLevel - CurrentLevel;// 累加升级点数(当前点数+升级的级数)float NewUpgradePoint = CurrentUpgradePoint + LevelUpgraded;// 更新角色的属性值SetNumericAttributeBase(UCHeroAttributeSet::GetLevelAttribute(), NewLevel); // 设置新等级SetNumericAttributeBase(UCHeroAttributeSet::GetPrevLevelExperienceAttribute(), PrevLevelExp); // 设置当前等级经验基准SetNumericAttributeBase(UCHeroAttributeSet::GetNextLevelExperienceAttribute(), NextLevelExp); // 设置下等级经验基准SetNumericAttributeBase(UCHeroAttributeSet::GetUpgradePointAttribute(), NewUpgradePoint); // 更新可分配升级点数}

UE5多人MOBA+GAS 28、创建资产类来管理GAS通用的资产、设置经验表来升级以及用MMC计算升级添加的属性值
UE5多人MOBA+GAS 28、创建资产类来管理GAS通用的资产、设置经验表来升级以及用MMC计算升级添加的属性值

使用MMC来计算角色升级的属性值

UE5多人MOBA+GAS 28、创建资产类来管理GAS通用的资产、设置经验表来升级以及用MMC计算升级添加的属性值

// 幻雨喜欢小猫咪#pragma once#include \"CoreMinimal.h\"#include \"GameplayModMagnitudeCalculation.h\"#include \"MMC_LevelBased.generated.h\"/** * */UCLASS()class CRUNCH_API UMMC_LevelBased : public UGameplayModMagnitudeCalculation{GENERATED_BODY()public:UMMC_LevelBased();float CalculateBaseMagnitude_Implementation(const FGameplayEffectSpec& Spec) const override;private:UPROPERTY(EditDefaultsOnly)FGameplayAttribute RateAttribute;FGameplayEffectAttributeCaptureDefinition LevelCaptureDefinition;};
// 幻雨喜欢小猫咪#include \"GAS/MMC/MMC_LevelBased.h\"#include \"GAS/Core/CHeroAttributeSet.h\"UMMC_LevelBased::UMMC_LevelBased(){LevelCaptureDefinition.AttributeToCapture = UCHeroAttributeSet::GetLevelAttribute(); // 捕获目标等级属性// 设置捕获对象,这里设置目标还是源都无所谓,因为是自己给自己LevelCaptureDefinition.AttributeSource = EGameplayEffectAttributeCaptureSource::Target;// 注册捕获属性RelevantAttributesToCapture.Add(LevelCaptureDefinition);}float UMMC_LevelBased::CalculateBaseMagnitude_Implementation(const FGameplayEffectSpec& Spec) const{// 获取ASC,用来获取属性UAbilitySystemComponent* ASC = Spec.GetContext().GetInstigatorAbilitySystemComponent();if (!ASC) return 0.f;float Level = 0.f;// 设置评估参数FAggregatorEvaluateParameters EvalParams;// 绑定源/目标标签EvalParams.SourceTags = Spec.CapturedSourceTags.GetAggregatedTags();EvalParams.TargetTags = Spec.CapturedTargetTags.GetAggregatedTags();// 获取目标对象的等级属性值GetCapturedAttributeMagnitude(LevelCaptureDefinition, Spec, EvalParams, Level);// 获取预设的成长属性值bool bFound;float RateAttributeVal = ASC->GetGameplayAttributeValue(RateAttribute, bFound);if (!bFound)return 0.f;return (Level - 1) * RateAttributeVal;}

蓝图中继承该mmc,创建需要调用的属性
UE5多人MOBA+GAS 28、创建资产类来管理GAS通用的资产、设置经验表来升级以及用MMC计算升级添加的属性值
再创建一个无限GE配置一下相关项
UE5多人MOBA+GAS 28、创建资产类来管理GAS通用的资产、设置经验表来升级以及用MMC计算升级添加的属性值
UE5多人MOBA+GAS 28、创建资产类来管理GAS通用的资产、设置经验表来升级以及用MMC计算升级添加的属性值

测试一下
UE5多人MOBA+GAS 28、创建资产类来管理GAS通用的资产、设置经验表来升级以及用MMC计算升级添加的属性值

UE5多人MOBA+GAS 28、创建资产类来管理GAS通用的资产、设置经验表来升级以及用MMC计算升级添加的属性值

调整生命值和法力值

这个最大生命增加后,血条并没有发生什么变化,因此需要在最大值更新的时候按照更新前的百分比修正一下

// 根据缓存的生命百分比和新最大生命值重新计算生命值void RescaleHealth();// 根据缓存的法力百分比和最大法力值重新计算法力值void RescaleMana();// 缓存的生命百分比UPROPERTY()FGameplayAttributeData CachedHealthPercent;ATTRIBUTE_ACCESSORS(UCAttributeSet, CachedHealthPercent)// 缓存的法力百分比UPROPERTY()FGameplayAttributeData CachedManaPercent;ATTRIBUTE_ACCESSORS(UCAttributeSet, CachedManaPercent)

在生命或法力发生变化的时候缓存一下百分比

void UCAttributeSet::PostGameplayEffectExecute(const FGameplayEffectModCallbackData& Data){if (Data.EvaluatedData.Attribute == GetHealthAttribute()){SetHealth(FMath::Clamp(GetHealth(), 0, GetMaxHealth()));SetCachedHealthPercent(GetHealth()/GetMaxHealth());}if (Data.EvaluatedData.Attribute == GetManaAttribute()){SetMana(FMath::Clamp(GetMana(), 0, GetMaxMana()));SetCachedManaPercent(GetMana()/GetMaxMana());}}void UCAttributeSet::RescaleHealth(){if (!GetOwningActor()->HasAuthority())return;if (GetCachedHealthPercent() != 0 && GetHealth() != 0){SetHealth(GetMaxHealth() * GetCachedHealthPercent());}}void UCAttributeSet::RescaleMana(){if (!GetOwningActor()->HasAuthority())return;if (GetCachedManaPercent() != 0 && GetMana() != 0){SetMana(GetMaxMana() * GetCachedManaPercent());}}

到角色类中添加最大生命和法力的回调,调用缓存更新值

// 最大生命值改变回调void MaxHealthUpdated(const FOnAttributeChangeData& Data);// 最大法力值改变回调void MaxManaUpdated(const FOnAttributeChangeData& Data);
void ACCharacter::BindGASChangeDelegates(){if (CAbilitySystemComponent){CAbilitySystemComponent->RegisterGameplayTagEvent(TGameplayTags::Stats_Dead).AddUObject(this, &ACCharacter::DeathTagUpdated);CAbilitySystemComponent->RegisterGameplayTagEvent(TGameplayTags::Stats_Stun).AddUObject(this, &ACCharacter::StunTagUpdated);CAbilitySystemComponent->RegisterGameplayTagEvent(TGameplayTags::Stats_Aim).AddUObject(this, &ACCharacter::AimTagUpdated);CAbilitySystemComponent->GetGameplayAttributeValueChangeDelegate(CAttributeSet->GetMoveSpeedAttribute()).AddUObject(this, &ACCharacter::MoveSpeedUpdated);CAbilitySystemComponent->GetGameplayAttributeValueChangeDelegate(UCAttributeSet::GetMaxHealthAttribute()).AddUObject(this, &ACCharacter::MaxHealthUpdated);CAbilitySystemComponent->GetGameplayAttributeValueChangeDelegate(UCAttributeSet::GetMaxManaAttribute()).AddUObject(this, &ACCharacter::MaxManaUpdated);}}void ACCharacter::MaxHealthUpdated(const FOnAttributeChangeData& Data){if (IsValid(CAttributeSet)){CAttributeSet->RescaleHealth();}}void ACCharacter::MaxManaUpdated(const FOnAttributeChangeData& Data){if (IsValid(CAttributeSet)){CAttributeSet->RescaleMana();}}

升级的时候血条本来100%升级完也是100%
UE5多人MOBA+GAS 28、创建资产类来管理GAS通用的资产、设置经验表来升级以及用MMC计算升级添加的属性值