ふろく

ホーム > ふろく > BASIC講座 > パソコンミニPC-8001でゲームを作ろう! 第四回
BASIC講座

パソコンミニPC-8001でゲームを作ろう! 第四回

OP

早いものでもう第四回ですね。
みなさん付いてこられてますか?

今回は「パドル移動」を解説します。
よって、キー入力判定が登場しますよ。
それでは、はりきっていきましょう~。
(あれっ?へんじがない。ただのxxxxのようだ)

※今回も作成するプログラムは、ページ最後のリンクからダウンロードすることができます。
…でもここまできたら、本文を読みながらご自身で入力をしてみませんか? 壁|.-)チラッ

なにはともあれキー入力判定

ゲームにおいてキー入力判定は、非常に重要なコアプログラムです。
常時リアルタイムにキーを取得しつつ、対応した処理をこなさないとなりませんし、
操作性が悪いと、もうそれだけで遊ぶ気もおきません。
よってこの部分だけは、そのマシンに特化した方法をとることになります。

BASICでキー入力情報をリアルタイムに取得する場合、INP命令を使います。以下の場合だと変数Pに返り値が入ります。
 P=INP(I/Oポート番号)
 

I/Oポート番号とは?:
コンピュータが周辺機器とやりとりするための口で、接続する機器ごとに番号が割り振られており、
この番号にアクセスすることで、データの送信/受信が行われます。
INPとはINPUTの略で入力を意味し、周辺機器からデータを受信します。
今回の講座では使いませんが、INPの逆でOUTという命令があり、
これはOUTPUTの略で出力を意味し、周辺機器へデータを送信します。
例えるなら、ポート番号は役所の窓口の番号と例えるとわかりやすいかもしれませんね。

キーボードの各キーは、ポート番号0~9に割り当てられていて、以下の図のように
格子状に配置されています。
なお、元となるこの図はF11キーを押しクイックリファレンスを表示した19ページにあります。

16進数を知らない方もいると思いますので、16進数と10進数の両方の解説を載せました。
わかる方を参照してください。

  • 16進数の場合
    上側に表示されているD0~D7がそれぞれbit0~bit7を示しています。
    キーは押されているところが「0」になりますので、全く押されていない場合は「&HFF」が返ってきます。
    (&HはBASICおける16進数表記です)
    それぞれの位置の値は、上図下側にある16進数の行に列挙しました。
    次に上図左側にあるのがポート番号です。
    Port0とPort1にあるキーはテンキー側のキーです。フルキー側のキーはPort2からです。
    さて例をあげてみます。
    P=INP(&H00)とした場合、Port0の行にある、テンキー側0キー~7キーまでの状態を取得できます。
    テンキーの4が押されていた場合、D4が0になりますので、bit4が0になり、変数Pには&HEFが返ります。
  • 10進数の場合
    上側に表示されているD0~D7は、2のn乗の値を表しています。
    つまりD0の箇所は2の0乗で1、D1の箇所は2の1乗で2、そしてD7の箇所は2の7乗で128の値を持ちます。
    全部足すと1+2+4+8+16+32+64+128で255となります。
    キーは押されているところが「0」になります。よって、全く押されていない場合は「255」が返ってきます。
    それぞれの位置の値は、上図下側にある10進数の行に列挙しました。
    次に上図左側にあるのがポート番号です。
    Port0とPort1にあるキーはテンキー側のキーです。フルキー側のキーはPort2からです。
    さて例をあげてみます。
    P=INP(0)とした場合、Port0の行にある、テンキー側0キー~7キーまでの状態を取得できます。
    テンキーの4が押されていた場合、D4が0になりますので、16が加算されなく、変数Pには239が返ります。

    逆に考えると、返ってきた値に、調べたいキーがあるD0~D7の数値で、
    論理演算のANDを行うと、そのキーが押されているのかわかります。
    ただ数値同士のANDは2進数/16進数を知らないと理解できない概念なので、てっとりばやく以下に一覧しました。

    指定PortにあるD0~D7のキーの状態を調べたい時、P=INP(指定ポート番号)を実行した後に以下を実行する。
    実行後のKYには、押されていなければ「その場所の値」が、押されていれば「0」が入ります。

