Chapter.3 スプライト表示ルーチン製作
[Chapter3 制作物]

今回は、TEST.BAS の中に記述してあるスプライト表示処理(PUT SPRITE)を、アセンブラルーチンへ移動します。

■アセンブラからのスプライトの表示方法
スプライトは VDP が表示しているわけですが、その VDP は、VRAM上のスプライト情報を元に、
スプライトの表示位置や、形状、色などを決めて、テレビモニタに送る信号を作っています。

今回は、PUT SPRITE の座標指定部分をアセンブラで作るわけですが、その情報が格納されているVRAM領域を
スプライトアトリビュートテーブルと呼びます。

SCREEN4 の場合、デフォルトでは 0x1E00番地がスプライトアトリビュートテーブルの先頭アドレスになります。
なので、sca_info.asm に SPRITE_ATTRIBUTE = 0x1E00 の定義を追加します。
プログラム上で、0x1E00 と書かれていると、何の値なのかわかりにくいですが、SPRITE_ATTRIBUTE と書いてあれば
スプライトアトリビュートテーブルのアドレスなんだと、わかりやすくなります。
.include "sca_info.asm" すれば、SPRITE_ATTRIBUTE と言う名前が利用可能になるので、今後も様々な定数を定義
していきます。

スプライトアトリビュートテーブルは、1スプライトあたり 4byte の記憶領域があり、先頭から
[Y座標][X座標][パターン番号][未使用] という並びになってます。
SCREEN1〜3 のスプライトモード1では、[未使用]の代わりに[色]があるのですが、SCREEN4以降のスプライトモード2
では、色指定の自由度が上がり「1ラインに1色指定できる」という拡張が成されたため、1byte では足りなくなりました。
そこで、色指定だけ別の領域へ移されています。その辺の詳細は、chapter.4 に記載します。

■sca_sprite.asm を作る
sca_shot.asm を追加したときのように、mk.bat や chapter3.link を修正してあります。

スプライト全般の処理を司る sca_sprite.asm というソースを作成します。
中には、sprite_init と sprite_update の2つのサブルーチンを作り込みます。
また、メモリ上にスプライトアトリビュートテーブルのメモリイメージを作成します。

sprite_init は、メモリイメージの初期値設定を行います。
でたらめな値のまま VRAM に設定してしまうと、画面にゴミが出てしまうためです。
たとえば、オールゼロだった場合、(0,0) に パターン0 で表示すると言う意味になるため、左上に何か表示されてしまいます。
そのような現象を防ぐために、しっかりと初期化しておくわけです。

プログラムは、単純にメモリに値を書き込む LD命令の羅列になっているので、ソースを見ていただければ分かると思います。
詳細な説明は省略させていただきます。

次に、sprite_update です。
これは、メモリ上に用意してあるスプライトアトリビュートテーブルのメモリイメージを、VRAM のスプライトアトリビュート
テーブルへ転送するためのルーチンです。

実施している処理としては、自機や自機弾の情報テーブルから座標値だけを読み出して、メモリ上のスプライトアトリビュート
テーブルへ書き込んでいます。
自機に至っては、2枚重ねの1枚目と2枚目に同じ座標が設定されるように書き込んでいます。
自機の2枚重ね処理は、プログラム1 の部分です。

プログラム1. 自機の2枚重ね処理
		; 2枚重ねスプライトの2枚目の座標を1枚目と重ねる
		ld	a, SCA_INFO_XL(ix)
		ld	SCA_INFO_XL2(ix), a
		ld	a, SCA_INFO_XH(ix)
		ld	SCA_INFO_XH2(ix), a
		ld	a, SCA_INFO_YL(ix)
		ld	SCA_INFO_YL2(ix), a
		ld	a, SCA_INFO_YH(ix)
		ld	SCA_INFO_YH2(ix), a
		

ix レジスタは、自機情報を保持していますが、そのすぐ後に自機情報その2(2枚目スプライト用)を用意してあります。
このその2の方の座標に、コピーしています。

自機弾の情報も繋がっているので、スプライトアトリビュートテーブルへ転送するルーチンは、自機1枚目・2枚目・弾1・2・3
を、「5個の情報テーブルから座標を引っ張ってくる処理」として記述すれば済むようになります。

情報テーブルから、スプライトアトリビュートテーブルの座標部分へ値をコピーしている部分がプログラム2 です。

プログラム2. 情報テーブルからスプライトアトリビュートテーブル(メモリ上)の座標を更新する処理
		; 情報テーブルの座標をDRAM上のアトリビュートテーブルイメージへ転送
		ld	de, #SCA_INFO_SIZE
		ld	hl, #sprite_attribute_table
		ld	b, #5
