Post

2026-05-15 TIL (58일차)

2026-05-15 TIL (58일차)

날씨 시스템 구현 중 겪은 단일 포인터와 인스턴스 관리에 대한 착각

오늘은 월드 전역에서 날씨를 관리하는 UWeatherSubsystem과 관련 데이터(EWeather, UWeatherPresetDataAsset), 그리고 날씨 변경을 알리는 OnWeatherChangedDelegate를 구현하는 과정에서 아주 기초적이지만 중요한 C++ 메모리 및 인스턴스 관리에 대한 깨달음을 얻었다.

날씨가 바뀔 때 월드에 존재하는 ‘모든 차량’에게 날씨 변경 신호를 보내야 하는 상황에서 내가 했던 논리적 착각과 그 해결 과정을 기록한다.


나의 초기 착각: “Base 포인터 하나만 있으면 다 되는 거 아니야?”

처음 UWeatherSubsystem을 설계할 때, 서브시스템 내부에 차량을 기억해 둘 변수로 단순히 단일 포인터를 생각했다.

1
2
3
// ❌ 착각했던 방식 (단일 포인터 사용)
UPROPERTY()
ATeam24VehiclePawn* WeatherVehicle;

나의 1차원적인 생각:

“어차피 레벨에 생성되는 모든 차량은 ATeam24VehiclePawn이라는 똑같은 Base 클래스를 기반으로 만들어지잖아? 그러니까 저 Base 포인터 하나에다가 델리게이트 신호(Broadcast)를 쏘면, 이 클래스를 상속받아 만들어진 모든 차량들에게 알아서 다 보내지는 거 아니야?”


문제 원인 파악: 클래스(설계도)와 인스턴스(실체)의 차이

조금만 더 깊게 생각해 보니, 나의 위 생각은 C++의 메모리 할당과 객체 지향의 기본을 헷갈린 엄청난 착각이었다.

내가 깨달은 진짜 문제점: 단일 포인터(ATeam24VehiclePawn*)는 한 번에 오직 단 한 개의 메모리 주소(하나의 차량 객체)만 가리킬 수 있다. 만약 멀티플레이 환경이나 AI 차량이 추가되어 레벨에 차량이 스폰될 때마다 저 단일 포인터에 등록(RegisterVehicle)을 해버린다면 어떤 일이 발생할까?

  1. 1번 차량 스폰 👉 WeatherVehicle 포인터가 1번 차량을 가리킴.
  2. 2번 차량 스폰 👉 WeatherVehicle 포인터가 기존 값을 버리고 2번 차량으로 갱신됨.
  3. 3번 차량 스폰 👉 WeatherVehicle 포인터가 3번 차량으로 갱신됨.

결과적으로 내가 WeatherVehicle->OnWeatherChangedDelegate.Broadcast()를 호출하면, 가장 마지막에 스폰되어 포인터를 덮어씌운 단 한 대의 차량(3번 차량)에게만 날씨 변경 신호가 전달된다. 나머지 1, 2번 차량은 포인터 참조가 끊어져 신호를 아예 받지 못하게 되는 치명적인 버그가 발생하는 것이다.


올바른 해결 방안: 컬렉션(TArray)을 이용한 다중 인스턴스 관리

월드에 존재하는 ‘모든’ 차량에게 신호를 보내려면, 단일 포인터가 아니라 생성되는 모든 차량의 포인터 주소를 차곡차곡 모아둘 배열(Array) 형태의 자료구조가 필요하다. 명세서에 적혀있던 “Subsystem Registered Vehicles 순회 시각 Pawn의 델리게이트를 Broadcast한다”라는 문장의 진짜 의미가 바로 이것이었다.

1
2
3
// (배열 사용)
UPROPERTY()
TArray<ATeam24VehiclePawn*> RegisteredVehicles;

2. 차량 등록 및 해제 로직 구현 차량이 BeginPlay에서 RegisterVehicle을 호출하면 배열에 ‘추가(Add)’하고, EndPlay에서 UnregisterVehicle을 호출하면 배열에서 ‘제거(Remove)’한다.

1
2
3
4
5
6
7
void UWeatherSubsystem::RegisterVehicle(ATeam24VehiclePawn* Vehicle)
{
    if (Vehicle && !RegisteredVehicles.Contains(Vehicle))
    {
        RegisteredVehicles.Add(Vehicle);
    }
}

3. 날씨 변경 시 순회하며 Broadcast 날씨가 바뀌면 단일 객체에 쏘는 것이 아니라, 배열에 담긴 모든 차량 객체를 순회(For-loop)하며 각각의 델리게이트를 작동시켜야 한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
void UWeatherSubsystem::SetWeather(EWeather NewWeather)
{
    // ... 날씨 변경 로직 ...

    // 등록된 모든 차량을 돌면서 각각 방송을 때려준다!
    for (ATeam24VehiclePawn* Vehicle : RegisteredVehicles)
    {
        if (Vehicle)
        {
            Vehicle->OnWeatherChangedDelegate.Broadcast(NewWeather);
        }
    }
}

