UE5多人MOBA+GAS 30、技能升级机制

文章目录


前言

重写技能基类的判断是否可以释放技能的函数CanActivateAbility,因为基本技能和被动的初始等级是1,技能数组里的需要通过学习才能释放。

cpp 复制代码
virtual bool CanActivateAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayTagContainer* SourceTags = nullptr, const FGameplayTagContainer* TargetTags = nullptr, OUT FGameplayTagContainer* OptionalRelevantTags = nullptr) const override;

获取技能的等级如果技能等级小于等于0返回false,不能释放

cpp 复制代码
bool UCGameplayAbility::CanActivateAbility(const FGameplayAbilitySpecHandle Handle,
	const FGameplayAbilityActorInfo* ActorInfo, const FGameplayTagContainer* SourceTags,
	const FGameplayTagContainer* TargetTags, FGameplayTagContainer* OptionalRelevantTags) const
{
	FGameplayAbilitySpec* AbilitySpec = ActorInfo->AbilitySystemComponent->FindAbilitySpecFromHandle(Handle);
	if (AbilitySpec && AbilitySpec->Level <= 0)
	{
		return false;
	}
	return Super::CanActivateAbility(Handle, ActorInfo, SourceTags, TargetTags, OptionalRelevantTags);
}

技能的升级

修改一下按键的输入

CGameplayAbilityTypesECAbilityInputID 添加一些新的输入

cpp 复制代码
UENUM(BlueprintType)
enum class ECAbilityInputID : uint8
{
	None							UMETA(DisplayName="None"),
	BasicAttack						UMETA(DisplayName="基础攻击"),
	Aim								UMETA(DisplayName="瞄准"),
	AbilityOne						UMETA(DisplayName="一技能"),
	AbilityTwo						UMETA(DisplayName="二技能"),
	AbilityThree					UMETA(DisplayName="三技能"),
	AbilityFour						UMETA(DisplayName="四技能"),
	AbilityFive						UMETA(DisplayName="五技能"),
	AbilitySix						UMETA(DisplayName="六技能"),
	AbilityQ						UMETA(DisplayName="Q技能"),
	AbilityE						UMETA(DisplayName="E技能"),
	AbilityF						UMETA(DisplayName="F技能"),
	AbilityR						UMETA(DisplayName="R技能"),
	Confirm							UMETA(DisplayName="确认"),
	Cancel							UMETA(DisplayName="取消")
};



判断是否满级

技能的升级之前需要先判断技能是否满级,在CAbilitySystemStatics中添加一个函数判断是否为满级

cpp 复制代码
	// 判断技能是否达到最大等级
	static bool IsAbilityAtMaxLevel(const FGameplayAbilitySpec& Spec, const float PlayLevel);
cpp 复制代码
bool UCAbilitySystemStatics::IsAbilityAtMaxLevel(const FGameplayAbilitySpec& Spec, const float PlayLevel)
{
	float MaxLevel;
	// 如果是大招进来了
	if (Spec.InputID == static_cast<int32>(ECAbilityInputID::AbilityR))
	{
		// 6~10 : 1
		// 11~15 : 2
		// 16~18 : 3
		MaxLevel = PlayLevel >= 16 ? 3 :
				   PlayLevel >= 11 ? 2 :
				   PlayLevel >= 6 ? 1 :
					0;
	}else
	{
		// Q、E、F的小技能
		/*
		* 1 ~ 2 :1
		* 3 ~ 4 :2
		* 5 ~ 6 :3
		* 7 ~ 8 :4
		* 9 ~18:5
		 */
		MaxLevel = PlayLevel >= 9 ? 5 :
					PlayLevel >= 7 ? 4 :
					PlayLevel >= 5 ? 3 :
					PlayLevel >= 3 ? 2 :
					1;
	}
	// Spec.InputID 
	return Spec.Level >= MaxLevel;
}

在ASC中升级技能

cpp 复制代码
	/**
	 * 服务器端处理能力升级请求
	 * 通过指定的ECAbilityInputID参数升级对应能力
	 * 包含可靠的网络验证机制
	 * @param InputID - 要升级的能力输入ID
	 */
	UFUNCTION(Server, Reliable, WithValidation)
	void Server_UpgradeAbilityWithID(ECAbilityInputID InputID);
	
	/**
	 * 客户端能力等级更新同步
	 * 当能力等级发生变化时触发网络同步
	 * 通过GameplayAbilitySpecHandle定位具体能力实例
	 * @param Handle - 能力实例句柄
	 * @param NewLevel - 新的能力等级数值
	 */
	UFUNCTION(Client, Reliable)
	void Client_AbilitySpecLevelUpdated(FGameplayAbilitySpecHandle Handle, int NewLevel);