sprite_update_loop:
		ld	a, SCA_INFO_YH(ix)
		ld	(hl), a
		inc	hl
		ld	a, SCA_INFO_XH(ix)
		ld	(hl), a
		inc	hl
		inc	hl
		inc	hl
		add	ix, de
		djnz	sprite_update_loop
		

最初の3命令は、下記のような意味です。

de には、情報テーブル1つ分のサイズをセットしておきます。
de は、ix への加算量を保持するレジスタとして利用します(add ix, #SCA_INFO_SIZE という命令が使えないため)
hl には、書き込み先であるメモリ上のスプライトアトリビュートテーブルの先頭アドレスをセットしておきます。
hl を転送先アドレスを保持するレジスタとして利用します。
b には、5回繰り返すために 5 をセットしておきます。

それ以降は繰り返し部分になります。
まず、スプライトアトリビュートテーブルの先頭は Y座標なので、情報テーブルから Y座標値を a レジスタへ読み出します。
それをすかさず hlレジスタが指し示すメモリへコピー。
hl を次のアドレス(X座標)へ進めます。
同様にして、情報テーブルから X座標値を aレジスタへ読み出します。
それをすかさず hlレジスタが指し示すメモリへコピー。
hl に3を加算するために inc hl を3回実施してます。これは、add hl, #3 という命令が存在しないためです。

add ix, de は、ix を次の情報テーブルの先頭アドレスを示すように移すための演算です。SCA_INFO_SIZE を加えています。

最後に djnz で、5回ループさせています。

この処理を脱けた時点で、メモリ上のスプライトアトリビュートテーブルは、目的の設定値に書き換わっています。
これを一気に VRAM へ転送するのが プログラム3 の部分です。

プログラム3. メモリ上のスプライトアトリビュートテーブルイメージを VRAM へ転送する
sprite_update_transfer:
		; アトリビュートテーブルイメージをVRAMへ転送
		ld	hl, #sprite_attribute_table
		ld	de, #SPRITE_ATTRIBUTE
		ld	bc, #(4*5)
		jp	LDIRVM
		

LDIRVM は、BIOSエントリです。
hlレジスタに転送先となる VRAM のアドレス、deレジスタに転送元となるメモリ上のアドレス、bcレジスタに転送するバイト数
をセットして call すると、メモリ上のデータを VRAMへ転送してくれる機能があります。
例によって LDIRVM は、sca_info.asm で定義してあります。具体的なアドレス値を知りたい場合は、sca_info.asm を見てください。

と言う具合に、VRAM更新は非常に簡単に実現できるのでした。

■何でわざわざメモリ上にスプライトアトリビュートテーブルのイメージを作成したのか?
BIOSエントリには、VPOKE hl, a のような動作をする BIOSエントリもあるのですが、これが非常に遅いのです。

VRAM は CPU に直接接続されているわけではなく、VDP の方に接続されています。
CPU から、VRAM へ値を書いたり、VRAM から値を読む場合は、VDP に対してアドレスを通知してから、読み書きする手順を
踏まなければなりません。
VPOKE hl, a のような動作は、その BIOSエントリにとっては「HLに何が指定されてくるか分からない」ため、毎回必ず
VDP に対してアドレスを通知する処理を実施してしまいます。

しかし、VDP は、その辺もある程度考慮して設計されていて、「連続するアドレスに同じ操作(読む or 書く)をする場合は
アドレス通知を省いても良い」という機能を付けてあります。
つまり、アドレスを通知して、その後に読み書きすると、VDP 内で受け取ったアドレス値を自動でインクリメントしてくれる
機能があるのです。

ちなみに、アドレスを通知するための手順は、下記のようになります。
I/Oポートの 0x99 に「アドレスの下位8bit」「アドレスの上位6bit + 読み込みなら 0x40加算」の順で2バイト書き込みます。
その後は、0x98 に読み書きすることで、VRAM から値を呼んだり、書いたりすることが出来ます。
この最初の2バイト書き込みを、なるべく避けることで無駄に遅くなるのを避けるわけです。

BASIC の PUT SPRITE 0,(X,Y) は、原理から考えて X,Y の2バイトは連続アクセスで書き込んでいると思いますが、
BASIC が内部的に重ね合わせように自動更新する スプライト1 の方は、再度アドレス指定しているはずです。

■test_main.asm/test.bas の修正
test_main.asm は、USR(n) の n のバリエーションを増やして、スプライトアトリビュートテーブルの更新処理を BASIC から
呼び出せるように修正します。

test.bas は、座標指定のための PUT SPRITE を削除して、代わりに test_main.asm で追加した USR(n) を呼び出すように修正します。


今回は、修正内容が簡単なので、説明もだいぶ省略させていただきました (^_^;


[▲トップページへ]