completed attack combo with GameplayEffects and GameplayCues

This commit is contained in:
Caleb Buhungiro
2025-07-09 22:55:17 +08:00
parent 0e12b3d0c3
commit 82fbcc72c4
29 changed files with 283 additions and 28 deletions

File diff suppressed because one or more lines are too long

View File

@@ -1,5 +1,4 @@
;METADATA=(Diff=true, UseCommands=true) ;METADATA=(Diff=true, UseCommands=true)
[/Script/GameplayTags.GameplayTagsSettings] [/Script/GameplayTags.GameplayTagsSettings]
ImportTagsFromConfig=True ImportTagsFromConfig=True
WarnOnInvalidTags=True WarnOnInvalidTags=True
@@ -18,4 +17,8 @@ NetIndexFirstBitSegment=16
+GameplayTagList=(Tag="ability.combo.change.combo03",DevComment="") +GameplayTagList=(Tag="ability.combo.change.combo03",DevComment="")
+GameplayTagList=(Tag="ability.combo.change.combo04",DevComment="") +GameplayTagList=(Tag="ability.combo.change.combo04",DevComment="")
+GameplayTagList=(Tag="ability.combo.change.end",DevComment="") +GameplayTagList=(Tag="ability.combo.change.end",DevComment="")
+GameplayTagList=(Tag="ability.combo.damage",DevComment="")
+GameplayTagList=(Tag="GameplayCue.cameraShake",DevComment="")
+GameplayTagList=(Tag="GameplayCue.hit.crunch.punch",DevComment="")
+GameplayTagList=(Tag="GameplayCue.hit.reaction",DevComment="")

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,30 @@
// Multiplayer By Caleb
#include "Animations/AN_SendTargetGroup.h"
#include "AbilitySystemBlueprintLibrary.h"
void UAN_SendTargetGroup::Notify(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation,
const FAnimNotifyEventReference& EventReference)
{
Super::Notify(MeshComp, Animation, EventReference);
if (!MeshComp || !MeshComp->GetOwner()) return;
if (!TargetSocketNames.Num()) return;
if (!UAbilitySystemBlueprintLibrary::GetAbilitySystemComponent(MeshComp->GetOwner())) return;
FGameplayEventData Data;
for (int i = 1; i < TargetSocketNames.Num(); ++i)
{
// const auto LocationInfo{NewObject<FGameplayAbilityTargetData_LocationInfo>()};
const auto LocationInfo{new FGameplayAbilityTargetData_LocationInfo()}; // heap allocation
FVector StartLoc{MeshComp->GetSocketLocation(TargetSocketNames[i - 1])};
FVector EndLoc{MeshComp->GetSocketLocation(TargetSocketNames[i])};
LocationInfo->SourceLocation.LiteralTransform.SetLocation(StartLoc);
LocationInfo->TargetLocation.LiteralTransform.SetLocation(EndLoc);
Data.TargetData.Add(LocationInfo);
}
UAbilitySystemBlueprintLibrary::SendGameplayEventToActor(MeshComp->GetOwner(), EventTag, Data);
}

View File

