비, 눈, 안개 속에서도 안전하게 주행해야 하고 그런 환경의 데이터는 실제로 모으기 어렵고 위험하기 때문에 시뮬레이터 안에서 악천후를 합성해보는 게 좋을 것 같아서 구현하기로 함
팀 작업이기 때문에 기본적인 명세서가 필요할 것 같아 작성함
spline 알고리즘 짜야하는 내 목표:
비나 눈이 오면 도로가 미끄러워지니까 그에 맞춰서 자율주행 차가 알아서 천천히 달리게 하자
주요로 본 사항 -> 도로의 마찰력이 바뀌었을 때 차가 원래대로 악셀을 밟았다가 미끄러지면서 속도가 순간적으로 빨라지거나 아니면 급브레이크를 밟아서 속도가 갑자기 줄어들지 않도록 터널을 지났을 때 초반은 속도를 일정하게 유지하도록 설정하고, 점차 속도를 천천히 줄여나가도록 설정하자
이미 있던 터널 시스템 이용하기
터널 시스템 구조
ATunnelTrigger (공간 트리거)
↓
Pawn의 OnTunnelToggleDelegate.Broadcast(bool)
↓
각 컴포넌트의 ApplyTunnelProfile(bool)
델리게이트 하나로 여러 컴포넌트에 동시에 신호를 뿌리는 팬아웃(fan-out) 구조
날씨 시스템도 같은 방식으로 구현
| 트리거 주체 | ATunnelTrigger (공간 기반) | UWeatherSubsystem (월드 전역) |
| 신호 타입 | bool | EWeather enum |
| 차량 내부 전달 | Pawn 델리게이트 | Pawn 델리게이트 |
| 컴포넌트 함수 | ApplyTunnelProfile(bool) | ApplyWeatherProfile(EWeather) |
| 상태 복원 | 베이스라인 캐싱 | 베이스라인 캐싱 |
매니저 액터 생성
날씨를 관리하는 무언가가 필요함
가장 먼저 떠올린 건 터널과 똑같은 방식 차용하기
날씨도 터널과 비슷하게 AWeatherManager라는 액터를 맵에 하나 두면 되지 않을까?
근데 터널은 Overlap으로 차를 알지만, 날씨는 맵 전체에 적용되니까 매니저가 직접 월드를 뒤져서 차를 찾아야 함
언리얼에는 TActorIterator라는 월드의 특정 종류 액터를 전부 훑는 도구가 있음 -> 이걸로 차를 찾을 계획
튜터님의 조언
튜터님이 WorldSubsystem을 사용해보라고 하셨다
WorldSubsystem 찾아보기!
쉽게 말하면 맵이 시작될 때 엔진이 자동으로 만들어 주는 보이지 않는 매니저
접속 코드
UWeatherSubsystem* Weather = GetWorld()->GetSubsystem<UWeatherSubsystem>();
Register 패턴이 뭔가
매니저가 차를 찾아다니는 게 아니라 차가 태어날 때 스스로 손을 드는 방식
등록된 명단만 들고 있다가, 공지(날씨 변경)가 생기면 명단 전체에 한 번에 뿌린다
차량이 BeginPlay에서 → WeatherSubsystem->RegisterVehicle(this)
도로가 BeginPlay에서 → WeatherSubsystem->RegisterRoad(this)
이 방식이 1차 계획의 문제를 전부 해결함
배치 깜빡할 일 없고(엔진이 자동 생성), 차가 여러 대여도 다 등록되며, 타이밍 문제 없고(각자 태어날 때 등록), 도중에 생긴 차도 등록하면 합류됨
원래 계획에서는 내가 이 코어(뼈대)를 다 만들 예정이었는데 팀원이 해주심
원래 내 작업: 코어 전체 + 자율주행 반응 + 통합 테스트
바뀐 내 작업: 자율주행 반응 + 통합 테스트
(코어는 팀원이 만든 인터페이스 위에 얹기)
팀원이 보낸 코드를 받음
명세서와 다른 부분 발견! -> DataAsset 도입
처음 계획
// 이렇게 하려고 했음
switch (Weather)
{
case EWeather::Rain: FrictionScale = 0.6f; break;
case EWeather::Snow: FrictionScale = 0.3f; break;
}
그런데 팀원이 만든 구조는 WeatherPresetDataAsset이라는 데이터 에셋을 두고, 실제 숫자는 콘텐츠 브라우저의 에셋 파일(DA_Weather_Rain 등)에서 입력하는 방식
팀원이 바꾼대로 코드와 데이터를 분리하는 게 좋다고 생각함
- 원래: 비의 마찰을 0.6에서 0.5로 바꾸려면 → 코드 수정 → 빌드(컴파일 대기) → 확인
- DataAsset: 비의 마찰을 바꾸려면 → 에디터에서 에셋 파일 열고 숫자만 고치면 됨
빌드를 안 기다려도 되고, 코드를 모르는 사람도 수치를 조정할 수 있음.
그럼 나는
1. DataAsset에 변수를 선언하기
2. 그 변수를 읽어서 적용하는 코드 짜기
터널과 날씨가 충돌하는 문제
코드를 짜려다보니 문제 발생
터널 시스템과 날씨 시스템이 같은 자율주행 파라미터를 건드림
터널에 들어가면 속도/시야 파라미터가 줄어듦
날씨가 나빠져도 비슷한 파라미터를 건드리고 싶음...
근데 그러면 문제 발생
1. 터널 진입 → 시야값 = 원본 × 0.6 (터널이 깎음)
2. 터널 안에서도 비 오는 시스템 적용됨 → 날씨도 같은 변수 건드림
3. 어느 게 진짜 원본인지 꼬임 → 터널 나가도 복원 안 됨
- 원본 하나만 두고, 최종값 = 원본 × 터널계수 × 날씨계수로 곱해서 누적
- 날씨는 터널과 다른 파라미터(LateralFriction)만 건드린다. 백업 변수도 따로 둔다.
- 터널과 날씨를 하나의 함수로 합쳐서 통합 관리
셋 중 하나 선택해야 하는데... 시간도 별로 없고 터널 코드 건드는 건 너무 위험하다고 생각해서
그냥 날씨는 터널과 다른 파라미터만 건드리기로 함
터널은 속도/시야를, 날씨는 마찰만 건드림
백업 변수도 bBaselineCached(터널용)와 bWeatherBaselineCached(날씨용)로 분리
서로 다른 변수를 건드리니 충돌이 없슴
마찰만 바꿨는데 속도가 알아서 줄어들게 설계 (원래 짠 코드가 그럼)
그래서 날씨 신호를 받으면 마찰 계수 하나만 바꾸면 됨
void USplineFollowerComponent::ApplyWeatherProfile(EWeather Weather)
{
// 처음 호출될 때 원본 마찰값을 딱 한 번 백업
if (!bWeatherBaselineCached)
{
BaselineLateralFriction = LateralFriction;
bWeatherBaselineCached = true;
}
// 현재 날씨의 DataAsset에서 배율을 읽어옴
float FrictionScale = 1.f; // 못 찾으면 원본 그대로 (안전장치)
if (UWorld* World = GetWorld())
{
if (UWeatherSubsystem* WeatherSub = World->GetSubsystem<UWeatherSubsystem>())
{
if (UWeatherPresetDataAsset* Preset = WeatherSub->GetCurrentWeatherPreset())
{
FrictionScale = Preset->LateralFrictionScale;
}
}
}
// 원본 × 배율 (현재값이 아니라 원본 기준으로 계산!)
LateralFriction = BaselineLateralFriction * FrictionScale;
}
BaselineLateralFriction * FrictionScale. 현재값이 아니라 백업한 원본에 곱함
현재값에 곱하면 날씨를 여러 번 바꿀 때 값이 오염됨
| 시작 | 0.8 | 0.8 |
| 비 (×0.6) | 0.48 | 0.48 |
| 눈 (×0.3) | 0.48×0.3 = 0.144 ❌ | 0.8×0.3 = 0.24 ✅ |
| 맑음 (×1.0) | 0.144 ❌ | 0.8 ✅ |
현재값에 곱하면 맑음으로 돌아와도 원본 복원이 안 됨
항상 원본 기준으로 계산해야 깨끗함
이건 터널 시스템의 BaselineMaxSpeed * TunnelSpeedScale에서 이미 쓰던 패턴