在ASC中实现技能在服务器中升级,并响应客户端的同步更新

cpp 复制代码
void void UCAbilitySystemComponent::Server_UpgradeAbilityWithID_Implementation(ECAbilityInputID InputID)
{
	// 获取可用升级点数
	bool bFound = false;
	float UpgradePoint = GetGameplayAttributeValue(UCHeroAttributeSet::GetUpgradePointAttribute(), bFound);
	// 检查可用升级点数是否大于0
	if (!bFound && UpgradePoint <= 0) return;

	// 获取玩家等级
	float CurrentLevel = GetGameplayAttributeValue(UCHeroAttributeSet::GetLevelAttribute(), bFound);
	if (!bFound) return;
	
	// 获取对应输入ID的技能
	FGameplayAbilitySpec* AbilitySpec = FindAbilitySpecFromInputID(static_cast<int32>(InputID));
	// 检查是否有该技能以及等级是否满级
	if (!AbilitySpec || UCAbilitySystemStatics::IsAbilityAtMaxLevel(*AbilitySpec,CurrentLevel))
	{
		return;
	}
	UE_LOG(LogTemp, Warning, TEXT("技能升级成功%d"),InputID)
	// 消耗一个技能点升级技能
	SetNumericAttributeBase(UCHeroAttributeSet::GetUpgradePointAttribute(), UpgradePoint - 1);
	AbilitySpec->Level += 1;
	// 标记 FGameplayAbilitySpec 状态已改变,通知 GAS 需要将其复制到客户端
	// (直接修改AbilitySpec成员后必须调用此函数)
	MarkAbilitySpecDirty(*AbilitySpec);

	// 通知客户端更新技能等级
	Client_AbilitySpecLevelUpdated(AbilitySpec->Handle, AbilitySpec->Level);
}

bool UCAbilitySystemComponent::Server_UpgradeAbilityWithID_Validate(ECAbilityInputID InputID)
{
	return true;
}

void UCAbilitySystemComponent::Client_AbilitySpecLevelUpdated_Implementation(FGameplayAbilitySpecHandle Handle,
	int NewLevel)
{
	// 通过句柄查找本地技能实例
	if (FGameplayAbilitySpec* const Spec = FindAbilitySpecFromHandle(Handle))
	{
		// 更新客户端技能等级
		Spec->Level = NewLevel;
		
		// 广播变更通知,刷新等客户端响应
		AbilitySpecDirtiedCallbacks.Broadcast(*Spec);
	}
}

由角色的输入调用ASC的升级功能

到角色CCharacter中调用技能的升级,

cpp 复制代码
protected:
	void UpgradeAbilityWithInputID(ECAbilityInputID InputID);
cpp 复制代码
void ACCharacter::UpgradeAbilityWithInputID(ECAbilityInputID InputID)
{
	if (CAbilitySystemComponent)
	{
		CAbilitySystemComponent->Server_UpgradeAbilityWithID(InputID);
	}
}

再到玩家角色中通过输入来调用该升级函数,添加一个新的输入用来判断Ctrl按键是否按下,用Ctrl+技能输入ID来进行升级

cpp 复制代码
	// 技能升级触发键
	UPROPERTY(EditDefaultsOnly, Category = "Input")
	TObjectPtr<UInputAction> LearnAbilityLeaderAction;
	
	// 技能升级触发按下
	void LearnAbilityLeaderDown(const FInputActionValue& InputActionValue);
	// 技能升级触发抬起
	void LearnAbilityLeaderUp(const FInputActionValue& InputActionValue);
	// 是否按下技能升级键
	bool bIsLearnAbilityLeaderDown = false;

在这里我只想要我的设定几个按键的技能可以升级

