모든 형식 페이지 ↓
맵·지형

SCT — 맵 섹션

출처: coreprime/kbot @ 0ab97bf · GitHub에서 편집

.sct 파일은 재사용할 수 있는 맵 지형 조각입니다 — Cavedog의 레벨 디자이너들은 공식 맵 에디터(TAE)에서 이런 조각 수십 개를 이어 붙여 완성된 .tnt 맵을 만들었습니다. 각 섹션에는 자체 타일 그래픽, 타일 그리드, 칸별 높이, 섬네일 미니맵이 들어 있습니다. 섹션은 구성 요소이고, TNT는 조립된 결과물입니다.

타일로 렌더링한 Greenworld 해안 섹션
kbot sct image로 렌더링한 sections/greenworld/coast/b_n_w02_135.sct

섹션 미니맵    섹션 높이 맵
내장 미니맵(왼쪽)과 고도 그리드(오른쪽).

Tip

직접 해 보세요.

kbot sct info       sections/greenworld/coast/b_n_w02_135.sct
kbot sct describe   sections/greenworld/coast/b_n_w02_135.sct
kbot sct image      sections/greenworld/coast/b_n_w02_135.sct -t out.png
kbot sct minimap    sections/greenworld/coast/b_n_w02_135.sct -t mini.png
kbot sct heightmap  sections/greenworld/coast/b_n_w02_135.sct -t height.png

CLI kbot sct 레퍼런스를 참고하세요.

Go에서 쓰기. formats/sct를 사용합니다.

import "github.com/coreprime/kbot/formats/sct"

f, _ := os.Open("sections/greenworld/coast/b_n_w02_135.sct")
defer f.Close()
s, _ := sct.LoadFromReader(f)
fmt.Printf("%dx%d tiles, %d unique, %d heights\n",
    s.Header.Width, s.Header.Height, s.Header.NumTiles, len(s.HeightMap))

디스크상 구조

┌─ Header (28 B) ──┐  version, pointers, dimensions
├─ Tile pixels ────┤  NumTiles × 1024 bytes (32×32 palette indices)
├─ Section data ───┤  Width × Height × int16 tile indices
├─ Height grid ────┤  (Width × 2) × (Height × 2) × {4 or 8} bytes
└─ Minimap ────────┘  128 × 128 palette indices

포인터 필드는 어떤 순서로든 섹션을 가리킬 수 있지만, Cavedog의 도구는 위에 보인 구조대로 내보내며 모든 서드파티 리더도 그렇게 가정합니다.

헤더(28바이트)

typedef struct {
    uint32 Version;     // 2 or 3 (TA's TAE always emits 3)
    uint32 PtrMinimap;  // → 128×128 minimap
    uint32 NumTiles;    // unique 32×32 tile count
    uint32 PtrTiles;    // → tile pixel block
    uint32 Width;       // section width in tiles
    uint32 Height;      // section height in tiles
    uint32 PtrData;     // → tile-index grid + heights
} SCTHeader;
필드 비고
Version 정식판 TA 섹션은 3. 일부 초기 프로토타입은 2를 쓰는데, 구조는 같지만 높이 항목이 4바이트가 아니라 8바이트입니다. kbot은 둘 다 받아들입니다.
Width, Height 둘 다 타일(32 px 단위)로 나타냅니다. TAE와 호환되는 섹션이라면 둘 다 4의 배수(즉 128 px의 배수)여야 합니다.
NumTiles 고유 타일 수. 데이터 섹션의 인덱스 그리드가 이 배열을 가리킵니다.

타일 픽셀

각 32 × 32 = 1024바이트인 블록 NumTiles개로, TA 팔레트를 가리키는 팔레트 인덱스입니다. 똑같은 타일이라도 형식이 자동으로 중복을 제거하지 않으므로, 잘못 만든 섹션은 공간을 낭비할 수 있습니다. kbot의 image 명령은 섹션에 쓰이지 않는 타일이 있으면 경고합니다.

섹션 데이터 — 타일 인덱스 + 높이 그리드

int16 tileMap[Height][Width];     // Index into the tile pixel array
                                  // (signed; negative values are unused
                                  // in retail content but valid)

struct HeightCell_V3 { uint8 h; int16 always_minus_one; uint8 always_zero; };
struct HeightCell_V2 { uint8 h; int16 always_minus_one; uint8 always_zero;
                       uint32 reserved; };

HeightCell heights[Height * 2][Width * 2];

높이 그리드는 타일 그리드보다 네 배 촘촘합니다 — 타일마다 16픽셀 해상도로 2 × 2 = 4개의 높이 표본이 있으며, 이는 TNT 속성 칸의 동작 방식과 같습니다. always_-1과 always_0 필드는 에디터가 남긴 임시 메타데이터이므로, 왕복 변환 때 어느 값을 다시 써도 됩니다.