ではサンプルコードを使って検証してみましょう。

テンキーの4と6を押すとそれぞれ押されたキーが表示されるはずです。
またキー判定を別々にしているので、同時に押されても判定できます。
これはシューティングゲームで、例えば6と8を同時に押された場合、斜めに移動させる時に必須の判定方法です。

INKEY$:
BASICにはリアルタイムに押されたキーを取得するINKEY$という命令があります。
確かにキーの情報は取れるのですが、「押した時」にしか反応しないのです。
もし8つ移動しようとした場合、8回キーを押さなくてはならないのです。
なのでキーが押しっぱなしの状態がわからないので、今回は使いませんでした。
ただ機種によっては、押しっぱなしの情報が取れるものもありますよ。

この章ではキー入力の方法について解説しました。
16進数を知らない方からすると、ハテナが飛び交ってしまったかもしれません。
ですので、これはこういうものと割り切ってしまうのも手です。
いよいよ次章でパドルを動かします!

パドルを動かそう、そうしよう

早速、以下のプログラムを追加しましょう。
4ブロックに分かれているので注意しながら入力してください。

ではブロックごとに解説します。

LIST 700-720は、キー入力をサブルーチン化しました。
変数K4にはテンキーの4の押下状態、変数K6にはテンキーの6の押下状態、を返します。
サブルーチン化したのは、のちのち使用するからです。

押下:
キーが押されているかどうかを表す言葉で、「おうか」と読みます。
言葉の使い方は、「4のキーの押下状態」や「6のキーが押下されている」というように使います。
「押下状態」といった場合、「押されているか」「押されていないか」の現在の状況を表し、
「押下されている」といった場合、キーが押されていることを表します。

LIST 1300-1310は、座標の初期設定です。
今回パドルの座標変数をPX、PYとしました。またPWはパドルの幅です。
後で幅を変更したいかな?と思って、あえて変数化しました。
ボールの初期位置は、パドルの中心から1つ上に設定しました。

LIST 1800-1810は、パドルの初期当たり判定の登録で、パドルの座標(PX,PY)からパドルの幅分(PW)の
当たりを登録しています。
なお当たり番号は他と区別するため(後で何か使うかもしれないので)、一応「2」を指定しています。

LIST 1900-1970が、実際のパドル移動のプログラムです。
1900行はわかりますよね?キー入力と、当たり判定サブルーチンに対する値の設定です。

さて、このプログラムは2つのブロックに分けられます。
LIST 1910-1930が4キーの判定で左移動、LIST 1940-1960が6キーの判定で右移動です。

さらに詳しく見てみましょう。1910行をご覧ください。
IFの後に、K4 OR PX<=3 とあり、THENの後は1940へ飛んでいますので、1920行と1930行はスキップされています。
ちなみに1920行と1930行はパドルを左移動させるプログラムです。

ここでちょっとしたテクニックを使っています。
K4には、キーが押されていなければ「何かしらの値」、押されていれば「0」が入っています。
つまり「何かしらの値」=押されていなかったら、THENに指定されている1940行に飛んで、
移動処理をスキップさせることができます。

では PX<=3 ですが、これはパドルが左端まで移動してしまっている状態を指します。
よってこれ以上、移動させることはできないので、移動処理をスキップさせています。

つまり、以下のように読み替えることができます。
・もしK4に値がある=キーが押されていない、だったら左移動処理はスキップ。
・または(キーが押されていても)パドルの座標が左端まで行っていたら、左移動処理はスキップ。
・よって左移動処理へ進む条件は、「キーが押されていて、かつ、パドルは左端にいない」となる。
ということになります。

いかがでしょうか?ちょっと面白いですよね。
ふつうであれば「押されていたら」とか「左端まで行っていない」と考えてしまうのですが、
プログラミングでは逆の考え方をした方がすっきりとする場合があります。
応用が利きますので、いろいろと考えてみてください。

