昔触っていたZ80アセンブラで何か作りたいなと思っていたところ以下の記事を発見!
コピペで遊ぼう レトロゲーム プログラミング ~ 1. セガ ゲームギアでHello World!
いろいろ触ってみることにしました。
とはいえゲームギアは触ったことはありません。
上記記事からゲームギアのハードウェア仕様書などを参考にしました。
画面表示構成を見てみると、BG(back ground)を表示する部分はPattern name table、BG表示するためのデータを置くところがPattern generator table、Pattern generator tableの約半分を共用する形でSprite generator tableを使用してSprite attribute tableを使ってスプライトを表示する仕組み。
Pattern generator tableの構成はパレット番号を組み合わせていてちょっとめんどくさい構造。パレットを白黒の2色だけにすると少しは簡単にできそう。
そんなわけで、Pattern name tableとPattern generator tableを1対1になるよう構成させて1ドットを置くプログラムを作成してみました。
上記記事のコードにおいてフォントデータが大きいので数字とアルファベット大文字だけ使うように64個分残した後を画面表示用に使います。
フォントデータをセットした後で以下を組み込み。
;-------------------------------------------------------------------------------
;name tableとpattern generatorを1対1で紐づけ
ld HL, $38cc | VRAMWrite ;32*2*3+6*2=204=$cc + $3800(name table)
ld DE, $40 ;font64個=$40(pattern generator number + font)
ld C,18 ;表示Y方向最大値
set_loop2:
push HL
call SetVDPAddress
push DE ;DE→HL
pop HL ;pattern generator address set
ld B, 20 ;表示X方向最大値
set_loop1:
ld A, L
out (VDPData), A
ld A, H
out (VDPData), A
ld DE, 1 ;pattern generator number + 1
add HL, DE ;next pattern generator address
djnz set_loop1
push HL ;HL→DE
pop DE ;pattern generator address update
pop HL
push DE
ld DE, $40 ;32*2(次の行へ)
add HL, DE ;name tableの次の行へ
pop DE
dec C
jr nz, set_loop2
画面に1ドットを置くプログラムは以下
;-------------------------------------------------------------------------------
;表示画面に点を置く
;B=X座標(0-159), C=Y座標(0-143)
;point_flag = 0(set:白セット), 1(clear:黒セット)
PointSet:
push HL
push DE
push BC
ld HL, 0
ld A, C
srl A ;÷2
srl A ;÷4
srl A ;÷8
jr z, ps_0
ld DE, 20
ps_1:
add HL, DE
dec A
jr nz, ps_1
ps_0:
ld A, B
srl A ;÷2
srl A ;÷4
srl A ;÷8
ld E, A
ld D, 0
add HL, DE ;HL = Y*20+X
push HL ;HL退避[1]
sla L ;x2
rl H
sla L ;x4
rl H
sla L ;x8
rl H ;HL = (Y*20+X)*8
ld DE, DISP_RAM
add HL, DE
push BC ;BC退避[1]
ld A, C
and %00000111
ld E, A
ld D, 0
add HL, DE ;DISP_RAMアドレス
ld A, B
and %00000111 ;bit番号
;bit番号を値に変換(bit0=$80,bit1=$40,,,,bit7=$01)
jr z, ps_6
ld B, A
ld A, $80
ps_7:
srl A
dec B
jr nz, ps_7
jr ps_8
ps_6:
ld A, $80
ps_8:
push AF ;AF退避[1]
ld A,(point_flag)
bit 0, A ;0 or 1 判定
jr nz, ps_4
;SET
pop AF ;AF復元[1]
or (HL) ;bit set
jr ps_5
ps_4;
;CLEAR
pop AF ;AF復元[1]
ld E, A ;E←A(data)
ld A, $ff
sub E ;A←($ff-data)
and (HL) ;andでbit clear
ps_5:
ld (HL), A ;DISP_RAM更新
ld (temp_data), A ;編集データ仮保存
;該当する Name Table の Pattern Generator Number を取得
;そのPattern Generator Addressを編集する
;Number = 20*Y+X+$40
;Address = Number * 32
pop BC ;BC復元[1]
pop HL ;HL復元[1]
ld DE, $40 ;FontData分加算
add HL, DE
sla L ;x2
rl H
sla L ;x4
rl H
sla L ;x8
rl H
sla L ;x$10
rl H
sla L ;x$20
rl H ;HL = (Y*20+X+$40)*32 Gen Address
ld A, C
and %00000111
jr z, ps_2
ld DE, 4 ;セル内Y位置オフセット加算
ps_3:
ADD HL, DE ;Gen編集該当アドレス
dec A
jr nz, ps_3
ps_2:
ld DE, VRAMWrite
add HL, DE
call SetVDPAddress
ld A, (temp_data)
out (VDPData), A ;VRAM Write
pop BC
pop DE
pop HL
ret
RAMに以下を設定
.equ point_flag $c000 ;b
.equ temp_data $c001 ;b
.equ DISP_RAM $c100 ;20*18*8=2880=$b40
Pattern generator tableの読み出しができなかったので、RAM上にPattern generator tableに設置するデータ(DISP_RAM)を置いて、そのデータを編集してPattern generator tableに書き込みしています。
Z80プログラミングも久しぶりだしGameGearの仕組みも完全に理解したわけではないのでプログラミングコードはあくまで参考までに・・・
mainloopに以下を置く。
;-------------------------------------------------------------------------------
;埋めてみる
ld A, 0
ld (point_flag), A ;SET
ld C, 144
gm_2:
ld B, 160
gm_1:
push BC
dec B ;decしてゼロで終了するので+1~+160, +1~+144でセットする
dec C
call PointSet
pop BC
dec B
jr nz, gm_1
dec C
jr nz, gm_2
- jr - ;STOP
画面全部を埋めてみました。
すると、ドット抜けが発生してしまいます。

