트리거-반응 동기화: 이벤트 방송 vs 매 프레임 폴링, 언제 무엇을 쓰는가
게임이나 시뮬레이션을 만들다 보면 "어떤 상황이 발생했을 때 다른 컴포넌트가 반응하게 하는" 코드를 자주 짠다. 캐릭터가 위험 구역에 들어가면 UI에 경고를 띄우고, 차량이 장애물 앞에 오면 속도를 줄이고, 아이템을 획득하면 인벤토리가 갱신되는 식이다.
이런 트리거-반응 동기화에 흔히 쓰는 두 가지 패턴이 있다.
A. 진입/해제 이벤트 방송 — 상태가 바뀐 순간에만 방송, 반응 쪽은 구독
B. 매 프레임 현재 상황 폴링 — 반응 쪽이 매 프레임 "지금 상황이 뭔지" 물어봄
두 패턴은 자주 같은 자리에 놓고 비교되지만 적절한 선택은 상황마다 다르다. 이 글에서는 자율주행 시뮬레이터 프로젝트에서 같은 종류의 트리거-반응을 두 가지 방식으로 의도적으로 다르게 설계한 사례를 통해, 언제 어느 쪽을 택해야 하는지의 판단 기준을 정리한다.
이벤트 방송 (위험 감지 시스템)
차량 거동에서 위험을 감지하는 시스템은 이벤트 방식으로 설계했다.
void UHazardDetectorComponent::CheckSlip()
{
// ... 측정 ...
// 진입 판정
if (!bSlipActive && SlipAngleDeg >= SlipEnterDeg)
{
bSlipActive = true;
BroadcastSlip(EHazardPhase::Enter, SlipAngleDeg, Car, Speed);
}
// 해제 판정
else if (bSlipActive && SlipAngleDeg < SlipExitDeg)
{
bSlipActive = false;
BroadcastSlip(EHazardPhase::Exit, SlipAngleDeg, Car, Speed);
}
}
핵심은 "진입한 순간"과 "해제된 순간"을 각각 별도 이벤트로 방송한다는 점이다. 구독자(데이터 로거)는 이 두 사건을 받아 시계열 데이터로 기록한다.
12.34초 Skid Enter 위치 X 슬립각 18도
12.89초 Skid Exit 위치 Y 슬립각 9도
매 프레임 폴링 (전방 감지 시스템)
차량이 전방 과속방지턱을 감지하고 속도를 줄이는 시스템은 폴링 방식으로 설계했다.
// SplineFollower의 UpdateTargetSpeed 안에서 매 프레임 호출
const float BumpSpeed = CheckSpeedBumpAhead(VehicleSpeedNow);
if (BumpSpeed > 0.f)
{
SpeedLimit = FMath::Min(SpeedLimit, BumpSpeed);
}
CheckSpeedBumpAhead는 매 프레임 "지금 전방에 과속방지턱이 있나"를 묻고, 있으면 줄여야 할 속도를, 없으면 -1을 반환한다. 진입을 따로 알릴 필요도 없고, 통과 후 복원을 위한 별도 로직도 없다. 통과하면 자연스럽게 검사 함수가 -1을 반환하고 SpeedLimit이 다른 제한들로 다시 결정된다.
왜 둘을 다르게 설계했나
같은 종류의 트리거-반응처럼 보이지만 설계 결정이 정반대로 갈린 이유는 명확하다.
위험 감지: "사건"이 분석 단위 → 시작과 끝을 각각 기록할 필요 있음
전방 감지: "현재 상황"이 결정 단위 → 상태를 기억할 필요 없음
위험 감지의 결과물은 데이터 분석에 쓰일 시계열 로그다. "12.34초에 슬립이 시작됐고 12.89초에 끝났다"라는 사건의 시작과 끝이 분석의 단위가 된다. 만약 매 프레임 "지금 슬립 중인가?"만 폴링했다면, 매 프레임 같은 상태가 60번씩 찍혀 분석이 불가능해진다.
전방 감지의 결과물은 차량의 현재 속도 결정이다. "지금 이 순간 전방에 무엇이 있는가"만 알면 충분하고, 언제 진입했는지나 얼마나 오래 있었는지는 결정에 영향을 주지 않는다. 매 프레임 같은 질문을 던지면 된다.
이벤트 방식의 장점과 함정
장점은 명확하다.
- 발생 빈도가 낮은 사건에서 효율적이다. 폴링은 일어나지 않는 상황도 매 프레임 확인하지만, 이벤트는 변화가 있을 때만 동작한다
- 구독자가 사건의 시작과 끝을 정확히 알 수 있다. 시계열 분석, 통계 집계, 로그 기록에 적합하다
- 발신자와 수신자가 결합하지 않는다. 델리게이트만 알면 누가 구독하든 발신자는 모른다
함정은 상태가 어긋날 수 있다는 점이다.
- 진입 이벤트는 방송했는데 해제 이벤트가 누락되면 수신자는 영영 "진입 상태"로 남는다
- 게임 중간에 합류한 컴포넌트는 "이미 진입 중인 상태"를 모른다. 별도 초기 동기화 로직이 필요하다
- 이벤트 순서가 꼬이면 (해제 → 진입처럼 거꾸로 도착하면) 상태가 잘못된다
이 함정들은 "사건"이 분석의 단위인 경우에는 감수할 만하다. 분석 데이터에서는 누락된 사건을 후처리로 찾을 수 있다. 하지만 "현재 결정"이 목적이라면 이 함정 하나가 곧장 차가 영영 감속 상태에 머무는 사고로 이어진다.
폴링 방식의 장점과 함정
장점은 단순함이다.
- 상태가 어긋날 수 없다. 매 프레임 새로 판단하기 때문에 이전 프레임의 잘못된 상태가 남지 않는다
- 진입과 해제를 따로 짤 필요가 없다. 통과는 단지 "더 이상 조건이 안 맞음"의 자연스러운 결과다
- 중간 합류 문제가 없다. 새로 들어온 컴포넌트도 다음 프레임에 정상 동작한다
함정은 비용이다.
- 발생 빈도가 낮은 사건도 매 프레임 확인한다. 검사 비용이 클수록 부담이 커진다
- 사건의 정확한 시작과 끝 시각을 모른다. "프레임 N에는 있었고 프레임 N+1에는 없었다"만 알 수 있다
- 두 프레임 사이에 일어난 짧은 사건은 놓칠 수 있다
이 함정들은 "현재 결정"이 목적인 경우에는 거의 문제가 되지 않는다. 매 프레임 결정하니까 시작 시각을 굳이 알 필요가 없고, 짧은 사건은 어차피 결정에 영향이 없다.
판단 기준
두 패턴 중 무엇을 쓸지의 판단은 "결과물이 무엇을 위한 것인가"에서 갈린다.
결과물이 분석할 데이터(시계열 로그, 사건 통계) → 이벤트 방송
결과물이 매 프레임 결정(속도, 위치, 상태 분기) → 매 프레임 폴링
같은 자율주행 시뮬레이터에서 위험 감지가 이벤트, 전방 감지가 폴링으로 갈린 것이 이 기준의 직접적인 적용 사례다. 분석을 위해서는 사건의 경계가 필요하고, 결정을 위해서는 현재 상황만 필요하다.
이 기준을 의식하지 못하면 두 가지 흔한 실수가 발생한다.
첫째, 매 프레임 결정해야 하는 자리에 이벤트 방식을 쓰는 경우. "차량이 위험 구역에 진입했다"는 이벤트를 받고 속도 제한 상태를 켰는데, 해제 이벤트가 누락되거나 순서가 꼬여서 차가 영영 감속 상태에 머문다. 진입/해제 두 사건의 정합성을 맞추는 데 코드 절반을 쓰게 된다.
둘째, 분석 데이터가 필요한 자리에 폴링을 쓰는 경우. 매 프레임 "지금 슬립 중인가?"를 로그에 찍으면 60Hz × 슬립 지속 시간만큼 같은 데이터가 쌓여 분석이 불가능해진다. 사후에 "연속된 같은 상태를 묶기"라는 후처리를 짜야 한다.
각 패턴의 장점이 다른 자리에서는 함정으로 변한다. 어느 쪽이 좋고 나쁜 게 아니라, 결과물의 성격에 맞춰 선택하는 것이 핵심이다.
함께 쓰는 경우
한 시스템 안에서 두 패턴이 같이 등장하기도 한다. 위 사례의 위험 감지 컴포넌트도 내부 로직을 잘 보면 매 프레임 폴링으로 측정하고, 그 결과가 임계값을 넘는 순간에만 이벤트를 방송한다.
매 프레임 폴링 (CheckSlip)
→ 측정 (현재 슬립각 계산)
→ 임계값 비교
→ 상태가 바뀐 순간에만 이벤트 방송 (Enter / Exit)
이건 두 패턴 중 하나를 고른 것이 아니라, "내부는 폴링으로 측정하고 외부에는 이벤트로 통보한다"는 조합이다. 측정 자체는 매 프레임 일어나야 정확하지만 (놓치면 안 되니까) 외부에 알리는 것은 사건 단위로만 일어나야 한다 (분석에 쓰여야 하니까).
이 조합 패턴은 다른 영역에도 그대로 쓰인다. 입력 처리에서 키 상태는 매 프레임 폴링하지만 "키가 막 눌렸다"는 사건만 다른 시스템에 알리는 것, 충돌 검사에서 매 프레임 위치를 비교하지만 "막 충돌했다"는 순간만 이벤트로 방송하는 것이 같은 구조다.
내부 측정과 외부 통보의 단위를 분리하는 감각이 두 패턴 중 어느 한쪽에 매몰되지 않게 해준다.
'etc' 카테고리의 다른 글
| 언리얼 델리게이트로 트리거-반응 결합도 낮추기, 그리고 토글 상태의 베이스라인 패턴 (0) | 2026.05.16 |
|---|---|
| 언리얼 Chaos Vehicle이 멈추지 않고 후진할 때: bReverseAsBrake와 상태 머신 이해 (0) | 2026.05.16 |
| 언리얼 런타임 SplineMeshComponent 동적 생성: 호출 순서·탄젠트 정규화·핫리로드 함정 (0) | 2026.05.16 |
| 언리얼 스플라인 경로 추종 차량이 도로 밖으로 튕길 때, 평면 조향과 이탈 페일세이프 분리 (0) | 2026.05.16 |
| 언리얼 자율주행 차량, 곡률 반경으로 안전 속도 계산하기 (V = √(μgR)) (0) | 2026.05.13 |
