UE5 Issues - Dash Skill (데디케이티드 서버)

2025. 5. 27. 17:57·Unreal Engine

1. Dash 애니메이션 세팅


Dash 애니메이션 같은 경우는 대체로 Root Motion 애니메이션이 많다.

그러나 우리가 원하는 속도나 거리, 시간을 이동시키기 위해서는 루트 모션을 사용하지 않고, 아래와 같은 세탕으로 코드를 잠금시킨 후 코드에서 움직이도록 하였다.

 

해당 애니메이션을 이용해 애니메이션 몽타주를 만들어 주면 된다.

 

 

2. 스킬 시스템과 연동


현재 스킬은 아래와 같은 구조로 구성되어 있다.

  • UObject를 상속받은 SkillBase 를 상속하여 만든 각각의 스킬 클래스 (실제 스킬의 실체)
  • UActorComponent를 상속받은 SkillComp를 통해 스킬 사용

스킬을 사용하는 흐름은 아래와 같이 정리할 수 있다.

  • PlayerController와 IMC 바인딩
void AGS_GuardianController::SetupInputComponent()
{
	Super::SetupInputComponent();

	UEnhancedInputComponent* EnhancedInputComponent = CastChecked<UEnhancedInputComponent>(InputComponent);
	if (IsValid(EnhancedInputComponent))
	{
		if (IsValid(CtrlInputAction))
		{
			EnhancedInputComponent->BindAction(CtrlInputAction, ETriggerEvent::Triggered, this, &AGS_GuardianController::CtrlInput);
			EnhancedInputComponent->BindAction(CtrlInputAction, ETriggerEvent::Completed, this, &AGS_GuardianController::CtrlStop);
		}
		if (IsValid(LeftMouseInputAction))
		{
			EnhancedInputComponent->BindAction(LeftMouseInputAction, ETriggerEvent::Started, this, &AGS_GuardianController::LeftMouseInput);
		}
		if (IsValid(RightMouseInputAction))
		{
			EnhancedInputComponent->BindAction(RightMouseInputAction, ETriggerEvent::Started, this, &AGS_GuardianController::RightMouseInput);
		}
	}
}
  • 바인딩 된 함수에서 각 Player의 함수 호출
void AGS_GuardianController::RightMouseInput(const FInputActionValue& InputValue)
{
	if (IsLocalController())
	{
		AGS_Guardian* Guardian = Cast<AGS_Guardian>(GetPawn());

		if (IsValid(Guardian))
		{
			Guardian->RightMouse();
		}
	}
}
  • 바인딩 된 함수에서 SkillComp를 통해 Skill 실행
void AGS_Drakhar::RightMouse()
{
	if (IsLocallyControlled())
	{
		//ultimate skill
		if (GuardianState != EGuardianState::Skill)
		{
			if (GetSkillComp()->IsSkillActive(ESkillSlot::Ready))
			{	
				GetSkillComp()->TryActivateSkill(ESkillSlot::Ultimate);
				ServerRPCStartSkill();
			}
			//dash skill
			else
			{
				GetSkillComp()->TryActivateSkill(ESkillSlot::Moving);
				ServerRPCStartSkill();
			}
		}		
	}
}
  • TryActivateSkill은 Server RPC 함수로 SkillBase의 ActivateSkill 함수를 실행
void UGS_SkillComp::TryActivateSkill(ESkillSlot Slot)
{
	if (!bCanUseSkill)
	{
		UE_LOG(LogTemp, Warning, TEXT("TryActivateSkill failed: bCanUseSkill = false"));
		return;
	}

	if (!GetOwner()->HasAuthority())
	{
		Server_TryActiveSkill(Slot);
		return;
	}

	if (SkillMap.Contains(Slot))
	{
		UGS_SkillBase* Skill = SkillMap[Slot];
		if (Skill)
		{
			if (Skill->CanActive())
			{
				Skill->ActiveSkill();
			}
		}
	}
}
  • ActivateSkill 함수에서 실제 스킬들의 동작이 이루어진다.

 

 

3. Dash 스킬의 로직


Dash 스킬의 동작은 아래와 같다.

  • 일정 시간동안 빠르게 일정 거리를 이동해야 함
  • 지나간 거리에 있는 몬스터들에게 데미지를 주어야 함

그러나 위의 로직을 UObject를 상속받은 클래스에서는 구현할 수 없다고 생각하여 SkillBase를 상속받은 클래스에서는 몽타주 재생만 실행하고, "애님 노티파이 스테이트" 를 사용하여, Dash 로직을 구현했다.