「真」と「偽」:
IF文には、判断条件として値や数式などが指定できるわけですが、
条件を満たすことを「真」といい、条件から外れる場合を「偽」といいます。
単純な値であれば、「0以外」が「真」、「0」が「偽」となります。
数式であれば、「条件を満たしている」が「真」、「条件から外れている」が「偽」となります。
最終的にIF文に指定されている条件が「真」であれば、THEN以降を処理すること、になります。

最後に移動処理について説明しましょう。
1920行~1930行側と、1940行~1950行側は移動方向が違うだけなので、
説明には1920行~1930行側の左方向への移動を取り上げます。

今から行う処理は、左に1つ移動する処理になります。
けれども1920行では当たりの抹消処理を1つ行っています。
その後、1930行でパドルの座標を左に1つ動かし、当たり判定の登録を1つ行っています。
(GOTO 1970は、左移動してからさらに右移動をすることは無いため、次の行からの右移動判定処理をスキップしているだけです)
さて、これはいったいどういうことでしょうか?

下図をご覧ください。
(ブロックに黒い枠が入っていますが、一個一個を認識しやすくするための補助線です)

おわかりいただけたでしょうか?
左に1つ移動するということは、右端の表示を1つ消して(紫部分)、左端に表示を1つ足す(緑部分)ということなんですね。
(なおこれは、パドルの表示を構成するキャラクタが全部同じだからできる手法です)

なぜこんな方法をとっているかというと、2つ理由があります。

1つは動作の高速化です。
パドルは全部で3つのブロックで構成されています。
もしパドルを全部消してから再表示となると、消去が3ブロック、表示が3ブロック、で計6ブロックの処理が必要です。
けれども今回の手法であれば2ブロックの処理で済みます。
(もちろん当たり判定の抹消と登録処理も込みでの話です)

もう1つは、表示のちらつきを無くすためです。
表示を消す必要がない部分に対して、いったん消してまた表示する処理を行うと、ちらつきが発生してしまうので、
その問題を回避するため、書き換えが必要ないところは何もしないようにしています。
(詳しくいうと、BASICでの書き換えは、モニタの走査線にシンクロしていないため)

ついにパドルを動かしてボールを打ち返すことができるようになりました!(パチパチパチ!)
ここから一気にゲームとしての飾り付けをしていきたいところなのですが、
実はパドルを実装したことによって、当たり判定に抜けがあることが発覚します。
次の章では、そのバグを修正します。

そんなパターン?!

さて、どんな状況で当たりが抜けてしまうのでしょうか?
以下の分解写真をご覧ください。

間に合うか?

よしここで、角反射。

アレッ?!抜けた!

そうなんです。
壁に反射判定後の行き先はチェックしていないので抜けてしまうのです。
現状では左右の壁とパドルの組み合わせ時にのみ発生するので、ミスにしてしまっても良いのですが、
今後プログラムを拡張してブロックを、「自由な形状」や「バラバラな配置」にすると、この不具合が顕著になってしまいます。
ですので、さくっと直してしまいます。

※なお今回の現象は、1300行と1310行を以下のようにすると、すぐに確認できます。

幸いにもパターンは少なく、全部で8種しかないので、全部列挙してしまいます。

まず、現象を分析してみましょう。
1.赤矢印方向からボールが移動してきます。
2.次に上下左右の1方向だけに壁があれば、その壁に対する反射処理を行います。
3.よって移動方向は緑矢印方向へと変更されます。
4.反射処理が終わったので、ボールの移動処理を行います。
5.移動は単純に座標を更新するだけなので、当たりは見ていないため、緑矢印の先にあるブロックに突っ込みます。

ということにより、分析はできましたので、次はどのように対処するかです。

4以降はあくまでもボールの移動処理なので、修正する点はありません。
問題となるのは、3の後に移動先のチェックがされていないことのように思えます。
ですので3と4の間に判定処理を追加することにします。

先ほどの上図を見てください。
A~D、1~4と記号が振られています。
これらは意図的に分けていて、A~Dは上下で反射するグループ、1~4は左右で反射するグループです。

…何か気が付きませんか?

そうです、変数Fです。Fに足している数値の意味が、ここで解き明かされます。

