これまでの知識を活かしてスプライトを使ってみます。SGDKはテキスト表示、1枚画像表示、BG面で遊ぶ、PCG定義を学ぶという手順で学ぶと理解しやすいです。
スプライト関係のライブラリはvdp_spr.hとsprite_eng.hの2つがあり、sprite_eng.hの方がより上位の実装のようです。単純にスプライトを表示するのであれば、vdp_spr.hを使えばよさそうです。
vdp_spr.hに用意されている関数はそれほど多くありません。まずは、1つのスプライトを表示してみたいので、VDP_setSprite()を使ってみます。 6番目の引数のlinkがよくわかりませんが、0を指定しておけばよさそうです。sizeは、メガドライブで表示できるスプライトサイズの指定です。メガドライブでは、8×8の定義パターン(BG面で使うものと共有)を1×1から4×4個の大きさまで並べて1つのスプライトとして表示できます。
まずは何の面白みもないキャラクタ定義用の画像を用意します。
8×8ドットの単色のブロックが16個です。この画像(block16.png)をres_image.resに
IMAGE block16_image "block16.png" -1
として定義しておいて、res_image.hでは
#ifndef _RES_IMAGE_H_ #define _RES_IMAGE_H_ extern const Image block16_image; #endif // _RES_IMAGE_H_
main関数から参照出来るようにしておきます。
main関数はこのようにします。
#include <genesis.h> #include "res_image.h" int main() { VDP_setPalette(0, block16_image.palette->data); VDP_loadTileSet(block16_image.tileset, TILE_USERINDEX, TRUE); VDP_setSprite(0, 0, 0, SPRITE_SIZE(1,1), TILE_ATTR_FULL(PAL0,1,0,0,TILE_USERINDEX+1), 0); VDP_updateSprites(); while(1) { VDP_waitVSync(); } return (0); }
VDP_setPalette()とVDP_loadTileSet()でPCGを定義します。この辺はBG面で遊んだ時と同じです。
VDP_setSprite()がスプライトの定義です。 vdp_spr.hのライブラリは、定義しただけでは表示されず、VDP_updateSprites()を実行することで、ライブラリ内部の定義情報をスプライト用のレジスタに書き込むようです。
実行結果はこちら。
TILE_USERINDEX+1のキャラクタ定義は赤いブロックです。これがスプライトとして表示されました。
これだけではBG面に書き込んだのと見た目が変わらないので、スプライトらしく1ドット単位で動かしてみます。スプライトの位置指定は、VDP_setSpritePosition()を使います。
#include <genesis.h> #include "res_image.h" int main() { int x = 0; VDP_setPalette(0, block16_image.palette->data); VDP_loadTileSet(block16_image.tileset, TILE_USERINDEX, TRUE); VDP_setSprite(0, 0, 0, SPRITE_SIZE(1,1), TILE_ATTR_FULL(PAL0,1,0,0,TILE_USERINDEX+1), 0); while(1) { x++; VDP_setSpritePosition(0, x, 0); VDP_waitVSync(); VDP_updateSprites(); } return (0); }
VDP_setSpritePosition()で表示位置を指定しただけでは実際の表示位置が変わらないことに注意が必要です。VDP_updateSprites()を実行することでスプライトに関するレジスタに値が書き込まれます。
メガドラのスプライトは最大4×4、つまり、32×32ドットのサイズを指定できます。この時のPCGの並びと表示の関係を確認してみます。
PCGの定義はこのようになっています(上のプログラムと同じ)。最初の16個はSGDKが使うようなので、USERINDEX(=16)からがユーザ用の定義領域になっています。
VDP_setSprite()でのサイズ指定と実行結果はこちら。
PCG定義は横方向に並んでいますが、スプライトは左上を基点にして縦→横の順に並ぶようです。
VDP_setSpriteDirect()のようなDirect系の命令は、VDP_updateSprites()でレジスタの更新を指示しなくても直接スプライトレジスタを書き換えられるようです。 関数の末尾にPがつくものは、SpriteDef構造体で指定する関数です。
link指定はメガドラの機能ではなく、SGDKのvdp_sprライブラリの機能です。メガドラの機能でした。
いまいち使い方を理解し切れていませんが、サンプルを挙げます。
#include <genesis.h> #include "res_image.h" int main() { VDP_setPalette(0, block16_image.palette->data); VDP_loadTileSet(block16_image.tileset, TILE_USERINDEX, TRUE); VDP_setSprite(0, 0, 0, SPRITE_SIZE(1,1), TILE_ATTR_FULL(PAL0,1,0,0,TILE_USERINDEX+1), 1); VDP_setSprite(1, 4, 4, SPRITE_SIZE(1,1), TILE_ATTR_FULL(PAL0,1,0,0,TILE_USERINDEX+2), 0); VDP_setSprite(2, 8, 8, SPRITE_SIZE(1,1), TILE_ATTR_FULL(PAL0,1,0,0,TILE_USERINDEX+3), 0); VDP_updateSprites(); while(1) { VDP_waitVSync(); } return (0); }
スプライトの定義は3つなのですが画面には2つしか表示されていないのがわかります。1つめのスプライト定義のlinkは1を指定していて、2つめのlinkは0を指定しています。これによってVDP_updateSprites()はスプライト0番、スプライト1番の順にスプライトの情報を更新します。linkが0だと、それで更新を終了するので、3つめのスプライトは表示されないのですね。
linkの指定を
VDP_setSprite(0, 0, 0, SPRITE_SIZE(1,1), TILE_ATTR_FULL(PAL0,1,0,0,TILE_USERINDEX+1), 2); VDP_setSprite(1, 4, 4, SPRITE_SIZE(1,1), TILE_ATTR_FULL(PAL0,1,0,0,TILE_USERINDEX+2), 0); VDP_setSprite(2, 8, 8, SPRITE_SIZE(1,1), TILE_ATTR_FULL(PAL0,1,0,0,TILE_USERINDEX+3), 0);
このようにすると、2番目のスプライト定義(スプライト1番)は表示されない事になります。link指定は便利なんだかどうなのかよくわかりませんし、そもそもこの認識があっているのかどうかも怪しいです。
あと、ついでといってはなんですが、スプライト番号が小さい方が上に表示されるみたいです。linkの順番が関係してました。
linkと描画順序の確認をしてみます。キャラクタ定義とプログラムはこれです。
VDP_setSprite(2, 0, 0, SPRITE_SIZE(1,1), TILE_ATTR_FULL(PAL0,1,0,0,TILE_USERINDEX+1), 1); VDP_setSprite(1, 2, 2, SPRITE_SIZE(1,1), TILE_ATTR_FULL(PAL0,1,0,0,TILE_USERINDEX+2), 0); VDP_setSprite(0, 4, 4, SPRITE_SIZE(1,1), TILE_ATTR_FULL(PAL0,1,0,0,TILE_USERINDEX+3), 2);
結果はこちら。
スプライトの定義順は2→1→0としましたが、定義順を変えても結果は同じでした。
まとめると「スプライト0番からlinkを辿って描画する。最初に描画するものが手前になる」というルールみたいです。描画順が逆のような気がしますが。
描画順を入れ替えたい場合、スプライト番号を差し替えるとかではなく、link順を変えることで制御できるということでしょうか。便利なような、あまり必要なさそうなような。