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

发布于:2025-07-26 ⋅ 阅读:(20) ⋅ 点赞:(0)


前言

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

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,不能释放

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 添加一些新的输入

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中添加一个函数判断是否为满级

	// 判断技能是否达到最大等级
	static bool IsAbilityAtMaxLevel(const FGameplayAbilitySpec& Spec, const float PlayLevel);

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中升级技能

	/**
	 * 服务器端处理能力升级请求
	 * 通过指定的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中实现技能在服务器中升级,并响应客户端的同步更新

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中调用技能的升级,

protected:
	void UpgradeAbilityWithInputID(ECAbilityInputID InputID);
void ACCharacter::UpgradeAbilityWithInputID(ECAbilityInputID InputID)
{
	if (CAbilitySystemComponent)
	{
		CAbilitySystemComponent->Server_UpgradeAbilityWithID(InputID);
	}
}

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

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

在这里我只想要我的设定几个按键的技能可以升级
在这里插入图片描述

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添加代码

	// 技能等级材质参数名
	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的客户端更新处广播的
在这里插入图片描述
在这里插入图片描述

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中添加一些函数

	// 检查当前是否可以支付技能消耗(法力等)
	// @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);

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;
}

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

	// 法力变化回调
	void ManaUpdated(const FOnAttributeChangeData& Data);
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中设置为可扩展浮点
在这里插入图片描述

在这里插入图片描述
升级后应用上去了
在这里插入图片描述
在这里插入图片描述
修改一下伤害的定义,让这个伤害也跟着一起升级

// 伤害效果定义
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;
};

伤害的获取改为如此
在这里插入图片描述

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
在这里插入图片描述

if (Result == 0) {
    NiagaraFloat = 1;
} else {
    float logVal = log10(abs(Result));
    NiagaraFloat = floor(logVal) + 1;
}

把这几个删了
在这里插入图片描述
添加一下,由于伤害的显示这边数字太大的时候似乎显示并不正常,因此我就给他进行限制到99999
在这里插入图片描述
最后应用一下

这下就能显示更高的伤害了
在这里插入图片描述


网站公告

今日签到

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