void UGS_DrakharDraconicFury::ActiveSkill()
{
	if (!CanActive())
	{
		return;
	}
	
	ExecuteSkillEffect();
}

void UGS_DrakharDraconicFury::ExecuteSkillEffect()
{
	StartCoolDown();
	//OwnerCharacter->GetSkillComp()->StartTimer(ESkillSlot::Ultimate);
	OwnerCharacter->MulticastRPCPlaySkillMontage((SkillAnimMontages[0]));
}

 

애님 노티파이 스테이트는 UAnimNotifyState 를 상속하여 만들 수 있고, Begin, Tick, End 함수가 존재하기 때문에 몽타주가 재생되는 동안의 로직을 구성하기에 적합하다.

 

애님 노티파이 스테이트를 만들면 아래와 같이 보라색으로 구간을 설정할 수 있다.

 

애님 노티파이 스테이트에서의 로직은 아래와 같이 구성했다.

  • 데미지 처리나, 거리 계산 등의 게임상에서 중요한 로직의 경우 서버에서 실행해야 하기 때문에 Server RPC를 통해 처리했다.
  • NotifyBegin
    • 대쉬 도중에는 콜리젼을 꺼서 다른 몬스터와 충돌되지 않도록 구현
    • 플레이어의 현재 위치를 기준으로 도착할 위치를 계산
  • NotifyTick
    • Lerp를 통해 이동할 거리를 계속 계산한 후 SetActorLocation으로 위치 이동
  • NotifyEnd
    • 콜리젼을 다시 원래 상태로 돌려두고
    • 지나온 길에 존재하는 모든 몬스터에게 데미지 부여
void UGS_ANS_DrakharDash::NotifyBegin(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation, float TotalDuration, const FAnimNotifyEventReference& EventReference)
{
	Super::NotifyBegin(MeshComp, Animation, TotalDuration, EventReference);
	if (AActor* Owner = MeshComp->GetOwner())
	{
		if (AGS_Drakhar* Drakhar = Cast<AGS_Drakhar>(Owner))
		{
			Drakhar->GetMesh()->SetCollisionEnabled(ECollisionEnabled::NoCollision);
			Drakhar->ServerRPCCalculateDashLocation();
		}
	}
}

void UGS_ANS_DrakharDash::NotifyTick(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation, float FrameDeltaTime, const FAnimNotifyEventReference& EventReference)
{
	Super::NotifyTick(MeshComp, Animation, FrameDeltaTime, EventReference);

	if (AActor* Owner = MeshComp->GetOwner())
	{
		if (AGS_Drakhar* Drakhar = Cast<AGS_Drakhar>(Owner))
		{
			//attack and moving
			Drakhar->ServerRPCDoDash(FrameDeltaTime);
		}
	}
}

void UGS_ANS_DrakharDash::NotifyEnd(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation, const FAnimNotifyEventReference& EventReference)
{
	Super::NotifyEnd(MeshComp, Animation, EventReference);
	
	if (AActor* Owner = MeshComp->GetOwner())
	{
		if (AGS_Drakhar* Drakhar = Cast<AGS_Drakhar>(Owner))
		{
			Drakhar->GetMesh()->SetCollisionEnabled(ECollisionEnabled::QueryAndPhysics);
			//데미지 처리
			Drakhar->ServerRPCEndDash();
		}
	}
}

 

NotifyTick 에서 동작하는 함수

  • Alpha 값은 DeltaTime을 대시를 진행할 시간인 Duration으로 나눠 보간을 진행하였다.
    • 프레임에 관계없이 Duration이 되는 순간 우리가 도착지점으로 가야하기 때문에 Alpha값을 계산하였고
    • Lerp를 통해 플레이어를 이동시켜 주었다.
  • 이 함수가 Tick으로 돌아가는 동안 DashAttackCheck()를 통해 공격 처리를 해주었다.
void AGS_Drakhar::ServerRPCDoDash_Implementation(float DeltaTime)
{
	DashInterpAlpha += DeltaTime / DashDuration;

	DashAttackCheck();
	
	if (DashInterpAlpha >= 1.f)
	{
		SetActorLocation(DashEndLocation);
	}
	else
	{
		const FVector NewLocation = FMath::Lerp(DashStartLocation, DashEndLocation, DashInterpAlpha);
		SetActorLocation(NewLocation, true);
		DashStartLocation = NewLocation;
	}
}

 

