パソコン活用研究5番街(Visual Basic、Excel(VBA)、BASIC プログラミング研究)

リアルタイムキースキャン(INPを使う)  (QB/方言)

1 INKEY$とINPのキー入力の仕組み

シューティングゲーム等で、リアルタイムにキー入力する時、一般的には
A$ = INKEY$
のようにします。
しかし、自分でシュティングゲームなどを作ったことのある人は、おわかりだと思いますが,INKEY$では
ちょっと具合の悪いこともあります。
まずは、キー入力の仕組みについて軽くおさらいしましょう。

(1) キー入力の仕組み
INKEY$でキー入力する過程をごく簡単に言うならば
   キーを押す
−> I/Oポートにキーコードが入る  
−−−あ)
−>BIOS(パソコンのシステム)がI/Oポートからキーコードを読み取り、キーバッファに蓄積
−>INKEY$でキーバッファの先頭のキーデータ読み取り

といったところでしょうか。
よろしいでしょうか。
ちなみに、I/Oポートというのは、パソコン本体(CPU)が外部周辺機器とデータのやり取りをするための
特別なメモリーです。例えば、キーコードはPC−98の場合 I/Oポートの41H番地に入ります。DOS/V
では60H番地に入ります。

(2) INP
ところで、押されたキーを調べるのに、もうひとつの手段があります。QBASICにはI/Oポートから
直接データを読み取る命令
INP があります(N88BASIC(86)にもある)。これは、I/Oポートから
直接データをよみとるという、ハードウエアに直結した命令です。上述のように DOS/Vでは、I/Oポート
60H番地にキーコードが入りますので Inp(&H60) とすれば、キーコードを読み取ることができます。
INP は上の流れで言えば、 あ) のところでキーコードを取り込むわけです。この方法はキーバッファに入る
前にキーコードをチェックするわけです。
なお、INP でキーコドをI/Oポートから読み取っても、それでI/Oポートのデータがなくなるわけではないの
で、そのデータはちゃんとキーバッファに蓄積されていきます。

ここで、ひとつ注意ですが、INKEY$で読み込むデータ(キーバッファに蓄積されているもの)はいわゆる
アスキーコードですが、I/Oポート60H番地に入るキーコードはこれとは、違うものです。DOS/Vでは
キーボードの種類によりますが、左上のキーから順に1、2、3 とキーコードが割り振られているようです。

さて、もうひとつ基礎知識です。同じキーを押しつづけると、オートキーリピート機能というのが働いて
キーが高速に連続して入力されます。オートリピートについては、ワープロ等で経験済みだと思いますが、
プログラムの処理の速度とオートリピピートの速度の関係が、リアルタイムキー入力で問題になってきます。
以下、それらの問題について見ていきます。


2  プログラムの処理がキー入力より遅い場合

(1) プログラムの処理がキー入力より遅い場合の実験
まずは、プログラムの処理がヘビーで、オートリピートより遅い場合についてみてみます。
キーバッファは機種による違いはありますが、だいたい30程度のキーコードをためることができます。
さて、ここで問題です。キーバッファにたまったキーコードを、INKEY$でよみとらなかったらどうなる
でしょうか。キーバッファはすぐ満杯になり新しいキーを入力できなくなります。だいたいのパソコン
では、キーバッファが一杯になると、ピーという警告音をだして、あなたをしかりつけるはずです(叱られて
も困るが)。次のプログラムを試して見てください。

' KEYINP.BAS coded by Tsuyoshi Kasai for QBASIC

DEFINT A-Z
PRINT "Hit any Key to start Hit space to end": b$ = INPUT$(1)
CLS
st:
LOCATE 2, 1
a$ = INKEY$: PRINT "[" + a$ + "] "
PRINT HEX$(INP(&H60)) + " "
FOR i = 0 TO 10000
FOR j = 0 TO 20
NEXT j
NEXT i

'a$ = INKEY$: PRINT a$
IF a$ = " " THEN END ELSE GOTO st

途中、for ... next ループで時間稼ぎをしているので、同じキーをおし続けていると、INKEY$でのキーデータ
の取り込みが間に合わなくて、ピーとパソコンに叱られるはずです(キーバッファがいっぱいになったため)。

さて、このプログラムですが、INKEY$, INP の両方でキーのデータを取り込んで、表示させています。
INKEY$ でとりこんだデータは[  ]の中に表示し、その下にINP でとりこんだキーコードは数値で表示して
います。キーコードをとりこんだ後、かなり無駄な時間稼ぎをしています。
INP でとりこんだキーコードの数値の意味については、後で触れます。