2030行を見ると、Fに1を足しています。この時は左右の壁に当たった時です。
2060行では、Fに2を足してします。この時は上下の壁に当たった時です。

今回、当たりが抜けてしまうのは1方向への当たりの時です。
よってFが1または2の値であったら、追加で移動先のチェックを行って、
当たりがあれば然るべき処理を行えるように、判断可能にしてあったんです。

なおFが3の場合は、上左・上右・下左、下右のいずれかで、同時に2方向当たった時なので、問題はありません。

その前に、今回の状況になった場合、最終的にはどの方向に反射すれば良いと思いますか?
答えは簡単で「飛んできた方向に戻す」です。
飛んできた方向には何もなかったわけなので、XYの移動方向を反転させて元に戻してしまいましょう。

解説を続けます。
1~4のグループは、Fが1の時です。
左右の壁の判定は済んでいるので、すでにX方向の反射処理は行われています。
よって移動先にブロックがあったら、追加でY方向の反転処理を行えば、XY軸両方の反射処理が完了します。
A~Dのグループは、Fが2の時です。
上下の壁の判定は済んでいるので、すでにY方向の反射処理は行われています。
よって移動先にブロックがあったら、追加でX方向の反転処理を行えば、XY軸両方の反射処理が完了します。

上記のアルゴリズムを反映させたプログラムが以下です。
前のプログラムを書き換えている行があるのでご注意ください。

ここで新しい命令が出てきました。
 ON 条件 GOTO 行番号1,行番号2,行番号3・・・
この命令は、条件の値によってGOTOの後に続いた行番号に飛びます。
条件が1の場合は行番号1に、条件が2の場合は行番号2というように、1から続く値で飛び先を選びます。
でもちょっと待ってください。
1からということは0の場合や、値が行番号の並びより多かったらどうなるのでしょうか?
答えは「ON~の命令は実行されない」です。
なおマイナスの値だとエラーになります。
今回のプログラムでは、Fが0の場合がありますが、次の行が、Fが0の時の処理なので問題はありません。

ON GOTO、ON GOSUB:
条件に番号を振って、複数の似たような処理へ飛ばすのに便利な命令です。
IF文で複数の条件を検査しながら飛ばすよりプログラムがすっきりします。
例えば以下のようにIF文を使って、
 IF A=1 THEN 100
 IF A=2 THEN 200
 IF A=3 THEN 300
 IF A=4 THEN 400
と、1つ1つチェックするより、
 ON A GOTO 100,200,300,400
とした方がよりプログラムが、よりすっきりするのがわかりますよね?
それからただ処理を飛ばすGOTOではなく、サブルーチンをコールするON GOSUBという命令もあります。
状況によって使い分けてください。

簡単に解説しましょう。

2070行では、移動先の座標を計算しつつ、当たりを確認しています。

2080行では、移動先に当たりが無かったら、これ以上のチェックは必要ないので飛んでいます。

2090行は、Fに対してON GOTOを行っています。なおFが0の場合は次の行に移行します。

2100~2110行は、Fが0の時、つまり角に当たった時の反射処理です。

2120行は、左右の壁に当たって、かつ移動先に当たりが有る場合の処理です。 
つまり今章の内容を反映したプログラムです。

2130行は、上下の壁に当たって、かつ移動先に当たりが有る場合の処理です。 
つまり今章の内容を反映したプログラムです。

当たり判定は奥が深いです。
単純そうに見えて、いざ作り始めると次々と問題が発生します。
けれどもそれを創意工夫で対処できた時は、「フッ、今回も勝ったな」とほくそ笑んでしまいます。
さて、もういい加減、完成へと進みたいですよね?
次の章ではボールを打ち出す処理を追加します。

ボールを自由に発射させよう

今回もいろいろ新しいことが多く、大変だったと思います。
最後の章くらいはさくっといきたいですよね。
ですので早速、追加のプログラムリストを公開します。

3ブロックに分かれていますので、入力にはご注意ください。
それと1540行にある斜めのキャラクタですが、GRPHキーとYキー、GRPHキーとTキーで入力できます。
JPキーボードの場合、GRPHキーはALTキーに割り当たっています。

