文章目錄
- 創建資產類
- 設置經驗
- 使用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中去掉被動和基礎屬性的
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);}
}
創建一個給英雄一個給小兵
分別配置一下
設置經驗
創建一個曲線表格
再把表格塞進去
創建經驗值的回調
// 判斷是否滿級
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%