Post

2026-05-19 TIL (60일차)

2026-05-19 TIL (60일차)

[TIL] 언리얼 엔진: 차량 복구 로직 리팩토링 및 자율주행 페일세이프(Fail-safe) 구현

자율주행 시뮬레이션 환경에서는 차량이 물리적 충돌이나 논리적 오류로 인해 맵 밖으로 튕겨 나가거나 도로를 완전히 이탈하는 상황이 발생할 수 있다.

오늘은 플레이어가 수동으로 차량을 리셋하는 기능과, 자율주행 시스템이 스스로 경로 이탈을 감지해 복구하는 페일세이프(Fail-safe) 기능을 통합하고 구조를 개선한 과정을 기록한다.


🛠️ 1. 차량 복구 로직의 범용성 개선 (리팩토링)

기존에는 플레이어가 특정 키를 누르면 실행되는 DoResetVehicle() 함수 안에 위치 계산 로직과 물리 엔진 초기화 로직이 모두 뭉쳐 있었다.

하지만 자율주행 컴포넌트(SplineFollower)에서도 “차량을 특정 위치로 강제 복구”시키는 기능이 필요해졌다. 코드 중복을 막기 위해 핵심 물리 제어 부분만 떼어내어 LocationRecoveryVehicle이라는 범용 함수로 분리(Extract Method)했다.

💻 리팩토링된 코드 (Team24VehiclePawn.cpp)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 1. 순수하게 '물리적 텔레포트 및 관성 리셋'만 담당하는 범용 함수
void ATeam24VehiclePawn::LocationRecoveryVehicle(const FVector& TargetLocation, const FRotator& TargetRotation)
{
    // ETeleportType::TeleportPhysics: 순간이동 시 궤적 사이의 물체와 충돌하지 않도록 물리 엔진을 잠시 끄고 이동
    SetActorTransform(FTransform(TargetRotation, TargetLocation, FVector::OneVector), false, nullptr, ETeleportType::TeleportPhysics);

    // 이동 속도와 회전 관성을 0으로 만들어, 절벽에서 떨어지던 힘이나 팽이처럼 돌던 힘을 완벽히 제거 (완전 정지)
    GetMesh()->SetPhysicsLinearVelocity(FVector::ZeroVector);
    GetMesh()->SetPhysicsAngularVelocityInDegrees(FVector::ZeroVector);
}

// 2. 플레이어 수동 리셋 로직 (이제 위 함수를 가져다 쓰기만 하면 됨)
void ATeam24VehiclePawn::DoResetVehicle()
{
    FVector ResetLocation = GetActorLocation() + FVector(0.0f, 0.0f, 50.0f); // 바퀴 파묻힘 방지 (Z축 +50)
    FRotator ResetRotation = GetActorRotation();
    ResetRotation.Pitch = 0.0f; // 쏠린 각도 초기화
    ResetRotation.Roll = 0.0f;  // 뒤집힌 각도 초기화

    LocationRecoveryVehicle(ResetLocation, ResetRotation);
}

💡 효과: 이제 Pawn 내부의 키 입력뿐만 아니라, 외부의 센서나 자율주행 컴포넌트에서도 위치/회전값만 던져주면 언제든 차량을 안전하게 텔레포트시킬 수 있게 되었다.


🚨 2. 스플라인 기반 자율주행 페일세이프 로직 구현

자율주행 컴포넌트(USplineFollowerComponent)에서 차량이 도로(Spline)를 벗어났을 때 스스로 판단하고 복구하는 로직을 추가했다. 무작정 텔레포트시키는 것이 아니라, ‘대기 후 복구’라는 인간적인(?) 안전장치를 두었다.

💻 이탈 감지 및 타이머 로직 (TickComponent)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
const FVector RoadHere = GetLocationAtDistance(CurrentDistance);