느낀점

“클래스(타입)가 같다고 해서 메모리 상의 실체(인스턴스)가 하나로 묶이는 것은 아니다. 다수의 객체를 통제하려면 단일 포인터가 아닌 배열(TArray)을 통해 각 객체의 주소값을 모두 쥐고 있어야 한다.”

데이터 에셋(Data Asset) vs 데이터 테이블(Data Table) 완벽 비교

언리얼 엔진에서 게임 데이터를 관리할 때 가장 많이 헷갈리는 두 가지 개념이 바로 데이터 에셋(Data Asset)데이터 테이블(Data Table)입니다.

자율주행 시뮬레이션의 날씨 시스템을 구현하면서 UWeatherPresetDataAsset을 사용하게 되었는데, 이 기회에 두 기능이 어떻게 다르고 언제 무엇을 써야 하는지 명확히 정리해 본다.


1. 데이터 에셋 (Data Asset) 이란?

데이터 에셋은 C++ 클래스(UDataAsset)를 기반으로 찍어내는 ‘데이터 덩어리(객체)’다.

  • 특징: 단순한 숫자나 문자열뿐만 아니라, 다른 에셋의 포인터(머티리얼, 파티클 시스템, 스태틱 메시 등)를 아주 유연하게 담을 수 있다.
  • 활용 예시 (날씨 시스템 프리셋):
    • DA_Weather_Rain (데이터 에셋 객체 1): 비 파티클(Niagara), 젖은 도로 피직스 머티리얼, 안개 밀도 0.5
    • DA_Weather_Snow (데이터 에셋 객체 2): 눈 파티클(Niagara), 눈길 피직스 머티리얼, 안개 밀도 0.8
  • 장점: 객체 지향의 특성(상속 등)을 그대로 가질 수 있고, 에디터 내에서 더블클릭해 직관적으로 컴포넌트나 에셋을 할당하기 좋다. 주로 프로그래머나 테크니컬 아티스트(TA)가 에디터 안에서 작업하기 좋은 구조다.

2. 데이터 테이블 (Data Table) 이란?

데이터 테이블은 엑셀이나 구글 스프레드시트처럼 행(Row)과 열(Column)로 이루어진 ‘표 형태의 데이터’다. C++의 구조체(FTableRowBase)를 기반으로 만들어진다.

  • 특징: 외부(Excel)에서 CSV나 JSON 파일로 데이터를 대량으로 작업한 뒤, 언리얼 엔진으로 한 번에 임포트(Import)해서 사용하는 방식에 최적화되어 있다.
  • 활용 예시 (RPG 게임의 아이템이나 능력치):
    • Row 1 (강철 검): 공격력 50, 가격 100, 스태틱 메시 참조
    • Row 2 (나무 지팡이): 공격력 10, 마력 30, 가격 50, 스태틱 메시 참조
  • 장점: 수백~수천 개의 방대한 데이터를 게임 기획자나 밸런스 디자이너가 엑셀로 쉽게 편집하고 한 번에 엔진에 적용할 수 있다.

3. 핵심 차이 비교 (어떨 때 무엇을 써야 할까?)

구분데이터 에셋 (Data Asset)데이터 테이블 (Data Table)
기본 구조C++ 클래스 (UDataAsset) 상속 객체구조체 (FTableRowBase) 기반의 2차원 표(Row)
관리 방식각 프리셋마다 개별 에셋 파일(.uasset)로 존재하나의 테이블 에셋 안에 여러 행으로 존재
주요 데이터에셋 포인터(머티리얼, 파티클 등), 복잡한 로직 세팅원시 데이터(Float, Int, String) 대량 관리
외부 툴 연동에디터 내부에서 직접 값을 세팅해야 함엑셀, CSV, JSON에서 작성 후 임포트 가능
주요 협업 주체프로그래머, 클라이언트 개발자, TA게임 기획자, 데이터/밸런스 디자이너
베스트 케이스종류가 적고 속성이 복잡한 경우
(예: 날씨 프리셋 4종, 차량 부품별 물리 세팅)
종류가 매우 많고 규격이 일정한 경우
(예: 수백 개의 무기 목록, 레벨별 요구 경험치 테이블)

요약

기획자가 엑셀로 수백 개의 수치를 뽑아서 관리해야 하는 대규모 데이터라면 데이터 테이블을 사용하고, 날씨 시스템 프리셋처럼 개발자가 에디터 안에서 파티클, 피직스 머티리얼, 수치값 등 여러 언리얼 에셋들을 하나의 묶음으로 예쁘게 묶어두고 스왑(Swap)하고 싶다면 데이터 에셋을 사용하자!

This post is licensed under CC BY 4.0 by the author.