screen 3 の画面に画像を表示するプログラム例を提示します。その前に、今回のサンプル提示で画像コンバーターをバージョンアップしましたので、そちらをお受け取りください。今回のバージョンアップで、画像も混合と分離が選択できるようにしました。ダウンロードは以下からお願いします。

PC-6001 画像コンバータ公開 ver.1.0.2
※ 一番下までスクロールしてください。


  • 画像を用意する
最終的には横2ドット縦4ドット単位で動かしたいと思います。PC-6001mkⅡの screen 3 は横長ドットで 1バイトで 4ドットを表します。この仕様なので、横方向に 2ドット単位で動かすのは困難です。そのため、最初から 2ドットズレた画像を用意して、必要に応じて切り替える事で、横方向に 2ドット動いているように見せかけます。

今回用意した画像はこんな感じです。
弾画像の素材
画像的には横2バイト縦12ドットが2枚です。ただ、横方向は有効サイズが6ドットとなっていて、2ドットは隙間になっています。この画像で用意するため、シフト処理などが不要で動作的には速いのですが、画像データが単純に2倍になるというデメリットがあります。また、横方向に2ドット移動という事は、ただでさえ遅い PC-6001mkⅡの処理速度が見た目上 1/2に落ちるという事にもなります。この辺りは自分が作ろうと思うゲームタイプに合わせて考慮してみてください。

さて、この画像を組み込みデータに変換します。
エディタから出力する
新しい画像コンバータ v1.0.2 だと、混合と分離、どちらも選択出力可能です。それぞれ解説していきます。


  • アドレス計算
ゲーム中の座標管理ですが、今回は無難に X,Y 座標で行います。横2ドット移動です。screen 3 は横の解像度が 160ドットなので、160÷2 = 80 となります。このため、クリッピングを考慮しなければ、X座標の有効範囲は 0 ~ 79 となります。同様に縦方向は4ドット単位の移動だと、screen 3 の縦解像度 200÷4 = 50 となりますので、有効範囲は 0 ~ 49 となります、

ここから実アドレスに変換するためには、address = X÷2+Y×4×40 という計算が必要になります。X の範囲は screen 3 だと 40バイトなので、Y 方向に 4ドット単位×横幅40バイトを掛けています。

160倍の計算は Z80はあまり得意ではありません。無理やり計算する場合は、まず 160を因数分解します。2×2×2×2×2×5 となりますので、仮に Acc に Y座標が格納されているとして、例えば以下の計算をします。

4 : ld c, a ; 現在の値をCに保存 4 : add a, a ; 2倍 4 : add a, a ; 4倍 4 : add a, c ; 5倍 **8bitの計算限界 4 : ld l, a 7 : ld h, 0 11 : add hl, hl ; 10倍 11 : add hl, hl ; 20倍 11 : add hl, hl ; 40倍 11 : add hl, hl ; 80倍 11 : add hl, hl ; 160倍

サイクル計算したところ、82サイクルでした。…まあ、如何にも遅そうですよね💦 そこで考慮するのがテーブルです。今回の場合は 50個の計算済みテーブル 100バイトを用意すれば問題解決します。が、データとして100バイトを持つのはどうなの?って思ってしまったので、最初に一回こっきりの初期化ルーチンで RAMにテーブルを作ってしまいます。また、多くの場合、RAM参照の方が ROM参照より高速なので、ROMプログラムの場合でも効率が上がるはずです。

ということで、まずはどこに作るか決めます。出来れば、0xNN00 とキリの良いアドレスに配置したいですよね。今回は、$FE00 に設定しました。

VRMTbl equ $FE00 ; VRAMオフセットテーブル SCRWidth equ 40 ; 画面横幅 SCRHeight equ 200 ; 画面縦幅

この VRMTbl の位置にテーブルを生成していきます。最初の初期化中は di 状態の事が多いと思いますので、遠慮無く push を使ってしまいます😁

;----------------------------------------------------------------------- ; VRAMテーブルを生成する ; ※ 割り込み禁止状態で呼び出す事! ; VRMCreateTbl:: ld b, SCRHeight / 4 ; B ループカウンタ ld hl, SCRWidth * (SCRHeight - 4) ; HL Y=49のアドレス ld de, (0 - SCRWidth * 4) & 0xFFFF ; DE 4ドット上のオフセット ld (WRK.Temp00), sp ; 現在のSPを待避 ld sp, VRMTbl + SCRHeight / 2 ; SPを配置位置の100バイト後方に .loop push hl ; テーブル値をメモリに配置 add hl, de ; HL アドレス位置を上に djnz .loop ; テーブル全て分処理する ld sp, (WRK.Temp00) ; SPを元に戻す ret