cpp 复制代码
void ACPlayerCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
	Super::SetupPlayerInputComponent(PlayerInputComponent);
	if (UEnhancedInputComponent* EnhancedInputComponent = CastChecked<UEnhancedInputComponent>(PlayerInputComponent))
	{
		// 绑定跳、看、走
		EnhancedInputComponent->BindAction(JumpInputAction, ETriggerEvent::Triggered, this, &ACPlayerCharacter::Jump);
		EnhancedInputComponent->BindAction(LookInputAction, ETriggerEvent::Triggered, this, &ACPlayerCharacter::HandleLookInput);
		EnhancedInputComponent->BindAction(MoveInputAction, ETriggerEvent::Triggered, this, &ACPlayerCharacter::HandleMoveInput);

		// 按下
		EnhancedInputComponent->BindAction(LearnAbilityLeaderAction, ETriggerEvent::Started, this, &ACPlayerCharacter::LearnAbilityLeaderDown);
		// 抬起
		EnhancedInputComponent->BindAction(LearnAbilityLeaderAction, ETriggerEvent::Completed, this, &ACPlayerCharacter::LearnAbilityLeaderUp);

		// 绑定技能输入
		for (const TPair<ECAbilityInputID, TObjectPtr<UInputAction>>& InputActionPair : GameplayAbilityInputActions)
		{
			EnhancedInputComponent->BindAction(InputActionPair.Value, ETriggerEvent::Triggered, this, &ACPlayerCharacter::HandleAbilityInput, InputActionPair.Key);
		}
	}
}

void ACPlayerCharacter::HandleAbilityInput(const FInputActionValue& InputActionValue, ECAbilityInputID InputID)
{
	bool bPressed = InputActionValue.Get<bool>();

	// 技能升级
	if (bPressed && bIsLearnAbilityLeaderDown)
	{
		// 只会升级Q、E、F、R技能
		if (InputID >= ECAbilityInputID::AbilityQ && InputID <= ECAbilityInputID::AbilityR)
		{
			UpgradeAbilityWithInputID(InputID);
		}
		return;
	}
	
	// 按下
	if (bPressed)
	{
		GetAbilitySystemComponent()->AbilityLocalInputPressed(static_cast<int32>(InputID));
	}
	else
	{
		GetAbilitySystemComponent()->AbilityLocalInputReleased(static_cast<int32>(InputID));
	}

	// 按下的是普攻键
	if (InputID == ECAbilityInputID::BasicAttack)
	{
		FGameplayTag BasicAttackTag = bPressed ? TGameplayTags::Ability_BasicAttack_Pressed : TGameplayTags::Ability_BasicAttack_Released;
		// 1. 本地直接广播(触发客户端即时反馈)
		// 2. 服务器RPC广播(确保权威状态同步)
		UAbilitySystemBlueprintLibrary::SendGameplayEventToActor(this, BasicAttackTag, FGameplayEventData());
		Server_SendGameplayEventToSelf(BasicAttackTag, FGameplayEventData());
	}
}

void ACPlayerCharacter::LearnAbilityLeaderDown(const FInputActionValue& InputActionValue)
{
	bIsLearnAbilityLeaderDown = true;
}

void ACPlayerCharacter::LearnAbilityLeaderUp(const FInputActionValue& InputActionValue)
{
	bIsLearnAbilityLeaderDown = false;
}

创建一个输入

技能图标的优化

技能升级材质,可升级技能图标的闪烁

复制代码
float2 coord = (uvcoord - float2(0.5, 0.5)) * 2;

float distanceToEdge = 1-abs(coord.x) > 1-abs(coord.y) ? 1 -  abs(coord.y) : 1-abs(coord.x);

float alpha = 0;

if(distanceToEdge < shadeThickness)
{
   alpha = 1 - distanceToEdge / shadeThickness;
}


return lerp(float3(0,0,0), shadeColor, alpha);



UTiling用于设置技能的最大等级(原本的基础上拓展了一下)

对技能图标AbilityGauge添加代码

