내배캠 TIL

[ 내배캠 TIL 260402] Unreal Grid & Fog of War 시스템 정리

xodn246 2026. 4. 2. 01:08

1. 데이터 구조 설계

RTS에서 지형(Grid)시야(Fog)는 역할이 완전히 다르기 때문에 분리해서 관리하는 것이 핵심이다.

핵심 포인트

  • Grid → 물리/지형 정보
  • Fog → 시야 정보
  • 메모리 절약을 위해 uint8 사용
// GridManager.h
USTRUCT(BlueprintType)
struct FGridCell 
{
    GENERATED_BODY()

    uint8 WalkableState; // 0: 이동불가, 1: 이동가능
    uint8 TerrainType;   // 0: 평지, 1: 숲, 2: 물 등

    FGridCell() : WalkableState(1), TerrainType(0) {}
};

// FogManager.h
USTRUCT(BlueprintType)
struct FFogCell 
{
    GENERATED_BODY()

    uint8 FogState; // 0: 미탐색, 1: 안개, 2: 현재시야

    FFogCell() : FogState(0) {}
};
 

정리

  • 역할 분리를 통해 확장성 확보
  • uint8 사용으로 대규모 배열 메모리 절약
  • Fog는 단순 상태값만 필요 → 더 가볍게 유지

2. 좌표 변환 (핵심 로직)

RTS에서 가장 중요한 부분 중 하나는
월드 좌표 ↔ 그리드 인덱스 변환


World → Grid Index

int32 AGridManager::WorldToGridIndex(FVector WorldLocation) const 
{
    FVector ActorLoc = GetActorLocation();

    float HalfWidth = (GridCountX * CellSize) * 0.5f;
    float HalfHeight = (GridCountY * CellSize) * 0.5f;

    FVector GridStartLoc = ActorLoc - FVector(HalfWidth, HalfHeight, 0.f);
    FVector RelativePos = WorldLocation - GridStartLoc;

    int32 X = FMath::FloorToInt(RelativePos.X / CellSize);
    int32 Y = FMath::FloorToInt(RelativePos.Y / CellSize);

    if (X < 0 || X >= GridCountX || Y < 0 || Y >= GridCountY)
        return -1;

    return X + (Y * GridCountX);
}
 

Grid Index → World

 
FVector AGridManager::GridIndexToWorld(int32 Index) const 
{
    if (!GridData.IsValidIndex(Index))
        return FVector::ZeroVector;

    int32 GridX = Index % GridCountX;
    int32 GridY = Index / GridCountX;

    FVector ActorLoc = GetActorLocation();

    FVector GridStartLoc = ActorLoc - FVector(
        (GridCountX * CellSize) * 0.5f,
        (GridCountY * CellSize) * 0.5f,
        0.f
    );

    float WorldX = GridStartLoc.X + (GridX * CellSize) + (CellSize * 0.5f);
    float WorldY = GridStartLoc.Y + (GridY * CellSize) + (CellSize * 0.5f);

    return FVector(WorldX, WorldY, ActorLoc.Z);
}
 

정리

  • Grid 기준점 = Actor 중심 기준 좌측 하단
  • 핵심 수식
    • Index = X + (Y * Width)
  • Cell 중심 좌표 계산 시 + CellSize * 0.5f 필수

3. 배열 초기화 최적화

대규모 Grid/Fog 데이터를 다루기 때문에
초기화 방식이 성능에 큰 영향을 준다.

// GridData
GridData.SetNumUninitialized(TotalCells);
for (int32 i = 0; i < TotalCells; ++i) {
    GridData[i].WalkableState = 1;
}

// FogData
FogData.SetNumZeroed(TotalCells);

정리

방식특징사용 이유
SetNumUninitialized 초기화 생략 기본값이 0이 아닐 때
SetNumZeroed 0으로 초기화 기본값이 0일 때

→ 불필요한 초기화 비용 제거 = 성능 최적화


4. Fog of War 업데이트 로직

기본 흐름은 매우 단순한 상태 머신이다.

처리 순서

  1. 현재 시야(2) → 안개(1)로 변경
  2. 유닛 주변을 다시 시야(2)로 갱신
void AFogManager::UpdateFog(const TArray<FVector>& ViewerLocations, float SightRadius) 
{
    // 1. 기존 시야를 안개로 변경
    for (auto& Cell : FogData) 
    {
        if (Cell.FogState == 2)
            Cell.FogState = 1;
    }

    // 2. 시야 제공자 기준 갱신
    for (const FVector& Loc : ViewerLocations) 
    {
        int32 CenterIndex = TargetGridManager->WorldToGridIndex(Loc);

        int32 RadiusInCells = FMath::RoundToInt(
            SightRadius / TargetGridManager->CellSize
        );

        // 중첩 for문 + 거리 체크
        // (x^2 + y^2 <= r^2)
    }
}
핵심 개념
  • Fog 상태 3단계
    • 0: 미탐색
    • 1: 안개 (과거 시야)
    • 2: 현재 시야
  • 원형 범위 체크
(dx * dx + dy * dy <= r * r)

 


5. 전체 구조 정리

시스템 분리 구조

GridManager
├── 지형 데이터
├── 좌표 변환
└── Cell 정보 관리

FogManager
├── 시야 데이터
├── 유닛 기반 시야 갱신
└── GridManager 참조