VDPの準備待ちが必要そうなので以下に修正。
;-------------------------------------------------------------------------------
;埋めてみる
ld A, 0
ld (point_flag), A ;SET
ld C, 144
gm_2:
ld B, 160
gm_1:
;----------
ld A, B
and 1
jr nz, gm_3
;2回に1回VDPの準備待ちを通す(3回以上では抜けが発生する)
;---
;VDPの準備待ち
call VDP_ready_wait
gm_3:
;----------
push BC
dec B ;decしてゼロで終了するので+1~+160, +1~+144でセットする
dec C
call PointSet
pop BC
dec B
jr nz, gm_1
dec C
jr nz, gm_2
- jr - ;STOP

成功しました、、、が描画完了するのにかなり時間がかかってしまいます。
遊ぶだけなら抜けても問題ないかなと思います。
ついでに乱数発生器を作成しました。
;----------------------------------------------------------------------
; random data get
; ret : A reg. (and random_data)
;----------------------------------------------------------------------
random_get:
push HL
add HL, SP
add A, H
add A, L
ld L, A
ld A, R
add A, L
ld HL, random_data
add A, (HL)
add A, (HL)
add A, (HL)
rlca
ld (HL), A
inc HL
add A, (HL)
add A, B
add A, C
add A, D
add A, E
rlca
ld (HL), A
pop HL
ret
ランダムシードをRAMに設置
.equ random_data $c002 ;b
mainloopに以下のコードを置いて乱数の分布を見てみます。
;-------------------------------------------------------------------------------
;ランダムPointSet
ld A, 0
ld (point_flag), A ;SET
call random_get
cp 159 ;範囲外チェック
jr nc, gm_0
ld B, A
call random_get
cp 143 ;範囲外チェック
jr nc, gm_0
ld C, A
call PointSet
gm_0:
ret
ドット置くコードができたのでここからいろいろ遊んでみようかと思います。
