학습일지/언리얼

차량 AI 스플라인 추종 + 브레이크 등 + 헤드라이트 + 사이드미러 구현

Tsukino Ren 2026. 5. 18. 22:34

언리얼 엔진에서 AI 차량이 스플라인 도로를 따라 자율주행하는 시스템을 만들면서 생긴 문제들을 해결했다.


1. SplineFollower - 커브에서 감속이 안 되는 문제

원인

LateralFriction 값이 너무 높으면 곡률 기반 안전속도가 높게 계산돼서 브레이크 명령이 거의 안 나온다.

안전속도 계산 공식:

V_max = sqrt(LateralFriction × 980 × Radius)

LateralFriction이 1.3이면 반경 1000cm 커브에서도 안전속도가 약 40km/h로 계산되기 때문에 차가 40km/h로 달려도 브레이크가 안 걸린다.

해결

Lateral Friction : 1.3 → 0.4
Brake Preview Dist : 8000 → 6000
Min Speed : 400 → 500

2. 브레이크 등 구현 - BlueprintImplementableEvent

구조

BrakeLights(bool bBraking)은 C++에서 선언하고 블루프린트에서 구현하는 방식이다.

// CityVehiclePawn.h
UFUNCTION(BlueprintImplementableEvent, Category="Vehicle")
void BrakeLights(bool bBraking);

C++에서 호출하면 블루프린트 이벤트 그래프에서 받아서 처리한다.

삽질 포인트 1 - Custom Event vs Event BrakeLights

블루프린트에서 Custom Event로 만들면 C++에서 호출해도 연결이 안 된다. 반드시 Event BrakeLights로 만들어야 한다.

삽질 포인트 2 - Braking 핀 반전

Select Float에서 Braking 핀이 반전되어 있어서 브레이크 밟을 때 등이 꺼지는 현상이 있었다. NOT Boolean 노드를 중간에 추가해서 해결했다.

C++ 수정

void ACityVehiclePawn::DoThrottle(float ThrottleValue)
{
    ChaosVehicleMovement->SetThrottleInput(ThrottleValue);
    ChaosVehicleMovement->SetBrakeInput(0.0f);
    BrakeLights(false); // 스로틀 밟으면 브레이크 등 끄기
}

void ACityVehiclePawn::DoBrake(float BrakeValue)
{
    ChaosVehicleMovement->SetBrakeInput(BrakeValue);
    ChaosVehicleMovement->SetThrottleInput(0.0f);
    BrakeLights(BrakeValue > 0.f);
}

블루프린트 구현

Event BeginPlay
    → Get Mesh
    → Create Dynamic Material Instance (Element Index=1, Source=M_SportsCar_Lights)
    → SET Chassis Material

Event BrakeLights
    → Set Scalar Parameter Value
        Target = Chassis Material
        Parameter Name = Opacity
        Value = Select Float (A=1.0, B=0.03, Pick A = NOT Braking)

3. 헤드라이트 - DirectionalLight Pitch로 낮밤 판단

머티리얼 슬롯이 앞뒤 통합이라 머티리얼로 분리가 불가능해서 Spot Light 컴포넌트로 대체했다.

낮밤 판단 로직

DirectionalLight의 Pitch 값이 0보다 크면 밤으로 판단한다.

Event Tick
    → Get All Actors Of Class (Directional Light)
    → GET [0] → Get Actor Rotation → Break Rotator
    → Y(Pitch) > 0.0
    → Branch
        True  → HeadLightLeft, HeadLightRight Set Visibility = true
        False → HeadLightLeft, HeadLightRight Set Visibility = false

Spot Light 설정값

Intensity : 10000
Attenuation Radius : 15000
Inner Cone Angle : 20
Outer Cone Angle : 40

4. 사이드미러 카메라

C++ 구현

// CityVehiclePawn.h
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="Components", meta=(AllowPrivateAccess="true"))
TObjectPtr<USceneCaptureComponent2D> LeftMirrorCapture;

UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="Components", meta=(AllowPrivateAccess="true"))
TObjectPtr<USceneCaptureComponent2D> RightMirrorCapture;

// CityVehiclePawn.cpp 생성자
LeftMirrorCapture = CreateDefaultSubobject<USceneCaptureComponent2D>(TEXT("LeftMirrorCapture"));
LeftMirrorCapture->SetupAttachment(GetMesh());

RightMirrorCapture = CreateDefaultSubobject<USceneCaptureComponent2D>(TEXT("RightMirrorCapture"));
RightMirrorCapture->SetupAttachment(GetMesh());

Render Target 설정

Render Target Format : RTF RGBA8   ← 고화질 포맷 쓰면 투명해짐!
Target Gamma : 1.0

SceneCaptureComponent2D 설정

Capture Source : Final Color (LDR) in RGB   ← 이거 안 하면 투명해짐!
Capture Every Frame : true

SceneColor vs Final Color 차이

  • SceneColor (HDR) : 렌더링 중간 단계 데이터. 알파 채널에 Inverse Opacity가 저장돼서 UI에서 투명하게 보임
  • Final Color (LDR) : 최종 렌더링된 화면. 알파 채널 정상. UI에 띄울 땐 이걸 써야 함

5. 급정거 카메라 피드백 - 델리게이트 + Timeline

C++ 델리게이트 추가

// CityVehiclePawn.h
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnEmergencyBrake, float, BrakeValue);

UPROPERTY(BlueprintAssignable, Category="Camera")
FOnEmergencyBrake OnEmergencyBrake;

// DoBrake에서 브로드캐스트
if (BrakeValue > 0.5f)
    OnEmergencyBrake.Broadcast(BrakeValue);

블루프린트 - Timeline으로 부드러운 카메라 이동

Event BeginPlay
    → Bind Event to On Emergency Brake
        → Create Event (Break Moving Camera)

Break Moving Camera (Custom Event)
    → Timeline Play from Start
        Update → SET BackSpringArm Target Arm Length = New Track 0

Timeline 커브:

0.0초 : 650 (시작)
0.3초 : 280 (앞으로 쏠림)
1.2초 : 650 (복귀)

마무리

BlueprintImplementableEvent는 블루프린트에서 반드시 Event XXX 로 받아야 하며 Custom Event로 만들면 연결 안 된다고 한다. Render Target을 UI Image에 쓸 때는 Final Color (LDR)로 변경해줘야 사용이 가능했다.

Timeline은 함수 안에서 못 쓰며 반드시 Custom Event 써야 한다

찾아보니 델리게이트는 C++에서 선언하고 블루프린트에서 바인딩하는 패턴이 실무에서 많이 쓰인다고 하여 실제로 그렇게 작성했다.