cpp 复制代码
	// 技能等级材质参数名
	UPROPERTY(EditDefaultsOnly, Category = "Visual")
	FName AbilityLevelParamName = "Level";

	// 能否释放技能材质参数名
	UPROPERTY(EditDefaultsOnly, Category = "Visual")
	FName CanCastAbilityParamName = "CanCast";
    
	// 是否有可用升级点材质参数名
	UPROPERTY(EditDefaultsOnly, Category = "Visual")
	FName UpgradePointAvailableParamName = "UpgradeAvailable";

	// 最大升级的材质参数名
	UPROPERTY(EditDefaultsOnly, Category = "Visual")
	FName MaxLevelParamName = "UTiling";

	// 技能等级进度条控件
	UPROPERTY(meta=(BindWidget))
	TObjectPtr<UImage> LevelGauge;


	// 技能所属的能力组件
	TObjectPtr<const UAbilitySystemComponent> OwnerAbilitySystemComponent;

	// 缓存的技能句柄
	FGameplayAbilitySpecHandle CachedAbilitySpecHandle;

	// 获取技能Spec
	const FGameplayAbilitySpec* GetAbilitySpec();

	// 技能是否学习
	bool bIsAbilityLearned = false;

	// 技能更新回调
	void AbilitySpecUpdated(const FGameplayAbilitySpec& AbilitySpec);

	// 刷新技能能否释放技能状态
	void UpdateCanCast();

	// 技能升级点变化回调
	void UpgradePointUpdated(const FOnAttributeChangeData& Data);

此处的监听是在,ASC的客户端更新处广播的

cpp 复制代码
void UAbilityGauge::NativeConstruct()
{
	Super::NativeConstruct();
	// 隐藏冷却计时器
	CooldownCounterText->SetVisibility(ESlateVisibility::Hidden);

	UAbilitySystemComponent* OwnerASC = UAbilitySystemBlueprintLibrary::GetAbilitySystemComponent(GetOwningPlayerPawn());
	if (OwnerASC)
	{
		// 监听技能释放
		OwnerASC->AbilityCommittedCallbacks.AddUObject(this, &UAbilityGauge::AbilityCommitted);

		// 监听技能规格更新
		OwnerASC->AbilitySpecDirtiedCallbacks.AddUObject(this, &UAbilityGauge::AbilitySpecUpdated);

		// 绑定升级点属性变化事件 - 当玩家获得/消耗升级点时触发
		OwnerASC->GetGameplayAttributeValueChangeDelegate(UCHeroAttributeSet::GetUpgradePointAttribute())
			.AddUObject(this, &UAbilityGauge::UpgradePointUpdated);

		// 初始化升级点显示(获取当前值并刷新UI)
        bool bFound = false;
        float UpgradePoint = OwnerASC->GetGameplayAttributeValue(UCHeroAttributeSet::GetUpgradePointAttribute(), bFound);
        if (bFound)
        {
            // 创建属性变化数据结构(模拟属性变化事件)
            FOnAttributeChangeData ChangeData;
            ChangeData.NewValue = UpgradePoint;
            
            // 手动调用升级点更新函数以刷新UI
            UpgradePointUpdated(ChangeData);
        }
        // 保存能力系统组件引用供后续使用
		OwnerAbilitySystemComponent = OwnerASC;
	}
	
	WholeNumberFormattingOptions.MaximumFractionalDigits = 0;
	TwoDigitNumberFormattingOptions.MaximumFractionalDigits = 2;
}

void UAbilityGauge::NativeOnListItemObjectSet(UObject* ListItemObject)
{
	IUserObjectListEntry::NativeOnListItemObjectSet(ListItemObject);
	AbilityCDO = Cast<UGameplayAbility>(ListItemObject);

	// 获取冷却和消耗
	float CoolDownDuration = UCAbilitySystemStatics::GetStaticCooldownDurationForAbility(AbilityCDO);
	float Cost = UCAbilitySystemStatics::GetStaticCostForAbility(AbilityCDO);

	// 设置冷却和消耗
	CooldownDurationText->SetText(FText::AsNumber(CoolDownDuration));
	CostText->SetText(FText::AsNumber(Cost));
	LevelGauge->GetDynamicMaterial()->SetScalarParameterValue(AbilityLevelParamName, 0);
}
// 这里我添加了技能等级的初始化
void UAbilityGauge::NativeOnListItemObjectSet(UObject* ListItemObject)
{
	IUserObjectListEntry::NativeOnListItemObjectSet(ListItemObject);
	AbilityCDO = Cast<UGameplayAbility>(ListItemObject);

	// 获取冷却和消耗
	float CoolDownDuration = UCAbilitySystemStatics::GetStaticCooldownDurationForAbility(AbilityCDO);
	float Cost = UCAbilitySystemStatics::GetStaticCostForAbility(AbilityCDO);

	// 设置冷却和消耗
	CooldownDurationText->SetText(FText::AsNumber(CoolDownDuration));
	CostText->SetText(FText::AsNumber(Cost));
	// 初始化技能等级
	LevelGauge->GetDynamicMaterial()->SetScalarParameterValue(AbilityLevelParamName, 0);
	// 初始化技能的最大等级
	// 获取当前技能规格
	const FGameplayAbilitySpec* AbilitySpec = GetAbilitySpec();
	if (AbilitySpec)
	{
		float MaxLevel = AbilitySpec->InputID == static_cast<int32>(ECAbilityInputID::AbilityR) ? 3 : 5;
		LevelGauge->GetDynamicMaterial()->SetScalarParameterValue(MaxLevelParamName, MaxLevel);
	}
}

