MSXの「GRAPHIC2の世界へようこそ」
SCREEN2 GRAPHIC2について
ようやく当初の第一目標だったこの段階に到達しました。
SCREEN1(GRAPHIC1)では文字の形を変えたり文字の色を変えることができることを前の記事で学習できていると思います。学習できていないなら前の記事を読み返してみてください。SCREEN1のこと(厳密にはGRAPHIC1モードのVRAMの扱いのこと)を理解出来ていないとGRAPHIC2はさらに難解だと思います。VRAMの仕組みがわからずに少年時代の筆者はMSXの勉強にさじを投げました・・。
さて、以降はSCREEN1モードを8割は理解できたひと向けに説明します。
SCREEN2(GRAPHIC2モード)についての要約は以下の図のとおりです。今回もスプライトについては説明しません。スプライトについては、後の記事で説明します。
パターンネームテーブルはGRAPHIC1と同じです。画面とVRAMのアドレスが1対1で対応しています。
GRAPHIC2モードはパターンジェネレータテーブルとカラーテーブルがGRAPHIC1モードの3倍になっています。GRAPHIC2モードでは画面を縦に3分割して上段、中段、下段というようにデータを設定します。
GRAPHIC2のパターンジェネレータテーブル
パターンジェネレータテーブルは画面の上段、中段、下段に対応した文字の形として各256文字ぶん用意されています。
1文字はGRAPHIC1と同様8バイトのデータで1文字の形を定義します。
256文字x8バイトx上中下(3)=6144バイトのアドレス空間になっています。
注意すべき点としては上段の文字の形を定義したら中段・下段の文字の形も同様に定義する必要がある。という点です。もちろん例えば同じ(’#’)という文字を上段・中段・下段でまったく別の形にすることも可能だとは思います。ですがゲームを作る場合、プログラミングがややこしくなるのであまりそういう作り方はしません。
GRAPHIC2のカラーテーブル
これこそがMSX GRAPHIC2モードの醍醐味です。
GRAPHIC2のカラーテーブルは1文字ごとに8バイトあります。これが何を意味するかというと文字の1ライン(横8ドット)ごとに前景色・背景色の2色を割り当てることができる。ということです。
GRAPHIC1では8文字ごとに同じ前景色・背景色の2色を割り当てることしかできませんでしたが、GRAPHIC2モードではグラフィックの表現力が格段にあがります。どういうこと?っていう点ですがGRAPHIC2モードでの文字の形と色は以下の図のようになります。
ソースコードを見てみよう
今回のソースコードはsample005.asm、initialize_gr2.asm、pcg_graphic2.asm、data_gr2.asmです。common.asmには変更はありません。いつものようにGitHubからダウンロードしてみてください。
sample005.asm
https://github.com/sailorman-msx/games/blob/main/src/sample005.asm
initialize_gr2.asm
https://github.com/sailorman-msx/games/blob/main/src/initialize_gr2.asm
pcg_graphic2.asm
https://github.com/sailorman-msx/games/blob/main/src/pcg_graphic2.asm
data_gr2.asm
https://github.com/sailorman-msx/games/blob/main/src/data_gr2.asm
魔法のおまじない:SETGRP
initialize_gr2.asmに以下の行が追加されました。
このSETGRP(007EH)というBIOSサブルーチンを呼び出すとGRAPHIC2モードにVDPが切り替わります。たったこれだけ?はい!たったこれだけです!
ちなみにBASICでも同様にこのBIOSをコールするとSCREEN1なんだけどGRAPHIC2の画面モードになったりします。
昔の愛読書MSX FANとかではSCREEN1.5とかって造語で呼ばれていた気がします。
; GRAPHIC2モードに変更する
call SETGRP
画面3分割の法則にのっとってパターンとカラーを設定する
pcg_graphic2.asmではパターンとカラーをVRAMに書き込んでいます。これは前回のSCREEN1の時も同じですが、今回のソースコードでは画面の上段、中段、下段にそれぞれデータを書き込むようなつくりになっています。
GetCharacterVRAMAddressサブルーチンでは画面上段に表示する文字のデータの先頭位置を取得しています。
例えば数字のゼロという文字は30Hですが、30Hに8倍かけた数値を取得して、その数値に0800H、1000Hなどを加算すると画面中段・画面下段のパターンジェネレータテーブルのVRAMアドレスが決まり、それらのアドレスに対してデータの書き込みを行なっています(LDIRVM)
カラーテーブルは画面上段に表示する文字のデータの先頭位置に2000Hを加算することで画面上段のカラーテーブルのアドレスとなり、2800H、3000Hを加算することでそれぞれ画面中段、画面下段のカラーテーブルのVRAMアドレスになるため、そのようにしてカラー情報を書き込んでいます(LDIRVM)
(抜粋)
;-----------------------------------------------
; パターンジェネレータテーブル処理
;-----------------------------------------------
; 定義する文字の数値を8倍し
; その結果をDEレジスタに格納する
call GetCharacterVRAMAddress
;
; 画面上段のパターンジェネレータテーブルに書き込む
;
ld hl, (WK_CHARDATAADR)
ld bc, 8 ; 8バイト転送する
call LDIRVM
;
; 画面中段のパターンジェネレータテーブルに書き込む
;
call GetCharacterVRAMAddress
ld hl, de
ld bc, $0800 ; HLレジスタに0800Hを加算する
add hl, bc
ld de, hl ; DEレジスタにHLレジスタの値を転送する
ld hl, (WK_CHARDATAADR)
ld bc, 8 ; 8バイト転送する
call LDIRVM
;
; 画面下段のパターンジェネレータテーブルに書き込む
;
call GetCharacterVRAMAddress
ld hl, de
ld bc, $1000 ; HLレジスタに1000Hを加算する
add hl, bc
ld de, hl ; DEレジスタにHLレジスタの値を転送する
ld hl, (WK_CHARDATAADR)
ld bc, 8 ; 8バイト転送する
call LDIRVM
ld hl, (WK_CHARDATAADR)
add hl, 8
ld (WK_CHARDATAADR), hl ; CHRPTNのアドレスを8バイト進める
;-----------------------------------------------
; カラーテーブル処理
;-----------------------------------------------
; 定義する文字の数値を8倍し
; その結果をDEレジスタに格納する
call GetCharacterVRAMAddress
ld hl, de
;
; 画面上段のカラーテーブルに書き込む
;
ld hl, $2000 ; HLレジスタに2000Hを加算する
add hl, de
ld de, hl ; DEレジスタにHLレジスタの値を転送する
ld hl, (WK_CHARDATAADR)
ld bc, 8 ; 8バイト転送する
call LDIRVM
;
; 画面中段のカラーテーブルに書き込む
;
call GetCharacterVRAMAddress
ld bc, $2800 ; HLアドレスに2800Hを加算する
add hl, bc
ld de, hl ; DEレジスタにHLレジスタの値を転送する
ld hl, (WK_CHARDATAADR)
ld bc, 8 ; 8バイト転送する
call LDIRVM
;
; 画面下段のカラーテーブルに書き込む
;
call GetCharacterVRAMAddress
ld hl, de
ld bc, $3000 ; HLアドレスに3000Hを加算する
add hl, bc
ld de, hl ; DEレジスタにHLレジスタの値を転送する
ld hl, (WK_CHARDATAADR)
ld bc, 8 ; 8バイト転送する
call LDIRVM
またCreateCharacterPatternサブルーチンの一番最初では、InitialPCGDatasサブルーチンをCALLして画面上段の文字の形を、画面中段と画面下段のパターンジェネレータテーブルにコピーしています。そして画面上段、画面中段、画面下段のカラーを前景色白、背景色黒の固定値にしています。この初期処理をしないと画面に文字を表示させようと思っても、カラーが定義されてなかったり画面中段や下段にはそもそもパターンが定義されていなかったり。。という理由などで何も表示されず、????と悩むことになってしまいます。お約束だと理解してもらえればそれで良いです。
;--------------------------------------------
; SUB-ROUTINE: CreateCharacterPattern
; パターンジェネレータテーブルと
; カラーテーブルを編集する
;--------------------------------------------
CreateCharacterPattern:
;----------------------------------------
; VRAMのPCG情報を初期化する
;----------------------------------------
call InitialPCGDatas
(中略)
;--------------------------------------------
; SUB-ROUTINE: InitialPCGDatas
; パターンジェネレータテーブルと
; カラーテーブルを初期化する
;--------------------------------------------
InitialPCGDatas:
;----------------------------------------
; パターンジェネレータテーブルの初期化
;----------------------------------------
; パターンジェネレータテーブルの
; 上段の情報をVRAMからRAMにコピーする
; 転送サイズは800バイト
ld hl, $0000
ld de, WK_VRAMTORAM
ld bc, $800
call LDIRMV
; パターンジェネレータテーブルの
; 上段の情報をVRAM(中段)にコピーする
; 転送サイズは800バイト
ld hl, WK_VRAMTORAM
ld de, $0800
ld bc, $0800
call LDIRVM
; パターンジェネレータテーブルの
; 上段の情報をVRAM(下段)にコピーする
; 転送サイズは800バイト
ld hl, WK_VRAMTORAM
ld de, $1000
ld bc, $0800
call LDIRVM
;----------------------------------------
; カラーテーブルの初期化
; カラーテーブルは
; 全ライン前景色白、背景色黒にする
;----------------------------------------
ld hl, $2000
ld bc, $1800 ; 256 x 3 x 8 = 6144 = 1800H
ld a, $F1
call FILVRM
ret
文字のパターンデータとカラーデータはdata_gr2.asmに記載してあります。
CHAR PATTERNという行の部分がパターンジェネレータテーブルに書き込むデータ、COLOR PATTERNという行の部分がカラーテーブルに書き込むデータです。パターンデータで1文字あたり8バイト、カラーデータで1文字あたり8バイトです。
data_gr2.asm 抜粋
;--------------------------------------------
; data_gr2.asm
; 固定データ(GRAPHIC2モード用)
;--------------------------------------------
; キャラクタパターンとカラーテーブル
CHRPTN:
defb $80 ; CHAR CODE
defb $FE, $FE, $FE, $00, $DF, $DF, $DF, $00 ; CHAR PATTERN
defb $81, $81, $81, $11, $81, $81, $D1, $D1 ; COLOR PATTERN
defb $81 ; CHAR CODE
defb $38, $38, $38, $7C, $BA, $38, $6C, $00 ; CHAR PATTERN
defb $51, $51, $51, $81, $81, $C1, $51, $11 ; COLOR PATTERN
(以下、略)
アセンブルして動かしてみよう
いつものとおりz80asmでアセンブルしてみましょう。
z80asm -b sample005.asm
アセンブルできたらsample005.binができますのでWebMSXでそれを動かしてみてください。以下のような画面が表示されるはずです。
今回はダンプ表示に使う文字の形も変えてみました。みづらくなっちゃったかな・・センスを磨きたい・・。
SCREEN1では1色だったブロックや青い人がいろんな色で構成されていることがわかると思います。青い人にも赤いシャツと緑のパンツを履かせてあげることができました。
ちなみに、青い人のパターンとカラーのデータは次のようになっています。
1文字だけのキャラを作る場合は7x7ドットを超えないように形を定義すると他のキャラとくっつかないので見た目がきれいです(筆者は1文字だけのキャラはあまり作りませんが)
あれ?右下になにかいるぞ?
右下にゲームキャラのようなグラフィックが表示されていると思います。これも文字です。文字の形と色を変えて組み合わせてるだけなのです。スプライトを使わなくともこのレベルの表現ができるのです。
なんて素敵なMSX!なんて美麗なGRAPHIC2!
もちろん、まだ動きませんが自分が作ったキャラクターをプログラムで動かすことができたら楽しいに決まってますよね!!
当noteでは今後の記事ではもちろんこれらのキャラクターをマシン語で動かすことも実践していく予定です。これからも記事を読んでもらえるとはげみになります。
昔、ザナドゥというゲームにデカキャラとよばれる敵キャラがいましたね。
このようなデカキャラも上記のように文字パターンとカラーを変更した文字を組み合わせることで表現しています。
FILVRM(0056H)について
initialize_gr2.asmでは FILVRMというBIOSサブルーチンが新しく登場しましたので説明します。
;----------------------------------------
; カラーテーブルの初期化
; カラーテーブルは
; 全ライン前景色白、背景色黒にする
;----------------------------------------
ld hl, $2000
ld bc, $1800 ; 256 x 3 x 8 = 6144 = 1800H
ld a, $F1
call FILVRM
HLレジスタに書き込み先となるVRAMの先頭アドレス、BCアドレスに書き込むバイト数、Aレジスタに書き込みたいデータの値をセットしたうえでFILVRMを呼び出すとAレジスタにセットされている値をBCアドレスで指定した数ぶんだけVRAMの先頭アドレスから埋めていきます。上記サンプルは、画面上段、中段、下段のカラーテーブルをすべてF1H(前景色白、背景色黒)で埋めているコードになります。
今回はカラーテーブルの初期処理として使っていますが、ゲーム作成においてはマップデータなど同じキャラクターで画面のほとんどを埋めたい場合などでも使います。
WRTVRM(004DH)について
sample005.asmのなかにWRTVRMというBIOSサブルーチンが新しく登場しましたので説明します。
ld hl, $1ADC
ld a, $98
call WRTVRM
WRTVRMはHLレジスタにデータを書き込みたいVRAMのアドレス、Aレジスタに書き込むデータの値をセットしてCALLすると、VRAMに1バイトだけそのデータを書き込みます。もちろんパターンネームテーブルのアドレスにWRTVRMすると画面に表示されることになります。文字やスプライトなどキャラクターを動かしたりするときに頻繁に使用することになります。いまはそのくらいの理解で良いです。
ここまでのおさらい
毎日投稿でここまで連載が続きました。
とりあえずここまででいろんなことが学習できたと思います。
・TMS9918というVDPにはGRAPHIC0モード、GRAPHIC1モード、GRAPHIC2モードの3つがあり、GRAPHIC2モードがいちばん表現力がある
・VRAMというのがあってMSXではそのアドレスにデータを入れると画面表示を制御できる
・VRAM上にパターンジェネレータテーブルとカラーテーブルというアドレス空間があってそのデータを書き換えることで文字の形や色を変えれる
・MSXでのマシン語の開発にはZ88DKとWebMSXを開発環境として使える
・マシン語は数字の羅列。覚えるのは大変なのでアセンブリ言語で書いてアセンブラでマシン語にアセンブルすれば便利
・アセンブリ言語にもいろんな命令があるけど基本的には変数やサブルーチンは自分でコツコツと作るしかない
・アセンブリ言語のプログラミングはそこそこ地味でシンドイ
・と言いつつも自分でなおかつ爆速のマシン語でキャラが作れるのは楽しい
マシン語(アセンブリ言語)も、VRAM、BIOSもと覚えることはたくさんあると思いますが、レジスタ、アドレス、VRAM、BIOSサブルーチンなどなど、ひとつひとつじっくり理解を深めながらやっていけばプログラミング自体はただの組み合わせでしかないのでそんなに難しくはありません(他のプログラミング言語と比べたらもちろん大変ですが・・)
そんなことよりもマシン語ゲームを作れそうだというイメージはなんとなくつかめたでしょうし、それがいちばん大切なことです。
楽しくないと長続きしませんからね。
今後の記事は常にGRAPHIC2モードでサンプルを動作させます。
次回はスプライトについて説明しようと思います。
スプライトまで理解できればVRAMのことをほぼ8割理解できたといっても過言ではないでしょう。もし、まだまだ「ちんぷんかんぷん」な状態だとするのであれば反復して過去記事を読んでみるとわかったりするかもしれません。
次回は少しお休みします
ちょっとかけあしでここまで記事を書いたのでしばらくお休みします。
当noteでもTwitterでもかまいませんので、読んだ感想、不明な点、間違ってるぞこれ!というようなご指摘等ありましたらコメントをお願いしますね。お待ちしています!
では、また!
マシン語講座:マシン語はかく語りき(データ圧縮を考慮しよう)
今回のグラフィックデータは何も圧縮を考慮していません。アセンブル結果はこんな感じでダラダラとデータが並んでいます。
00000210 c9 80 fc fc fc 00 cf cf cf 00 81 81 81 11 a1 a1 |?.???.???.....??|
00000220 f1 11 81 38 38 38 7c ba 38 6c 00 51 51 51 81 81 |?..888|?8l.QQQ..|
00000230 c1 51 11 30 7e 46 46 46 46 46 46 7e f1 f1 f1 f1 |?Q.0~FFFFFF~????|
00000240 71 71 71 71 31 38 18 18 18 18 18 18 18 f1 f1 f1 |qqqq18.......???|
00000250 f1 71 71 71 71 32 7e 06 06 7e 40 40 7e 7e f1 f1 |?qqqq2~..~@@~~??|
00000260 f1 f1 71 71 71 71 33 7e 06 06 7e 06 06 06 7e f1 |??qqqq3~..~...~?|
00000270 f1 f1 f1 71 71 71 71 34 7e 60 60 66 66 7e 06 06 |???qqqq4~``ff~..|
00000280 f1 f1 f1 f1 71 71 71 71 35 7e 40 40 7e 06 06 06 |????qqqq5~@@~...|
00000290 7e f1 f1 f1 f1 71 71 71 71 36 7e 40 40 7e 46 46 |~????qqqq6~@@~FF|
000002a0 71 71 f1 f1 f1 f1 71 71 71 71 37 7e 06 06 06 06 |qq????qqqq7~....|
000002b0 06 06 06 f1 f1 f1 f1 71 71 71 71 38 7e 46 46 7e |...????qqqq8~FF~|
000002c0 46 46 46 7e f1 f1 f1 f1 71 71 71 71 39 7e 46 46 |FFF~????qqqq9~FF|
000002d0 46 7e 06 06 06 f1 f1 f1 f1 71 71 71 71 41 7c 46 |F~...????qqqqA|F|
000002e0 46 46 7e 46 46 46 f1 f1 f1 f1 71 71 71 71 42 7c |FF~FFF????qqqqB||
000002f0 46 46 46 7e 46 46 7e f1 f1 f1 f1 71 71 71 71 43 |FFF~FF~????qqqqC|
00000300 7c 46 46 40 40 40 46 7e f1 f1 f1 f1 71 71 71 71 ||FF@@@F~????qqqq|
00000310 44 7c 46 46 46 46 46 46 7e f1 f1 f1 f1 71 71 71 |D|FFFFFF~????qqq|
00000320 71 45 7e 40 40 7e 40 40 7e 7e f1 f1 f1 f1 71 71 |qE~@@~@@~~????qq|
00000330 71 71 46 7e 40 40 7e 40 40 40 40 f1 f1 f1 f1 71 |qqF~@@~@@@@????q|
00000340 71 71 71 98 00 1f 3f 7f 7f 3f 0f 07 11 f1 71 41 |qqq...?..?...?qA|
00000350 41 71 81 61 99 07 0f 3f 7f 7f 3f 1f 00 61 81 71 |Aq.a...?..?..a.q|
00000360 41 41 71 f1 11 9a 00 f8 fc fe fe fc f0 e0 11 f1 |AAq?...???????.?|
00000370 71 41 41 71 81 61 9b e0 f0 fc fe fe fc f8 00 61 |qAAq.a.???????.a|
00000380 81 71 41 41 71 f1 11 a8 0f 1f 1f 1f 15 15 15 0f |.qAAq?.?........|
00000390 d1 d1 d1 f1 f1 f1 f1 f1 a9 10 7f 3b 1f 1b 0f 3e |????????..;...>|
000003a0 1e f1 f1 f1 f1 f1 f1 81 81 aa f8 f8 fc fc fc fc |.??????..???????|
000003b0 fc f8 d1 d1 d1 d1 d1 d1 d1 d1 ab e8 dc dc dc e8 |?????????ѫ?????|
000003c0 f0 3c 3c f1 f1 f1 f1 f1 f1 81 81 00 |?<<??????...|
一番上のC9の次の80からずっとキャラクタのパターンデータとカラーデータです。今回の場合はブロック、青いひと、16進表記で使う文字(16種)、右下に表示しているキャラ2体(8文字)で合計26文字だけです。26文字だけで442バイトほど使っています。これ、PCGキャラを100文字にしたらと考えると単純計算で4倍なので1768バイトほど消費してしまうことになります。これにさらにゲーム用のマップデータやらなんやら考えるとチリもつもればなんとやらでかなり無駄に消費することになります。
つまりメモリ容量が貧弱なMSXなので「データ圧縮」について考慮する必要があるのです。
簡単なデータ圧縮方法はデータを可変長にするやりかたです。
文字パターンを圧縮するのは非効率だと思いますがマップデータであればかなり効果的になります。
あとの記事で実装できたらそれを紹介したいと思います。