![見出し画像](https://assets.st-note.com/production/uploads/images/93105243/rectangle_large_type_2_4d58647057f1461ca83c872ee647eff0.png?width=1200)
MSX 雑誌投稿プログラム Wallaby!(ワラビー)にエディット機能を追加する(その1)
突然だが、みなさんは Wallaby!(以下「ワラビー」)というゲームをご存知だろうか――で始まる、前回の記事の続きである。そんな前回の記事は、こちらから。
○今回のミッション
さて、早速本題に入る。今回のミッションは、このゲームにエディット機能を追加することだ。
そういえば、当時の雑誌における編集者のコメントでも、
「ステージ数をもっと増やすか、エディットできるようになっていたら……。」
というのがあったが、今回は、そんな希望をかなえるものでもある。
実は、前回に引き続き、これもプログラムのデータ部分を変更すれば、標準の面を変更することができる。というのは、360行目が1面のデータ、370行目が2面のデータ……となっており、単にこれらの文字列を変更するだけでいいのだ。
具体的には、次の360行目の DATA文について、最初のAをUに変えれば、ゲーム画面の一番左上の何もないところ(ブランク部分)に「りんご」が表示されるのを確認できる。
360 DATA AAAMAAIIIIIAAAAAAEAAIIIAAAAAEEEAEAUIUAAAAAEQAEEAMIMAAAAUEEAAAAEIMAAAAMAAAMAIIIEAAMAEEEEEEEEEAAIIIIUAAAAEEEAIIIIIEEEEEEEEEIIIII,1,2
このアルファベットと各パーツの関係は次のとおりだ。
A:ブランク
M:ブロック
Q:爆弾
E:壊れる壁
I:壊れない壁
U:りんご
そして、DATA文の最後の2つの数字が、ワラビーの初期出現位置のマップ上の X座標と Y座標だ。
これらの情報さえあれば、理論的には、自分で好きな面を作成できる。しかし、このようなアルファベットの羅列から、グラフィカルな面データを想像できる人は、そうはいないだろう。手っ取り早く、紙と鉛筆で作った面を、手でアルファベットに変換するのもいいし、エクセルなどの各自慣れた環境でエディットし、その画面から面データへ変換させるプログラムを作っても楽しいかもしれない。
ただ、いずれの方法をとっても、最終的には、その面データ(DATA文)を MSX 側へ持っていく必要がある。それならば、やはり MSX で、直接グラフィカルに編集できた方がよい。特に、こまめに「作ってはプレイ」というような感じでエディットする場合は、MSX 内で完結させることは必須だろう。
そして、そのエディットの画面の操作イメージとしては、やはり、前回も何度か出てきた(ファミコンの)「ロードランナー」のエディットのイメージだ。
○エディット仕様の検討
MSX のコントローラー(ジョイパッド)は、ファミコンでいうところのスタートボタンとセレクトボタンがなく、要は、IIコンに相当するボタン(十字キーとABボタン)のみだ。ボタン数も少ないので、それぞれのボタンにどの機能を割り当てるのかをよく考える必要がある。
ワラビーの原作者の他の投稿プログラムに、TUBO(ツボ、1995年2月号の MSX・FAN に掲載)というのがあり、これは、「ぐっすんおよよ」や「レミングス」のような、操作できない主人公をゴールに誘うアクションパズルゲームなのだが、この TUBO には、実は、標準でエディットモードが搭載されている。
この TUBO のエディットモードのキー操作は、「Bボタンを押しながら十字キーを押すことで、配置するパーツを選択し、単に十字キーのみを押すことで、エディット画面上のカーソルを動かし、Aボタンで、選んだパーツを配置する」という操作方法だ。このエディット方法は、今ならマリオメーカーなどを想像したら分かりやすいかもしれないが、この場合は、例えば、ブロックなどの同じパーツを連続して配置する場合は、比較的短時間で配置でき、直感的にも「パレットからパーツを選んで配置、パーツを選んで配置……。」という流れになり、今となっては非常に一般的な方法と言っていいだろう。
今回、ワラビーのエディットモードにおいても、このようないわゆるマリオメーカー方式も検討したが、そうなると、やはり、どうしても画面上に「パレット部分」が必要になり、画面構成が大きく変わってしまう。それに、「何かを押しながら」というプログラムも、どうしても複雑になってしまう……。
よって、今回は、「ロードランナー」のエディット機能に準じ、単に Aボタンを押すことで、現在のカーソルの位置にあるパーツを、次のパーツに変更させる方法で実装したい。ただし、「ロードランナー」では、Bボタンで前のパーツに戻すことができたが、今回は、この機能は実装しない。Bボタンは、エディット終了機能に当てるためだ。
要するに、多少操作性に難があり、決してユーザビリティ的にもベストな方法とは言えないが、MSX 内で完結させ、かつ、プログラムをシンプルにすることを優先した。
○改造②「エディット機能の追加」
ということで、具体的な改造の解説だ。まずは、面セレクト中に、Bボタンを押して、エディットモード(500行目)へ移動する。そして、十字キーが押されたら700行目、Aボタンが押されたら800行目に飛び、Bボタンが押されるまでは、600行目に戻ってこの間を繰り返す。その間、Bボタンが押されたら、再度、面セレクトモードへ復帰する。これが、エディットモードのメインだ。
75 IFINKEY$=CHR$(13)ORSTRIG(3)THEN500
500 'エディットモード概要
510 '・[スティック]でカーソルを動かす
520 ' ・その際、ワラビーは画面から消す
530 '・[スペース]でカーソル位置のパーツを次のパーツへ変更する
540 '・[リターン]で、フロアセレクトへ復帰する
550 ' ・その際、カーソル位置をワラビーの位置とする
600 'エディットモードメイン
620 S=STICK(0)ORSTICK(1):IFS>0THENGOSUB700
630 IFSTRIG(0)+STRIG(1)THENGOSUB800
650 IFINKEY$=CHR$(13)ORSTRIG(3)THENELSE600
660 '[リターン]
690 GOTO70
これで、取り急ぎ山を切り開いた状態か。今から、アスファルトを流し、道路の舗装に入る。
・カーソルを動かす(700行目以降)
カーソルを動かすにも、まずは、カーソルのスプライトの定義からだ。イメージは、「ロードランナー」のカーソルなので、単なる正方形であり、スプライトの定義もシンプルだ。
37 'カーソルスプライト定義
38 FORI=0TO31:SP$=SP$+CHR$(VAL("&HFF")):NEXT:SPRITE$(4)=SP$
スプライトパターンは、当時の雑誌解説記事に次のとおり使われているのが書いてあったので、空き番号の「4」を使用した。
0:ワラビー左向き
1:ワラビー右向き
2:ワラビー左パンチ
3:ワラビー右パンチ
また、カーソルの座標は、今回、ワラビーのそれと共有させることとしたため、ワラビーの(マップ上の)座標である X(F) と Y(F) をそのまま使ってもいいのだが、プログラムの可読性を上げるために、これらを一度置き換えたい。具体的には、エディットモードに入った際に、これらを一旦 XA と YA に控え、エディットモードから復帰する時に、その値を X(F) と Y(F) に代入するのだ。具体的には、次のとおり。
560 XA=X(F):YA=Y(F)
680 X(F)=XA:Y(F)=YA
そして、カーソルは、上下左右でループさせたい。具体的には次のとおりだ。
700 '[スティック]
730 XA=XA+((S=7)-(S=3)):XA=XA-1:XA=XA+14:XA=XAMOD14:XA=XA+1
740 YA=YA+((S=1)-(S=5)):YA=YA-1:YA=YA+9:YA=YAMOD9:YA=YA+1
750 RETURN
一見、ややこしい処理に見えるが、要は、カーソルがループするよう、それぞれ 1<=XA<=14、1<=YA<=9 となるように補正しているだけだ。
折角の機会なので……ここは、もう少し掘り下げて解説する。この部分は、当時、初心者向けの解説書では目にした覚えはないのだが、ポイントは、(S=7) の部分の「=」が、「『代入演算子』ではなく『比較演算子』である」ということだ(要は、C言語でいうところの「==」に該当する。)。かつ、この比較の結果「真」の場合は「true」ではなく「-1」が返る。よって、カーソルの右を押すと (S=3) が -1 となり、その結果 XA が 1 加算される、ということなのだ。
この辺、慣れないうちは、素直に IF 文を使った方が分かりやすいかもしれないが、慣れてしまうと、逆に1行で表せるこの表現の方がスッキリする。まあ、数学の公式のようなものと言えるだろうか。
以上、ここまでで、カーソルの動きは完了なのだが、実際にカーソルを表示させる部分が必要だ。これは、エディットモードのメインループに入れたい。色はもちろん(「ロードランナー」に合わせ)、うすい黄色(11)だ。
640 PUTSPRITE0,(XA*16,YA*16-1),11,4
これで、画面上にカーソルが出現し、自由に動かせる。
・パーツを次のパーツへ変更する(800行目以降)
実は、今回、ここが一番難航した。
最初は、単純にカーソル位置の VRAM を読み込み、その読み込んだパーツの次のパーツを画面に表示させるようにし、エディットモードが終了した際に、再度、全ての VRAM を読み込んで、それを面データへと変換する作りとしていた。しかし、このような方法では、どうしても処理に時間がかかった。具体的には、エディットモード終了時の面データ変換処理に、10秒程度もかかってしまうのだ! 画面が固まったと思うことから、どの部分を読み取っているかを(プログレスバーの代わりとして)カーソルで動かしながら待つようにしたが、それでも目で追えるほどのスピードであり、テストプレイと面の修正を頻繁に行き来する場合は、この10秒が苦痛でしかなかった。
一体、どの処理に時間がかかっているのだろうと調べてみると、どうも VRAM を読むための命令である VPEEK に時間がかかるようだ。よって、この辺のロジックは、抜本的な修正が必要になった。
代替案として、画面に描くタイミングと同じタイミングで、同時に面データも変更させる方法を考えた。この方法の方が計算コストとメモリコストがかかる感じがしたのだが、処理速度としては、VPEEK よりも早く、即採用とした。この方法だと、何しろエディットモード終了時の面データ変換処理が一切不要になり(その都度面データを更新しているので)、かなり快適になった。
と、前置きが長くなったが、具体的に、次のパーツ、次のパーツ……と変更させるには、配列が便利だ。かつ、実際に画面に表示させるパーツ(りんごのパーツなど)と面データ(「U」など)を対応できるようにしないといけない。これは、同じ配列番号にすればいいだろう。
ということで、まずは、実際に描くパーツの定義からだ。原作においても、30行目にブランク、ブロック、爆弾が定義されているので、それに準じ、他のパーツも定義しておきたい。
25 'VRAM設定、B$(0):ブランク、B$(1):ブロック、B$(2):爆弾
31 'B$(3):壊れる壁、B$(4):壊れない壁、B$(5):りんご
32 B$(3)=CHR$(96)+CHR$(96)+A$+CHR$(96)+CHR$(96):B$(4)="cc"+A$+"cc":B$(5)="pr"+A$+"qs"
また、面データ変換用に、同様に同じ順で定義しておきたい。
33 'C$:フロアデータ(インデックスはB$に同じ)
34 C$(0)="A":C$(1)="M":C$(2)="Q":C$(3)="E":C$(4)="I":C$(5)="U"
ここまで定義すれば、あとは、上記の仕様を、そのまま BASIC に翻訳するだけだ。
830 CA$=MID$(F$(F),(YA-1)*14+XA,1):FORI=0TO5
840 IFCA$=C$(I)THENI=I+1:I=IMOD6:BA$=B$(I):CA$=C$(I):GOTO850ELSENEXT
850 LOCATEXA*2,YA*2:PRINTBA$:MID$(F$(F),(YA-1)*14+XA,1)=CA$
860 RETURN
まず、830行目が、カーソルの位置にある面データを、メモリから読み取り、それを CA$ へ格納する。その後、そのデータが、何のパーツであるかを確定するため、一つずつ順に照らし合わせる。そして、840行目で、それが何番目のパーツであるかが確定したら、その数字を1増やし(6になったら0にループさせ)、画面に表示するパーツ(BA$)とメモリに格納するパーツ(CA$)とを準備し、850行目で、画面表示とメモリ格納のそれぞれを行う。
ちなみに、この850行目の MID$ の使い方は、私にとっては大いに新鮮だった。EXCEL関数でも MID関数があり、文字列から部分文字列を取り出すことをやっている方も多いと思うが、850行目の MID$ は、その逆の動作というか、文字列の中のとある部分の文字を置き換える処理なのだ(EXCEL では、REPLACE関数と同様の仕様だろうか。)。今回、MID$ のこのような使い方は初めて知ったのだが、この命令のおかげで、プログラムが非常にシンプルとなった。しかし、BASIC でも、まだまだ新しい発見があるのは奥が深い……。
……あと、実は、バグの原因というか、バグを仕込んでしまった気もするのだが、そこは大目に見てもらいたい(笑)。それは、850行目の、FOR~NEXT の間にある GOTO で、強引にこのループから抜け出している部分のことなのだ。このような場合は、本来は、EXIT FOR(C言語では、break文)を使いたいところだが、MSX BASIC では、それに該当するものがない。もう少し上手く書けば、ループカウンタを調整した上て、NEXTの直前に飛ばすようにもできただろうが……取り急ぎ、今回は、シンプルさを追求するために、この手間を省略した。
……では、このままでは何が問題かというと、NEXT が実行された際の戻る場所(FOR のアドレス?)が、スタックエリアからクリアされないまま残ってしまったままになっているのではないか? という心配である。つまり、この処理を繰り返していけば、それが蓄積していき、いつかメモリが枯渇してしまうのではいか、ということなのだ。ただ、この点に関しては、しばらくエディットを続けても、エラーとなることはなかった。もしかしたら、GOTO をすることで、スタックエリアがクリアされてくれたのか、スタックエリアが上書きされ続けているのか……。この辺は、私の知識では到底及ばない範囲なので、ここでは省略する。
○残された課題は……?
これで、取り急ぎエディットができるようにはなったが、何だか快適といえるものではなかった。
まず、3つ目くらいの面をエディットしようとすると、その際、決まって画面が固まる。そして、これは再現度100%だ。バグを仕込んだことになるだろう。
あとは、カーソルが意外と不親切だ。「ロードランナー」のように、カーソルを点滅させないと、パーツの変更を確認するためには、一度、カーソルを動かさないといけない。
加えて、今、現在のモードが、面セレクト中なのか、エディット中なのかが分かりにくい。同じ画面構成だし、何か区別できるものがほしい。
あとは、やはり、音がないと寂しいか。操作した時の反応がないと、これまた分かりにくい。
この辺の問題解決については……長くなったため、次回に送りたい。
【関連記事】
この記事が気に入ったらサポートをしてみませんか?