@@ -3,6 +3,7 @@
#include "GAS/CAttributeSet.h" #include "GAS/CAttributeSet.h"
#include "GameplayEffectExtension.h"
#include "Net/UnrealNetwork.h" #include "Net/UnrealNetwork.h"
void UCAttributeSet::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const void UCAttributeSet::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
@@ -14,6 +15,31 @@ void UCAttributeSet::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLi
DOREPLIFETIME_CONDITION_NOTIFY(UCAttributeSet, MaxMana, COND_None, REPNOTIFY_Always); DOREPLIFETIME_CONDITION_NOTIFY(UCAttributeSet, MaxMana, COND_None, REPNOTIFY_Always);
} }
void UCAttributeSet::PreAttributeChange(const FGameplayAttribute& Attribute, float& NewValue)
{
if (Attribute == GetHealthAttribute())
{
NewValue = FMath::Clamp(NewValue, 0.0f, GetMaxHealth());
}
if (Attribute == GetManaAttribute())
{
NewValue = FMath::Clamp(NewValue, 0.0f, GetMaxMana());
}
}
void UCAttributeSet::PostGameplayEffectExecute(const struct FGameplayEffectModCallbackData& Data)
{
// Super::PostGameplayEffectExecute(Data);
if (Data.EvaluatedData.Attribute == GetHealthAttribute())
{
SetHealth(FMath::Clamp(GetHealth(), 0, GetMaxHealth()));
}
if (Data.EvaluatedData.Attribute == GetManaAttribute())
{
SetMana(FMath::Clamp(GetMana(), 0, GetMaxMana()));
}
}
void UCAttributeSet::OnRep_Health(const FGameplayAttributeData& OldValue) void UCAttributeSet::OnRep_Health(const FGameplayAttributeData& OldValue)
{ {
GAMEPLAYATTRIBUTE_REPNOTIFY(UCAttributeSet, Health, OldValue); GAMEPLAYATTRIBUTE_REPNOTIFY(UCAttributeSet, Health, OldValue);

View File

@@ -3,7 +3,9 @@
#include "GAS/CGameplayAbility.h" #include "GAS/CGameplayAbility.h"
UAnimInstance* UCGameplayAbility::GetOwnerAnimInstance() #include "Kismet/KismetSystemLibrary.h"
UAnimInstance* UCGameplayAbility::GetOwnerAnimInstance() const
{ {
if (const auto OwnerSkeletalMeshComp{GetOwningComponentFromActorInfo()}) if (const auto OwnerSkeletalMeshComp{GetOwningComponentFromActorInfo()})
{ {
@@ -11,3 +13,46 @@ UAnimInstance* UCGameplayAbility::GetOwnerAnimInstance()
} }
return nullptr; return nullptr;
} }
TArray<FHitResult> UCGameplayAbility::GetHitResultFromSweepLocationTargetData(
const FGameplayAbilityTargetDataHandle& TargetDataHandle, float SphereSweepRadius, bool bDrawDebug,
bool bIgnoreSelf) const
{
TArray<FHitResult> OutResults;
TSet<AActor*> HitActors;
for (const auto TargetData : TargetDataHandle.Data)
{
const FVector StartLoc{TargetData->GetOrigin().GetTranslation()};
const FVector EndLoc{TargetData->GetEndPoint()};
TArray<TEnumAsByte<EObjectTypeQuery>> ObjectTypes;
ObjectTypes.Add(UEngineTypes::ConvertToObjectType(ECC_Pawn));
TArray<AActor*> ActorsToIgnore;
if (bIgnoreSelf) ActorsToIgnore.Add(GetAvatarActorFromActorInfo());
const auto DrawDebugTrace{bDrawDebug ? EDrawDebugTrace::ForDuration : EDrawDebugTrace::None};
TArray<FHitResult> Results;
UKismetSystemLibrary::SphereTraceMultiForObjects(
this,
StartLoc,
EndLoc,
SphereSweepRadius,
ObjectTypes,
false,
ActorsToIgnore,
DrawDebugTrace,
Results,
false
);
for (const auto Result : Results)
{
if (HitActors.Contains(Result.GetActor())) continue;
HitActors.Add(Result.GetActor());
OutResults.Add(Result);
}
}
return OutResults;
}

View File

@@ -3,6 +3,7 @@
#include "GAS/GA_Combo.h" #include "GAS/GA_Combo.h"
#include "AbilitySystemBlueprintLibrary.h"
#include "GameplayTagsManager.h" #include "GameplayTagsManager.h"
#include "Abilities/Tasks/AbilityTask_PlayMontageAndWait.h" #include "Abilities/Tasks/AbilityTask_PlayMontageAndWait.h"
#include "Abilities/Tasks/AbilityTask_WaitGameplayEvent.h" #include "Abilities/Tasks/AbilityTask_WaitGameplayEvent.h"
@@ -60,6 +61,17 @@ void UGA_Combo::ActivateAbility(const FGameplayAbilitySpecHandle Handle,
WaitComboChangeEventTask->EventReceived.AddDynamic(this, &UGA_Combo::ComboChangedEventReceived); WaitComboChangeEventTask->EventReceived.AddDynamic(this, &UGA_Combo::ComboChangedEventReceived);
WaitComboChangeEventTask->ReadyForActivation(); WaitComboChangeEventTask->ReadyForActivation();
} }
if (K2_HasAuthority())
{
const auto WaitTargetingEventTask{
UAbilityTask_WaitGameplayEvent::WaitGameplayEvent(
this,
GetComboTargetEventTag()
)
};
WaitTargetingEventTask->EventReceived.AddDynamic(this, &UGA_Combo::DoDamage);
WaitTargetingEventTask->ReadyForActivation();
}
SetupWaitComboInputPressed(); SetupWaitComboInputPressed();
} }
@@ -91,6 +103,11 @@ FGameplayTag UGA_Combo::GetComboChangedEventEndTag()
return FGameplayTag::RequestGameplayTag("ability.combo.change.end"); return FGameplayTag::RequestGameplayTag("ability.combo.change.end");
} }
FGameplayTag UGA_Combo::GetComboTargetEventTag()
{
return FGameplayTag::RequestGameplayTag("ability.combo.damage");
}
void UGA_Combo::SetupWaitComboInputPressed() void UGA_Combo::SetupWaitComboInputPressed()
{ {
const auto WaitInputPress{UAbilityTask_WaitInputPress::WaitInputPress(this)}; const auto WaitInputPress{UAbilityTask_WaitInputPress::WaitInputPress(this)};
@@ -117,3 +134,49 @@ void UGA_Combo::TryCommitCombo()
ComboMontage ComboMontage
); );
} }
TSubclassOf<UGameplayEffect> UGA_Combo::GetDamageEffectForCurrentCombo() const
{
if (const auto OwnerAnimInstance{GetOwnerAnimInstance()})
{
const FName CurrentSectionName{OwnerAnimInstance->Montage_GetCurrentSection(ComboMontage)};
const auto FoundEffectPtr{DamageEffectMap.Find(CurrentSectionName)};
if (FoundEffectPtr) return *FoundEffectPtr;
}
return DefaultEffectClass;
}
void UGA_Combo::DoDamage(FGameplayEventData Data)
{
auto HitResults{
GetHitResultFromSweepLocationTargetData(
Data.TargetData,
TargetSweepSphereRadius,
false,
true
)
};
for (const FHitResult& HitResult : HitResults)
{
const auto GameplayEffect{GetDamageEffectForCurrentCombo()};
const auto AbilityLevel{GetAbilityLevel(GetCurrentAbilitySpecHandle(), GetCurrentActorInfo())};
const auto EffectSpecHandle{MakeOutgoingGameplayEffectSpec(GameplayEffect, AbilityLevel)};
const auto AbilityTargetData{UAbilitySystemBlueprintLibrary::AbilityTargetDataFromActor(HitResult.GetActor())};
auto EffectContextHandle{MakeEffectContext(GetCurrentAbilitySpecHandle(), GetCurrentActorInfo())};
EffectContextHandle.AddHitResult(HitResult);
EffectSpecHandle.Data->SetContext(EffectContextHandle);
const auto ActiveGameplayEffectHandles{
ApplyGameplayEffectSpecToTarget(
GetCurrentAbilitySpecHandle(),
CurrentActorInfo,
CurrentActivationInfo,
EffectSpecHandle,
AbilityTargetData
)
};
}
}

