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

文章目录

  • 前言
  • 技能的升级
    • 修改一下按键的输入
    • 判断是否满级
    • 在ASC中升级技能
    • 由角色的输入调用ASC的升级功能
  • 技能图标的优化
    • 技能升级材质,可升级技能图标的闪烁
    • 刷新技能升级后的蓝耗和CD,以及蓝不够时技能进入灰色状态
  • 修复伤害数字特效只显示3位数的问题


前言

重写技能基类的判断是否可以释放技能的函数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 : 3MaxLevel = 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);// 检查可用升级点数是否大于0if (!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;// 获取技能Specconst 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;// 手动调用升级点更新函数以刷新UIUpgradePointUpdated(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,否则falsestatic 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 剩余的冷却时间(秒),如果不在冷却中返回0static 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;// 手动调用升级点更新函数以刷新UIUpgradePointUpdated(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
在这里插入图片描述
最后应用一下

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/pingmian/90989.shtml
繁体地址,请注明出处:http://hk.pswp.cn/pingmian/90989.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

笔试——Day22

文章目录第一题题目思路代码第二题题目&#xff1a;思路代码第三题题目&#xff1a;思路代码第一题 题目 添加字符 思路 枚举所有字符串a与字符串b相对应的位置 代码 第二题 题目&#xff1a; 数组变换 思路 贪心 以最大值为基准元素&#xff0c;判断其他元素能否变为最…

__getattr__和 __getattribute__ 的用法

1、__getattr__ 的用法当实例对象访问一个不存在的属性时&#xff0c;会执行 __getattr__ 方法&#xff0c;如果属性存在的话&#xff0c;就不会执行案例 class Person:def __init__(self, name, age):self.name nameself.age agedef get_info(self):return f"name: {se…

信息化项目验收测试实战指南

在当今数字化转型的大背景下&#xff0c;信息化项目验收建设已成为企业提升运营效率、优化管理流程的重要手段。然而&#xff0c;很多企业在投入大量资金建设信息系统后&#xff0c;却常常面临系统上线后无法满足实际业务需求的困境。究其原因&#xff0c;往往是由于忽视了信息…

牛顿拉夫逊法PQ分解法计算潮流MATLAB程序计算模型。

牛顿拉夫逊法&PQ分解法计算潮流MATLAB程序计算模型。本程序模型基于MATLAB进行潮流计算&#xff0c;建议先安装matpower插件&#xff08;MATLAB中非常重要的潮流计算的插件&#xff09;。本程序可进行牛拉法和PQ分解法潮流计算的切换&#xff0c;对比潮流计算的结果。很适合…

Go语言实战案例-计算字符串编辑距离

在自然语言处理、拼写纠错、模糊搜索等场景中,我们经常需要衡量两个字符串之间的相似度。编辑距离(Edit Distance) 就是一个经典的衡量方式,它描述了将一个字符串转换为另一个字符串所需的最少操作次数。 一、问题定义:什么是编辑距离? 编辑距离,也称为 Levenshtein Di…

Java时间与日期常用方法

DateDate date new Date(); //获取当前时间 System.out.println(date.getYear() 1900); // 必须加上1900 System.out.println(date.getMonth() 1); // 0~11&#xff0c;必须加上1 System.out.println(date.getDate()); // 1~31&#xff0c;不能加1Ca…

【MySQL】从连接数据库开始:JDBC 编程入门指南

个人主页&#xff1a;♡喜欢做梦 欢迎 &#x1f44d;点赞 ➕关注 ❤️收藏 &#x1f4ac;评论 目录 &#x1f31f;一、什么是JDBC&#xff1f; &#x1f31f;二、JDBC编程的步骤 ✨使用步骤 ✨DriverManger &#x1f4ab;定义 &#x1f4ab;DriverManger的主要功能 …

重生之我在暑假学习微服务第一天《MybatisPlus-上篇》

本系列参考黑马程序员微服务课程&#xff0c;有兴趣的可以去查看相关视频&#xff0c;本系列内容采用渐进式方式讲解微服务核心概念与实践方法&#xff0c;每日更新确保知识点的连贯性。通过系统化学习路径帮助开发者掌握分布式系统构建的关键技术。读者可通过平台订阅功能获取…

odoo-060 git版本:发布/生产版本落后开发版本部署

文章目录问题起源目前解决问题起源 周五提交了一个版本&#xff0c;本来打算使用这个版本的&#xff0c;周末更新。 下一个功能比较复杂&#xff0c;周一提交&#xff0c;结果周末没有更新&#xff0c;导致现在还有没测试过的不能发布的。 说明&#xff1a; 原来只有一个mast…

