MSX マップを表示する 基礎編
前回記事まででVRAMを使ったキャラクター作りや当たり判定、マシン語での条件分岐やビット操作でのデータ圧縮などについて学習してきました。
今回からの記事は画面表示系の総集編として「マップの表示」についての説明を開始します。まずは序章として「マップ作成法についての基礎」についての解説です。sample009をダウンロードしてください。
https://github.com/sailorman-msx/games/tree/main/src/sample009
実行してみる
sample009.asmをアセンブルして動作させると次のような画面が表示されます。
おー・・・、なんか、一気にゲーム画面っぽくなった気がしませんか??
マップの表示方法にはいくつか種類があって代表的なのは以下のとおりです。(もちろん2Dですよ!MSX1で3Dなんて表現はムチャすぎます!)
固定
スクロールや画面切り替えなし、ディグダグとかパックマンとか
画面切り替え
初期ゼルダのようにキャラクターがはじっこに言ったら画面を切り替える方式
強制スクロール
グラディウス(ヨコスクロール)とかゼビウス(タテスクロール)とか勝手にスクロールする方式
ピープホールスクロール
キャラクターの動きにあわせてスクロールする方式(ドラクエとかドルアーガの塔とか)
これからのマップ作成の記事では、ピープホールスクロール方式をベースにしたいと思っています。
さて、上記画面イメージですが、表示させているテクニックは以下のとおりです。(テクニックとまでも言えませんが)
;--------------------------------------------
; マップデータ(初期画面)のデータを
; VRAM(1800H-1AFFH)に書き込む
;--------------------------------------------
ld de, $1800
ld hl, MAPDATA_DEFAULT
ld bc, 768
call LDIRVM
MainLoop:
ここではMAPDATA_DEFAULT(data_map.asm)のアドレスから768バイトをVRAMの1800H(パターンネームテーブル:画面)の先頭アドレスに転送しているだけです。それではマップデータを見てみましょう。
マップデータ(非圧縮)
マップデータはdata_map.asmにあります。今回のデータはマップデータというよりも画面全体を表示してるだけのデータとなっています。
;--------------------------------------------
; data_map.asm
; 固定データ(マップ作成用)
;--------------------------------------------
MAPDATA_DEFAULT:
; ゲーム背景の描画データ
; MAPデータ非圧縮方式(768バイト)
defb "################################" ; + 0
defb "#$$$$$$$$$$$$$$$$$$$$#%%%%%%%%%#" ; + 1
defb "#$$$$$$$$$$$$$$$$$$$$#%%%%%%%%%#" ; + 2
defb "#$$$$$$$$$$$$$$$$$$$$#%%%%%%%%%#" ; + 3
defb "#$$$$$$$$$$$$$$$$$$$$#%%%%%%%%%#" ; + 4
defb "#$$$$$$$$$$$$$$$$$$$$#%%%%%%%%%#" ; + 5
defb "#$$$$$$$$$$$$$$$$$$$$#%%%%%%%%%#" ; + 6
defb "#$$$$$$$$$$$$$$$$$$$$#%%%%%%%%%#" ; + 7
defb "#$$$$$$$$$$$$$$$$$$$$#%%%%%%%%%#" ; + 8
defb "#$$$$$$$$$$$$$$$$$$$$#%%%%%%%%%#" ; + 9
defb "#$$$$$$$$$$$$$$$$$$$$###########" ; +10
defb "#$$$$$$$$$$$$$$$$$$$$###########" ; +11
defb "#$$$$$$$$$$$$$$$$$$$$###########" ; +12
defb "#$$$$$$$$$$$$$$$$$$$$###########" ; +13
defb "#$$$$$$$$$$$$$$$$$$$$###########" ; +14
defb "#$$$$$$$$$$$$$$$$$$$$###########" ; +15
defb "#$$$$$$$$$$$$$$$$$$$$###########" ; +16
defb "#$$$$$$$$$$$$$$$$$$$$###########" ; +17
defb "#$$$$$$$$$$$$$$$$$$$$###########" ; +18
defb "#$$$$$$$$$$$$$$$$$$$$###########" ; +19
defb "#$$$$$$$$$$$$$$$$$$$$###########" ; +20
defb "################################" ; +21
defb " " ; +22
defb " " ; +23
今回のサンプルコードではキャラクタパターンを次のとおり変更しています。カッコ内はキャラクターコードです。
#(23H) ← 青色の外周
$(24H) ← 床(歩ける)
%(25H) ← 右側の水色の部分
スペース(20H) ← 空白(画面下の部分)
MAPDATA_DEFAULTでは、画面の1行ずつに対してどの文字を表示するかを並べています。GRAPHIC2モードではヨコ32文字xタテ24行なので768文字(768バイト)です。
上記、defbの並びと画面の表示状態を照らし合わせるとわかりやすいと思います。defbのコメントの + 0 とかっていうのは「0行目」といった意味でコメントを書いています。
非圧縮のマップデータだと広いマップに対応できない
さて、今回のマップデータはソースコードが読みやすくてとてもわかりやすいですが難点があります。それは広いマップにしようとするとその広さぶんだけの容量を必要としてしまうことです。今回のサンプルでは歩ける部分の表示域がヨコ20xタテ20なので400バイト必要です。表示域の5倍の広さのマップデータを用意しようとすると単純計算でヨコ100xタテ100で10000バイト必要になります。1024バイトで1キロバイトなので9.76キロバイト・・。
MSXでの約10KBはとんでもない無駄づかいです・・。
そのためマップデータを圧縮する必要があります。
よくある簡単な圧縮法(タイル表現を数ビットに圧縮する)
簡単な圧縮法は1バイトで表示データ1文字を表すのではなく、数ビットで複数種類のキャラクターを表す方法です。マップテクニックでは、マップの要素ひとつ(例えばボンバーマンのブロック1個)のことをタイルと言います。以降はタイルと表現しますね。ここから、当noteでは毎度おなじみビット操作の話になります。ついてきてください(汗)
表現したいタイルの種類数を決める
まずはじめに、何ビットで何種類のタイルを表現しようとするか。
ということを決める必要があります。
例えば、表示するタイルが壁と床の2パターンしかない場合は1ビットですみます。0(0B)は床、1(1B)は壁という感じです。
1ビットで1タイルを表現するのであれば、8タイルぶんを1バイトで表現できます。例えば、7CH(01111100B)というマップデータの場合
床(0)壁(1)壁(1)壁(1)壁(1)壁(1)床(0)床(0)
といった感じで表現することができるようになります。
表示するタイルが4パターンあれば2ビットですみます。
0(00B)は床、1(01B)は壁、2(10B)は穴、3(11B)は宝
という感じですかね。
2ビットで1タイルを表現するのであれば、4タイルぶんを1バイトで表現できます。
例えば、7CH(01111100B)というマップデータの場合
マップデータを先頭の第7ビット目から、2ビットずつに分解します。
01 , 11, 11, 00
↑こんな感じで分割です。
これをタイルのパターンに変換すると
壁(01)宝(11)宝(11)床(00)
という感じになります。
何ビットで何を表現するか?という点についてですが、4ビットもあれば大抵のタイルは表現できます。4ビットは0から15までの数値を表現できるので16種類は表現できるためです。4ビットでも1バイトで2タイル表現できますよね。
こんな感じで1バイト分で複数個のタイルを表現できるようにする圧縮テクニックになります。
ついてきてますか?(汗)
まあ、次回以降の記事でもっと理解できるようになると思います。
とにかくデータ量を減らすことを考える
当noteでは何度もくちすっぱくお伝えしていますが、MSXは極端に容量が少ないです。前述した画面イメージ(PNG形式)は197KBもあります。2681バイトでできたsample009.binの実行結果の画像が197KB。わけがわかりません・・。
MSX1のROMの場合、HDDとかMSX1には存在しないので、他のデバイス(媒体)からデータを引っ張ってくることも困難です。とにかくデータ量を減らすことを考える必要があるのです。マップタイルの1種類を表現するために1バイトも使っては資源の無駄づかいなのです。マップデータはそうそう変化するデータではありませんよね?であれば、そんなデータのために極力バイト数を使わないようにすることを考える必要があります。
現代のコンピューターではテラバイト、ペタバイトも扱えるほどになっていてデータ量のありがたみはわからないと思います。MSXのマシン語プログラミングをやることでそのありがたみが実感できると思います。
いや、筆者としては、ぜひそのありがたみを実感してほしいくらいです。
いま次回以降の記事のネタを仕込み中ですが
1タイルあたり16ドットx16ドット(4バイト)
2ビット1タイルの圧縮の画面で45タイルx45タイルの規模のマップ
を構想しています。
これを単純に1タイルあたり1バイトのマップデータにしようとすると
45 x 45 で 2025バイト必要です。
2ビット1タイル方式で圧縮すると540バイトくらいにまで圧縮できます。
4分の1くらいまで圧縮。
この違いは大きいです。(プログラムはしんどいけれど)
次回以降の予定
今回はここまでです。
次回以降では、今回紹介したデータ圧縮法を使ってピープホール方式できちんとしたマップを表示して、キャラクターの動きにあわせてスクロールできる内容にしたいと思います。
スクロールするマップ表示はひたすらアドレス変換の地獄です。
・キャラクターの論理的な位置をRAM上の物理アドレスに変換してゴニョゴニョ
・変換したアドレスをさらにVRAM上の物理アドレスに変換してゴニョゴニョ
こういったことの繰り返しになります。
ビットシフトや論理演算も多用することになりますので過去記事を復習しておいてください。
サンプルができるまで時間がかかることが予想されますのでそのへんはご容赦くださいませ。
では、また!
セーラー服が似合うおじさんです。猫好き、酒好き、ガジェット好き、楽しいことならなんでも好き。そんな「好き」をつらつらと書き留めていきます。