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 업데이트 로직
기본 흐름은 매우 단순한 상태 머신이다.
처리 순서
- 현재 시야(2) → 안개(1)로 변경
- 유닛 주변을 다시 시야(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 참조
'내배캠 TIL' 카테고리의 다른 글
| [내배캠 TIL 260406] Unreal Fog of War 서버연동 정리 (0) | 2026.04.06 |
|---|---|
| [ 내배캠 TIL 260403] Unreal RenderTarget 기반 Fog of War 구현 (0) | 2026.04.03 |
| [ 내배캠 TIL 260325] Unreal Engine 네트워크 복제 시스템 정리 (0) | 2026.03.25 |
| [ 내배캠 TIL 260325 ] C++ 로또 문제 (완전탐색 → 해시 최적화) (0) | 2026.03.25 |
| [내배캠 TIL 260324] C++ 약수 개수 구하기 (완전탐색 vs √N 최적화) (0) | 2026.03.24 |