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
英文地址,請注明出處:http://en.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;并且講解一下各個服務之間…