const FGameplayAbilitySpec* UAbilityGauge::GetAbilitySpec()
{
	// 技能组件和技能对象不存在
	if (!OwnerAbilitySystemComponent || !AbilityCDO) return nullptr;

	// 技能缓存句柄无效,重新查找
	if (!CachedAbilitySpecHandle.IsValid())
	{
		// 根据技能类查找规格
		FGameplayAbilitySpec* FoundAbilitySpec = OwnerAbilitySystemComponent->FindAbilitySpecFromClass(AbilityCDO->GetClass());
        
		// 缓存找到的规格句柄(Handle)
		CachedAbilitySpecHandle = FoundAbilitySpec->Handle;
		return FoundAbilitySpec;
	}

	// 技能缓存句柄有效,返回缓存的规格
	return OwnerAbilitySystemComponent->FindAbilitySpecFromHandle(CachedAbilitySpecHandle);
}

void UAbilityGauge::AbilitySpecUpdated(const FGameplayAbilitySpec& AbilitySpec)
{
	// 检测技能是否为该图标技能
	if (AbilitySpec.Ability != AbilityCDO) return;

	// 更新学习状态
	bIsAbilityLearned = AbilitySpec.Level > 0;

	// 更新显示的技能等级
	LevelGauge->GetDynamicMaterial()->SetScalarParameterValue(AbilityLevelParamName, AbilitySpec.Level);

	// 刷新技能能否释放的状态
	UpdateCanCast();
}

void UAbilityGauge::UpdateCanCast()
{
	Icon->GetDynamicMaterial()->SetScalarParameterValue(CanCastAbilityParamName, bIsAbilityLearned ? 1 : 0);
}

void UAbilityGauge::UpgradePointUpdated(const FOnAttributeChangeData& Data)
{
	// 检查是否有可用升级点
	bool HasUpgradePoint = Data.NewValue > 0;
    
	// 获取当前技能规格
	const FGameplayAbilitySpec* AbilitySpec = GetAbilitySpec();
    
	if (AbilitySpec && OwnerAbilitySystemComponent)
	{
		// 获取玩家等级
		bool bFound;
		float CurrentLevel = OwnerAbilitySystemComponent->GetGameplayAttributeValue(UCHeroAttributeSet::GetLevelAttribute(), bFound);
		if (bFound)
		{
			// 如果技能已达目前的最大等级,不显示升级提示
			if (UCAbilitySystemStatics::IsAbilityAtMaxLevel(*AbilitySpec, CurrentLevel))
			{
				Icon->GetDynamicMaterial()->SetScalarParameterValue(UpgradePointAvailableParamName, 0);
				return;
			}
		}
	}
    
	// 更新UI材质显示(1=可升级,0=不可升级)
	Icon->GetDynamicMaterial()->SetScalarParameterValue(UpgradePointAvailableParamName, HasUpgradePoint ? 1 : 0);
}

两个技能都可以升级

2级小技能只能升到一级,已经升级的不会闪烁

刷新技能升级后的蓝耗和CD,以及蓝不够时技能进入灰色状态

CAbilitySystemStatics中添加一些函数