次に、このプログラムで、またパソコンにピーと叱られるまで、すばやくいろんなキーを押してみて下さい。
キーを押し終わってからも、[a]  [w]などと、のろのろ表示されていませんか。
これは、キーを押しおわってからも inkey$ が一生懸命キーバッファからデータを読み取っているからです。
プログラムにわざと無駄な処理をいれて、遅くしているので、こうなってしまったわけです。(キー入力より
プログラムのワンターンが遅い!)
これは、リアルタイムなシューティングゲームでは非常に困ったことになります。以前に押したキーが
キーバッファにたまっていて、予期せぬ動きをキャラがすることになります。キャラを左に動かそうと
思ったのに、キーバッファに残っていた前のキーデータのせいで右に動いてしまったということがおこり
ます。この問題の解決についても後で触れます。

一方、その下の INP で読み取った数値はどうなっていますか。キーを押しおわると80(16進)以上の数値に
なっているはずです。INP で読み取ったキーコードはキーを押していれば1 から 7F(16進)。
キーがおされていなければ80(16進)になっています(筆者の推測)。
INP はI/Oポートから直接コードを読み取っていますので、どのキーを押しているか、はなしたかを
まさにリアルタイムでチェックできるわけです。

さてさて、同じリアルタイムキー入力といっても、 INKEY$と INP で結構違いのあることがわかりました
でしょうか。
昔々のBASICの投稿プログラムでは、リアルタイムキー入力といば、ともかくINPを使って
というのが、流行?していましたが、まあ、こう見るとINP に軍配があがりそうです。
ただし! INP も万全ではありません。キーバッファがいっぱいになっちゃうと、キーの入力ができなくなる
ので、INP も使えなくなります。

(2)  解決編
先に書いたように、キーバッファに前に入力したキーデータが残っていると、リアルタイムゲームでは、
非常に不便なことになります。キャラを左に動かそうと思ったのに、右に動いたなんてことがおこります。
この問題を防ぐには、キーバッファにたまったキーデータを空にして、最後に入力されたデータだけ生かして
やればよいわけです。
そのためには、以下のような方法があります。

keyin:
a$=inkey$
if a$<>"" then b$=a$: goto keyin

キーバッファが空でなければ、Keyin: に戻り、空になったら抜けるようにします。この時、最後のキーデータ
はb$に入っています。


この場合、あえてINPを使う必要もありませんが、INPを使うなら、以下のようにもできます。
a$=inkey$でキーバッファからデータをひとつ捨ててから(キーバッファが一杯だとINPが使えないため)、
INPでキーコードを読み取ります。こっちの方がスピードも速いです。

a$=inkey$
k=inp(&h60)

3 プログラムの処理がキー入力よりはやい場合

まず、次のプログラムを実行してみて下さい。
ひたすら、INKEY$とINPを繰り返し、2000回くりかえしたら、INKEY$でキーデータをとりこんだ
回数を表示しておわります。最初のプログラムと同じで、[ ]にはINKEY$にキーデータを表示します。
数値はINPにより読み取られた値です。
まずは、同じキーを押しっぱなしにしてオートリピートを効かして実験して見てください。ほとんど[ ]
が連続しているはずです。これは、プログラムのワンターンがオートリピートより速くて、キーの入力
がされていないということです。このように、プログラムの処理がはやい場合、オートリピートの時は
INKEY$では、キー入力を本当の意味でリアルタイムには取り込めないということです。
一方、INPでは、同じキーを押しっぱなしにしていれば、そのキーコードは常にI/Oポートに入っている
ので、常におされている状況をキャッチできます。
プログラムの処理が、キー入力(特にオートリピート)より速い場合は、INPを使うのがベターでしょう。

DEFINT A-Z
PRINT "Hit any Key to start ": b$ = INPUT$(1)
CLS
count = 0
FOR j = 0 TO 2000
a$ = INKEY$: PRINT "[" + a$ + "]";
IF a$ <> "" THEN count = count + 1
PRINT HEX$(INP(&H60)) + " ";
NEXT j
PRINT : PRINT "COUNT of INKEY$:"; count; "/2000"
PRINT "push space key"
st:
a$ = INKEY$
IF a$ = " " THEN END ELSE GOTO st



4 おまけ

INPなんかないというパソコンの話(蛇足)。
CPUに6809を使ったパソコンでは、INPに相当する命令はありません。6809は周辺機器との入出力
にI/Oポートのような特別なメモリを使わず、通常のメモリの一部に周辺機器との入出力用の番地を
割りあてます。従ってINPとかいう命令は当然ありません。通常のメモリをアクセスするための PEEK,POKE
がそのままつかえます。

ついでに、キーバッファなんかないという話。
昔のパソコンでは(FM−8もそうだった)、キーバッファなんてなくて、キーは1個しか入力できない機種も
ありました。こういう機種ではキーを捨てるとか全然考えなくてよかったものです。

TOP PAGE >Basic活用研究目次