View File

@@ -0,0 +1,26 @@
// Multiplayer By Caleb
#pragma once
#include "CoreMinimal.h"
#include "GameplayTagContainer.h"
#include "Animation/AnimNotifies/AnimNotify.h"
#include "AN_SendTargetGroup.generated.h"
/**
*
*/
UCLASS()
class CRUNCH_API UAN_SendTargetGroup : public UAnimNotify
{
GENERATED_BODY()
public:
virtual void Notify(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation,
const FAnimNotifyEventReference& EventReference) override;
private:
UPROPERTY(EditAnywhere, Category="Ability")
FGameplayTag EventTag;
UPROPERTY(EditAnywhere, Category="Ability")
TArray<FName> TargetSocketNames;
};

View File

@@ -39,6 +39,26 @@ class CRUNCH_API UCAttributeSet : public UAttributeSet
public: public:
virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override; virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;
/**
* An "On Aggregator Change" type of event could go here, and that could be called when active gameplay effects are added or removed to an attribute aggregator.
* It is difficult to give all the information in these cases though - aggregators can change for many reasons: being added, being removed, being modified, having a modifier change, immunity, stacking rules, etc.
*/
/**
* ---------------------------------------------------------------------------------------------------------------------------
*
* Called just before any modification happens to an attribute. This is lower level than PreAttributeModify/PostAttribute modify.
* There is no additional context provided here since anything can trigger this. Executed effects, duration based effects, effects being removed, immunity being applied, stacking rules changing, etc.
* This function is meant to enforce things like "Health = Clamp(Health, 0, MaxHealth)" and NOT things like "trigger this extra thing if damage is applied, etc".
*
* NewValue is a mutable reference so you are able to clamp the newly applied value as well.
*/
virtual void PreAttributeChange(const FGameplayAttribute& Attribute, float& NewValue) override;
/**
* Called just after a GameplayEffect is executed to modify the base value of an attribute. No more changes can be made.
* Note this is only called during an 'execute'. E.g., a modification to the 'base value' of an attribute. It is not called during an application of a GameplayEffect, such as a 5 ssecond +10 movement speed buff.
*/
virtual void PostGameplayEffectExecute(const struct FGameplayEffectModCallbackData& Data) override;
ATTRIBUTE_ACCESSORS(UCAttributeSet, Health); ATTRIBUTE_ACCESSORS(UCAttributeSet, Health);
ATTRIBUTE_ACCESSORS(UCAttributeSet, MaxHealth); ATTRIBUTE_ACCESSORS(UCAttributeSet, MaxHealth);