// 현재 위치와 도로 중앙선 사이의 거리가 허용 오차(MaxRoadDeviation)를 벗어났는가?
if (FVector::Dist(VehicleLoc, RoadHere) > MaxRoadDeviation)
{
    CurrentOffRoadTime += DeltaTime; // 이탈 시간 누적

    if (CurrentOffRoadTime >= MaxOffRoadTime)
    {
       // 허용 시간 초과: 강제 복구 가동
       RecoverVehicleToRoad(); 
       return; 
    }
    else
    {
       // 아직 대기 시간: 엑셀을 떼고 브레이크를 밟아 추가 이탈 방지
       ApplySpeedCommand(0.f, VehicleSpeed);
       ApplySteeringCommand(0.f);
       return;
    }
}
else
{
    // 정상 주행 중: 타이머 초기화 및 '마지막으로 안전했던 거리' 갱신
    CurrentOffRoadTime = 0.f;
    LastValidDistance = CurrentDistance;
}

💻 자율 복구 실행 로직

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void USplineFollowerComponent::RecoverVehicleToRoad()
{
    ATeam24VehiclePawn* Pawn = OwnerPawn.Get();
    if (!Pawn) return;

    // 마지막 정상 거리 정보를 바탕으로 스플라인 상의 3D 위치/방향 획득
    FVector ResetLocation = GetLocationAtDistance(LastValidDistance);
    FVector RoadDirection = GetDirectionAtDistance(LastValidDistance);
    ResetLocation.Z += 50.0f; // 땅 파묻힘 방지

    FRotator ResetRotation = RoadDirection.Rotation();
    ResetRotation.Pitch = 0.0f;
    ResetRotation.Roll = 0.0f;

    // 앞서 만든 범용 함수 호출!
    Pawn->LocationRecoveryVehicle(ResetLocation, ResetRotation);
    CurrentOffRoadTime = 0.f;
}

🤔 Q. 이탈 전 위치를 기억했는데 왜 무조건 도로 ‘정중앙’으로만 복귀할까?

(블루프린트 단축키로 강제 순간이동시켜서 그런 것이 아닐까 의심했지만, 원인은 완전히 다른 곳에 있었다!)

LastValidDistanceCurrentDistance를 매 프레임 저장하니까, 차가 차선 우측으로 치우쳐서 달리다 이탈했다면 우측으로 복귀해야 할 것 같지만 항상 도로 한가운데로 떨어지는 현상을 발견했다. 이는 언리얼 스플라인(Spline) 컴포넌트가 거리를 계산하고 반환하는 근본적인 원리 때문이다.

🔍 원인 분석: 1D 거리값 vs 3D 공간 좌표

  1. CurrentDistance가 저장하는 값은 X: 100, Y: 200, Z: 50 같은 3D 월드 좌표가 아니다.
  2. 이 값은 출발선으로부터 도로를 따라 ‘몇 미터(cm)를 달려왔는가’를 나타내는 1D 스칼라 값(예: 3500.0f)이다.
  3. 복구할 때 호출하는 GetLocationAtDistance(LastValidDistance) 함수는 엔진 내부적으로 GetLocationAtDistanceAlongSpline을 실행한다.
  4. 이 함수는 “스플라인 곡선을 따라 3500cm 이동했을 때의 3D 좌표를 줘!”라는 뜻이다. 스플라인 곡선은 도로의 ‘정중앙 뼈대’로 깔려 있으므로, 결국 무조건 스플라인 라인 위(도로 중앙)의 좌표를 뱉어내게 되는 것이다.

💡 결론:

블루프린트 강제 이동 때문이 아니라, 우리가 저장한 LastValidDistance 자체가 좌우 편향(Offset) 정보가 없는 종방향 진행 거리이기 때문에 도로 정중앙으로 스폰되는 것은 지극히 정상적이고 안전한 의도된 동작이다! (오히려 중앙으로 복귀하는 것이 자율주행 알고리즘이 다시 라인을 잡기에는 가장 이상적인 위치다.)

This post is licensed under CC BY 4.0 by the author.