SDKシリーズデータフォーマット

SDKシリーズデータフォーマット

Postby Quaraage » June 12th, 2016, 7:15 pm

※この文書はhttp://www.dkc-atlas.com/forum/viewtopic.php?f=38&t=1167を翻訳・加筆したものです。

目次
    共通 8x8タイルフォーマット
    共通 8x8タイルマップ(背景・前景)
    共通 32x32タイルセット(マップチップ)
    共通 32x32タイルマップ(地形)
    共通 スプライトグラフィック
    共通 スプライト配置データ
    共通 バナナ配置パターン

    DK1 8x8圧縮グラフィックデータ
    DK1 カメラ範囲
    DK1 バナナ配置データ
    DK1 地形判定
    DK1 地形判定マップ
    DK1 当たり判定領域
    DK1 アニメーション
    DK2/3 圧縮データ
Trainee Trekker
Bananas received 37
Posts: 54
Joined: 2015

Re: SDKシリーズデータフォーマット

Postby Quaraage » June 12th, 2016, 8:40 pm

共通 8x8タイルデータ

タイルデータは1bpp, 2bpp, 3bpp, 4bpp, 7bpp, 8bppの6つのフォーマットがありますが、SDKシリーズでは2bppおよび4bpp以外は殆ど使われていないのでこの2つのみ解説します。

2bppタイル(4色)
2bppタイルは1ピクセルの色指定を2bitで行います。8x8=64画素ですので2bppタイルは16バイトのサイズになります。

①先頭から2バイトずつ区切ります。すると8つのブロックに分かれますが、横のn列目のデータがn番目のブロックに対応します。
②各ブロックの1バイト目が上位ビット、2バイト目が下位ビットに対応しています。
③各列の左から順番に各ブロックのbit7,6,5,・・・0を対応させます。

Image

