[UE5] 스킬 시전 및 판정 시스템 구현 | Skill Activate System

seunghyun·2024년 5월 9일
0

ProjectGO

목록 보기
5/7

📌 1차 요구사항

스킬마다 다른 충돌 확인 타입을 지정하기

기대 효과

  • 개별 스킬마다 (TankerQ, TankerW, HealerQ, HealerW, ….) 스킬 RPC를 만드는 대신, GOSkillBase에서 공통적인 동작을 하도록 추상화할 수 있을 것이고, 재활용성도 높을 것이다.

  • 스킬 실행과 관련된 데이터는 UGOSkillBase를 통해 관리하고, AGOPlayerCharacter는 스킬 실행을 요청하는 역할을 할 수 있다.

enum class ESkillCollisionType : uint8
{
	LineTraceSingle   UMETA(DisplayName = "Line Trace Single"),
	LineTraceMulti    UMETA(DisplayName = "Line Trace Multi"),
	SweepSingle       UMETA(DisplayName = "Sweep Single"),
	SweepMulti        UMETA(DisplayName = "Sweep Multi"),
	OverlapMulti      UMETA(DisplayName = "Overlap Multi")
};

위와 같이 나눈 여러 스킬 충돌 유형에 따라 처리할 수 있도록,

충돌에 대한 구조체를 담는 구조체를 생성하여 애니메이션 노티파이 시 이 충돌 타입에 따라서 처리하도록 설정했다.

USTRUCT()
struct FGOOutHitCollisionStructure {
	GENERATED_BODY()

	FHitResult OutHitResult;
	TArray<FHitResult> OutHitResults;
	TArray<FOverlapResult> OutOverlaps;
};

그리고 모든 스킬이 상속받는 UObject인 UGOSkillBase에서, 아래와 같은 함수를 특정 지점에서 호출하도록 해준다.

void UGOSkillBase::ExecuteSkill(ESkillCollisionType SkillCollisionType)
{
	switch (SkillCollisionType)
	{
	case ESkillCollisionType::LineTraceSingle:
		PerformLineTraceSingle(GetTotalSkillStat(), OutHitCollisionStruct.OutHitResult);
		break;
	case ESkillCollisionType::LineTraceMulti:
		PerformLineTraceMulti(GetTotalSkillStat(), OutHitCollisionStruct.OutHitResults);
		break;
	case ESkillCollisionType::SweepSingle:
		PerformSweepSingle(GetTotalSkillStat(), OutHitCollisionStruct.OutHitResult);
		break;
	case ESkillCollisionType::SweepMulti:
		PerformSweepMulti(GetTotalSkillStat(), OutHitCollisionStruct.OutHitResults);
		break;
	case ESkillCollisionType::OverlapMulti:
		PerformOverlapMulti(GetTotalSkillStat(), OutHitCollisionStruct.OutOverlaps);
		break;
	}
}

void UGOSkillBase::PerformLineTraceSingle(const FGOSkillStat& Stats, FHitResult& OutHitResult)
{
	if (!SkillOwnerCharacter) return;
	AGOCharacterBase* OwnerActor = Cast<AGOCharacterBase>(SkillOwnerCharacter);
	FVector Forward = OwnerActor->GetActorForwardVector();
	FVector Start = OwnerActor->GetActorLocation() + Forward * OwnerActor->GetCapsuleComponent()->GetScaledCapsuleRadius();
	FVector End = Start + Forward * Stats.DamageRange;

	//FHitResult HitResult; // 원래 이렇게 하는 방식 대신에 Out 파라미터를 사용
	FCollisionQueryParams CollisionParams;
	CollisionParams.AddIgnoredActor(OwnerActor); // 스킬 사용자는 무시

	HitDetected = OwnerActor->GetWorld()->LineTraceSingleByChannel(OutHitResult, Start, End, CCHANNEL_GOACTION, CollisionParams);
	...
}

앞서 기재한 애니메이션 노티파이 시 스킬의 타입에 따라 처리가 되도록 아래와 같이 함수를 작성했다.

void AGOPlayerCharacter::SkillAttackHitCheck()
{
	// 소유권을 가진 클라이언트가 공격 판정을 진행합니다.  
	if (IsLocallyControlled())
	{
		GO_LOG(LogGONetwork, Log, TEXT("%s"), TEXT("Begin"));
		

		TObjectPtr<UGOSkillBase> CurrentSkill = GetSkillCastComponent()->GetCurrentSkill();
		bool HitDetected = CurrentSkill->GetHitDetected();
		UE_LOG(LogTemp, Warning, TEXT("CurrentSkill Hit: %s"), *CurrentSkill->GetName());

		FGOOutHitCollisionStructure& OutHitCollisionStructure = CurrentSkill->GetOutHitCollisionStructure();

		FHitResult OutHitResult = OutHitCollisionStructure.OutHitResult;
		TArray<FHitResult> OutHitResults = OutHitCollisionStructure.OutHitResults;
		TArray<FOverlapResult> OutOverlaps = OutHitCollisionStructure.OutOverlaps;

		ESkillCollisionType CurrentSkillCollisionType = CurrentSkill->GetSkillCollisionType();
		float SkillDamage = CurrentSkill->GetTotalSkillStat().DamageMultiplier * Stat->GetTotalStat().BaseDamage;

		float HitCheckTime = GetWorld()->GetGameState()->GetServerWorldTimeSeconds();

		if (!HasAuthority())
		{
			if (HitDetected)
			{

				AActor* HitActor = nullptr;
				switch (CurrentSkillCollisionType)
				{
				case ESkillCollisionType::LineTraceSingle:
					HitActor = OutHitCollisionStructure.OutHitResult.GetActor();
					if (IsValid(HitActor)) {
						ServerRPCNotifySkillHitTest(OutHitResult, SkillDamage);
					}
					break;
					
					...


📌 2차 요구사항

스킬 타입을 고려
SkillDataTable → SkillAffectType 에 따라서 스킬 적용이 달라지도록 해주기

UENUM(BlueprintType)
enum class ESkillAffectType : uint8
{
	None UMETA(Hidden),

	Damage UMETA(DisplayName = "Damage"),
	Defense UMETA(DisplayName = "Defense"),
	Heal UMETA(DisplayName = "Heal"),
	Buff UMETA(DisplayName = "Buff"),
	Debuff UMETA(DisplayName = "Debuff"),

	Max UMETA(Hidden)
};

📌 마지막 요구사항

궤적 및 Cosmetic 시각화

진행 중

0개의 댓글

관련 채용 정보