* Tutorial.7 スプライト(1) [#iddd2fef]

** スプライト [#x70b0e63]
スプライトとは8x8〜64x64サイズの画像を最大128個まで表示し、~
シューティングゲームでいう自機や弾、RPGでいう主人公キャラなどに使います。~

今回登場する配置領域、及びデータ構造は以下のとおりです。~

| 名前                                               | アドレス     |
| パレットデータ(256色1パレット or 16色16パレット) | 0x5000200〜  |
| キャラクタデータ(タイルモード時)                 | 0x6010000〜  |
| キャラクタデータ(ビットマップモード時)           | 0x6014000〜  |
| 属性(Object Attribute Memory)                      | 0x7000000〜  |

#ref(clip_1.gif,nolink)

キャラクタデータはタイルモードとビットマップモードでは''位置が変わる''ので注意してください。~
早速、パレットデータ、キャラクタデータ、属性(Object Attribute Memory)の3つをそれぞれ見ていきましょう。~


*** パレットデータ [#fc633cc4]
データフォーマットはmode3で使った色データと同じです。~
GBATEKから引用してみました。~

 Color Palette RAM
 BG and OBJ palettes are using separate memory regions:
 
 05000000-050001FF - BG  Palette RAM (512 bytes, 256 colors)
 05000200-050003FF - OBJ Palette RAM (512 bytes, 256 colors)
 
 Color Definitions
 Each color occupies two bytes (same as for 32768 color BG modes):
 
 Bit   Expl.
 0-4   Red Intensity   (0-31)
 5-9   Green Intensity (0-31)
 10-14 Blue Intensity  (0-31)
 15    Not used

OBJ Palette RAMが今回のターゲットです。~
データ構造はそのままに、16色をパレット16個か、256色をパレット1個で指定することも可能です。~

*** キャラクタデータ [#rdc6f6b1]
キャラクタデータは、パレット番号を指定した集まりです。~
単位は1キャラクタを8x8ドットとしています。~

|  色  | ドット単位の格納方法                            | サイズ |
|  16  | 横2ドット=1byte(下位4bitが左、上位4bitが右を) | 32byte |
| 256  | 横1ドット=1byte                                 | 64byte |

16色の場合、4bitでちょうど16色指定できるのでピッタリのサイズですね。~
256色も0x0 - 0xffまでなのでデータを無駄にすることなく使うことができます。~

*** キャラクタデータの格納 [#t400ad4d]
たとえば以下のキャラクタデータがあったとしましょう。~
- 256x128
#ref(clip_2.png,nolink)

このとき、キャラクタ番号は~
 空白 - 0
 !    - 1
 "    - 2
 #    - 3
 
 (以下略・・・)

となります。8x8単位のみを扱うのでしたらこのキャラクタ番号を指定し、~
表示するだけで話は終わります。ところが8x8単位以上のものを表示したい場合、~
格納方法に工夫がいります。~
#ref(clip_3.png,nolink)

こちらは先ほどの画像を2倍表示して8x8単位に点線を加えたものです。~
取り出し方法には1次元、2次元があります。仮に32x32で、1次元の取り出しを~
キャラクタ番号96から行うと、次のような結果になります。~
#ref(clip_4.png,nolink)

96, 97, 98・・・と取り出した為、円がきれいに表示されませんでした。~
では224に変えてみましょう。~
#ref(clip_5.png,nolink)

うまく行きました。~
個人的には1次元の取り出し方を推奨します。なぜならデータが連続しているため~
キャラクタデータを入れ替えるときにとても便利だからです。~

シューティングゲームなどでは頻繁にキャラクタデータの変更を行います。~
ボス戦なら画面いっぱいにキャラクタデータを使うので今のうちに~
1次元の方法に慣れておくのがいいでしょう。~

入れ替えがない場合は2次元が使えます。~
画像データを8x8単位に加工する必要がないのでうまく使い分けでください。~

*** 属性(Object Attribute Memory) [#a58b52c1]
スプライト1つ1つに対して、属性という単位で取り扱います。~
構造体を表すと次のようになりますが、attr0〜2までの中には~
様々なパラメータが存在して、いきなり覚えようというのは難しいと思います。~

今は軽く読み流す程度で結構です。~


 typedef struct {
 	u16 attr0;
 	u16 attr1;
 	u16 attr2;
 	u16 dummy;
 } OBJATTR;

#ref(clip_6.gif,nolink)

 OBJ Attribute 0 (R/W)
 
   Bit   Expl.
   0-7   Y-Coordinate           (0-255)
   8     Rotation/Scaling Flag  (0=Off, 1=On)
   When Rotation/Scaling used (Attribute 0, bit 8 set):
     9     Double-Size Flag     (0=Normal, 1=Double)
   When Rotation/Scaling not used (Attribute 0, bit 8 cleared):
     9     OBJ Disable          (0=Normal, 1=Not displayed)
   10-11 OBJ Mode  (0=Normal, 1=Semi-Transparent, 2=OBJ Window, 3=Prohibited)
   12    OBJ Mosaic             (0=Off, 1=On)
   13    Colors/Palettes        (0=16/16, 1=256/1)
   14-15 OBJ Shape              (0=Square,1=Horizontal,2=Vertical,3=Prohibited)


 OBJ Attribute 1 (R/W)
 
   Bit   Expl.
   0-8   X-Coordinate           (0-511)
   When Rotation/Scaling used (Attribute 0, bit 8 set):
     9-13  Rotation/Scaling Parameter Selection (0-31)
           (Selects one of the 32 Rotation/Scaling Parameters that
           can be defined in OAM, for details read next chapter.)
   When Rotation/Scaling not used (Attribute 0, bit 8 cleared):
     9-11  Not used
     12    Horizontal Flip      (0=Normal, 1=Mirrored)
     13    Vertical Flip        (0=Normal, 1=Mirrored)
   14-15 OBJ Size               (0..3, depends on OBJ Shape, see Attr 0)
           Size  Square   Horizontal  Vertical
           0     8x8      16x8        8x16
           1     16x16    32x8        8x32
           2     32x32    32x16       16x32
           3     64x64    64x32       32x64


 OBJ Attribute 2 (R/W)
 
   Bit   Expl.
   0-9   Character Name          (0-1023=Tile Number)
   10-11 Priority relative to BG (0-3; 0=Highest)
   12-15 Palette Number   (0-15) (Not used in 256 color/1 palette mode)

#ref(clip_7.gif,nolink)

** スプライト操作の汎用関数 [#bfa170c7]
こちらで用意した関数を通して簡単な(大雑把な)扱いを知ってください。~

 void SpriteInit(void)
 {
 	u32 i;
 	for(i=0; i<128; i++)
 	{
 		SpriteMove(i, 240, 160);
 	}
 }

すべてのスプライトを画面外に移動させます。~
非表示にするといった機能はありますが、念のため必ず初期化時に実行します。~

 void SpriteMove(u32 num, s32 x, s32 y)
 {
 	OBJATTR* sp = (OBJATTR*)OAM + num;
 
 	sp->attr1 &= 0xfe00;
 	sp->attr0 &= 0xff00;
 	sp->attr1 |= (x & 0x01ff);
 	sp->attr0 |= (y & 0x00ff);
 }

スプライトの移動処理です。~
範囲はGBAの液晶画面サイズ240x160と同じではなく、512x256となっています。~

 void SpriteSetSize(u32 num, u32 size, u32 form, u32 col)
 {
 	OBJATTR* sp = (OBJATTR*)OAM + num;
 
 	sp->attr0 &= 0x1fff;
 	sp->attr1 &= 0x3fff;
 	sp->attr0 |= col  | form | (160);
 	sp->attr1 |= size | (240);
 }

スプライトの形状と色数を変更します。~
引数は''gba.h''の説明にあるものから選択して使用します。~
詳細はソースコードを読んでください。~

 void SpriteSetChr(u32 num, u32 ch)
 {
 	OBJATTR* sp = (OBJATTR*)OAM + num;
 
 	sp->attr2 &= 0xfc00;
 	sp->attr2 |= ch;
 }

スプライトで使用するキャラクタ番号を指定します。~
このほかにもいろいろな関数を用意できますが、今回はこれだけを使います。~

| 関数名        | 用途                 |
| SpriteInit    | 初期化               |
| SpriteMove    | 移動                 |
| SpriteSetSize | サイズの設定         |
| SpriteSetChr  | キャラクタ番号の設定 |

** スプライトの表示例 [#h37e14bc]
 #include "lib/gba.h"
 #include "res.h"
 
 //---------------------------------------------------------------------------
 void WaitForVsync(void)
 {
 	while(*(vu16*)0x4000006 >= 160) {};
 	while(*(vu16*)0x4000006 <  160) {};
 }
 //---------------------------------------------------------------------------
 void SpriteMove(u32 num, s32 x, s32 y)
 {
 	OBJATTR* sp = (OBJATTR*)OAM + num;
 
 	sp->attr1 &= 0xfe00;
 	sp->attr0 &= 0xff00;
 	sp->attr1 |= (x & 0x01ff);
 	sp->attr0 |= (y & 0x00ff);
 }
 //---------------------------------------------------------------------------
 void SpriteSetSize(u32 num, u32 size, u32 form, u32 col)
 {
 	OBJATTR* sp = (OBJATTR*)OAM + num;
 
 	sp->attr0 &= 0x1fff;
 	sp->attr1 &= 0x3fff;
 	sp->attr0 |= col  | form | (160);
 	sp->attr1 |= size | (240);
 }
 //---------------------------------------------------------------------------
 void SpriteSetChr(u32 num, u32 ch)
 {
 	OBJATTR* sp = (OBJATTR*)OAM + num;
 
 	sp->attr2 &= 0xfc00;
 	sp->attr2 |= ch;
 }
 //---------------------------------------------------------------------------
 void SpriteInit(void)
 {
 	u32 i;
 	for(i=0; i<128; i++)
 	{
 		SpriteMove(i, 240, 160);
 	}
 }
 //---------------------------------------------------------------------------
 int main(void)
 {
 	// モード設定
 	SetMode(MODE_0 | OBJ_ENABLE | OBJ_1D_MAP);
 
 	u16* oam = OBJ_BASE_ADR;	// キャラクタデータ
 	u16* pal = OBJ_COLORS;		// パレットデータ
 	u32 i;
 
 	for(i=0; i<sprTilesLen/2; i++)
 	{
 		oam[i] = sprTiles[i];
 	}
 
 	for(i=0; i<16; i++)
 	{
 		pal[i] = sprPal[i];
 	}
 
 	SpriteInit();
 
 	// !(ビックリマーク)の表示
 	SpriteSetSize(0, OBJ_SIZE(Sprite_8x8), OBJ_SQUARE, OBJ_16_COLOR);
 	SpriteSetChr (0, 1);
 	SpriteMove   (0, 20, 20);
 
 	u32 x = 40;
 	u32 y = 40;
 
 	// 円の表示
 	SpriteSetSize(1, OBJ_SIZE(Sprite_32x32), OBJ_SQUARE, OBJ_16_COLOR);
 	SpriteSetChr (1, 224);
 	SpriteMove   (1, x, y);
 
 	for(;;)
 	{
 		WaitForVsync();
 
 		if( !(REG_KEYINPUT & KEY_UP)   ) y--;
 		if( !(REG_KEYINPUT & KEY_DOWN) ) y++;
 		if( !(REG_KEYINPUT & KEY_LEFT) ) x--;
 		if( !(REG_KEYINPUT & KEY_RIGHT)) x++;
 
 		SpriteMove   (1, x, y);
 	}
 }

 SetMode(MODE_0 | OBJ_ENABLE | OBJ_1D_MAP);

スプライトを使用するにはOBJ_ENABLEを入れます。~
OBJ_2D_MAPは、OBJ_1D_MAPとの2択で、取り出し方が1次元か2次元か指定します。~

 // !(ビックリマーク)の表示
 SpriteSetSize(0, OBJ_SIZE(Sprite_8x8), OBJ_SQUARE, OBJ_16_COLOR);
 SpriteSetChr (0, 1);
 SpriteMove   (0, 20, 20);

引数の1番目はスプライト番号を表しています。ビットマップモードの場合、~
メモリマップが変わる為、先頭番号が512番目からになることは忘れないでください。~

*** 動作画面とデバッグ画面 [#m9a1ff76]
#ref(clip_8.png,nolink)
#ref(clip_9.png,nolink)

キー入力できます。適当に動かしてみてください。


** 履歴 [#v77dd331]
- 2014/12/23