Game Development/Devlog

[UE5] 스킬 구현, 타격-피격 시스템, 처형 기능 구현 (프로젝트ASR - 개발일지4)

Dlaiml 2024. 8. 18. 23:54

 

 

[UE5] Blader 캐릭터 기초 조작 전투 구현, 모션 워핑을 사용한 타겟팅 시스템 (프로젝트ASR - 개발일

이전 포스팅에 이어 작성하는 프로젝트 개발일지입니다. GitHub - yjunej/ProjectASR: ProjectASR - Developed with Unreal Engine 5.4ProjectASR - Developed with Unreal Engine 5.4. Contribute to yjunej/ProjectASR development by creating an

dlaiml.tistory.com

 

 

GitHub - yjunej/ProjectASR: ProjectASR - Developed with Unreal Engine 5.4

ProjectASR - Developed with Unreal Engine 5.4. Contribute to yjunej/ProjectASR development by creating an account on GitHub.

github.com

 

이전 포스팅 이후 많은 기능 구현을 마쳤다. 기간 내에 마무리하기로 하였던 두 번째 캐릭터의 스킬 2개와, 회피, 방어, 처형모션까지 우선 기능적으로 동작하게 구현을 마쳤으며 적 캐릭터도 간단한 공격, 콤보 / 타격-피격 Interface /  Behavior Tree의 구현을 끝냈다.

Stride Warping의 도입과 State Machine 개선으로 캐릭터 애니메이션도 개선하였다.

 

코드를 작성하는데 사용한 시간보다 사운드, 특수효과, 모델을 제작하거나 카메라 위치, 캐릭터 조작 등을 개선하기 위해 다른 게임을 분석하는 시간이 더욱 많았다.

 

 

 

포스팅을 매일 쓰기가 어려워 대신 하루 작업이 끝나고 유튜브에 간단하게 영상을 업로드하고있다. (위 영상 유튜브 채널 참고)

 

  • 스킬 구현
  • 타격 - 피격 시스템 구현
  • 처형, 가드 기능 구현

 

스킬 구현

 

모션워핑과 Time Dilation을 사용하여 만든 두 번째 스킬이다. Q키 입력시 준비 동작에 진입하며 Global Time Dilation을 조정하여 시간이 느려지는 느낌을 추가하였다. 

시전 이후 틱 마다 카메라 기준 전방 지점에서 SphereTrace를 진행하고 Hit이 발생한 Enemy를 배열에 저장해 둔다.

 

// Blader.cpp
if (bIsUltCharging)
{
    UltTargets.Empty();
    UCameraComponent* FollowCam = GetFollowCamera();
    FVector TraceEnd;
    TArray<TEnumAsByte<EObjectTypeQuery>> ObjectTypes;
    ObjectTypes.Add(UEngineTypes::ConvertToObjectType(ECollisionChannel::ECC_Pawn));

    TraceEnd = FollowCam->GetComponentLocation() + FollowCam->GetForwardVector() * 1000;
    TArray<FHitResult> HitResults;

    TArray<AActor*> ActorsToIgnore;
    ActorsToIgnore.Add(Cast<AActor>(this));


    bool bHit = UKismetSystemLibrary::SphereTraceMultiForObjects(
        GetWorld(),
        GetActorLocation() + GetFollowCamera()->GetForwardVector() * 1000,
        GetActorLocation() + GetFollowCamera()->GetForwardVector() * 1000,
        1000,
        ObjectTypes,
        false,
        ActorsToIgnore,
        EDrawDebugTrace::None,
        HitResults,
        true
    );
}

 

Q키를 다시 떼는 Release 이벤트 이후에는 저장된 Enemy 배열의 첫 Enemy의 Location, Rotation을 Warp Target으로 지정한다.

 

if (UltTargets.Num() > 0)
{
    AActor* Target = UltTargets[UltTargetIndex];

    GetMotionWarpingComponent()->AddOrUpdateWarpTargetFromLocationAndRotation(
        FName("Ult"), Target->GetActorLocation(), Target->GetActorRotation()
    );
    CharacterState = EASRCharacterState::ECS_Attack;
    PlayUltAttackMontage();
}
else
{
    ResetUlt();
}

 

이후 한 번의 타격 애니메이션이 시작 시점에 Motion Warping을 사용하여 첫 Target에게 접근하고 애니메이션이 끝나는 시점에 Anim Notify를 삽입하여 다시 다음 타겟을 지정하도록 하였다

 

// UltHandleNextAttack.cpp (AnimNotify)
if (UltIndex < UltTargets.Num())
{
    const AActor* Target = UltTargets[UltIndex];
    if (Blader->GetMotionWarpingComponent() == nullptr)
    {
        return;
    }

    Blader->GetMotionWarpingComponent()->AddOrUpdateWarpTargetFromLocationAndRotation(
    FName("Ult"), Target->GetActorLocation(), Target->GetActorRotation()
    );
    Blader->PlayUltAttackMontage();
}
else
{
    Blader->UltEnd();
}

 

마지막 공격에는 저장해 둔 제자리로 다시 돌아와서 마무리하고 칼을 집어넣는 모션에 맞춰 만들어둔 HitInterface를 사용하여 대미지를 적용하였다.

 

타격 - 피격 시스템

HitInterface를 만들어 공격하는 대상과 공격받는 대상이 각자의 로직을 자신의 클래스에서 담당할 수 있도록 하였다. 

공격 애니메이션 몽타주의 애님 노티파이 목록인데 AN_Trace라는 애님 노티파이를 사용하여 타격을 처리하고 있다.

 

공격 애니메이션에서 정확히 칼로 베는 시점에 AN_Trace 노티파이에서 캐릭터의 Sphere Trace 함수를 호출한다. SphereTrace는 Trace를 진행하고 HitResult를 확인하여 Hit 이벤트가 발생하였으면 적 캐릭터 클래스의 Hit 인터페이스의 함수를 호출한다. 

 

HitInterface->GetHit(HitResult, this, BaseDamage, DamageType);


Enemy 클래스의 GetHit 함수는 피격을 처리하는 함수로 

1. 캐릭터의 State가 Death라면 Return

2. 대미지만큼 체력 감소

3. 캐릭터의 Movment Component를 확인하여 체공 중이 아니고(공중 콤보공격 도중 죽지 않도록) 체력이 0 이하라면 사망처리(애니메이션, State 업데이트)

4. 대미지타입 매핑에 따른 State 업데이트 및 애니메이션 재생

 

 

AN_Trace 애님 노티파이는 Input으로 대미지 타입과 대미지, 공격 범위를 요구하고 Hit 인터페이스를 통해 이를 전달받은 피격 캐릭터는 대미지타입 -> State, 애니메이션 TMap에 따라 자신의 State를 업데이트하고 애니메이션을 재생한다. 

 

 

위와 같이 타격-피격 시스템을 설계하고 나서 다양한 공격에 따른 피격 애니메이션, State 관리를 더욱 쉽게 할 수 있었다.

 

처형, 가드 기능 구현

가장 상단의 유튜브 영상 링크를 보면 적의 공격을 방어하고 적의 체력이 낮아지자 타겟팅 점이 빨간색으로 변하면서 적을 처형하는 것을 볼 수 있다.

 

우선 가드는 적의 공격을 받고 나서 플레이어의 Hit 인터페이스에서 처리하였으며 현재 캐릭터가 가드 상태에 있을 경우 대미지타입 Map에 따른 State 업데이트, 애니메이션 재생이 이루어지지 않고 가드와 관련된 애니메이션과 대미지에 따른 넉백을 적용받는다.

// Gaurd
if (CharacterState == EASRCharacterState::ECS_Guard && IsAttackFromFront(HitResult))
{
    FVector KnockbackForce = -GetActorForwardVector() * Damage * 10;
    LaunchCharacter(KnockbackForce, true, true);
    PlayAnimMontage(GuardAcceptMontage);
    // TODO: Add Stability System
    return;
}

 

공격 방향이 캐릭터의 전방인지 벡터의 내적을 통해 확인하는 IsAttackFromFront 함수를 추가하여 캐릭터의 가드 유효 범위를 지정하였다.

 

bool AASRCharacter::IsAttackFromFront(const FHitResult& HitResult) const
{
	FVector AttackDirection = (HitResult.TraceStart - HitResult.TraceEnd).GetSafeNormal();
	FVector CharacterForward = GetActorForwardVector();
	FVector NormalizedAttackDirection = AttackDirection.GetSafeNormal();
	float DotProduct = FVector::DotProduct(CharacterForward, NormalizedAttackDirection);
	return DotProduct > 0.5f; 
}

 

처형 기능은 Motion Warping을 사용하였으며 적 캐릭터의 체력에 변동이 있을 때마다 Broadcast 하는 Delegate를 사용하여 UserWidget에서 처형 가능 체력 이하일 경우 타겟팅 마커를 빨간색으로 바꾸고 애니메이션을 재생하도록 하였다. 

 

 

처형 장면을 위한 Camera Actor를 따로 두고 Blend를 통해 장면을 전환시켰으며 처형당하는 적 캐릭터의 경우 공격자를 향하도록 Rotation을 변경하였고 애니메이션 프레임을 맞추어 칼을 찌르고 뽑는 애니메이션과 피격당하는 캐릭터의 애니메이션이 자연스럽게 진행되도록 하였다.

 

 (이전에 다루었던 Motion Warping을 사용하여 구현한 기능이라 코드 설명은 생략하겠습니다)