押されたキーの取得

司エンジンを作ってる土屋つかささんのブログで、DXRubyのキー入力に関する話が出ていたので便乗。
http://d.hatena.ne.jp/t_tutiya/20150912/1442022885

キー入力に関する話は地味に厄介で、例えばテキスト入力をしたいのならアスキーコードを返すべきだし、シフトキーと同時に押したら小文字を返すべきであって、更に1フレーム内に複数押された場合には並べて返さなければならないし、押しっぱなしにしたらオートリピートしてくれないといけない。んでも、例えばタイピングゲームを作りたいならそういう動きは一切いらない。
Windowsの場合、現在押されているキーを直接取得するにはDirectInputが便利ではあるのだが、これは別に必要なものではなくて、Windowsが送ってくるメッセージを解釈すればよい。メッセージによってアスキーコードを送ってきてくれたり、キーコードを送ってきてくれたりするので、そのへんでうまいことやれば色々な入力に柔軟に対応できる。
DXRubyではDirectInputを使ってみた都合上、基本的にはテキスト入力に対応できるようなインターフェイスが用意されていないが、DXRuby1.5devではメッセージを処理するInput::IMEモジュールを追加しているので、get_stringで文字列が取得できる。1.5devを使わずタイピングゲームを作るような猛者もたまにいるようである。

さて、現状の1.4系ではそういうわけで「このキーが押されたか」は判定できるが、「押されたキーを知りたい」というのは微妙にやりづらい。できないわけではなくて、Input.keysで押されているキーを取得することはできるので、前フレームのそれを保存しておいて、配列の差を取れば新規に押されたキーがわかる。
ただし、これを使って取得できるのはキー定数に格納されている値の一覧であるわけで、この情報でどのように処理するかが問題になる。
サンプルをひとつ。

require 'dxruby'

old_keys = []

# キー定数に定義された値と、そのキーの文字(定数から抽出)を関連付けるハッシュを生成する
keys_hash = {}
DXRuby.constants.each do |sym|
  if sym =~ /^K_(.)$/ or sym =~ /^K_NUMPAD(.)$/
    keys_hash[DXRuby.const_get(sym)] = $1.to_sym
  end
end

# 速すぎると文字が見えないので遅くする
Window.fps = 10

Window.loop do
  # 前回のフレームとの差を取って新規に押されたキーの配列を生成する
  keys = Input.keys - old_keys

  # 今回のキー一覧を保存しておく
  old_keys = Input.keys

  # 新規に押されたキーを表示する
  keys.each.with_index do |s, i|
    if keys_hash[s]
      Window.draw_font(0, i * 24, keys_hash[s].to_s, Font.default)
    end
  end
end

この例ではDXRubyモジュール下の定数からキー定数の「K_(一文字)」と「K_NUMPAD(一文字)」を選択して、keys_hashを生成する。keys_hashはkeyがキー定数に設定された値で、valueが(一文字)の部分をシンボル化したものとなる。メイン処理ではInput.keysを使って前フレームから新しく押されたキーを取得して、keys_hashを参照して返ってきたシンボルを表示している。
つっつサンプルではキー定数を配列に入れているので、キー定数→インデックスという変換をしていたが、hashに突っ込んで変換するなら変換先は都合に合わせて好きな物をいれればよい。アスキーコードが欲しいならアスキーコードを入れておくし、数字キーだけ判定して数字を返して欲しければそのようにする。
なんにせよ1.4系の入力機能はテキスト入力には向かないのであまりオススメしないが、頑張ればできそうではある。
しかしもうちょいうまい手はないものか。