일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | ||
6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 |
Tags
- gas
- photon fusion2
- Multiplay
- ability task
- gameplay tag
- 언리얼 엔진
- nanite
- 게임개발
- rpc
- os
- listen server
- gameplay ability system
- unity
- map design
- UI
- CTF
- dirty cow
- 유니티
- stride
- gravity direction
- attribute
- animation
- 게임 개발
- MAC
- local prediction
- gameplay effect
- 언리얼엔진
- Unreal Engine
- Replication
- Aegis
Archives
- Today
- Total
Replicated
[Drag Down] 액션 어빌리티 리팩토링 본문
Dodge, PushingCharacter, JumpPushingCharacter 세 개의 상위 클래스를 만들어서 리팩토링하자
뭐 하나 바꿀 때 다 바꾸기 슬슬 귀찮아졌다

// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "Abilities/GameplayAbility.h"
#include "DDGA_ActionBase.generated.h"
/**
*
*/
UCLASS()
class DRAGDOWN_API UDDGA_ActionBase : public UGameplayAbility
{
GENERATED_BODY()
public:
UDDGA_ActionBase();
virtual void ActivateAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, const FGameplayEventData* TriggerEventData) override;
protected:
virtual void EndAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, bool bReplicateEndAbility, bool bWasCancelled) override;
virtual void OnAvatarSet(const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilitySpec& Spec) override;
UPROPERTY()
TObjectPtr<UAnimMontage> ActionMontage;
UPROPERTY()
TObjectPtr < class UAbilityTask_WaitGameplayEvent > EventTask;
UPROPERTY()
TSubclassOf< class UGameplayEffect > DownStaminaEffect;
UPROPERTY()
TObjectPtr<class ADDCharacterBase> AvatarCharacter;
UPROPERTY()
TObjectPtr<class UAbilitySystemComponent> ASC;
bool bIsEventTriggered;
};
// Fill out your copyright notice in the Description page of Project Settings.
#include "GA/DDGA_ActionBase.h"
#include "AbilitySystemComponent.h"
#include "Abilities/Tasks/AbilityTask_WaitGameplayEvent.h"
#include "Character/DDCharacterBase.h"
#include "GameFramework/CharacterMovementComponent.h"
#include "DragDown.h"
UDDGA_ActionBase::UDDGA_ActionBase()
{
NetExecutionPolicy = EGameplayAbilityNetExecutionPolicy::LocalPredicted;
InstancingPolicy = EGameplayAbilityInstancingPolicy::InstancedPerActor;
static ConstructorHelpers::FClassFinder<UGameplayEffect> DownStaminaEffectRef(TEXT("/Game/Blueprint/GA/GE/BPGE_DownStamina.BPGE_DownStamina_C"));
if (DownStaminaEffectRef.Succeeded())
{
DownStaminaEffect = DownStaminaEffectRef.Class;
}
bIsEventTriggered = false;
ActivationBlockedTags.AddTag(FGameplayTag::RequestGameplayTag(FName("Player.State.UsingAbility")));
}
void UDDGA_ActionBase::ActivateAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, const FGameplayEventData* TriggerEventData)
{
if (ActivationInfo.GetActivationPredictionKey().IsValidKey())
{
FDateTime Now = FDateTime::Now();
FString Timestamp = FString::Printf(TEXT("%d-%02d-%02d %02d:%02d:%02d.%03d"),
Now.GetYear(), Now.GetMonth(), Now.GetDay(),
Now.GetHour(), Now.GetMinute(), Now.GetSecond(), Now.GetMillisecond());
UE_LOG(LogDD, Warning, TEXT("[%s][NetMode %d] Client-side(%s) prediction running"),
*Timestamp, GetWorld()->GetNetMode(), *ActorInfo->AvatarActor.Get()->GetName());
}
if (HasAuthority(&ActivationInfo))
{
FDateTime Now = FDateTime::Now();
FString Timestamp = FString::Printf(TEXT("%d-%02d-%02d %02d:%02d:%02d.%03d"),
Now.GetYear(), Now.GetMonth(), Now.GetDay(),
Now.GetHour(), Now.GetMinute(), Now.GetSecond(), Now.GetMillisecond());
UE_LOG(LogDD, Warning, TEXT("[%s][NetMode %d] Server-side(%s) authority running"),
*Timestamp, GetWorld()->GetNetMode(), *ActorInfo->AvatarActor.Get()->GetName());
}
bIsEventTriggered = false;
}
void UDDGA_ActionBase::EndAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, bool bReplicateEndAbility, bool bWasCancelled)
{
bIsEventTriggered = false;
if (EventTask && EventTask->IsActive())
{
EventTask->EndTask();
}
// for Local Prediction Role Back
if (AvatarCharacter)
{
AvatarCharacter->GetCharacterMovement()->SetMovementMode(EMovementMode::MOVE_Walking);
}
if (ASC)
{
ASC->RemoveLooseGameplayTag(FGameplayTag::RequestGameplayTag(FName("Player.State.UsingAbility")));
}
Super::EndAbility(Handle, ActorInfo, ActivationInfo, bReplicateEndAbility, bWasCancelled);
}
void UDDGA_ActionBase::OnAvatarSet(const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilitySpec& Spec)
{
Super::OnAvatarSet(ActorInfo, Spec);
AvatarCharacter = Cast<ADDCharacterBase>(ActorInfo->AvatarActor.Get());
ASC = ActorInfo->AbilitySystemComponent.Get();
}
상위 클래스로 빼고, 코드를 좀 더 정리했다
ASC같은 건 캐싱해서 쓰도록 OnAvatarSet에서 설정
그 밖에 ActivateAbility같은 건 미묘하게 다 달라서 그냥 하위 클래스에서 지정하도록 한다
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "Abilities/GameplayAbility.h"
#include "GA/DDGA_ActionBase.h"
#include "DDGA_Dodge.generated.h"
/**
*
*/
UCLASS()
class DRAGDOWN_API UDDGA_Dodge : public UDDGA_ActionBase
{
GENERATED_BODY()
public:
UDDGA_Dodge();
virtual void ActivateAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, const FGameplayEventData* TriggerEventData) override;
private:
UFUNCTION()
void OnDodgeEventReceived(FGameplayEventData Payload);
virtual bool CanActivateAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayTagContainer* SourceTags = nullptr, const FGameplayTagContainer* TargetTags = nullptr, OUT FGameplayTagContainer* OptionalRelevantTags = nullptr) const override;
float NecessaryStamina;
bool bIsAvtivated;
};
// Fill out your copyright notice in the Description page of Project Settings.
#include "GA/DDGA_Dodge.h"
#include "DDGA_Dodge.h"
#include "AbilitySystemComponent.h"
#include "Abilities/Tasks/AbilityTask_WaitGameplayEvent.h"
#include "DragDown.h"
#include "AbilitySystemBlueprintLibrary.h"
#include "GameFramework/Character.h"
#include "GameFramework/CharacterMovementComponent.h"
#include "Character/DDCharacterBase.h"
#include "Misc/DateTime.h"
#include "Attribute/DDAttributeSet.h"
UDDGA_Dodge::UDDGA_Dodge()
{
static ConstructorHelpers::FObjectFinder<UAnimMontage> DodgeMontageRef(TEXT("/Script/Engine.AnimMontage'/Game/Animation/Montage/AM_Manny_Dodge.AM_Manny_Dodge'"));
if (DodgeMontageRef.Succeeded())
{
ActionMontage = DodgeMontageRef.Object;
}
NecessaryStamina = 10.0f;
}
void UDDGA_Dodge::ActivateAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, const FGameplayEventData* TriggerEventData)
{
Super::ActivateAbility(Handle, ActorInfo, ActivationInfo, TriggerEventData);
bIsAvtivated = false;
if (AvatarCharacter)
{
if (AvatarCharacter->GetCharacterMovement()->IsFalling())
{
bool bReplicatedEndAbility = true;
bool bWasCancelled = false;
EndAbility(Handle, ActorInfo, ActivationInfo, true, false);
return;
}
AvatarCharacter->GetCharacterMovement()->SetMovementMode(EMovementMode::MOVE_None);
}
// UAbilityTask_PlayMontageAndWait를 썼었는데 플레이어가 Multicast 날리는 거로 변경
// 이유: 클라이언트A가 클라이언트 B, C의 애니메이션을 UAbilityTask_PlayMontageAndWait로 복제받으면 느림
// RPC가 훨씬 빨라서 다른 클라이언트 애니메이션 동기화가 더 잘되고
// 본인은 Local Prediction을 통해 지연 완화
if (AvatarCharacter)
{
if (ASC)
{
ASC->AddLooseGameplayTag(FGameplayTag::RequestGameplayTag(FName("Player.State.Dodge")));
ASC->AddLooseGameplayTag(FGameplayTag::RequestGameplayTag(FName("Player.State.UsingAbility")));
FGameplayEffectSpecHandle EffectSpecHandle = MakeOutgoingGameplayEffectSpec(DownStaminaEffect);
if (EffectSpecHandle.IsValid())
{
EffectSpecHandle.Data->SetSetByCallerMagnitude(FGameplayTag::RequestGameplayTag(FName("Data.StaminaUsed")), -NecessaryStamina);
ApplyGameplayEffectSpecToOwner(CurrentSpecHandle, CurrentActorInfo, CurrentActivationInfo, EffectSpecHandle);
}
FScopedPredictionWindow ScopedPrediction(ASC, !AvatarCharacter->HasAuthority());
AvatarCharacter->NetMulticastPlayAnimMontage(ActionMontage, FName());
}
}
// Wait task
EventTask = UAbilityTask_WaitGameplayEvent::WaitGameplayEvent(
this,
FGameplayTag::RequestGameplayTag(FName("Event.DodgeEnd"))
);
EventTask->EventReceived.AddDynamic(this, &UDDGA_Dodge::OnDodgeEventReceived);
EventTask->ReadyForActivation();
}
void UDDGA_Dodge::OnDodgeEventReceived(FGameplayEventData Payload)
{
UE_LOG(LogDD, Log, TEXT("[NetMode : %d] OnDodgeEventReceived"), GetWorld()->GetNetMode());
if (ASC)
{
ASC->RemoveLooseGameplayTag(FGameplayTag::RequestGameplayTag(FName("Player.State.Dodge")));
}
bool bReplicatedEndAbility = true;
bool bWasCancelled = false;
EndAbility(CurrentSpecHandle, CurrentActorInfo, CurrentActivationInfo, bReplicatedEndAbility, bWasCancelled);
}
bool UDDGA_Dodge::CanActivateAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayTagContainer* SourceTags, const FGameplayTagContainer* TargetTags, OUT FGameplayTagContainer* OptionalRelevantTags) const
{
if (!Super::CanActivateAbility(Handle, ActorInfo, SourceTags, TargetTags, OptionalRelevantTags))
{
return false;
}
if (ASC == nullptr) return false;
const UDDAttributeSet* AttributeSet = ASC->GetSet<UDDAttributeSet>();
if (AttributeSet == nullptr) return false;
float CurrentStamina = AttributeSet->GetStamina();
if (CurrentStamina < NecessaryStamina)
{
return false;
}
return true;
}
이런식으로 분리해서 좀 더 간결해졌다
CanActivateAbility는 AttackStateComponent 쓰는 애가 좀 미묘하게 달라서 일단 분리 유지
+ 스테미나 안 쓰는 액션이 추가될 수도 있으니 그냥 분리해두자
'언리얼 엔진 > Drag Down (캡스톤 디자인)' 카테고리의 다른 글
[Drag Down] Stamina UI Interpolation (0) | 2025.04.06 |
---|---|
[Drag Down] Gameplay Tag 관리 리팩토링 (0) | 2025.04.06 |
** [Drag Down] UI 자체 Local Prediction 구현 ** (0) | 2025.04.05 |
[Drag Down] 주기적으로 호출되는 이펙트를 예측 실행하면 안된다.. (0) | 2025.04.05 |
[Drag Down] Local Prediction 스테미나 자동 회복 시스템 Engine & Bug in Gameplay Effect (비추천) (0) | 2025.04.05 |