けものみち

まったりと、きのむくままに。

Unity でタイピングゲームを実装をしたので覚え書き

ここ数週間はタイピングの練習にとてもはまっていて、1日1時間、長いと3〜4時間はタイピング練習に費やしてしまっている状態です。いわゆるZタイパーなんてのも目標の一つにいれています。

最近のお気に入りは Weather Typing です。

f:id:WhiteFox-Lugh:20190911073755p:plain

f:id:WhiteFox-Lugh:20190911073809p:plain

複数のワードをランダムに組み合わせて文章を生成しているのでマンネリ化しないし、結構面白い文章が生成されるのでかなり気に入ってます。中でも「試用版おいなりさん」が出てきたときは好きすぎてハンネにしてたぐらいです。


さて、今回の記事は、ちょうどタイピングにはまっていることだし、Unity でタイピングゲームを実装すればいいじゃないか!ということになって実装してみることにしました。

(※ 覚書みたいなものなので気が向いたら Qiita にでもコード付きでまとめなおすかもしれません)


タイピングの入力の判定について

今回実装しようとしたのは e-typingタイ速寿司打など多くのタイピングゲームで採用されている逐次判定方式です。簡単に言うと、一つキーが押されるたびに正しい入力かどうかを判定する方式です。文章も英文ではなく和文です。

この方式なのですが、一見そこまで難しくなさそうなのですが、実装するにあたって考えなければならない点がいくつかあります。

1. 複数入力に対応しないといけない

「し」は si, shi, ci の3通り*1あるとか、「しゅ」は syu, shu の他に「し」+「ゅ」と打つ方法(si + lyu, si + xyu ...)があるとか一つの文章に対して結構色々な入力が考えられます。

特に厄介なのが、

  • 「ん」: 「ん」の後ろに母音、ナ行、ヤ行、ニャ、ニュ、ニョなどがきた時、または文末の「ん」は nn または xn *2 でなければならない。それ以外は n も可
  • 「っ」: 後ろに続く子音を2回入力する。「っか」は kka になるし、「っし」は ssi になる。ただし、「っ」を単独で入力する方法は ltu, xtu, ltsu, xtsu と4パターンあり、「っ」+「か」などと入力する(ltu + ka)のも正解としなければならない

です。これらをうまく判定する方法を考えます。

2. 複数入力に対応すると可能なパターン数が爆発的に多くなる

先ほど挙げたように、ひらがな一文字に対して2通り、3通り、場合によっては「しぇ」などのように入力パターンが10通りを越える場合もあり、ひらがなで高々20文字程度の文章であっても、可能な入力パターンが軽く10万、100万と爆発してしまうことがわかるかと思います。

例えば「一家に一台」という例文があった時、愚直に入力パターンを列挙すると i kka ni i ti da i, yi kka ni i ti da i, i ltu ka ni i ti da i, i ltu ka ni yi chi da i, ... などとかなりのパターンが出てくることがわかります。

なので判定のアルゴリズムをきちんと考えないと、判定自体が遅くなってしまい動作が重くなってしまうことが考えられます。

3. キー入力と判定

多くのサイトでは Update() を用いてキーを拾う実装になっているようだったのでとりあえず最初はそう実装していました。

しかし、実際自分でテストプレイしてみると、高速で入力したときに「正しいキーを押したのに押してない判定」になることが結構ありました。

これには Update() が呼ばれた時とキーを押した時が一致しなかったため入力を拾ってくれないとか、そもそもタイピング速度が 60fps で判定できる早さを超えている(タイプウェルなどで測定すると得意な入力に対しては 1フレームを切ることがあるようです)など、いろいろ原因がありそうなので別の方法をとるしかありません。


実装の概要

ひらがな→ローマ字入力へのマッピング

「あ」→ a、「ふ」→ fu, hu、「ん」→ n, nn, xn、「しゃ」→ sya, sha のようにひらがなからローマ字入力へのマップを行う Dictionary を作りました。

あらかじめ文章をひらがなで書いておいて、このひらがなの文章をパースすることで考えられるすべての入力パターンを列挙できるようになります。例えば「一家に一台エアコン」という例文があったとして、変換のイメージは以下のような感じです。

i k ka ni i ti da i e a ko n
yi ltu ca yi chi yi nn
xtu xn
ltsu
xtsu

あとはひらがなの文章の今何文字目をみているかを記録する変数や、各ローマ字入力において今アルファベットの何文字目を見ているかなどを記録すればよいです(この辺の細かい実装については割愛します)。

ただし、上記の例のように「っか」を「kca」と入力するパターンは不正解とする、文末の「ん」は n は不可など例外的なパターンの処理も必要となります。

これで1キーあたりの判定時間は高々20通りくらいのチェックで済むようになったので判定関連に関しては十分高速だと思います。

入力を OnGUI() で受け取る

www.sophiehoulden.com

高速で入力するタイパーに対応する方法に関しては、いい感じの記事が英語で見つかったのでこれを参考にしました。

すごくざっくり言うと、OnGUI() はフレームレートから独立しているので、Event.current を用いてキーを入力するたびにそのキーが何かを受け取って適当に保持しておき、Update() が呼ばれた時にこれらを一括で処理するというものです。

Event.current ではキーを押した時と離したときの2箇所判定があるので、判定は KeyDown の時だけになるように、また、マウスのクリックなど、キーボード以外のイベントは受け付けないようにします。

入力されたキーを次の Update() が呼ばれるまで保持するデータ構造はシンプルに Queue で実装しました。

このように OnGUI() を用いたところ、高速にタイプしても入力漏れがなくなり実際に公開されているタイピングゲームとだいぶ近くなりました。

今の実装では、キーが押された時刻は特に保持してはいないのですが、上記の参考元の記事では、キーを押した時刻を保持しておくと記述があるので、その情報を用いて順番が前後しないようにした方がより安全かもしれません。


入力画面のプロトタイプ

UI 面とかはさておき、とりあえずそこそこの速度で打ってみたり、わざと普段使わない入力方式やよくある打ち間違えなどを混ぜて打ってみたり、問題文を前半ワード + 後半ワードの形式にしてランダムで生成して、ちゃんとうまくパースできているかを確認したりしてみました。だいぶいい感じになったと思います。


このあとは

スコア計算、スコア保存、レーティング機能(音ゲーマー感ありますね)、初心者用にもうちょっと短い文章でタイピング&ローマ字入力を表示してくれる機能、ローマ字だけではなく英文入力に対応するくらいまでは実装してみたいと思います。英文の方が実装は楽そう…。

そこそこの中身になったら、UI を整えて、無料で使えそうなバックエンドも用意して、ランキング機能とかユーザーごとに記録を保存するとか、ツイート機能とかつけてみてネットに公開してみんなにプレイしてもらってもいいんじゃないかなとも考えています。

*1:ci で「し」を入力できるのはタイピング特有っぽいです

*2:これもタイピング特有の入力です