★★できた! となったらぜひSNSなどで「#ゲームギア」などのハッシュタグをつけて報告してね! 筆者が草葉の陰で喜ぶよ!★★
1. はじめに
セガ ゲームギアは1990年10月に発売されたセガの携帯型ゲームハード。当時、国内の携帯ゲーム機市場はモノクロの任天堂 ゲームボーイが主流でしたが、本機はカラー液晶を備え全世界で1000万台ほど普及しました。
なお、写真のちいかわはゲームギアとなんの関係もありません。
本記事では、私のようなド素人でも順番を追ってコピペすれば「ゲームギアのエミュレーターでプログラムが動く」ことを目標としています。
プログラミング言語はZ80のアセンブリ言語、開発環境はWindows11。プログラムそのものの内容にはあまり踏み込まず概略の説明が中心となります。
「細かいことはいいんだよ! ワイもさっぱりわかんねぇから」という姿勢であります。わからないところはChatGPTや詳しい方に聞いてくださいませ。
記載内容の間違い、誤記、誤字脱字、へんてこなコードなどは当方の責任です。皆様方におかれましては正しく変換してもらいつつ暖かくご指導を賜れれば幸いです。
なお、準備は第一回目から引き継いでいるためご一読をおすすめいたします。
2. 準備するもの
前回準備したものはこちら。今回も使います。
- エディタ(プログラムを書くためのソフト)
- メモ帳(普段はVisual Studio Codeを使っています)
- アセンブラとリンカ(書いたプログラムを機械で読めるようにするプログラム)
- WLA-DX
- ゲームギアミュレーター
- Gearsystem
導入は1回目をどうぞ(再・・・アネット再び?)。
本記事で準備するのは以下。記事中で紹介します。今ダウンロードしなくて大丈夫です。
- 画像編集ソフト
- PicBear - タイルデータ、タイルマップ(ネームテーブル)、パレット変換ツール
- BMP2tile
3. プログラム作成の流れ
今回はゲームギアのバックグラウンド面に画像を表示させ、方向キーを押すと次の画像に行くプログラムを組みます。
なお、サクっとコピペで動かしたい方は4節と5節を飛ばして「6. レッツコピペ ~ 画像を切り替え表示するプログラム 」から進めてください。コピペすれば動くはずです、たぶん・・・
今回の流れは以下の通りです。
- 表示させる画像の準備
- ゲームギア用のデータ作成
- レッツコピペ ~ 画像を切り替え表示するプログラム
- 実行ファイルの作成
- ゲームギアエミュレーターで画像切り替え
- おまけ、参考文献など
4. 表示させる画像の準備
表示させる画像の準備です。今回はChatGPTで生成した画像を使いながら、ゲームギアで表示可能な内容にしていきます。
画像を2枚作りましょう。
4-1. ChatGPT プロンプト
ChatGPTの画像生成を利用して画像を作りました。
1枚目の画像プロンプトは「画像を作成 快活な少女の絵と「げむぎあさん」というタイトルを載せた8ビットゲーム機向けのタイトル画像を作成してください。」で生成し、ダウンロードしました。
2枚目は適当に作ってもらうか、6節にあるプログラム用のデータをダウンロードして使ってみてください。
4-2. ゲームギアのスクリーンの大きさ
ゲームギアのスクリーン領域は256×224px。うち画面の表示領域は160x144pxとなります。
今回はプログラムを簡便にするため下記の要領で256×224pxのビットマップ形式の画像フォーマットを作り、表示領域である160×144pxの画像を入れ込むことにします。
下記はスクリーンの概念図です。内側の160×144px部が実際にディスプレイに表示できる領域。その外周は非表示ですが画像データ持つことのできる領域です。舞台袖のような非表示領域があることで、スクロール処理が忙しくなかったりと様々なメリットがあります。
4-3. Pic Bearの準備
画像の編集にはFenrir社のPicBear(開発終了)を利用します。パレットの編集が簡単というのが理由ですが、セキュリティが不安な場合は他のソフトでも同じことができれば問題ありません。
ダウンロードは下記からどうぞ
4-3-1. 画像の新規作成
PicBearを開き画面上部メニュー「ファイル - 新規作成」で256×224pxのファイルを作成しましょう。ファイル名はお任せします。
4-3-2. グリッドの表示
レトロゲームでよくあるのは8×8ドットの「タイル」による画像管理です。そのため、8×8pxのグリッドを表示しておくと便利。画面上部メニュー「表示 - グリッドの表示」にチェックを入れ、さらに「表示 - グリッドサイズの変更 - 2: 8×8」を選択します。
4-3-3. 表示したい画像のリサイズ
ChatGptで作った画像をPicBearで開きます。
開いたら画面上部メニュー「イメージ - イメージのリサイズ」を選択し、横幅160pxに合わせてリサイズします。表示領域から外れたところは次にトリミングを行います。
4-3-4. 画像の貼り付けとトリミング
縮小をかけた前述の画像を画面上部「選択範囲 - すべて選択」「編集 - コピー」でコピーをします。
次にグリッドをつけてある表示用画像のフォーマットの方に貼り付けます。
画面左の移動ツールで画像を移動させます。横から48px(グリッド6マス分)、上から24px(グリッド3マス分)の位置に貼り付けましょう。
表示領域と非表示領域の位置関係的には下のような感じになります。
下部の56px分の非表示領域にはみ出た分は使わないので範囲選択してデリーとボタンを押し削除します。
なお、はみ出てしまってもデータ容量が増えるだけで問題はありません。
4-3-5. 画像の16色への減色
ゲームギアは4096色中32色が使えます。ただし、背景面用のパレット16色と背景・スプライト共用のパレット16色の二つの色情報を切り替えて使うため、背景単体で使えるのは16色となります。
減色処理のため画像をインデックスカラー16色に変換します。
インデックスカラーは使用する色数だけをパレット(カラーテーブル)に格納し、各ドット(ピクセル)の色を対応する色番号(インデックス値)で指定する画像形式。データ量がコンパクトになる利点があります。
PicBearで画面上部メニュー「イメージ ‐ イメージタイプ ‐ インデックスカラー」でインデックスカラーに変換できます。
今回は下記のように16色かつ誤算分散を指定して変換しました。
変換した画像をビットマップ形式で保存します。
記事では1枚目をchapter_2_1_b.bmp、2枚目をchapter_2_2_b.bmpと名前を付けて保存しました。
本格的なゲームを制作する場合、色むらの修正やパレットを整理するとデータが小さくなるのでもう一段の処理があるとよいと思います。
4-3-6. 画像その1とその2(わかりやすいようにガイドを表示させています)
chapter_2_1b.bmp
chapter_2_2b.bmp (内容はひみつ)
その2は本記事6節のデータを使うと出現します。お楽しみということで・・・
5. ゲームギア用のデータ作成
ゲームギアの画像表示には以下の3種類のデータが必要です。
- 画像を8×8ドットなどに分解したタイルパターン
- タイルパターンの並び方を指定するタイルマップ(ネームテーブル)
- 使用する色情報を格納するカラーパレット
本記事では、ビットマップ画像からゲームギアに対応したデータを作成するため、Maxim氏の変換ツール「BMP2Tile」を使用してデータを作成します。
5-1. BMP2Tileの準備
英語圏のセガ野郎が集まるSMS Power!内の下記ページにアクセスします。Download at GitHubからZIPファイルをダウンロードして適当なところに展開します。
ダウンロード
https://www.smspower.org/maxim/Software/BMP2Tile
フォルダ内のBMP2TileGUIをクリックして起動すれば成功です。
5-2. ファイルの読み込み
起動したBMP2TileのSouceのページに変換したビットマップファイルをドラッグアンドドロップするか、Browseからファイル指定します。なお、縦横が8ドットの倍数でなかったり、カラー形式がおかしいとはじかれます。
5-3. タイルパターンの出力
Tilesタブを開くとタイルパターン情報がでます。インデックス番号ごとに8×8ドットのタイル情報が格納されます。
Remove duplicateにチェックを入れて重複するタイルを取り除きます。また、今回のプログラムでは Planar形式で読み込むため Planar tile outputにチェックを入れます。
最後にincludeファイルとして保存します。本記事では1枚目の画像はpattern_1.inc、2枚目はpattern_2.incとして保存しました。それぞれのファイルはプログラムの作業フォルダと同じとこに格納しましょう。
5-4. タイルマップ(ネームテーブル)の出力
Tilemapタブを開くとタイルマップ情報が出ます。各タイルの並び順が格納されています。
そのままincludeファイルとして保存します。1枚目の画像はname_table_1.inc、2枚目はname_table_2.incとして保存します。プログラムの作業フォルダに格納します。
5-5. カラーパレットの出力
Paletteタブを開くとカラーパレット情報が出ます。この画面ではGG(12bit RGB)に切り替えます。
ゲームギアは4096色の色数があります。色番号は12ビットで表されます。ちなみにマスターシステムは64色なので6ビットで表されます。それぞれ12桁の二進数、6桁の二進数で表せる数値となっています。
パターンデータなどと同様にincludeファイルとして保存します。1枚目の画像はpalette_1.inc、2枚目はpalette_2.incとして保存します。プログラムの作業フォルダに格納します。
これで画像のデータの準備完了です。
6. レッツコピペ ~ 画像を切り替え表示するプログラム
6-1. フォルダとファイルの構成
今回のプログラムづくりにあたって、本記事での作業フォルダの構成です。パターン、ネームテーブル、パレットのデータもプログラムファイルと同じところに置く前提で進めます。
C:/
└ ggProgrammings
└ chapter_2_showBgs/
├ chapter_2.asm ;プログラムファイル(5-2で作ります)
├ pattern_1.inc
├ pattern_2.inc
├ name_table_1.inc
├ name_table_1.inc
├ palette_1.inc
├ palette_1.inc
└ その他ファイル
4節、5節を飛ばした場合は、以下からデータをダウンロードしてプログラムと同じフォルダに格納しましょう。
画像データ一式のダウンロード
下記からimgData.zipをダウンロードし、ファイルを作業フォルダにコピーしましょう。
ダウンロードはここを押してね!
6-2. アセンブリプログラムのファイルを作る
メモ帳を開き、作業フォルダにchapter_2.asmとして保存します。ファイルの種類を「すべてのファイル」にするのを忘れずに。
6-3. コピペ1 ~ 初期設定のコピペ
開いているchapter_2.asmに上から順にコピペしていけばOKです。
;==============================================================
; 1.WLA-DX バンク、スロット設定
;==============================================================
.memorymap
defaultslot 0
slotsize $8000
slot 0 $0000
.endme
.rombankmap
bankstotal 1
banksize $8000
banks 1
.endro
;==============================================================
; 2. 変数定義
;==============================================================
.define VDPControl $bf
.define VDPData $be
.define VRAMWrite $4000
.define CRAMWrite $c000
.define ButtonInput $dc
;==============================================================
; 3. メモリ領域の確保
;==============================================================
.define Input $C000
;==============================================================
; 4. SDSCタグ SMSロムヘッダー
;==============================================================
.sdsctag 0.1,"Show Backgrounds!", "GG Programming Tutorial 2","SEGA-YAROW"
;========================================
; 5. バンク指定
;========================================
.bank 0 slot 0
.org $0000
;==============================================================
; 6. 割り込みモードの指定
;==============================================================
di ; disable interrupts
im 1 ; Interrupt mode 1
jp main ; jump to main program
.org $0066
;==============================================================
; 7. ポーズボタン制御
;==============================================================
; Do nothing
retn
6-3. コピペ2 ~ VDPの下準備
;==============================================================
; 8. プログラム本体
;==============================================================
main:
ld sp, $dff0 ;スタックポインタ
;========================================
; 8-1.VDP設定
;========================================
;VDPの準備待ち
init_vdp:
in A,($7E)
cp $B0
jp nz, init_vdp
;VDP設定の書き込み
ld hl,VDPInitData
ld b,VDPInitDataEnd-VDPInitData
ld c,VDPControl
otir
;==============================================================
; 8-2.VRAMを初期化
;==============================================================
; 1. Set VRAM write address to $0000
ld hl,$0000 | VRAMWrite
call SetVDPAddress
; 2. Output 16KB of zeroes
ld bc,$4000 ; Counter for 16KB of VRAM
clear_vram:
xor a
out (VDPData),a ; Output to VRAM address, which is auto-incremented after each write
dec bc
ld a,b
or c
jr nz,clear_vram
6-4. コピペ3 ~ 画像の読み込みやキー入力の検出
;==============================================================
; 8-3. 1枚目の画像読み込み
;==============================================================
Page1:
call TurnOffScreen
;==============================================================
; 1枚目のパレットデータをパレットテーブルへ書き込む
;==============================================================
; 1. Set VRAM write address to CRAM (palette) address 0
ld hl,$0000 | CRAMWrite
call SetVDPAddress
; 2. Output colour data
ld hl,PaletteData
ld bc,PaletteDataEnd-PaletteData
call CopyToVDP
;==============================================================
; 1枚目のタイルパターンをパターンテーブルへ書き込む
;==============================================================
; 1. Set VRAM write address to tile index 0
ld hl,$0000 | VRAMWrite
call SetVDPAddress
; 2. Output tile data
ld hl,TileData ; Location of tile data
ld bc,TileDataEnd-TileData ; Counter for number of bytes to write
call CopyToVDP
;==============================================================
; 1枚目のネームテーブルを書き込む
;==============================================================
; 1. Set VRAM write address to tilemap index 0
ld hl,$3800 | VRAMWrite
call SetVDPAddress
; 2. Output tilemap data
ld hl,NameTableData
ld bc, NameTableDataEnd-NameTableData
call CopyToVDP
; Turn screen on
ld a,%11000000
; |||||||`- 固定 0
; ||||||`-- 1スプライトにつき2タイル利用(8x16ドット)
; |||||`--- 固定 0
; ||||`---- 固定 0
; |||`----- 固定 0
; ||`------ VBlank 割り込み
; |`------- ディスプレイ起動
; `------- 固定 1
out (VDPControl),a
ld a,$81
out (VDPControl),a
;==============================================================
; 8-4. キー入力の検出
;==============================================================
-:
CALL GetKey
LD A, (Input)
BIT 3,A
JP Z, Page2
Jr -
;==============================================================
; 8-5. 2枚目の画像読み込み
;==============================================================
Page2:
call TurnOffScreen
;==============================================================
; 2枚目のパレットデータをパレットテーブルへ書き込む
;==============================================================
; 1. Set VRAM write address to CRAM (palette) address 0
ld hl,$0000 | CRAMWrite
call SetVDPAddress
; 2. Output colour data
ld hl,PaletteData2
ld bc,PaletteData2End-PaletteData2
call CopyToVDP
;==============================================================
; 2枚目のタイルパターンをパターンテーブルへ書き込む
;==============================================================
; 1. Set VRAM write address to tile index 0
ld hl,$0000 | VRAMWrite
call SetVDPAddress
; 2. Output tile data
ld hl,TileData2 ; Location of tile data
ld bc,TileData2End-TileData2 ; Counter for number of bytes to write
call CopyToVDP
;==============================================================
; 2枚目のネームテーブルを書き込む
;==============================================================
; 1. Set VRAM write address to tilemap index 0
ld hl,$3800 | VRAMWrite
call SetVDPAddress
; 2. Output tilemap data
ld hl,NameTableData2
ld bc, NameTableData2End-NameTableData2
call CopyToVDP
; Turn screen ON
ld a,%11000000
out (VDPControl),a
ld a,$81
out (VDPControl),a
-:
CALL GetKey
LD A, (Input)
BIT 2,A
JP Z, Page1
Jr -
6-4-1. キー入力の検出
プログラム中の8-4ではキー入力の判定を行っています。
この次のプログラムでGetKeyという補助関数を作りキー入力の状態を本体から取得。Inputという変数にキーの状態を記録ます。
記録した1バイト(8ビット)の情報のうち右を押している場合は下位から3ビット目(0ビット目から数えるので下位から4番目)が0から1へと変化します。これをトリガーとして2枚目の表示へ移るようにしています。
6-5. コピペ4 ~ 補助関数やデータのよみこみ
;==============================================================
; 補助関数
;==============================================================
SetVDPAddress:
; Sets the VDP address
; Parameters: hl = address
push af
ld a,l
out (VDPControl),a
ld a,h
out (VDPControl),a
pop af
ret
CopyToVDP:
; Copies data to the VDP
; Parameters: hl = data address, bc = data length
; Affects: a, hl, bc
-: ld a,(hl) ; Get data byte
out (VDPData),a
inc hl ; Point to next letter
dec bc
ld a,b
or c
jr nz,-
ret
GetKey:
IN A, ($DC)
LD (Input), A
RET
TurnOffScreen:
ld a,%10000000
out (VDPControl),a
ld a,$81
out (VDPControl),a
ret
;==============================================================
; Data
;==============================================================
; VDP initialisation data
VDPInitData:
.db $04,$80,$00,$81,$ff,$82,$ff,$85,$ff,$86,$ff,$87,$00,$88,$00,$89,$ff,$8a
VDPInitDataEnd:
TileData:
.include "pattern_1.inc"
TileDataEnd:
NameTableData:
.include "name_table_1.inc"
NameTableDataEnd:
PaletteData:
.include "palette_1.inc"
PaletteDataEnd:
TileData2:
.include "pattern_2.inc"
TileData2End:
NameTableData2:
.include "name_table_2.inc"
NameTableData2End:
PaletteData2:
.include "palette_2.inc"
PaletteData2End:
7. 実行ファイルの作成
WLA-DXを使ってゲームギア上で実行するためのファイルを作成します。ここではWLA-DXがインストールされてパスが通っている状態を前提に進めていきます。
WLA-DXのダウンロードやインストールの手順は下記をご覧ください。
7-1. 中間ファイルの作成
作成したchapter_2.asmをWLA-DX付属のwla-z80を使って機械で読めるファイルの一歩手前に変換します。
7-1-1. フォルダの移動
まず、コマンドプロンプトを開きchapter_2.asmファイルのあるフォルダまで移動しましょう。
フォルダのパスはWindowsのフォルダ上で右クリックをして「パスのコピー」をしておくとコピペで済みます。
C:\Users\hotge> cd "C:\ggProgramings\chapter_2_showBgs"
7-1-2. 中間ファイル(oファイル)の作成
次にWLA-DXに付属するwla-z80というプログラムでchapter_2.asmをchapter_2.oという機械語ファイルに変換します。
C:\ggProgramings\chapter_2_showBgs> wla-z80 -o chapter_2.o chapter_2.asm
作業フォルダ内にchapter_1.oができていれば完了です。
7-2. 実行ファイル(ggファイル)の作成
7-2-1. 実行ファイル作成の準備(リンクファイル作成)
仕上げにゲームギアで実行可能な実行ファイル(chapter_2.gg)を作るため、リンク処理を行います。アセンブラではリンカというプログラムを使って仕上げます。
この処理を行う手前でどんなファイルをリンクするかを指示するファイルを作ります。
メモ帳で新規作成して、linkfile.lnkを以下の内容で作りましょう。chapter_2.asmのあるフォルダに保存してください。「名前を付けて保存」をするときにファイルの種類が「すべてのファイル」になっているか確認しましょう。
[objects]
chapter_2.o
7-2-2. 実行ファイルの作成 (リンク処理)
WLA-DXに付属するwlalinkというプログラムで、ゲームギアで実行可能なファイル「chapter_2.gg」を作成します。
コマンドプロンプトで以下のようなコマンドで実行できます。
C:\ggProgramings\chapter_2_showBgs> wlallink linkfile.lnk chapter_2.gg
作業フォルダ内にchapter_2.ggができていれば完成です。
8. ゲームギアエミュレーターで画像切り替え
ゲームギアエミュレーターGearsystemを使って実行してみます。ここではGearsystemがインストールされている前提で進めていきます。
Gearsystemのダウンロードやインストールは下記をご覧ください。
8-1. プログラムの実行で画面切り替え
Gearsystemが立ち上がったら「Open Rom」から「chapter_2.gg」を開きましょう。
画像が表示されれば成功です。
右方向キーを押すと画面が2枚目に切り替わります。左方向キーで戻ります。
8-2. デバッグモードで中身をのぞく
前回記事ではVRAMの中をのぞきました。今回はワークメモリの中をのぞいてみます。
Gearsystem 画面上部メニューの「Debug - Enable」でデバッグモードになります。「Show Output Screen」から「Show VRAM Viewr」の部分にチェックがついてなかったらつけましょう。
8-2-1. ワークメモリ領域
プログラム中の下記の部分で、キー入力状況を取得してメモリに格納しています。
まずDisassemblerでContinueの状態にします。次にMemory EditorでRAMのタブを開きます。
キー入力状態を取得するたびに、ワークラムのアドレスC000(変数名 Input)に状態が格納されます。
キー入力の状態が変化すると画像の部分がピコピコ更新されるので見てみてね!
8-2-2. 無駄の多いネームテーブル
VRAM Viewerでタイルパターンをのぞいてみると、青背景が何枚も重複しています。実際にはデータ上では微妙に内容が違うため重複の削除処理をすり抜けた猛者たちです。
画像処理を行うとき、同じインデックスカラーで塗りつぶしておけば無駄がなくなります。容量の限られるレトロゲームでは容量の無駄遣いやメモリ占有は親の仇であります。
9. おつかれさまでした
画面切り替えできましたか? 興味のある部分を少しづつ調べると理解がはかどるかと思います。参考文献は記事の最後に用意しました。
ぜひみんなトライしてくれよな!
10. おまけ 実機で動かしてみる
恒例の実機で動かしてみるです。今回はキー入力までできちゃうので、感動もひとしおです。手順は下記の記事を参考にしてね。
実機で動くととてもうれしい。ハピハピハッピーであります。
参考資料・謝辞
本記事は以下の情報を参考にしました。
イラスト
書籍
- 堀 桂太郎, 浅川 毅 『Z80アセンブラ入門』 東京電機大学出版局
サイト