YotoR模型:Transformer与YOLO新结合,打造“又快又准”的目标检测模型

【导读】在目标检测领域&#xff0c;YOLO系列以其高效的推理速度广受欢迎&#xff0c;而Transformer结构则在精度上展现出强大潜力。如何兼顾二者优势&#xff0c;打造一个“又快又准”的模型&#xff0c;是近年来研究热点之一。本文介绍的一项新研究——YotoR&#xff08;You …

白杨SEO:流量的本质是打开率?搞用户搜索流量的玩法怎么做?

大家好&#xff0c;我是白杨SEO&#xff0c;专注研究SEO十年以上&#xff0c;全网SEO流量实战派&#xff0c;AI搜索优化研究者。上周六参加了生财航海家在杭州举行的私域运营大会&#xff0c;主题是围绕私域获客&#xff0c;私域IP&#xff0c;AI私域&#xff0c;精细化管理。白…

Java优雅使用Spring Boot+MQTT推送与订阅

在物联网&#xff08;IoT&#xff09;和智能设备横行的今天&#xff0c;你有没有遇到这样的问题&#xff1a;服务端需要实时把报警、状态更新、控制指令推送给客户端&#xff1b;安卓 App、嵌入式设备、网页等终端&#xff0c;需要轻量且稳定的连接方式&#xff1b;HTTP 太“重…

多目标粒子群优化(MOPSO)解决ZDT1问题

前言 提醒&#xff1a; 文章内容为方便作者自己后日复习与查阅而进行的书写与发布&#xff0c;其中引用内容都会使用链接表明出处&#xff08;如有侵权问题&#xff0c;请及时联系&#xff09;。 其中内容多为一次书写&#xff0c;缺少检查与订正&#xff0c;如有问题或其他拓展…

Coze Studio概览(三)--智能体管理

本文简要分析了Coze Studio中智能体管理功能&#xff0c;包括功能、架构以及核心流程。Coze Studio 智能体管理功能分析 1. 智能体管理架构概览 Coze Studio的智能体管理系统基于DDD架构&#xff0c;主要包含以下核心模块&#xff1a; 后端架构层次&#xff1a; API层 (coze): …

idea运行tomcat日志乱码问题

原因在于idea和tomcat文件编码格式不一样。可以把idea编码改成UTF-8 File | Settings | Editor | File Encodings 里面把GBK都改成UTF-8help里面 Edit Custom VM Options 添加一行-Dfile.encodingUTF-8重启idea

Javaweb - 13 - AJAX

发送请求的几种方式1. 浏览器的地址框中输入地址&#xff0c;回车2. html --> head --> scrip / linkimg 自动发送请求&#xff0c;无需手动触发3. a 标签&#xff0c;form 表单标签需要手动控制提交产生&#xff0c;且往往需要在新的页面上获得响应信息4. 运行 JS 代码…

qt常用控件-06

文章目录qt常用控件-06spinBox/doubleSpinBoxdateTimeEditdialSliderlistWIdgettableWidgettreeWidget结语很高兴和大家见面&#xff0c;给生活加点impetus&#xff01;&#xff01;开启今天的编程之路&#xff01;&#xff01; 今天我们进一步c11中常见的新增表达 作者&#…

小智源码分析——音频部分(二)

一、利用创建好的对象来调用音频服务 上周从上图的getaudiocode()方法进去感受了一下底层小智的构造如何实现。所以用一个codec来接收我们所构造的音频对象。下来是用构造好的音频对象来调用音频初始化服务Initialize&#xff0c;因为启动函数Application函数的类中有audio_ser…

菜鸟的C#学习(四)

文章目录一、格式说明符1.1、数字格式说明符&#xff08;适用于数值类型&#xff1a;int, double, decimal 等&#xff09;1. 标准数字格式2. 自定义数字格式1.2、日期时间格式说明符&#xff08;适用于 DateTime, DateTimeOffset&#xff09;1. 标准日期时间格式2. 自定义日期…

基于黑马教程——微服务架构解析(二)

本篇文章基于黑马程序员的微服务课程内容&#xff0c;结合个人学习过程中的理解与思考进行整理。本节将围绕以下几个问题展开&#xff1a;什么是网关和配置管理前面那篇文章&#xff0c;我们了解如何把一个单体的项目拆成分布式微服务项目&#xff0c;并且讲解一下各个服务之间…