これでテーブルが出来ましたので参照します。

;----------------------------------------------------------------------- ; 横2ドット縦4ドット単位の座標系を実アドレスに変換する ; HL = X,Y 座標 ; out: HL アドレス ; 破壊: AF, HL ; VRMAddress:: sla l ; Yを2倍 ld a, h ; Acc Xを入れる rra ; Acc 1/2 ld h, VRMTbl / 256 ; HL アドレステーブル add a, (hl) ; 下位アドレスにXを加算 inc l ld h, (hl) ; H VRAMアドレス上位 ld l, a ; HL アドレス ret nc ; 桁上がりが無ければ終了 inc h ; 上位を+1 ret

ざっくりで70サイクルです。意外と速くなっていませんが、こちらはX座標の計算も入っているためです。160倍の取り出し部分だけを見ると…

8 : sla l ; Yを2倍
7 : ld h, VRMTbl / 256 ; HL アドレステーブル 7 : ld a, (hl) ; 下位アドレスにXを加算 4 : inc l 7 : ld h, (hl) ; H VRAMアドレス上位 4 : ld l, a ; HL アドレス

37サイクルです。82サイクルからの変化ですので、かなり大きい高速化だと分かります。Z80 ではこのようなテーブルによる高速化がかなり効きます。
※ 絶対ヘルシーだよね、製造は中国でも販売は国産だし買おうかなあ…


  • 混合画像データを画面に表示する
同じ位置で L, H とデータが格納されている形式です。この形式は単純描画だと重いのですが、重ね合わせを行う場合や、描画時の色ズレを見えにくくするのに有効です。まずはプログラムをドンッ!

;----------------------------------------------------------------------- ; 横2バイト×縦12バイトの画像を描画する(混合形式) ; HL = 描画 X,Y 位置 ; DE = データアドレス ; GRPDraw2x12Mix:: call VRMAddress ; HL 描画アドレス ld bc, (0 - 0x2000 + SCRWidth - 1) & 0xFFFF ld iyl, 11 .loop ld a, (de) inc e ld (hl), a set 5, h ld a, (de) inc de ld (hl), a inc hl res 5, h ld a, (de) inc e ld (hl), a set 5, h ld a, (de) inc de ld (hl), a add hl, bc dec iyl jr nz, .loop ld a, (de) inc e ld (hl), a set 5, h ld a, (de) inc de ld (hl), a ret

set 5,h で +0x2000 としています。screen 3は、各ページの先頭は 0x0000, 0x4000, 0x8000 と、16KB 単位で、データ部(H)は 0x2000 加算した場所にあります。0x2000 の上位バイト 0x20 は 0010_0000b です。ちょうど 5番目にビットがありますよね?そのため、set 5,h とすると +0x2000 と全く同じ効果となるのです。同様に res 5,h で -0x2000と同じになります。

上記を見れば分かりますが、やっぱり重そうですよね。ざっくり計算だと 1622サイクル強ほど処理時間が掛かります。あと、1命令フェッチする毎にメモりウェイトがかかる場合は、プログラムが長いので、これもマイナス要因となります。


  • 分離画像データを画面に表示する
混合形式だと重かったですが、分離形式だとどうでしょうか。まずはプログラムを提示します。

;----------------------------------------------------------------------- ; 横2バイト×縦12バイトの画像を描画する(分離形式) ; HL = 描画 X,Y 位置 ; DE = データアドレス ; GRPDraw2x12Sep:: call VRMAddress ; HL 描画アドレス ex de, hl ; 描画を HL→DE の流れとする push de call .draw ; L 描画 pop de set 5, d ; DE 描画アドレスを H 側に .draw ld iyl, 11 .loop ldi ldi ld a, SCRWidth - 2 add a, e ld e, a jr nc, .chk inc d .chk dec iyl jr nz, .loop ldi ldi ret

一気にシンプルになったのが分かるかと思います。これは殆ど下位アドレスの変更で処理が完結しているためです。途中で分岐があるので正しいサイクル数算出は難しいのですが、ざっくり計算すると凡そ 1400サイクルほどとなります。
※ おそらく計測するともう少し短い時間で終わるとは思います。

プログラムが劇的に短くなりましたが、思ったほどは速くないですよね。add a,e 周辺の処理が軽くなるともう少し高速化するのですが…。PC-8001と違ってPC-6001系は割り込みが動いていますので、むやみに SP 使うわけにもいかず、とりあえずこんな処理としています。

※ これからの季節にこれを買おうか悩んでいます。