미니맵

128 × 128개의 팔레트 인덱스입니다. 섹션의 타일 너비/높이와 상관없이 크기가 고정되어 있으며, 미니맵은 레터박스를 두지 않고 섹션의 가로세로 비율에 맞게 늘어납니다.


읽는 순서

header = read_struct(SCTHeader)
assert header.Version in (2, 3)

seek(header.PtrTiles)
tiles = [read(1024) for _ in range(header.NumTiles)]

seek(header.PtrData)
tile_map = read_int16_array(header.Width * header.Height)

entry_size = 4 if header.Version == 3 else 8
attrs_w, attrs_h = header.Width * 2, header.Height * 2
heights = [read(entry_size)[0] for _ in range(attrs_w * attrs_h)]

seek(header.PtrMinimap)
minimap = read(128 * 128)

실제 예제 — b_n_w02_135.sct

$ kbot sct describe sections/greenworld/coast/b_n_w02_135.sct
Version:    3
Section:    4 × 4 tiles  (128 × 128 px)
Tiles:      64 unique
Heights:    8 × 8 cells (height range 50–62)
Minimap:    128 × 128

4×4 타일 섹션은 TAE가 받아들이는 가장 작은 크기입니다(128 px의 배수여야 함). 섹션에 배치 자리는 4 × 4 = 16개뿐인데도 고유 타일을 64개 참조하는 것은, 각 32×32 칸 자체가 디자이너가 여러 변형을 원했던 가장자리 전환 부분으로 둘러싸여 있기 때문입니다. 이는 정상입니다 — 섹션은 저장 공간을 더 쓰는 대신 이음새를 자유롭게 섞을 수 있게 합니다.


TNT가 섹션을 쓰는 방식

.tnt 맵의 타일 풀은 레벨 디자이너가 그 맵에 "칠한" 모든 .sct의 합집합이며, TAE가 나중에 중복을 제거합니다. 그래서 두 TNT가 같은 타일을 쓰고 있다면 거의 언제나 같은 SCT에서 온 것입니다.

이 연결은 빌드할 때만 존재합니다. 실행 중인 엔진은 섹션을 절대 열지 않고, 이미 합쳐진 TNT만 봅니다. 섹션은 에디터 아카이브(worlds.hpi)와 캠페인 월드 아카이브 안의 sections/greenworld/coast/, sections/lava/hills/ 같은 곳에 들어 있습니다.


주의할 점

Warning

TAE가 섹션을 로드하려면 Width × Height가 4의 배수여야 합니다. 형식 자체는 이를 강제하지 않으므로 3×3 타일 섹션을 쓸 수도 있고 kbot도 아무 문제 없이 렌더링하지만, Cavedog의 에디터는 알쏭달쏭한 "section misaligned" 오류와 함께 이를 거부합니다.

  • Version == 2는 높이 칸이 4바이트가 아니라 8바이트입니다. 첫 바이트는 여전히 고도이고, 나머지 7바이트는 쓸 때 0으로 채워도 되는 에디터 임시 값입니다.
  • 128×128 미니맵은 레터박스 없이 늘어나 섹션의 가로세로 비율에 맞춰집니다. 4×16 섹션은 미니맵에서 끔찍하게 찌그러져 보입니다.
  • Version 말고는 시그니처가 없습니다. SCT 후보를 검증하려면 Version ∈ {2, 3}인지, 그리고 네 포인터 필드가 모두 파일 안을 가리키는지 확인하세요.
  • 높이 범위는 전적으로 디자이너에게 달려 있습니다. TAE는 0–255로 제한하지만, 섹션은 완전히 평평할 수도(값 하나), 전체 범위를 쓸 수도 있습니다. TNT 렌더러는 표시하기 전에 정규화하지만, SCT는 그렇지 않습니다.

일반적인 크기

에셋 Cavedog sections/**/*.sct에서 관찰된 범위
파일 크기 30 KB – 2 MB
섹션 크기(타일) 4×4 – 32×32
섹션당 고유 타일 30–600
헤더 오버헤드 항상 28바이트
미니맵(항상 128×128 팔레트) 16384바이트
worlds.hpi의 섹션 수 약 5000개(모든 지형 테마 합계)

함께 보기

  • TNT — 섹션을 재료로 쓰는, 조립된 맵 형식.
  • PAL — 타일 픽셀과 미니맵을 모두 칠하는 데 쓰는 팔레트.
  • 용어집 — tile, attribute cell, heightmap.