プログラムの概要を説明しますね。
4と6のキーでパドルを左右に動かして、発射位置を決めます。
なお、多少左右の移動幅に制限をかけています。
ボールはパドルの中心に追従して表示され、発射方向のガイドは移動した方向に合わせて
表示位置と表示キャラクタを変更しています。
0のキーを押すとボールが発射されます。
なお発射するまでパドルの当たり判定を設定する必要はないので、描画処理だけを行っています。
(ちなみに点滅させているのは演出です。点滅させたくなければ1430行のTHENの後の飛び先を、
1460,1470,1500のいずれかにすることで点滅する種類を変更できますよ)

と、これだけだと味気ないので、ブロックごとに説明します。

LIST 1300-1310は、パドルの位置を設定したら、すぐにLIST 1500-1570の描画ブロックに飛んでいます。
なぜかというと、パドル以外の表示物に関しては、描画ブロック内で全て処理するようにして、
初期化プログラムなどを必要としないように、一カ所にまとめたからです。
このように、初期化部分とループ部分というように二カ所で処理させるより、
一カ所にまとめることで、プログラム修正の手間を減らしたり、
そのほか片方の修正忘れで矛盾が出るのを回避したりと、バグの温床を減らすのに役立ちます。

LIST 1400-1470は、キー入力と全表示物の消去を行っています。
ちょっとだけ注意する点として、キー入力でパドルの座標が更新される前に、
1400行でLOCATEを使って、パドルの消去位置を確定させてあることです。
実はこのブロック内に新しく出てきた記号があります。
1450行のPRINT部分に注目してください。
ダブルクォーテーション(")の後に、セミコロン(;)が付いてますよね?
これは「次回のPRINT文字を改行せずに続けて表示せよ」という意味になります。
通常PRINT文は、文字を表示した後に改行して次の行の先頭に表示位置が移動しますが、
セミコロンを付けるとくっつけて表示することができます。

LIST 1500-1570は、パドル、ボール、ガイドの表示と、0のキー判定を行っています。
ここで2つほど詳しく説明しましょう。
1つめは、1540行に注目してください。A$という変数が登場しています。
発射する向きに合わせて使用するキャラクタを選んでいるのですが、今まで変数といえば
数値を代入するだけでした。
実は変数は数値だけでなく、文字も代入することが可能なんです。
その場合は、変数の後ろにダラー($)を付けて、文字をダブルクォーテーションで
囲んだ文字列を指定します。
もちろん文字列ですから複数の文字を指定することも可能です。
2つめは、1570行目です。INKEY$という命令があります。
 A$=INKEY$
これは押されたキーの文字を取得する命令で、上の例だとA$に押された文字が入ります。
第1章のコラムでちらっと説明しましたが、押されたことがわかるだけでよければ、
こんな使い方ができるわけです。(例えばYes/Noを聞く時のYとNとか)
ここでは0キーが押されるまでループを繰り返すのに使っています。

これでやっとパドルの処理が完成しましたね。
お疲れ様でした~。

第四回まとめ

今回でややこしい話は終了です!
次回は最後の仕上げにかかります。
ゲームに必要なルールの導入や、飾り付けです。(でもなんだかんだ言っていろいろあるんですよね…)
次回で完成するので、ぜひ最後までお付き合いくださいね。
100 PRINT “第五回を閲覧しますか? (y/n)”
200 A$=INKEY$:IF A$=”” THEN 200
300 IF A$<>“y” THEN 300 ELSE PRINT “もう一度聞きます。”;:GOTO 100
400 第五回(最終回)

今回入力したプログラムを追加したcmtファイルです。
BLOCK4.cmt
入力してもうまく動かなかった場合にお使いください。
例によって使い方は、ファイルをダウンロードして、パソコンミニPC-8001のシステムが
入っている microSDの /boot/PCM の下にコピーしてください。
システムを起動して F9->MEDIA にて、 BLOCK4.cmt を SET し、BASIC上で
CLOAD “BLOCK4
で、ロードできます。

©HAL Laboratory, Inc.