cpp 复制代码
	// 检查当前是否可以支付技能消耗(法力等)
	// @param AbilitySpec - 要检查的技能规格数据
	// @param ASC - 所属的能力系统组件
	// @return 如果资源足够支付消耗返回true,否则false
	static bool CheckAbilityCost(const FGameplayAbilitySpec& AbilitySpec, const UAbilitySystemComponent& ASC);

	// 检查技能消耗(静态)
	static bool CheckAbilityCostStatic(const UGameplayAbility* AbilityCDO, const UAbilitySystemComponent& ASC);

	// 获取技能的当前法力消耗值
	// @param AbilityCDO - 技能的默认对象(Class Default Object)
	// @param ASC - 所属的能力系统组件(用于获取属性修饰符)
	// @param AbilityLevel - 当前技能等级
	// @return 计算后的实际法力消耗值
	static float GetManaCostFor(const UGameplayAbility* AbilityCDO, const UAbilitySystemComponent& ASC, int AbilityLevel);
	
	// 获取技能的当前冷却时间
	// @param AbilityCDO - 技能的默认对象
	// @param ASC - 所属的能力系统组件(用于获取冷却修饰符)
	// @param AbilityLevel - 当前技能等级
	// @return 计算后的实际冷却时间(秒)
	static float GetCooldownDurationFor(const UGameplayAbility* AbilityCDO, const UAbilitySystemComponent& ASC, int AbilityLevel);
	
	// 获取技能的剩余冷却时间
	// @param AbilityCDO - 技能的默认对象
	// @param ASC - 所属的能力系统组件
	// @return 剩余的冷却时间(秒),如果不在冷却中返回0
	static float GetCooldownRemainingFor(const UGameplayAbility* AbilityCDO, const UAbilitySystemComponent& ASC);
cpp 复制代码
bool UCAbilitySystemStatics::CheckAbilityCost(const FGameplayAbilitySpec& AbilitySpec,
	const UAbilitySystemComponent& ASC)
{
	// 获取技能
	const UGameplayAbility* AbilityCDO = AbilitySpec.Ability;
	if (AbilityCDO)
	{
		// 调用技能的检查消耗方法
		return AbilityCDO->CheckCost(AbilitySpec.Handle, ASC.AbilityActorInfo.Get());
	}
	// 技能无效
	return false;
}

bool UCAbilitySystemStatics::CheckAbilityCostStatic(const UGameplayAbility* AbilityCDO, const UAbilitySystemComponent& ASC)
{
	if (AbilityCDO)
	{
		// 使用空句柄调用检查(适用于未实例化的技能)
		return AbilityCDO->CheckCost(FGameplayAbilitySpecHandle(), ASC.AbilityActorInfo.Get());
	}

	return false;  // 无有效技能对象时默认返回false
}

float UCAbilitySystemStatics::GetManaCostFor(const UGameplayAbility* AbilityCDO, const UAbilitySystemComponent& ASC,
	int AbilityLevel)
{
	float ManaCost = 0.f;
	if (AbilityCDO)
	{
		// 获取消耗效果
		UGameplayEffect* CostEffect = AbilityCDO->GetCostGameplayEffect();
		if (CostEffect && CostEffect->Modifiers.Num() > 0)
		{
			// 创建临时的效果规格
			FGameplayEffectSpecHandle EffectSpec = ASC.MakeOutgoingSpec(
				CostEffect->GetClass(), 
				AbilityLevel, 
				ASC.MakeEffectContext()
			);
			
			// 获取技能的消耗效果的静态效果值
			CostEffect->Modifiers[0].ModifierMagnitude.AttemptCalculateMagnitude(
				*EffectSpec.Data.Get(),
				ManaCost
				);
		}
	}
	// 返回绝对值(确保消耗值始终为正数)
	return FMath::Abs(ManaCost);
}

float UCAbilitySystemStatics::GetCooldownDurationFor(const UGameplayAbility* AbilityCDO,
	const UAbilitySystemComponent& ASC, int AbilityLevel)
{
	float CooldownDuration = 0.f;
	if (AbilityCDO)
	{
		// 获取技能关联的冷却效果
		UGameplayEffect* CooldownEffect = AbilityCDO->GetCooldownGameplayEffect();
		if (CooldownEffect)
		{
			// 创建临时的效果规格
			FGameplayEffectSpecHandle EffectSpec = ASC.MakeOutgoingSpec(
				CooldownEffect->GetClass(), 
				AbilityLevel, 
				ASC.MakeEffectContext()
			);
            
			// 计算冷却效果的实际持续时间(考虑冷却缩减属性)
			CooldownEffect->DurationMagnitude.AttemptCalculateMagnitude(
				*EffectSpec.Data.Get(), 
				CooldownDuration
			);
		}
	}

	// 返回绝对值(确保冷却时间始终为正数)
	return FMath::Abs(CooldownDuration);
}

