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

发布于:2025-07-24 ⋅ 阅读:(21) ⋅ 点赞:(0)


创建资产类

在这里插入图片描述

// 幻雨喜欢小猫咪

#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中去掉被动和基础属性的
在这里插入图片描述

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)
	{
		// 赋予技能 等级为 1
		GiveAbility(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);
	}
}

创建一个给英雄一个给小兵
在这里插入图片描述
在这里插入图片描述
分别配置一下
在这里插入图片描述

在这里插入图片描述

设置经验

创建一个曲线表格
在这里插入图片描述
再把表格塞进去
在这里插入图片描述
创建经验值的回调

// 判断是否满级
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);     // 更新可分配升级点数
}

在这里插入图片描述
在这里插入图片描述

使用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,创建需要调用的属性
在这里插入图片描述
再创建一个无限GE配置一下相关项
在这里插入图片描述
在这里插入图片描述

测试一下
在这里插入图片描述

在这里插入图片描述

调整生命值和法力值

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

	// 根据缓存的生命百分比和新最大生命值重新计算生命值
	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%
在这里插入图片描述


网站公告

今日签到

点亮在社区的每一天
去签到