잘 작동되는 거 확인
속도는 왜 안 건드렸나
비가 오면 차가 느려져야 하는데, 코드에서 속도를 직접 건드리지 않았음 그래도 차가 느려지기 때문
이유는 기존 자율주행 코드에
곡선 안전속도는 속도 = √(마찰 × 중력 × 곡선반경) 공식으로 계산됨.
마찰을 낮추면 안전속도가 자동으로 낮아짐
-> 그 낮아진 목표를 향해 차가 부드럽게(FInterpTo라는 보간 함수로) 감속
그래서 비 오는 순간 급정거가 아니라 자연스럽게 느려짐
마찰 하나만 바꿨는데 시스템이 알아서 연쇄 반응
알게된 것
의문이 들었음
도로의 PM이 변경되어서 마찰력이 실제로 변화되는 게 내 코드랑 상관이 없는데?
내가 짠 ApplyWeatherProfile은 비가 오면 LateralFriction이라는 값을 낮춤
근데 실제 도로의 물리 재질을 바꾸는 코드는 따로 있음
여기서 갑자기 의문
비가 오면 미끄러워지는 건데, 왜 내 마찰 코드랑 실제 도로 마찰이 따로 놀지?
이거 설계가 잘못된 거 아닌가?
ai 도움 받아서 파악해보니
| 판단 층 | "비 오니 천천히" 마음먹음 | ApplyWeatherProfile → LateralFriction 낮춤 → 자율주행이 곡선을 천천히 돌도록 판단 | 나 (자율주행) |
| 물리 층 | 젖은 노면이 실제로 미끄러움 | 도로 PhysicsMaterial 교체 → 물리 엔진이 타이어를 실제로 미끄러지게 계산 | 팀원 (물리/도로) |
내가 만진 LateralFriction은 판단 층
이건 자율주행 알고리즘이 "곡선을 얼마나 빨리 돌아도 되나"를 계산할 때 쓰는 가정값임
즉, 자율주행의 머릿속에서만 쓰는 계산용 숫자
도로 PhysicsMaterial은 물리 층
언리얼 물리 엔진이 타이어와 노면의 실제 마찰을 계산할 때 쓰는 값
자율주행이 뭘 생각하든 상관없이, 물리적으로 차를 미끄러뜨리는 진짜 값
두 층이 합쳐져야 시뮬레이터의 진짜 가치
팀원이 물리 층을 마저 구현하면, 비 오는 날 이렇게 된다.
유저가 비 켬 → 날씨 변경
│
├─[판단 층, 나] LateralFriction ↓
│ → 자율주행: "곡선 안전속도 낮춰야지" → 천천히 몰려고 시도
│
└─[물리 층, 팀원] 도로 물리 재질 = 비 버전 (마찰 ↓)
→ 물리 엔진: 타이어가 실제로 더 잘 미끄러짐
그러면 두 층이 서로를 검증함
- 내 판단이 정확하면: 자율주행이 적절히 감속 → 미끄러운 도로에서도 안 미끄러지고 잘 돈다. 성공.
- 내 판단이 너무 안일하면: 충분히 감속 안 함 → 미끄러운 도로에서 실제로 미끄러져 코스 이탈. 알고리즘의 약점이 드러남
트러블슈팅 모음
아직 없음
'프로젝트 > 자율주행 위험구간 분석 시뮬레이터' 카테고리의 다른 글
| [트러블슈팅] 터널과 날씨 시스템의 충돌 (0) | 2026.05.19 |
|---|---|
| [트러블슈팅] 날씨 별 속도가 동일, 마찰력 줄어들면 코너에서 추락 (0) | 2026.05.18 |
| 터널 시스템 구현하기 (0) | 2026.05.13 |
| [트러블슈팅] 차량을 도로 끝에서 멈추게 하기 (0) | 2026.05.11 |
| [트러블슈팅] 커브길에서 차량이 도로 밖으로 튕겨나가는 문제 (0) | 2026.05.11 |
