いさぽん.COM「つくる」に挑戦中

ゲーム系プログラマによる特に方針のないブログ。技術系とカレー、ラーメンネタ多めだったはずが、最近はダイエットネタ多め。

【Android】【iOS】 タッチイベントは1フレームに数回来る。時がある。

f:id:no-operand:20150906225621p:plain

デレステをプレイしていて何度か起こった症状ですが、タップが効かなくなることがありました。実際の症状がソレかどうかはわかりませんが、俺もプログラム組んでいるときに似たような症状に悩まされたことが……

ボタン処理の基礎

ボタンの処理の基礎部分ですが、パルスやトリガーと言った「押された」という情報が必要です。同時に、指が離されるまではグラフィックの表示を押しっぱなし状態にしないとならなりません。順に書くと下のようになります。

  1. 押された(Press)のイベントが来たら「押された」という処理にする
  2. 仮にタップ位置が移動した(Move)が来ても「押された」扱いを続ける
  3. 離された(Release)のイベントが来たら「離された」扱いにする

タップイベントは同時に来る

通常60フレームで動いていれば、1フレームの処理時間は大体18ms(ミリ秒)です。普通に考えると、18msの間に押されて(Press)移動して(Move)離されて(Release)が来ることは無いと思います。そんな速度で指を動かせる人なんて普通いませんから。

が、これが来る時があるんです。特に離されて(Release)から押されて(Press)が。つまり、1フレームの間に数回のタッチイベントが来ることがあるので、昔ながらのゲームの作り方のように1フレームに1回の入力処理を行うようなプログラムを組んでると、イベントを取りこぼすことになります。結果、「ボタンが押されたまま」ということに陥りがちです。

なぜタップイベントが同時に来るのか

今のスマホの静電式のタップ検出は、実際にはほんの少し遅れてやってきます。これはOSというよりはハードウェアの仕様によるところが大きいのですが、プログラム(というかCPU)のレベルで見るとかなり大きな遅延があります。ちなみにですが、この検出はiOSよりもAndroidのほうが全般的にレスポンスよく取得できます(iOSのほうがワンテンポ遅れます)。そのため、実際に押されたかどうかのタイミングとは別にプログラムで取得できるタイミングがズレることが稀にあるのです。

ユーザーの立場でゲームを遊んでいるときの対処法

ほとんどのプログラムはアプリがバックグラウンドへ移行した時にこれらの状態をリセットするように作られているのが定石なので、電源ボタンを押すなどしてOSのイベントを発生させると解消することができます。こうなったときは落ち着いて電源ボタンをワンプッシュです。

プログラム上での対処法

大体こんな感じになるはずです。

void Update()
{
  InputStatus  is = GetInputStatus();
  if (is.IsPress()
  {
  }

  GraphicsUpdate();
}

スマホでは次のようになります。

void Update()
{
  InputStatus  is;
  do
  {
    is = PopInputStatusQueue();
    if (is.IsPress()
    {
    }
  } while (is.HasEvent());

  GraphicsUpdate();
}

単純に、イベントがあればイベントがなくなるまで取り続けるだけです。一応気を付けてほしいのが、仕様上は1フレームの間に2回以上のPressなどを受け取ることがあるということです。「そんなことないだろ?」と思うかもしれませんが「処理落ちしたりすると普通に起こりうる」ということです。今回症状がこれとは限りませんが、デレスタのようにグラフィックスが凝っているものは気を付けたいポイントです。

AndroidNDKネイティブプログラミング第2版

AndroidNDKネイティブプログラミング第2版

iOSとAndroidの同時開発ならやっぱりC++がおすすめ。ならばAndroidはNDKで書くのがベスト。