Replicated

[Drag Down] 회피 만들기 본문

언리얼 엔진/Drag Down (캡스톤 디자인)

[Drag Down] 회피 만들기

라구넹 2025. 4. 1. 13:42

상대방의 공격을 피할 수도 있어야 한다

회피를 만들자

저 태그를 가지고 있으면 공격이 작동하지 않도록 설정할 것이다

그리고 저 태그가 날아오면 회피 해제

모션을 풀로 회피하면 너무 쉽다

HitCheck랑 코드 다 똑같고 태그 이름만 다름

 

 

// 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"

UDDGA_Dodge::UDDGA_Dodge()
{
	NetExecutionPolicy = EGameplayAbilityNetExecutionPolicy::LocalPredicted;
	InstancingPolicy = EGameplayAbilityInstancingPolicy::InstancedPerActor;

	static ConstructorHelpers::FObjectFinder<UAnimMontage> DodgeMontageRef(TEXT("/Script/Engine.AnimMontage'/Game/Animation/Montage/AM_Manny_Dodge.AM_Manny_Dodge'"));
	if (DodgeMontageRef.Succeeded())
	{
		DodgeMontage = DodgeMontageRef.Object;
	}

	bIsDodged = false;
}

void UDDGA_Dodge::ActivateAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, const FGameplayEventData* TriggerEventData)
{
	Super::ActivateAbility(Handle, ActorInfo, ActivationInfo, 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());
	}

	bIsDodged = false;

	AvatarCharacter = Cast<ACharacter>(ActorInfo->AvatarActor.Get());
	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을 통해 지연 완화
	ADDCharacterBase* Character = Cast<ADDCharacterBase>(AvatarCharacter);
	if (Character)
	{
		UAbilitySystemComponent* ASC = CurrentActorInfo->AbilitySystemComponent.Get();
		if (ASC)
		{
			ASC->AddLooseGameplayTag(FGameplayTag::RequestGameplayTag(FName("Player.State.Dodge")));

			FScopedPredictionWindow ScopedPrediction(ASC, !AvatarCharacter->HasAuthority());
			Character->NetMulticastPlayAnimMontage(DodgeMontage, 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());

	UAbilitySystemComponent* ASC = CurrentActorInfo->AbilitySystemComponent.Get();
	if (ASC)
	{
		ASC->RemoveLooseGameplayTag(FGameplayTag::RequestGameplayTag(FName("Player.State.Dodge")));
	}

	bool bReplicatedEndAbility = true; 
	bool bWasCancelled = false; 
	EndAbility(CurrentSpecHandle, CurrentActorInfo, CurrentActivationInfo, bReplicatedEndAbility, bWasCancelled); 
}

void UDDGA_Dodge::EndAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, bool bReplicateEndAbility, bool bWasCancelled)
{
	if (EventTask && EventTask->IsActive())
	{
		EventTask->EndTask();
	}

	// for Local Prediction Role Back
	if (AvatarCharacter)
	{
		AvatarCharacter->GetCharacterMovement()->SetMovementMode(EMovementMode::MOVE_Walking); 
	}

	bIsDodged = false;

	Super::EndAbility(Handle, ActorInfo, ActivationInfo, bReplicateEndAbility, bWasCancelled); 
}

void UDDGA_Dodge::CancelAbility(const FGameplayAbilitySpecHandle Handle, const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilityActivationInfo ActivationInfo, bool bReplicateCancel)
{
	if (EventTask && EventTask->IsActive())
	{
		EventTask->EndTask();
	}

	// for Local Prediction Role Back
	if (AvatarCharacter)
	{
		AvatarCharacter->GetCharacterMovement()->SetMovementMode(EMovementMode::MOVE_Walking);
	}

	bIsDodged = false;

	Super::CancelAbility(Handle, ActorInfo, ActivationInfo, bReplicateCancel);
}

앞에서 연속 공격 처리까지 해놔서 코드가 수정할게 많이 없다

그냥 태그 달고 떼고가 차이가 끝

그리고 어빌리티 끝내는 처리는 Event.DodgeEnd 발생 시로 처리

 

void UDDGA_PushingCharacter::ProcessPush(const FGameplayAbilityTargetDataHandle& TargetDataHandle)
{
	int32 Idx = 0;
	while (UAbilitySystemBlueprintLibrary::TargetDataHasHitResult(TargetDataHandle, Idx))
	{
		ACharacter* Character = nullptr;

		FHitResult HitResult = UAbilitySystemBlueprintLibrary::GetHitResultFromTargetData(TargetDataHandle, Idx);
		if (HitResult.GetActor())
		{
			Character = Cast<ACharacter>(HitResult.GetActor());
		}
		
		if (Character)
		{
			UAbilitySystemComponent* TargetASC = UAbilitySystemBlueprintLibrary::GetAbilitySystemComponent(Character);

			if (TargetASC && TargetASC->HasMatchingGameplayTag(FGameplayTag::RequestGameplayTag(FName("Player.State.Dodge"))))
			{
				UE_LOG(LogDD, Log, TEXT("Character %s is dodging - skipping LaunchCharacter"), *Character->GetName());
				++Idx;
				continue;
			}

			if ( AttackStateComponent == nullptr )
			{
				UE_LOG(LogDD, Log, TEXT("UDDGA_PushingCharacter::ProcessPush - No AttackStateComponent"));
				return;
			}

			if ( AvatarCharacter == nullptr )
			{
				UE_LOG(LogDD, Log, TEXT("UDDGA_PushingCharacter::ProcessPush - No AvatarCharacter"));
				return;
			}

			FVector LaunchDirection = AvatarCharacter->GetController()->GetControlRotation().Vector(); // 내가 바라보는 방향

			FVector LaunchVelocity = LaunchDirection * AttackStateComponent->GetPower();
			LaunchVelocity.Z = 0.0f;
			LaunchVelocity.Z += AttackStateComponent->GetZPower();

			Character->LaunchCharacter(LaunchVelocity, true, true);
		}

		UE_LOG(LogDD, Log, TEXT("HitResult : %s"), *HitResult.GetActor()->GetName());

		++Idx;
	}
}

그리고 미는 함수엔 태그 Player.State.Dodge 달려있으면 못 밀도록 설정

 

노티파이 달아주면 끝

 

void UDDGA_Dodge::OnDodgeEventReceived(FGameplayEventData Payload)
{
	UE_LOG(LogDD, Log, TEXT("[NetMode : %d] OnDodgeEventReceived"), GetWorld()->GetNetMode());

	UAbilitySystemComponent* ASC = CurrentActorInfo->AbilitySystemComponent.Get();
	/*if (ASC)
	{
		ASC->RemoveLooseGameplayTag(FGameplayTag::RequestGameplayTag(FName("Player.State.Dodge")));
	}*/

	bool bReplicatedEndAbility = true; 
	bool bWasCancelled = false; 
	EndAbility(CurrentSpecHandle, CurrentActorInfo, CurrentActivationInfo, bReplicatedEndAbility, bWasCancelled); 
}

일단 테스트를 위해 태그 떼는 건 임시로 삭제

 

 

태그 잘 적용되는 걸 확인 가능

 

이벤트도 잘 발생

 

이제 다음은 어빌리티 발동 중에 다른 어빌리티 발동을 못하게 막는 것이다

안하면 어빌리티가 꼬인다