Blog

ENGINEERING NOTE

[Unreal Engine] 업데이트 패턴

해당 포스팅은 Stuart Butler , Tom Oliver의 Game Development Patterns with Unreal Engine 5를 바탕으로 작성한 내용입니다.게임 루프에서 각 게임 객체의 상태를 매 프레임(또는 일정한 Tick)마다 업데이트하는 구조

Unreal Engine
해당 포스팅은 Stuart Butler , Tom Oliver의 Game Development Patterns with Unreal Engine 5를 바탕으로 작성한 내용입니다.

업데이트 패턴(Update Pattern)이란?

게임 루프에서 각 게임 객체의 상태를 매 프레임(또는 일정한 Tick)마다 업데이트하는 구조.

즉, 모든 객체는 게임 루프의 한 부분을 차지하며 각 객체는 매 프레임 update() 같은 메서드를 호출받아 자신의 상태를 갱신한다.

이를 통해 게임은

  • 물리 연산
  • AI 판단
  • 이동 처리
  • 애니메이션 갱신
  • 충돌 검사

등을 프레임마다 안정적으로 수행할 수 있다.

업데이트 패턴을 사용하는 이유

업데이트 패턴은 단순 반복이 아니라 게임 전체를 일정한 리듬으로 동기화시키는 설계 철학이다. 또한 현대의 대부분의 엔진이나 게임에서는 이러한 업데이트 패턴을 기반으로 구성되어 있어 학습의 접근성이나 시스템의 구축을 빠르게 진행할 수 있다.

이로 인한 장점은 다음과 같다.

1. 모든 객체가 균일한 방식으로 업데이트됨

각 객체는 동일한 방식으로 동작을 갱신한다:

cpp
void GameLoop() {
    for (auto object : objects) {
        object->Update(deltaTime);
    }
}

이 덕분에 객체는 서로 독립적으로 움직이되, 같은 규칙 아래에서 안정적으로 동작할 수 있다.

2. 모듈화 + 확장 용이

각 객체는 자신의 업데이트 로직만 신경 쓰면 되기 때문에 새로운 기능을 추가해도 전체 게임 루프는 건드리지 않아도 된다.

업데이트 패턴의 남용 및 오용

게임 개발에서 Tick(), Update(), 매 프레임 반복되는 루프는 너무 익숙한 개념이다. 하지만 모든 로직을 업데이트 패턴 안에 우겨 넣는 순간, 프로젝트는 곧바로 유지보수 지옥으로 빨려 들어간다. 업데이트 패턴은 강력한 도구지만, 잘못 사용되면 성능 저하와 복잡성 증가라는 치명적인 결과를 낳는다.

아래는 업데이트 패턴 남용이 발생시키는 대표적인 문제점들이다.

1. 매 프레임 검사(Per-frame Polling)로 인한 성능 저하

만약 매 프레임마다 어떠한 상태를 확인하기 위한 코드를 짰다고 가정해보자.

cpp
void UMyComponent::Tick(float DeltaTime)
{
    if (Health < 0)
        Die();

    if (IsInRange(Target))
        Attack();
}

이런 경우

  • 매 프레임 조건 검사 → CPU 낭비
  • 더 많은 객체가 추가될수록 선형적으로 성능이 떨어짐

이 발생할 수 있으며 성능 병목으로 이어지기 쉽다.

2. 비결정성(Non-determinism) 증가

Tick 기반 로직은 다음 문제를 자주 만든다:

  • 실행 순서가 undefined
  • 어떤 Tick이 먼저 실행되느냐에 따라 결과가 달라짐
  • Network/Replication 환경에서는 더 심각해짐
  • 특정 프레임에서만 발생하는 희귀 버그 발생

Tick을 많이 쓰면 쓸수록 “시점 관련 버그”가 걷잡을 수 없이 늘어난다. 또한 테스트와 리팩토링 역시 힘들어진다.

따라서 이러한 점들을 충분히 숙지하며 업데이트 함수 로직을 결정하는 것이 좋다.


Unreal Engine으로 보는 업데이트 패턴

Unreal Engine은 업데이트 패턴을 Actor Tick, Component Tick, Animation Tick, AI Tick처럼 계층적으로 나누어 처리한다. 그중에서 개발자가 가장 자주 마주하는 것이 바로 C++의 Tick 함수와 블루프린트의 Event Tick이다.

Tick은 매 프레임 호출되는 함수로, 엔진의 게임 루프에서 순차적으로 호출된다. 업데이트 패턴이 Unreal Engine에서 어떻게 구현되는지 가장 직접적으로 확인할 수 있는 지점이기도 하다.

C++의 Tick 함수

기본 Actor의 Tick은 아래 형태를 따른다.

Unreal Engine은 성능 최적화를 위해 디폴트로 Tick을 비활성화한 상태로 Actor를 생성한다. 그래서 Tick을 사용하려면 다음처럼 명시적으로 켜야 한다.

cpp
// .h
virtual void Tick(float DeltaTime) override;
cpp
// .cpp
AMyActor::AMyActor()
{
    PrimaryActorTick.bCanEverTick = true;  // Tick 활성화
}

void AMyActor::Tick(float DeltaTime)
{
    Super::Tick(DeltaTime);

    // 매 프레임 실행되는 로직
    CurrentLocation += MoveDirection * Speed * DeltaTime;	// example
}

블루프린트의 Tick 함수

블루프린트에서는 다음 이벤트로 Tick을 받을 수 있다.

다음 포스팅에서는 언리얼 엔진에서 프로젝트 예시를 통해 Tick 함수를 최적화하는 방법과 프로파일링에 대해 다루어보겠다.