증상:
- 차량이 잘 달리다가 커브에서 갑자기 튕겨져 나감
- 튕긴 차량이 도로 밖으로 떨어진 후 도로 밑에서 계속 주행
- 자율주행은 여전히 정상 주행 중이라고 판단

1차 수정
// SplineFollowerComponent.cpp - ComputeSteeringErrors
const FVector ToLA = (LAPos - VehicleLocation).GetSafeNormal();
Errors.PositionError = FMath::FindDeltaAngleDegrees(
VehicleYaw,
FMath::Atan2(ToLA.Y, ToLA.X) * (180.f / PI));
// X, Y만 사용 → Z 무시
조향 계산이 X, Y 평면만 사용하고 있는 것?
Z(높이)를 무시하니까 차가 도로 밑에 있어도 평면 위치만 정상이면 괜찮다고 판단한다고 추측
// 차량 높이와 같은 평면으로 보정
FVector LAPosFlat = LAPos;
LAPosFlat.Z = VehicleLocation.Z;
이게 아닌 것 같음
AI에게 물어보니 원래 조향은 xy만 보는 게 자연스럽다고 함
생각해보니까 진짜 그럼
핸들 회전축 = 수직 (Z축)
↓
핸들 각도 변화 = 수평 (X-Y 평면)
↓
조향 계산이 X-Y 평면만 보는 게 자연스러움
2차 수정
// 차량 위치를 도로 위 거리로 변환
const float InputKey = Spline->FindInputKeyClosestToWorldLocation(VehicleLocation);
CurrentDistance = Spline->GetDistanceAlongSplineAtSplineInputKey(InputKey);
이 코드는 차량과 가장 가까운 도로 위 점을 찾는데 문제는 차량이 도로 밑에 30m 떨어져 있어도 가장 가까운 점은 여전히 그 위 도로 점
도로 (옆에서 본 모습):
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ← 도로 (지상)
↑
30m
↓
● ← 차량 (도로 밑 30m 깊은 곳)
여기서 함수가 가장 가까운 도로 위 점을 찾으면
도로의 모든 점 중에서:
- 차량 바로 위 점: 30m 떨어짐
- 다른 도로 점들: 더 멀리 떨어짐 (예: 50m, 100m...)
→ 차량 바로 위 점이 가장 가까움
→ 그 점의 거리값을 CurrentDistance에 저장
차량의 CurrentDistance = 234m
-> 234m 지점 방향으로 운전 명령
차량이 30m 아래에 있는지는 모르는 것
도로를 벗어났을 때 운전을 멈추게 하는 코드를 작성해야겠다고 생각함.
그래서 조향 코드는 그대로 두고 별도의 이탈 감지 로직(페일세이프) 을 추가하기로 결정
페일세이프 추가
// SplineFollowerComponent.h
UPROPERTY(EditAnywhere, Category = "Autopilot|Path")
float MaxRoadDeviation = 1500.f; // 15m
// SplineFollowerComponent.cpp - TickComponent
const FVector RoadHere = GetLocationAtDistance(CurrentDistance);
if (FVector::Dist(VehicleLoc, RoadHere) > MaxRoadDeviation)
{
ApplySpeedCommand(0.f, VehicleSpeed);
ApplySteeringCommand(0.f);
return;
}
FVector::Dist(VehicleLoc, RoadHere)
이건 3D 거리를 측정하기 때문에 차량이 도로 밑으로 떨어지면 감지됨
차량 위치: (1000, 500, -3000) ← Z가 -3000
도로 점: (1000, 500, 100)
3D 거리 = 3100 → MaxRoadDeviation(1500) 초과 → 페일세이프 작동
근데 이건 추후에 다른 팀원이랑 코드 병합하고 정확하게 수정해야할듯!
새로운 문제
내가 깐 spline에 도로 mesh가 붙어있는 게 아니고 이미 있는 도로 mesh 위에 spline을 깔다보니 차량이 붕 떠있고 주행이 부자연스러움!
[월드 상태]
RoadActor (스플라인만)
↓
Terrain (지형) 위에 깔림
↓
차량이 지형 위에서 주행
↓
지형 굴곡 때문에 튕김 발생
RoadActor에 메시 시각화 추가
ASplineMeshActor 상속받으려고 했는데, 알아보니까
USplineComponent USplineMeshComponent
ASplineMeshActor는 USplineMeshComponent를 가진 액터라서 이걸 상속받으면 내가 짜놓은 자율주행 코드가 깨짐.
(함수가 없으니까)
→ AActor 상속 유지 + USplineComponent + USplineMeshComponent들 동적 생성
// RoadActor.h
UPROPERTY(EditAnywhere, Category = "Road|Mesh")
TObjectPtr<UStaticMesh> RoadMesh;
UPROPERTY(EditAnywhere, Category = "Road|Mesh")
float SegmentLength = 500.f;
UPROPERTY()
TArray<TObjectPtr<USplineMeshComponent>> SplineMeshes;
또 문제 발생
도로 메시가 스플라인과 어긋난 위치에 생성됨
스플라인: ────────────
도로 메시: ←──────────── (살짝 옆에 떨어짐)
호출 순서를 잘못 했던 거였음
// 잘못된 순서
SetupAttachment(RootComponent);
RegisterComponent();
SetMobility(Movable);
SetStaticMesh(RoadMesh);
SetStartAndEnd(...);
SetForwardAxis(ForwardAxis);
SetupAttachment와 RegisterComponent를 너무 일찍 호출해서 메시가 잘못된 위치로 등록됨
// 올바른 순서: 모든 설정 끝낸 후 부착/등록
USplineMeshComponent* SMC = NewObject<USplineMeshComponent>(...);
SMC->SetMobility(EComponentMobility::Movable);
SMC->AttachToComponent(SplineComponent, ...);
SMC->SetForwardAxis(ForwardAxis, false); // 메시 적용 전!
SMC->SetStaticMesh(RoadMesh);
SMC->SetStartAndEnd(StartPos, StartTan, EndPos, EndTan, true);
SMC->RegisterComponent(); // 마지막!
또 문제 발생
메시 위치는 맞춰졌는데 이번엔 곡선 구간에서 도로가 직선처럼 보임