解凍ルーチンは以下のように実装されます (MS Visual C#)
Spoiler!
Code: Select all
// TileData: 元データ(16バイト)
// return: 解凍後のデータ(各画素の色番号の2次元配列)
int[,] TileDecode2bpp (byte[] TileData)
{
    int[,] DecodedTile = new int[8,8];

    for(int y = 0; y < 8; y++)
    {
        byte pixel1 = TileData[2 * y];
        byte pixel2 = TileData[2 * y + 1];

        DecodedTile[0, y] = ((pixel2 & 0x80) >> 6) | ((pixel1 & 0x80) >> 7);
        DecodedTile[1, y] = ((pixel2 & 0x40) >> 5) | ((pixel1 & 0x40) >> 6);
        DecodedTile[2, y] = ((pixel2 & 0x20) >> 4) | ((pixel1 & 0x20) >> 5);
        DecodedTile[3, y] = ((pixel2 & 0x10) >> 3) | ((pixel1 & 0x10) >> 4);
        DecodedTile[4, y] = ((pixel2 & 0x08) >> 2) | ((pixel1 & 0x08) >> 3);
        DecodedTile[5, y] = ((pixel2 & 0x04) >> 1) | ((pixel1 & 0x04) >> 2);
        DecodedTile[6, y] = ((pixel2 & 0x02) >> 0) | ((pixel1 & 0x02) >> 1);
        DecodedTile[7, y] = ((pixel2 & 0x01) << 1) | ((pixel1 & 0x01) >> 0);
    }
    return DecodedTile;
}


4bppタイル(16色)
基本的に2bppと同様です。
1ピクセルの色番号指定を4bitで行います。8x8=64画素ですので1タイル32バイトのデータとなります。

①2bppと同様に先頭から2バイトずつ区切ります。すると16個のブロックに分かれますが、n個目とn+8個目のブロックがn列目のデータに対応します。
②n個目のブロックが下位2ビット、n+8個目のブロックが上位2ビットを格納しています。
③各列の左から順番に各ブロックのbit7,6,5,・・・0を対応させます。

Image

解凍ルーチンは以下のように実装されます (MS Visual C#)
Spoiler!
Code: Select all
// TileData: 元データ(32バイト)
// return: 解凍後のデータ(各画素の色番号の2次元配列)
int[,] TileDecode2bpp (byte[] TileData)
{
    int[,] DecodedTile = new int[8,8];

    for(int y = 0; y < 8; y++)
    {
        byte pixel1 = TileData[2 * y];          //n個目のブロック
        byte pixel2 = TileData[2 * y + 1];    //n個目のブロック
        byte pixel3 = TileData[2 * y + 16];  //n+8個目のブロック
        byte pixel4 = TileData[2 * y + 17];  //n+8個目のブロック

        DecodedTile[0, y] = ((pixel4 & 0x80) >> 4) | ((pixel3 & 0x80) >> 5) | ((pixel2 & 0x80) >> 6) | ((pixel1 & 0x80) >> 7);
        DecodedTile[1, y] = ((pixel4 & 0x40) >> 3) | ((pixel3 & 0x40) >> 4) | ((pixel2 & 0x40) >> 5) | ((pixel1 & 0x40) >> 6);
        DecodedTile[2, y] = ((pixel4 & 0x20) >> 2) | ((pixel3 & 0x20) >> 3) | ((pixel2 & 0x20) >> 4) | ((pixel1 & 0x20) >> 5);
        DecodedTile[3, y] = ((pixel4 & 0x10) >> 1) | ((pixel3 & 0x10) >> 2) | ((pixel2 & 0x10) >> 3) | ((pixel1 & 0x10) >> 4);
        DecodedTile[4, y] = ((pixel4 & 0x08) >> 0) | ((pixel3 & 0x08) >> 1) | ((pixel2 & 0x08) >> 2) | ((pixel1 & 0x08) >> 3);
        DecodedTile[5, y] = ((pixel4 & 0x04) << 1) | ((pixel3 & 0x04) >> 0) | ((pixel2 & 0x04) >> 1) | ((pixel1 & 0x04) >> 2);
        DecodedTile[6, y] = ((pixel4 & 0x02) << 2) | ((pixel3 & 0x02) << 1) | ((pixel2 & 0x02) >> 0) | ((pixel1 & 0x02) >> 1);
        DecodedTile[7, y] = ((pixel4 & 0x01) << 3) | ((pixel3 & 0x01) << 2) | ((pixel2 & 0x01) << 1) | ((pixel1 & 0x01) >> 0);
    }
    return DecodedTile;
}
Trainee Trekker
Bananas received 37
Posts: 54
Joined: 2015

Re: SDKシリーズデータフォーマット

Postby Quaraage » June 12th, 2016, 9:05 pm

共通 8x8タイルマップ

8x8タイルマップは背景や前景などに利用されています。このタイルマップの構造は、PPUのレジスタに依存します。

タイルマップの1つのタイルの大きさを指定するレジスタは以下のものです。
0x2105 BGMODE

このレジスタの上位4bitは各BGレイヤーで使用するタイルの大きさを指定します(上位bitからBG4,3,2,1)。
0なら8x8、1なら16x16となります。
なお、画面モード5および6時は常時16x16、画面モード7時は常時8x8指定となります。

タイルマップの構造を決定するレジスタは以下の4つです。それぞれBG1~4に対応します。
0x2107 BG1SC
0x2108 BG2SC
0x2109 BG3SC
0x210A BG4SC

各レジスタの上位6bitはタイルマップの開始アドレス(実アドレスは0x800をかけたもの)をあらわし、下位2ビットでタイルマップの垂直/水平ミラーを指定します。
各タイルマップは32マスx32マス(1024タイル)のサイズとされます。
画像サイズは、タイルマップの各構成タイルが8x8の場合は256ピクセルx256ピクセル、16x16の場合は512ピクセルx512ピクセルとなります。

垂直/水平ミラー
0/0:32x32 単一マップのみ。
0/1:32*64 1つ目のタイルマップの直後の内容が右側に配置される。
1/0:64*32 1つ目のタイルマップの直後の内容が下側に配置される。
1/1:64*64 1つ目のタイルマップの直後の内容が右側、その次は下側、さらにその次は右下側に配置される。

各タイルマップのタイル指定は16ビットで行い、そのデータは以下のようになっています。
vhopppcc cccccccc

v:垂直反転フラグ このフラグを立てると8x8タイルを垂直反転する
h:水平反転フラグ このフラグを立てると8x8タイルを水平反転する
o:優先度 前後関係(深度)を指定します
ppp:パレット指定 VRAM上のパレットの番号。地形用パレットは常に最初の128色を利用する。
cccccccccc:タイル番号 使用する8x8タイルの番号

BGMODEレジスタで16x16タイルが指定されている場合、16x16タイルは4つの8x8タイルを使って以下のように構成されます。
タイル   タイル+1 
タイル+16 タイル+17
Trainee Trekker
Bananas received 37
Posts: 54
Joined: 2015

Re: SDKシリーズデータフォーマット

Postby Quaraage » June 12th, 2016, 9:10 pm

共通 マップチップ構成

マップチップ構成は、8x8タイルマップと非常によく似た方法で行われます。
1つのマップチップは32x32、すなわち16枚の8x8タイルで構成されます。
このとき、1つの8x8タイルの指定は2バイトで行われるため、マップチップ1個の構成データは32バイトとなります。左上から右に向かって順に指定が行われます。
すなわち、以下のとおりです。
0|1|2|3
4|5|6|7
8|9|A|B
C|D|E|F

この各8x8タイルの指定方法は、8x8タイルマップのときと全く同様です。すなわち、
vhopppcc cccccccc

v:垂直反転フラグ このフラグを立てると8x8タイルを垂直反転する
h:水平反転フラグ このフラグを立てると8x8タイルを水平反転する
o:優先度 前後関係(深度)を指定します
ppp:パレット指定 VRAM上のパレットの番号。地形用パレットは常に最初の128色を利用する。
cccccccccc:タイル番号 使用する8x8タイルの番号
Trainee Trekker
Bananas received 37
Posts: 54
Joined: 2015

Re: SDKシリーズデータフォーマット

Postby Quaraage » June 12th, 2016, 9:21 pm

共通 32x32タイルマップ構成

地形データに使用される32x32タイルマップの構成も、8x8タイルマップおよび32x32マップチップと殆ど同じように行われます。
ただし、各ステージに設定されている座標体系の設定値によって、タイルマップの各データの読み出し順が変わることに注意してください。

横面
左上から下に向かって16マスを読み出します。16マスを読み出したら右に1列ずれてまた上から下へ16マスを読み出します。これを繰り返します。

↓ 00 10 20 ・・・
読 01 11 21
出 02 12 22
方 ・・・
向 0F 1F 2F

縦面
左上から右に向かって(タイルマップ幅で指定された値/32)マス分を読み出します。読み出し終わったら下に1列ずれてまた同じだけ読み出します。これを繰り返します。

(たとえば幅768のとき) →1列は24マス
→→読出方向
00 01 02 03 ・・・ 17
18 19 1A 1B ・・・ 30
31 32 33 34 ・・・

各マスのマップチップ指定は2バイトで行い、8x8タイルマップおよびマップチップ構成のときと全く同じ方法です。
ただし、パレット指定は無視されます。すなわち、
vho---cc cccccccc

v:垂直反転フラグ このフラグを立てると8x8タイルを垂直反転する
h:水平反転フラグ このフラグを立てると8x8タイルを水平反転する
o:優先度 前後関係(深度)を指定します
cccccccccc:タイル番号 使用するマップチップの番号
Trainee Trekker
Bananas received 37
Posts: 54
Joined: 2015

Re: SDKシリーズデータフォーマット

Postby Quaraage » June 12th, 2016, 9:25 pm

共通 スプライトグラフィック

グラフィックタイルはすべて8x8タイルを元に構成され、複数の8x8タイルをまとめて16x16タイルなどとして利用することも出来ます。
SDKシリーズではすべてのスプライトは8x8タイルおよび16x16タイルを組み合わせて表現されます。
※SNESのハードウェア自体の機能としては32x32タイルや64x64タイルも利用できますが、SDKシリーズでは使用が想定されていないのでソフトウェア側のサポートがなされておらず、利用できません。

すべてのスプライトグラフィックは8バイトのヘッダーのあと、可変長データであるタイル配置データ、タイルデータ本体と続きます。
スプライト用グラフィックはVRAMの前半に格納される決まりになっており、また、パレットは後半128色を利用しますが、1つのスプライトが利用できる色は16色のみです。すなわち、128/16=8本のスプライト用パレットが存在することになります。VRAM上でのデータの割り当てやパレット割り当ては別のコードで管理されているため気にしなくてかまいません。

1.ヘッダ
ヘッダは8バイトあり、各バイトが個別に意味を持ちます。
Byte 0:16x16タイルの枚数
Byte 1:8x8タイルAの枚数
Byte 2:8x8タイルAのデータオフセット
Byte 3:8x8タイルBの枚数
Byte 4:8x8タイルBのデータオフセット
Byte 5:8x8タイルCのデータオフセット
Byte 6:8x8タイルCのVRAMロード位置
Byte 7:8x8タイルCの枚数

オフセット類はすべて8x8タイル換算で考えます。たとえば、16x16タイルが5枚あるとき、8x8タイルAのオフセットは5*4=20タイル目と計算します。
実際のROM上でのデータ位置は、タイルデータの開始アドレスにオフセットを32倍したものを足したものです。(4bppタイルは1枚32バイト)

2.タイル配置情報
16x16タイル、8x8タイルA、8x8タイルBの順にタイルの配置座標を指定します。x、y座標の順に符号なし8ビット。
0x80 (128)が中央、すなわち設置座標になります。なお、各タイルの左上隅の座標で指定します。
8x8タイルCの座標データは8x8タイルCの有無にかかわらず指定しません

3.グラフィックデータ
16x16タイル、8x8タイルA、8x8タイルB、8x8タイルCの順に格納されます。なお、格納単位はタイルの種類にかかわらず8x8タイルとなります。
16x16タイルの格納方法はやや特殊で、
1枚目の左上、1枚目の右上、2枚目の左上、2枚目の右上、・・・、1枚目の左下、1枚目の右下、・・・
となっています。そのあとに8x8タイルA、B、Cが続きます。(それぞれあれば)
各8x8タイル自体のデータフォーマットは上を参照してください。
Trainee Trekker
Bananas received 37
Posts: 54
Joined: 2015

Re: SDKシリーズデータフォーマット

Postby Quaraage » June 12th, 2016, 10:00 pm

共通 スプライト配置データ

スプライト配置データは8バイトの構造体で、4つの16ビット値から構成されます。

uword 0:パラメータ
uword 1:X座標
uword 2:Y座標
uword 3:スプライト指定

パラメータ
パラメータはスプライトの性質を決定するものです。SDKでは0x0000~0x0011が使われます。
SDK2/3では構造化されており、各bitが固有の意味を持ちます。

SDK1 パラメータ一覧
Spoiler!
0x0000 配置データの終了宣言
0x0001 標準
0x0002 GFX読込を行わない
0x0003 開始時に画面内にある場合に限りロード抑制。1回スクロールアウトさせるとロード
0x0004 詳細不明(ロード範囲の拡大?)
0x0005 スプライト配置データの追加読込。このパラメータに限りスプライトの種類はバンク3D内の16bitアドレスを示す。
0x0006 ロード範囲の縮小(縦)
0x0007 エレベーター用。おそらく縦のロード範囲の拡大
0x0008 天候操作。おそらくスプライトの種類はアセンブリコードか何かのポインタ(バンク不明)。
0x0009 縦面スプライト再配置スクリプト用。おそらくスプライトの種類はアセンブリコードか何かのポインタ(バンク不明)。
0x000A 隠しタイマー。種類には設定時間を記述する。
0x000B 隠しタイマーが切れる前にスクロールインさせると出現する。
0x000C トリガースプライト。種類には何を設定しても無効(反映されない)。なお、単独で用いると周辺のスプライトのロード抑制。0x0005のスプライトセット内で用いる。
0x000D ロード範囲の拡大
0x000E 不明。トリガースプライトのような挙動を示す。
0x000F ロード範囲の縮小(縦)?縦面専用。
0x0010 カメラ操作用。エンガードのボーナスの隠しカメラにのみ設定。
0x0011 不明。トリガースプライトのような挙動を示す。


SDK2/3 パラメータ構造
上位バイト:識別番号(0x00~0x1F)
同じ識別番号を持ったスプライトは、そのうちのどれか1つがスクロールインすれば(画面内にあるなし関係なく)すべてロードされる。
識別番号を指定しない場合は0を設定する。SDK1のパラメータ0x0005によるスプライトセットの代用。

下位バイトの上位4ビット:ロード範囲
値を変えるとロードされるために近づかなければならない範囲、およびアンロードさせるために遠ざからなければならない範囲が変わる。
頻繁に画面外に出てしまうボスや、画面外に置くことの多いワープバレル等はこの値を操作してロード範囲を適切に変更する。0が既定値。

下位バイトの下位4ビット:パラメータ
SDK2/3で意味が変わる。

SDK2
Spoiler!
0x0 配置データの終了宣言
0x1 標準
0x2 標準
0x3 ロード禁止
0x4 ロード禁止
0x5 ロード禁止
0x6 ロード禁止
0x7 GFX修正 大きいスプライトは必ずこれを指定。
0x8 ロード禁止
0x9 GFX読込を行わない。制御スプライト系は全部これを使う
0xA ボーナスステージのクリア条件を満たしたときに出現
0xB (未使用)特定の条件を満たしたときに出現
0xC (未使用)特定の条件を満たしたときに出現
0xD 「クリーバーに3回攻撃し、ボスクリーバーがこちらに1回攻撃する」条件を満たしたときに出現。ロード処理自体も制御する非常に特殊なパラメータ
0xE GFX読込を行わない。
0xF 標準


SDK3
Spoiler!
0x0 配置データの終了宣言
0x1 標準
0x3 GFX修正 大きいスプライトは必ずこれを指定。
0x4 制御スプライト・スプライトジェネレータ・DKコイン用
0x7 ボーナスステージのクリア条件を満たしたときに出現
0x8 Normalモード時のみ出現
0x9 Normal/HARDRモードのみ出現
0xA TUFSTモードのみ出現
0xB Normal/HARDRモードのみ出現
0xC コイン類・バナナ


X座標:16ビット下駄履き表現。実際の値は256を引いた値
Y座標:16ビット下駄履き表現。実際の値は256を引いた値

スプライト指定
SDK1:バンク3D内のスプライト定義アドレス(一部例外あり)
SDK2/3:スプライト定義の配列ポインタの要素番号*2
Trainee Trekker
Bananas received 37
Posts: 54
Joined: 2015

Re: SDKシリーズデータフォーマット

Postby Quaraage » June 12th, 2016, 10:10 pm

共通 バナナ配置パターン構造

バナナ配置パターンは構造化されていて、非常に簡潔にまとまっています。

Byte0:横の本数(32本まで)
Byte1:縦の本数(24本まで)
Byte2以降:バナナの配置データ (bool型、1マスにつき1ビット)

バナナの配置は左上から右下に向かって行われます。
なお、65C816はバイト単位での読み出ししかサポートしていないので、余分なビットはすべて0で埋め、データサイズは1バイトの整数倍になるようにします。

バナナ配置パターンのデコードは、以下のように実装されます (MS Visual C#)
Spoiler!
Code: Select all
bool[,] BananaDecode(byte[]data)
{
    byte width = data[0];    //幅
    byte height = data[1];   //高さ

    if (width >= 32 || height >= 24) return null;    //不正なパターン

    bool[,] set = new bool[width, height];
    int read = 2;    //読出し位置
    int x = 0, y = height - 1, BitCount = 0;
    while (x < width)
    {
        if (x < 32 && y < 24) set[x, y] = (data[read] & (1 << BitCount)) != 0;
        else return null;

        if (++BitCount == 8)
        {
            BitCount = 0;
            read++;
        }
        if (y > 0) y--;
        else
        {
            y = height - 1;
            x++;
        }
    }
    return set;
}
Trainee Trekker
Bananas received 37
Posts: 54
Joined: 2015

Re: SDKシリーズデータフォーマット

Postby Quaraage » July 19th, 2016, 9:01 pm

SDK2/3 圧縮データの仕様

SDK2/3では地形データ、グラフィックデータなどが圧縮されています。これら圧縮データの形式は以下の通りとなっています。

<頻度テーブル><圧縮コマンド><データ(あれば)><圧縮コマンド><データ(あれば)>・・・

1.頻度テーブル
圧縮ヘッダは、出現頻度の高いデータをリスト化したもので、一部の圧縮コマンドによって暗黙的に入力されるデータとなります。
8bitデータ4つ、16bitデータ17個の計38バイトの大きさを持ちます。

byte0: 連続出現しやすい8bitデータ1 (最頻値)
byte1: 連続出現しやすい8bitデータ2
byte2: 単独出現しやすい8bitデータ1 (最頻値)
byte3: 単独出現しやすい8bitデータ2
byte4: 単独出現しやすい16bitデータ1 (最頻値)
byte6: 単独出現しやすい16bitデータ2
・・・
byte36: 単独出現しやすい16bitデータ17

なお、圧縮処理を実装する際、このサンプル選出、特に16bitサンプルにとりわけ注意を必要とします。

散発的に出現する16bit値W1と連続的に出現する傾向のある16bit値W2がソース内に同じ確率で現れる場合、W1を優位にしたほうがよい結果が得られます。これは以下の理由によります。
    W1が頻度テーブルから漏れたとき、W1の書込み1回には最低2.5bytesのコストがかかるが、W1が頻度テーブルに入っていれば、W1の書込み1回には最大でも1byteのコストしかからない。
    W2が頻度テーブルから漏れたときと漏れなかったときでは、せいぜい1~2bytes程度のコスト差しかない。
    W1とW2の出現確率が同じ場合は、単発の書込み回数が多いW1のほうがコスト差が影響する。

W1と同じ優先度になる16bit値W3は、散発的にW1と同じ確率で現れるか、W3が連続している塊がW1と同じ確率で出現するような性質を持ったものとなります。
つまり、W3が連続的に出現する場合は、W1より高い確率でソース内に現れなければ同じ優先度になりません。
これが、頻度テーブルに採用される値の殆どが単独出現しやすいものとなっている理由です。

2.圧縮コマンド
圧縮データはすべて4bit単位で取り扱われます。そのため、読み出し中に頻繁に4bitずれますが、コード側では適宜ビットシフトを行って対処しています。

[0X]
ROMからXバイトをコピーします。すなわち、0Xに続くデータをXバイト読み取ります。
Xが0の場合は展開処理の終了宣言です。すなわち圧縮データは00で終わる必要があります。

[1XX]
XXを1回入力します。

[2XXYY]
XXYYを1回入力します。(Big Endianであることに注意)

[3XYY]
YYをX+3回入力します。X+3回と定義されているのは、入力回数が2回以下の場合は別のコマンドのほうが効率がよいからです。

[4X]
頻度テーブルの連続出現8bit値1をX+3回入力します。

[5X]
頻度テーブルの連続出現8bit値2をX+3回入力します。

[6]
頻度テーブルの16bitデータ1を1回入力します。

[7]
頻度テーブルの単独出現8bit値1を1回入力します。

[8]
頻度テーブルの単独出現8bit値2を1回入力します。

[9X]
X+2バイト前から2バイトをコピーします。

[AXYY]
X+3+YYバイト前からX+3バイトをコピーします。[9X]の拡張コマンドです。

[BXYYZ]
YYZ+0x103バイト前からX+3バイトをコピーします。[AXYY]の拡張コマンドです。(Big Endianなのに注意)

[CXYYZZ]
YYZZバイト前からX+3バイトをコピーします。[BXYYZ]の拡張コマンドです。(Big Endianなのに注意)

[D]
直前の1バイトをコピーします。

[E]
直前の2バイトをコピーします。

[FX]
頻度テーブルの16bitデータX+1を1回入力します。

※[0X]以外のコピーコマンドのコピー元ソースは、展開したデータを格納しているバッファ(通常7F:0000)です。圧縮データを参照するわけではありません。
Trainee Trekker
Bananas received 37
Posts: 54
Joined: 2015


Return to 日本語フォーラム (Japanese Forum)

Who is online

Users browsing this forum: No registered users and 1 guest