すべての形式のページ ↓
マップと地形

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];

高さのグリッドは、タイルのグリッドの4倍の密度です。各タイルには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が後から重複を除いたものです。そのため、2つの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}であることと、4つのポインターフィールドがすべてファイル内に収まっていることを確認します。
  • 高さの範囲は完全にデザイナー次第です。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 — タイルのピクセルとミニマップの両方の色付けに使うパレット。
  • 用語集 — タイル、属性セル、ハイトマップ。