영상 자료와 BP_Road, ai를 참고해서 발견한 것:
// 기존
SMC->SetStartAndEnd(StartPos, StartTan, EndPos, EndTan, true);
스플라인에서 가져온 탄젠트(StartTan, EndTan)의 길이가 segment 실제 길이와 안 맞아서 메시가 출렁이거나 직선이 됨.
// 탄젠트 길이를 segment 실제 길이에 맞춰야 곡선이 자연스러움
StartTan = StartTan.GetSafeNormal() * ActualSegLen;
EndTan = EndTan.GetSafeNormal() * ActualSegLen;
SMC->SetStartAndEnd(StartPos, StartTan, EndPos, EndTan, true);
ai 도움을 받아 탄젠트 길이를 segment 길이로 정규화 해서 곡선을 표현함
또!!!!! 문제 발견
메시가 누적됨

같은 위치에 메시가 여러 층으로 겹쳐서 생성되는 현상이 발생
// 기존 ClearSplineMeshes
void ARoadActor::ClearSplineMeshes()
{
for (USplineMeshComponent* SMC : SplineMeshes)
{
SMC->DestroyComponent();
}
SplineMeshes.Reset();
}
언리얼 핫리로드 후 SplineMeshes 배열은 초기화되지만 액터에 실제 붙어있는 메시 컴포넌트는 그대로 남음
그래서 배열을 순회해도 아무것도 안 지워지고 새 메시만 계속 쌓인 것
void ARoadActor::ClearSplineMeshes()
{
// 액터에 붙어있는 모든 SplineMeshComponent를 직접 검색
TArray<USplineMeshComponent*> ExistingMeshes;
GetComponents<USplineMeshComponent>(ExistingMeshes);
for (USplineMeshComponent* SMC : ExistingMeshes)
{
if (SMC) SMC->DestroyComponent();
}
SplineMeshes.Reset();
}
코드 수정
GetComponents<USplineMeshComponent> 는 액터에 실제 붙어있는 모든 컴포넌트를 가져오기 때문에 누적 문제를 해결함
알게된 것
1. 호출 순서가 중요한 함수들
SetForwardAxis → SetStaticMesh → SetStartAndEnd → RegisterComponent
이 순서를 지키지 않으면 메시 위치/회전이 어긋남
모든 설정 끝낸 후 등록
2. 탄젠트 정규화의 중요성
스플라인 메시의 곡선 표현은 탄젠트 길이를 segment 길이에 맞추는 것이 핵심
StartTan = StartTan.GetSafeNormal() * ActualSegLen;
EndTan = EndTan.GetSafeNormal() * ActualSegLen;
3. 핫리로드와 컴포넌트 추적
배열에 추적된 컴포넌트만 정리하는 것이 항상 안전한 건 아님
액터에 실제 붙어있는 컴포넌트를 직접 검색(GetComponents<T>)하는 게 핫리로드 후에도 안정적
'프로젝트 > 자율주행 위험구간 분석 시뮬레이터' 카테고리의 다른 글
| 터널 시스템 구현하기 (0) | 2026.05.13 |
|---|---|
| [트러블슈팅] 차량을 도로 끝에서 멈추게 하기 (0) | 2026.05.11 |
| 파라미터 (0) | 2026.05.08 |
| 코드 정리와 확장 준비 (0) | 2026.05.08 |
| 커브에서 안 미끄러지는 속도 제어 (0) | 2026.05.08 |
