JIN

[UE5] 플레이어 공격의 충돌 처리 - ( ShapeComponent Sweep ) [2]

destroyer 2024. 10. 31. 06:00

최종 결과 영상

베지어 곡선

2차 베지어 곡선

베지어 곡선은, 조절점을 사용해 정의하는, 특별한 형태의 곡선이다. 2차 베지어 곡선은 시작점, 중간점, 끝점으로 만들어지는 곡선으로, 시작에서 중간점까지, 중간에서 끝점까지의 1차 베지어 곡선을 구한다. 시간 T에 대한 각각의 베지어 곡선의 지점 별로 위 그림과 같이 베지어 곡선을 한 번 더 구함으로써 곡선이 만들어지게 된다.

베지어 곡선 구성 과정

이 베지어 곡선을 이용해 이전 프레임과 현재 프레임의 위치를 직선이 아닌 곡선 이동 하도록 수정하고자 한다.

이전 프레임과 현재 프레임(초록색)의 중간 지점(빨간색)

우선 이전 프레임과 현재 프레임의 중간 지점을 구해서 디버그로 찍어보았다. 이 중간점을 기준으로 베지어 곡선을 만들어도, 세 점이 일직선 상에 있기 때문에 곡선이 만들어지지 않는다.

외적으로 제어점 구하기

외적을 이용해 적절한 곡선을 만들 제어점 위치 구하기

1. 이전 프레임 위치에서 현재 프레임 위치까지의 벡터(P2 - P1)의 단위벡터를 구한다.

2. 구한 단위벡터에 UP 벡터를 외적하여 수직이 되는 방향 벡터를 구한다.

3. P2-P1의 중간 지점으로부터 구한 수직 방향으로 (P2-P1)의 거리의 절반만큼 제어점을 이동시킨다.

4. 이동시킨 제어점을 기준으로 베지어 곡선을 구한다.

프레임 별 위치 (초록색) 제어점(빨간색)

각각의 프레임 위치에 대해 수직으로 뻗은 베지어 곡선 제어점을 확인할 수 있다.

이 제어점을 기준으로 베지어 곡선을 만들도록 코드를 수정하자.

베지어 곡선 적용한 코드

void AM4_PlayerWeaponSword::Tick(float DeltaTime)
{
    Super::Tick(DeltaTime);
    UCapsuleComponent* CapsuleComp = Cast<UCapsuleComponent>(CollisionComp);
    FVector CurrentWeaponLoc = CapsuleComp->GetComponentLocation();
    float CapsuleHalfHeight = CapsuleComp->GetScaledCapsuleHalfHeight();
    float CapsuleRadius = CapsuleComp->GetScaledCapsuleRadius();
    // 이전 위치가 설정되지 않은 경우 초기화
    if (PrevWeaponLoc.IsZero())
    {
        PrevWeaponLoc = CurrentWeaponLoc;
        return;
    }

    // 충돌 감지가 활성화된 경우에만 실행
    if (bOnCollision)
    {
        FVector Trajectory = CurrentWeaponLoc - PrevWeaponLoc;
        // 원형 궤적을 만들기 위한 제어점 계산
        FVector Forward = (CurrentWeaponLoc - PrevWeaponLoc).GetSafeNormal();
        FVector Right = FVector::CrossProduct(Forward, FVector::UpVector);
        FVector ControlPoint = (PrevWeaponLoc + CurrentWeaponLoc) * 0.5f;
        // 베지어 곡선 중간점 위치 살짝 틀어주기
        ControlPoint += Right * FVector::Dist(PrevWeaponLoc, CurrentWeaponLoc) * 0.5f;
        int NumSteps = FMath::CeilToInt(Trajectory.Size() / 30.0f); // 보간 스텝 수 결정
        for (int32 i = 0; i <= NumSteps; ++i)
        {
            float Alpha = i / static_cast<float>(NumSteps);
            FVector InterpolatedPosition = BezierLerp(PrevWeaponLoc, ControlPoint, CurrentWeaponLoc, Alpha);
           // 각 보간된 위치에서 충돌 감지 실행 ( 구현 예정 )

            // 디버그 캡슐을 표시하여 궤적 시각화
            DrawDebugCapsule(GetWorld(), InterpolatedPosition, CapsuleHalfHeight, CapsuleRadius, GetActorQuat(), FColor::Green, false, 1.f);
        }
    }
    // 이전 위치 업데이트
    PrevWeaponLoc = CurrentWeaponLoc;
}