데미지 처리 로직

  • 구현하면서 신경 쓴 부분은 대시를 하며 이동하는 동안에 충돌한 것들에 대해서 한번씩만 데미지를 입히기 위해 TSet 자료 구조를 사용했다.
TSet<AGS_Character*> DamagedCharacters;

void AGS_Drakhar::DashAttackCheck()
{
	TArray<FHitResult> OutHitResults;	
	const FVector Start = GetActorLocation();
	const FVector End = Start + GetActorForwardVector() * 100.f;
	FCollisionQueryParams Params(NAME_None, false, this);

	bool bIsHitDetected = GetWorld()->SweepMultiByChannel(OutHitResults, Start, End, FQuat::Identity,
		ECC_Camera, FCollisionShape::MakeCapsule(100.f, 200.f), Params);
	
	if (bIsHitDetected)
	{
		for (auto const& OutHitResult : OutHitResults)
		{
			AGS_Character* DamagedCharacter = Cast<AGS_Character>(OutHitResult.GetActor());
			if (IsValid(DamagedCharacter))
			{
				DamagedCharacters.Add(DamagedCharacter);
			}
		}		
	}
}

 

 

4. 트러블 슈팅


대쉬 이동 스킬 시 벽을 뚫고 지나가는 등의 문제가 존재

  • SetActorLocation 함수의 두번째 파라메터를 true로 하여 충돌이 발생하도록 구현
  • 그러나 이 경우, 몬스터와도 충돌하여 날아가는 문제가 생긴다.
void AGS_Drakhar::ServerRPCDoDash_Implementation(float DeltaTime)
{
	DashInterpAlpha += DeltaTime / DashDuration;

	DashAttackCheck();
	
	if (DashInterpAlpha >= 1.f)
	{
		SetActorLocation(DashEndLocation);
	}
	else
	{
		const FVector NewLocation = FMath::Lerp(DashStartLocation, DashEndLocation, DashInterpAlpha);
		SetActorLocation(NewLocation, true);
		DashStartLocation = NewLocation;
	}
}

 

콜리젼 채널을 사용하여 문제 해결

  • 스킬 시도 중에는 Pawn 채널을 무시하고
  • 스킬이 끝나면 Pawn 채널을 다시 Block 하도록 구현
void AGS_Drakhar::ServerRPCDoDash_Implementation(float DeltaTime)
{
	GetCapsuleComponent()->SetCollisionResponseToChannel(ECC_Pawn, ECR_Ignore);

	...
}

void AGS_Drakhar::ServerRPCEndDash_Implementation()
{
	...
    
	GetCapsuleComponent()->SetCollisionResponseToChannel(ECC_Pawn, ECR_Block);
}

 

결과

 

'Unreal Engine' 카테고리의 다른 글

Unreal Engine - 데디케이티드 서버 11 (콤보 공격 최적화)  (0) 2025.06.02
Unreal Engine - 데디케이티드 서버 10 (콤보 공격)  (0) 2025.05.30
Unreal Engine - 데디케이티드 서버 9 (게임 종료)  (0) 2025.05.04
Unreal Engine - 데디케이티드 서버 8 (게임 흐름)  (0) 2025.05.02
Unreal Engine - AI (2)  (0) 2025.04.29
'Unreal Engine' 카테고리의 다른 글
  • Unreal Engine - 데디케이티드 서버 11 (콤보 공격 최적화)
  • Unreal Engine - 데디케이티드 서버 10 (콤보 공격)
  • Unreal Engine - 데디케이티드 서버 9 (게임 종료)
  • Unreal Engine - 데디케이티드 서버 8 (게임 흐름)
gbleem
gbleem
gbleem 님의 블로그 입니다.
  • gbleem
    gbleem 님의 블로그
    gbleem
  • 전체
    오늘
    어제
    • 분류 전체보기 (188)
      • Unreal Engine (73)
      • C++ (19)
      • 알고리즘(코딩테스트) (31)
      • TIL (60)
      • CS (4)
      • 툴 (1)
  • 블로그 메뉴

    • 홈
    • 카테고리
  • 링크

    • 과제용 깃허브
    • 깃허브
    • velog
  • 공지사항

  • 인기 글

  • 태그

    매크로 지정자
    character animation
    C++
    DP
    const
    템플릿
    actor 클래스
    Vector
    싱글턴
    additive animation
    gamestate
    blend pose
    addonscreendebugmessage
    motion matching
    applydamage
    map을 vector로 복사
    cin함수
    enhanced input system
    BFS
    상속
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
gbleem
UE5 Issues - Dash Skill (데디케이티드 서버)
상단으로

티스토리툴바