float UCAbilitySystemStatics::GetCooldownRemainingFor(const UGameplayAbility* AbilityCDO,
	const UAbilitySystemComponent& ASC)
{
	if (!AbilityCDO) return 0.f;

	// 获取冷却效果
	UGameplayEffect* CooldownEffect = AbilityCDO->GetCooldownGameplayEffect();
	if (!CooldownEffect) return 0.f;

	// 创建查询条件:查找此技能对应的冷却效果
	FGameplayEffectQuery CooldownEffectQuery;
	CooldownEffectQuery.EffectDefinition = CooldownEffect->GetClass();

	float CooldownRemaining = 0.f;
	// 获取所有匹配效果的剩余时间
	TArray<float> CooldownRemainings = ASC.GetActiveEffectsTimeRemaining(CooldownEffectQuery);
	// 找出最长的剩余时间
	for (float Remaining : CooldownRemainings)
	{
		if (Remaining > CooldownRemaining)
		{
			CooldownRemaining = Remaining;
		}
	}
	return CooldownRemaining;
}

回到技能图标中,添加法力回调

cpp 复制代码
	// 法力变化回调
	void ManaUpdated(const FOnAttributeChangeData& Data);
cpp 复制代码
void UAbilityGauge::NativeConstruct()
{
	Super::NativeConstruct();
	// 隐藏冷却计时器
	CooldownCounterText->SetVisibility(ESlateVisibility::Hidden);

	UAbilitySystemComponent* OwnerASC = UAbilitySystemBlueprintLibrary::GetAbilitySystemComponent(GetOwningPlayerPawn());
	if (OwnerASC)
	{
		// 监听技能释放
		OwnerASC->AbilityCommittedCallbacks.AddUObject(this, &UAbilityGauge::AbilityCommitted);

		// 监听技能规格更新
		OwnerASC->AbilitySpecDirtiedCallbacks.AddUObject(this, &UAbilityGauge::AbilitySpecUpdated);

		// 绑定升级点属性变化事件 - 当玩家获得/消耗升级点时触发
		OwnerASC->GetGameplayAttributeValueChangeDelegate(UCHeroAttributeSet::GetUpgradePointAttribute())
			.AddUObject(this, &UAbilityGauge::UpgradePointUpdated);
        
		// 绑定法力值属性变化事件 - 当玩家法力值变化时触发
		OwnerASC->GetGameplayAttributeValueChangeDelegate(UCAttributeSet::GetManaAttribute())
			.AddUObject(this, &UAbilityGauge::ManaUpdated);

		// 初始化升级点显示(获取当前值并刷新UI)
        bool bFound = false;
        float UpgradePoint = OwnerASC->GetGameplayAttributeValue(UCHeroAttributeSet::GetUpgradePointAttribute(), bFound);
        if (bFound)
        {
            // 创建属性变化数据结构(模拟属性变化事件)
            FOnAttributeChangeData ChangeData;
            ChangeData.NewValue = UpgradePoint;
            
            // 手动调用升级点更新函数以刷新UI
            UpgradePointUpdated(ChangeData);
        }

		// 保存能力系统组件引用供后续使用
		OwnerAbilitySystemComponent = OwnerASC;
	}
	
	WholeNumberFormattingOptions.MaximumFractionalDigits = 0;
	TwoDigitNumberFormattingOptions.MaximumFractionalDigits = 2;
}

void UAbilityGauge::AbilitySpecUpdated(const FGameplayAbilitySpec& AbilitySpec)
{
	// 检测技能是否为该图标技能
	if (AbilitySpec.Ability != AbilityCDO) return;

	// 更新学习状态
	bIsAbilityLearned = AbilitySpec.Level > 0;

	// 更新显示的技能等级
	LevelGauge->GetDynamicMaterial()->SetScalarParameterValue(AbilityLevelParamName, AbilitySpec.Level);

	// 刷新技能能否释放的状态
	UpdateCanCast();

	// 并显示新的冷却时间和法力消耗
	float NewCooldownDuration = UCAbilitySystemStatics::GetCooldownDurationFor(AbilitySpec.Ability, *OwnerAbilitySystemComponent, AbilitySpec.Level);
	float NewCost = UCAbilitySystemStatics::GetManaCostFor(AbilitySpec.Ability, *OwnerAbilitySystemComponent, AbilitySpec.Level);
	CooldownDurationText->SetText(FText::AsNumber(NewCooldownDuration));
	CostText->SetText(FText::AsNumber(NewCost));
}
void UAbilityGauge::UpdateCanCast()
{
	const FGameplayAbilitySpec* AbilitySpec = GetAbilitySpec();
	
	// 技能学习才能亮起来
	bool bCanCast = bIsAbilityLearned;
	
	// 看蓝量是否够
	if (AbilitySpec && OwnerAbilitySystemComponent)
	{
		if (!UCAbilitySystemStatics::CheckAbilityCost(*AbilitySpec, *OwnerAbilitySystemComponent))
		{
			bCanCast = false;
		}
	}
	// 更新UI材质显示(1=可释放,0=不可释放)
	Icon->GetDynamicMaterial()->SetScalarParameterValue(CanCastAbilityParamName, bCanCast ? 1 : 0);
}