FVector AM4_PlayerWeaponSword::BezierLerp(const FVector& Start, const FVector& Control, const FVector& End, float T)
{
    FVector Lerp1 = FMath::Lerp(Start, Control, T);
    FVector Lerp2 = FMath::Lerp(Control, End, T);
    return FMath::Lerp(Lerp1, Lerp2, T);
}

베지어 곡선을 적용한 궤적

어느정도 보완이 된 모습은 보이지만, 아직 완벽하게 보간되지는 않는다.

회전 보간

마지막으로 콜리전의 회전을 부드럽게 보간하려고 한다. 회전의 경우 정확하게 보간하기 위해 쿼터니언을 사용해 보간했다.

회전 보간까지 적용한 최종 코드

void AM4_PlayerWeaponSword::Tick(float DeltaTime)
{
    Super::Tick(DeltaTime);
    UCapsuleComponent* CapsuleComp = Cast<UCapsuleComponent>(CollisionComp);
    FVector CurrentWeaponLoc = CapsuleComp->GetComponentLocation();
    // 현재 회전
    FQuat CurrentRotation = GetActorQuat();
    float CapsuleHalfHeight = CapsuleComp->GetScaledCapsuleHalfHeight();
    float CapsuleRadius = CapsuleComp->GetScaledCapsuleRadius();
    // 이전 위치가 설정되지 않은 경우 초기화
    if (PrevWeaponLoc.IsZero())
    {
        PrevWeaponLoc = CurrentWeaponLoc;
        PrevWeaponRot = CurrentRotation;
        return;
    }

    // 충돌 감지가 활성화된 경우에만 실행
    if (bOnCollision)
    {
        FVector Trajectory = CurrentWeaponLoc - PrevWeaponLoc;
        // 원형 궤적을 만들기 위한 제어점 계산
        FVector Forward = (CurrentWeaponLoc - PrevWeaponLoc).GetSafeNormal();
        FVector Right = FVector::CrossProduct(Forward, FVector::UpVector);
        FVector ControlPoint = (PrevWeaponLoc + CurrentWeaponLoc) * 0.5f;
        // 베지어 곡선 중간점 위치 살짝 틀어주기
        ControlPoint += Right * FVector::Dist(PrevWeaponLoc, CurrentWeaponLoc) * 0.5f;
        int NumSteps = FMath::CeilToInt(Trajectory.Size() / 30.0f); // 보간 스텝 수 결정
        for (int32 i = 0; i <= NumSteps; ++i)
        {
            float Alpha = i / static_cast<float>(NumSteps);
            FVector InterpolatedPosition = BezierLerp(PrevWeaponLoc, ControlPoint, CurrentWeaponLoc, Alpha);
            // 회전 보간 추가
            FQuat InterpolatedRotation = FQuat::Slerp(PrevWeaponRot, CurrentRotation, Alpha);
            // 각 보간된 위치에서 충돌 감지 실행 ( 구현 예정 )

            // 디버그 캡슐을 표시하여 궤적 시각화
            DrawDebugCapsule(GetWorld(), InterpolatedPosition, CapsuleHalfHeight, CapsuleRadius, InterpolatedRotation, FColor::Green, false, 1.f);
        }
    }
    // 이전 위치 업데이트
    PrevWeaponLoc = CurrentWeaponLoc;
    // 이전 회전 업데이트
    PrevWeaponRot = CurrentRotation;
}

최종 보간 결과

대 만 족