시야 한계와 안개 시스템: 던전 탐험 게임을 만드는 FOV 알고리즘과 지형 탐색 구현 기술
2D/3D 던전 탐험 게임에서 플레이어의 시야(FOV) 한계와 안개(Fog of War) 시스템을 구현하는 핵심 알고리즘 및 셰이더 기법을 살펴봅니다.

던전 탐험과 플레이어 시야(FOV) 시스템의 진화
로그라이크와 던전 크롤러, 톱다운 액션 게임에 이르기까지 플레이어의 시야(Field of View, FOV) 제한과 전장의 안개(Fog of War)는 탐험의 긴장감을 형성하는 핵심 요소입니다. 시야 시스템은 단순한 미학적 연출을 넘어 플레이어에게 제한된 정보만을 제공하여 선택의 위험과 보상을 극대화하는 레벨 디자인 도구로 활용됩니다.
최근 인디 게임 개발 생태계에서는 과거 텍스트 기반 그리드 알고리즘에 의존하던 전통적인 방식을 넘어 GPU 셰이더와 테셀레이션을 결합하여 연속적이고 부드러운 시야 경계를 구현하는 기술이 적극적으로 도입되고 있습니다.

시야 계산의 핵심 알고리즘: 레이캐스팅과 섀도캐스팅
그리드 기반 지형에서 시야 영역을 계산할 때 가장 널리 쓰이는 두 가지 접근법은 광선 추적(Raycasting)과 대칭형 섀도캐스팅(Symmetric Shadowcasting)입니다.
1. 그리드 레이캐스팅 (Grid Raycasting)
플레이어 위치에서 주변의 일정 반경 내 360도 방향으로 광선을 쏘아 장애물과의 충돌 여부를 검사하는 방식입니다. 구현이 단순하지만 타일 외곽선 근처에서 광선이 빗나가 가시 영역에 구멍이 생기는 간극 문제(Artifact)가 발생할 수 있습니다.
2. 대칭형 섀도캐스팅 (Symmetric Shadowcasting)
현재 대다수 던전 크롤러에서 표준으로 채택하는 방식입니다. 시야 중심을 기준으로 8개의 옥턴트(Octant) 영역을 나누어, 장애물이 만드는 그림자 부채꼴(Shadow Cone)을 스캔하며 비가시 영역을 제외합니다.
시야 거리 판정에는 주로 유클리드 거리를 사용하며 중심점에서 특정 좌표까지의 거리는 다음과 같은 공식으로 계산합니다.
이 거리가 설정된 시야 반경 보다 작고 그림자 부채꼴에 포함되지 않는 셀만 가시 상태로 판정합니다.
flowchart TD
A[시야 계산 시작] --> B[플레이어 위치 기준 8개 옥턴트 순회]
B --> C[각 옥턴트 내 행/열 그리드 스캔]
C --> D{장애물 타일 여부 검사}
D -- 예 --> E[그림자 부채꼴 구간 업데이트 및 가시성 차단]
D -- 아니오 --> F[해당 타일을 가시 영역으로 표시]
E --> G{옥턴트 스캔 완료?}
F --> G
G -- 아니오 --> C
G -- 예 --> H[실시간 FOV 맵 생성 완료]
3단계 지형 탐색 상태와 안개(Fog of War) 데이터 관리
던전 탐험 게임에서 지형 탐색 상태는 일반적으로 3가지 단계로 분류하여 관리합니다.
- 미탐색 (Unexplored): 플레이어가 한 번도 방문하지 않아 완전히 검은색으로 덮인 영역입니다.
- 탐색됨 (Explored / Visited): 과거에 방문하여 지형 형태는 기억하지만 현재 시야 밖이라 실시간 몬스터나 아이템 정보는 숨겨진 영역입니다.
- 가시 영역 (Visible): 현재 플레이어의 시야 한계 내에 있어 지형과 동적 개체가 모두 실시간으로 보이는 영역입니다.
이러한 상태 데이터를 효율적으로 처리하기 위해 그리드 맵의 각 셀에 상태 비트(Bitmask)를 부여합니다. 아래는 C# 기반으로 지형 탐색 상태를 관리하는 심플한 구조 예시입니다.
[System.Flags]
public enum TileVisibility
{
None = 0,
Explored = 1 << 0,
Visible = 1 << 1
}
public class FogOfWarManager
{
private TileVisibility[,] visibilityMap;
private int width, height;
public void UpdateVisibility(HashSet<(int x, int y)> visibleTiles)
{
// 기존 Visible 상태 해제 (Explored 상태는 유지)
for (int x = 0; x < width; x++)
{
for (int y = 0; y < height; y++)
{
visibilityMap[x, y] &= ~TileVisibility.Visible;
}
}
// 새롭게 계산된 시야 영역 적용
foreach (var (x, y) in visibleTiles)
{
visibilityMap[x, y] |= TileVisibility.Explored | TileVisibility.Visible;
}
}
}

셰이더와 RenderTarget을 활용한 부드러운 안개 렌더링
타일 단위로 안개를 끄고 켜는 방식은 도트 스타일의 고전 게임에 잘 어울리지만 현대적인 인디 게임에서는 타일 경계가 각져 보여 시각적 몰입감을 저해할 수 있습니다. 이를 해결하기 위해 RenderTarget(렌더 텍스처)과 프래그먼트 셰이더를 결합한 알파 가공 기법을 사용합니다.
- 저해상도 가시성 텍스처 작성: CPU 또는 Compute Shader에서 계산된 가시성 값을 R8 포맷 등의 저해상도 RenderTarget에 픽셀 단위로 기록합니다. (Visible=1.0, Explored=0.5, Unexplored=0.0)
- 쌍선형 보간 (Bilinear Filtering): 렌더 텍스처를 메인 카메라 해상도로 확대할 때 GPU의 보간 기능을 활용하여 자연스러운 텍스처 블러링 효과를 얻습니다.
- 화면 합성 셰이더: 메인 게임 화면을 그릴 때 가시성 텍스처의 알파 값을 참조하여 미탐색 영역에는 검은색 필터를 탐색 영역에는 흑백 반전 또는 명도 감소 처리를 수행합니다.
이와 같은 구조를 활용하면 복잡한 물리 계산 없이도 부드럽게 넘어가며 깊이감 있는 던전 탐험 분위기를 연출할 수 있습니다.



