3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

z88dk-z80asm+ゲームギアでグローバル変数とOAM定数の便利な管理方法(defvars)

Last updated at Posted at 2024-03-10

はじめに

現在、z88dk の z80asm を用いてゲームギアのゲームを開発しているのですが、z80asm の defvars がグローバル変数やOAM(スプライト)の定数管理に便利だったので紹介します。

本書は基本的にゲームギア・プログラミング向けに書いていますが、MSX や VGS-Zero のフルアセンブリ言語でのプログラミングでも応用可能かと思われます。

なお、ゲームギア・プログラミングの基礎については以下の記事をお読み頂ければだいたい分かると思われます。

defvars

詳細は以下のドキュメントに書かれています。

defvars は 起点となるアドレス値 を指定して、サイズ情報付きのラベル を列挙する形で記述します。

(凡例)

defvars 起点アドレス
{
    ラベル1  {ds.b | ds.w | ds.p | ds.q}  個数
    ラベル2  {ds.b | ds.w | ds.p | ds.q}  個数
    ラベル3  {ds.b | ds.w | ds.p | ds.q}  個数
      :
}
  • ds.b = 8 bits (1 byte)
  • ds.w = 16 bits (2 bytes)
  • ds.p = 24 bits (3 bytes)
  • ds.q = 32 bits (4 bytes)

(具体例)

vars.asm
defvars $1000
{
    hoge ds.b 1       ; hoge = $1000 (1 byte x 1)
    hige ds.b 3       ; hige = $1001 (1 byte x 3)
    fuga ds.w 1       ; fuga = $1004 (2 bytes x 1)
}

ゲームギアでの用例

ゲームギアで defvars を用いたグローバル変数と OAM 定数管理の用例を記します。

(1) グローバル変数

ゲームギアの RAM の先頭アドレスは 0xC000 です。

VGS-Zero の RAM の先頭アドレスも同じく 0xC000 です。

MSX はスロット構成によりケースバイケースですが、ROM ゲームを開発する場合の初期状態はゲームギアと同様に 0xC000 (slot3) が RAM という前提でコードを書いても問題ないものと思われます。

そこで、起点アドレス 0xC000defvars でグローバル変数を管理できます。

vars.asm
; グローバル変数
defvars $c000
{
    player_x ds.w 1   ; プレイヤーの X 座標(実数)
    player_y ds.w 1   ; プレイヤーの Y 座標(実数)
    joypad   ds.b 1   ; ジョイパッドの入力保持
}

上記のラベル joypad にゲームギアのジョイパッド入力状態を保持するコードの例を示します。

io.asm
.io_joypad
    ; ===== start ボタン以外の入力を拾う =====
    in a, ($dc)
    and $3F      ; 上位2ビット(外部入力)をマスク
    ld b, a
    ; ===== start ボタンの入力を拾う =====
    in a, ($00)
    and $80      ; スタートボタン(最上位ビット)以外をマスク
    or b         ; ビットレイアウト: `s-abrldu`
    ; =====  グローバル変数 joypad に格納 =====
    ld (joypad), a
    ret

(2) OAM 定数管理

ゲームギアでは 64 枚のスプライトを利用することができますが、「何番目のスプライトを何につかうか」 という情報管理に defvars を用いると便利です。

vars.asm
; OAM アドレス管理
defvars $6800          ; OAM アドレス ($2800) + アドレス設定フラグ ($4000)
{
    oam_y ds.b 64      ; Y座標, Y座標, Y座標...
    oam_unused ds.b 64 ; 未使用領域
    oam_x ds.b 128     ; X座標, パターン, X座標, パターン...
}

; スプライトインデクスの管理
defvars $0000
{
    oi_jiki ds.b 6     ; 自機のスプライト (6枚)
    oi_shot ds.b 4     ; 自機ショットのスプライト (4枚)
    oi_spray ds.b 8    ; 水しぶきのスプライト (8枚)
}

OAM のデータ構造は次のような形になっています。

offset size description
+0x00 64 bytes Y 座標
+0x40 64 bytes 未使用領域
+0x80 128 bytes X 座標 + パターン番号

オブジェクト単位の連番構造になっていない構造のため、defvars では OAM のインデクスのみ管理しておき、VRAM アドレスを設定する時に OAM アドレスとインデクス値を加算する形で実装するのがスマートだと考えられます。

以下、6 枚のスプライトを使ったキャラクタ(自機)の Y 座標、X 座標、パターン番号を設定するサンプルコードを示します。

なお、以下のコードは VRAM の OAM アドレスが 0x2800 の前提で記述しています。(こちらの記事Hello, World を表示するサンプルと同じ構成です)

; VRAM アドレスやレジスタ設定用のサブルーチン
.vdp_setreg
    ld a, l
    out ($bf), a
    ld a, h
    out ($bf), a
    ret

; 次の並びで 6 枚のスプライトを使った自機の OAM を設定するサブルーチン
; +-------+-------+-------+
; | ptn00 | ptn01 | ptn02 |
; +-------+-------+-------+
; | ptn10 | ptn11 | ptn12 |
; +-------+-------+-------+
.render_jiki
    ; jiki の Y 座標を OAM に設定
    di
    ld hl, oam_y + oi_jiki
    call vdp_setreg
    ld a, (player_y + 1)   ; NOTE: 座標は固定小数点数で上位 8 bits (アドレス上はリトルエンディアン) が整数部分
    out ($be), a   ; ptn00 の Y 座標を設定 
    out ($be), a   ; ptn01 の Y 座標を設定
    out ($be), a   ; ptn02 の Y 座標を設定
    add $08
    out ($be), a   ; ptn10 の Y 座標を設定
    out ($be), a   ; ptn11 の Y 座標を設定
    out ($be), a   ; ptn12 の Y 座標を設定

    ; jiki の X 座標 と パターン番号 を OAM に設定
    ld hl, oam_x + oi_jiki * 2
    call vdp_setreg
    ld a, (player_x + 1)
    out ($be), a
    ld a, player_ptn00
    out ($be), a

    ld a, (player_x + 1)
    add $08
    out ($be), a
    ld a, player_ptn01
    out ($be), a

    ld a, (player_x + 1)
    add $10
    out ($be), a
    ld a, player_ptn02
    out ($be), a

    ld a, (player_x + 1)
    out ($be), a
    ld a, player_ptn10
    out ($be), a

    ld a, (player_x + 1)
    add $08
    out ($be), a
    ld a, player_ptn11
    out ($be), a

    ld a, (player_x + 1)
    add $10
    out ($be), a
    ld a, player_ptn12
    out ($be), a
    ei
    ret

上記コード中のポイントは以下の箇所です。

    ld hl, oam_y + oi_jiki
    ld hl, oam_x + oi_jiki * 2

アセンブリソース上では定数の加算や乗算ができ、演算結果が求まった状態でアセンブルされるので動作上の速度への影響がありません。

スプライトの優先順位を入れ替えたい場合、スプライトインデクスの defvars の並び順を変えて再アセンブルするだけで簡単に調整ができます。

例えば、以下のようにコード変更すれば水しぶきのスプライト(oi_spray)の描画有線順位が最優先(若いスプライト番号)になります。

; スプライトインデクスの管理
defvars $0000
{
    oi_spray ds.b 8    ; 水しぶきのスプライト (8枚)
    oi_jiki ds.b 6     ; 自機のスプライト (6枚)
    oi_shot ds.b 4     ; 自機ショットのスプライト (4枚)
}
3
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?