void UAbilityGauge::ManaUpdated(const FOnAttributeChangeData& Data)
{
	UpdateCanCast();
}

蓝不够的时候就会变成灰色

创建一个曲线表格

到GE中设置为可扩展浮点

升级后应用上去了

修改一下伤害的定义,让这个伤害也跟着一起升级

cpp 复制代码
// 伤害效果定义
USTRUCT(BlueprintType)
struct FGenericDamageEffectDef
{
	GENERATED_BODY()

public:
	FGenericDamageEffectDef();

	// 伤害类型
	UPROPERTY(EditAnywhere)
	TSubclassOf<UGameplayEffect> DamageEffect;

	// 基础伤害大小
	UPROPERTY(EditAnywhere)
	FScalableFloat BaseDamage;

	// 属性的百分比伤害加成
	UPROPERTY(EditAnywhere)
	TMap<FGameplayAttribute, float> DamageTypes;

	// 力的大小
	UPROPERTY(EditAnywhere)
	FVector PushVelocity;
};

伤害的获取改为如此

cpp 复制代码
void UCGameplayAbility::MakeDamage(const FGenericDamageEffectDef& Damage, int Level)
{
	float NewDamage = Damage.BaseDamage.GetValueAtLevel(GetAbilityLevel());
	//通过标签设置GE使用的配置
	for(auto& Pair : Damage.DamageTypes)
	{
		bool bFound ;
		float AttributeValue = GetAbilitySystemComponentFromActorInfo()->GetGameplayAttributeValue(Pair.Key, bFound);
		if (bFound)
		{
			NewDamage += AttributeValue * Pair.Value / 100.f;
		}
	}
	GetAbilitySystemComponentFromActorInfo()->SetNumericAttributeBase(UCAttributeSet::GetBaseDamageAttribute(), NewDamage);
}

添加一个伤害的值,随便设

然后GA中设置一下

修复伤害数字特效只显示3位数的问题

发现特效的显示似乎有点问题,来到特效里面进行修改一下

创建一个hlsl

hlsl 复制代码
if (Result == 0) {
    NiagaraFloat = 1;
} else {
    float logVal = log10(abs(Result));
    NiagaraFloat = floor(logVal) + 1;
}

把这几个删了

添加一下,由于伤害的显示这边数字太大的时候似乎显示并不正常,因此我就给他进行限制到99999

最后应用一下

这下就能显示更高的伤害了

相关推荐
ulias21211 分钟前
Linux系统中的权限问题
linux·运维·服务器
青花瓷1 小时前
Ubuntu下OpenClaw的安装(豆包火山API版)
运维·服务器·ubuntu
问简2 小时前
docker 镜像相关
运维·docker·容器
Dream of maid3 小时前
Linux(下)
linux·运维·服务器
齐鲁大虾3 小时前
统信系统UOS常用命令集
linux·运维·服务器
Benszen3 小时前
Docker容器化技术实战指南
运维·docker·容器
ZzzZZzzzZZZzzzz…3 小时前
Nginx 平滑升级:从 1.26.3 到 1.28.0,用户无感知
linux·运维·nginx·平滑升级·nginx1.26.3·nginx1.28.0
一叶知秋yyds4 小时前
Ubuntu 虚拟机安装 OpenClaw 完整流程
linux·运维·ubuntu·openclaw
专吃海绵宝宝菠萝屋的派大星5 小时前
使用Dify对接自己开发的mcp
java·服务器·前端
斯普信云原生组5 小时前
Prometheus 环境监控虚机 Redis 方案(生产实操版)
运维·docker·容器