View File

@@ -13,6 +13,10 @@ UCLASS()
class CRUNCH_API UCGameplayAbility : public UGameplayAbility class CRUNCH_API UCGameplayAbility : public UGameplayAbility
{ {
GENERATED_BODY() GENERATED_BODY()
protected: protected:
UAnimInstance* GetOwnerAnimInstance(); UAnimInstance* GetOwnerAnimInstance() const;
TArray<FHitResult> GetHitResultFromSweepLocationTargetData(const FGameplayAbilityTargetDataHandle& TargetDataHandle,
float SphereSweepRadius = 30.f, bool bDrawDebug = false,
bool bIgnoreSelf = true) const;
}; };

View File

@@ -21,6 +21,7 @@ public:
const FGameplayEventData* TriggerEventData) override; const FGameplayEventData* TriggerEventData) override;
static FGameplayTag GetComboChangedEventTag(); static FGameplayTag GetComboChangedEventTag();
static FGameplayTag GetComboChangedEventEndTag(); static FGameplayTag GetComboChangedEventEndTag();
static FGameplayTag GetComboTargetEventTag();
private: private:
UFUNCTION() UFUNCTION()
@@ -29,11 +30,24 @@ private:
void SetupWaitComboInputPressed(); void SetupWaitComboInputPressed();
void TryCommitCombo(); void TryCommitCombo();
UPROPERTY(EditDefaultsOnly, Category="Targetting")
float TargetSweepSphereRadius{30.f};
UPROPERTY(EditDefaultsOnly, Category="Gameplay Effect")
TSubclassOf<UGameplayEffect> DefaultEffectClass;
UPROPERTY(EditDefaultsOnly, Category="Gameplay Effect")
TMap<FName, TSubclassOf<UGameplayEffect>> DamageEffectMap;
TSubclassOf<UGameplayEffect> GetDamageEffectForCurrentCombo() const;
UPROPERTY(EditDefaultsOnly, Category="Amination") UPROPERTY(EditDefaultsOnly, Category="Amination")
TObjectPtr<UAnimMontage> ComboMontage; TObjectPtr<UAnimMontage> ComboMontage;
UFUNCTION() UFUNCTION()
void ComboChangedEventReceived(FGameplayEventData Data); void ComboChangedEventReceived(FGameplayEventData Data);
UFUNCTION()
void DoDamage(FGameplayEventData Data);
FName NextComboName; FName NextComboName;
}; };