スマートフォンWebアプリではスリープ時のsetTimeout()に注意

指定時間後に指定のJavaScript関数を実行するsetTimeout()。Webゲーム開発のメインループに使用される機会も多いですね。

ただ、このsetTimeout()はAndroidやiOSといったスマートフォン/タブレット端末のWebブラウザでは注意する必要があります。スマートフォンやタブレット端末では、アプリがバックグランドに入るスリープの概念があり、この状態ではsetTimeout()の呼び出し(というかJavaScriptの実行そのもの)も保留されてしまうのです。

たとえば、JavaScript関数func_a()の最後に

setTimeout(30, func_a);

と記述することで「30ms程度の間隔でfunc_aが呼び出される」メインループを作ったとしましょう。この関数は、Webブラウザが前面に表示されユーザーが操作を行っている間は確かに30ms程度ごとに呼び出されます(もちろんfunc_aの処理内容次第ですが)。

しかし、省電力機能でスリープしたりユーザーがホームボタンを押すなどしてWebブラウザがバックグランドに隠れてしまえば、次に呼び出されるのは復帰後ということになるのです。その場合、func_aが呼び出されるのは、1分後かもしれないし10分後、1時間後かもしれません。

この問題は、「前回の処理からの経過時間」によって処理を行う場合に問題になる点です。たとえば、前回の経過時間に応じてマップのスクロール量を決めるような場合ですね。30ms程度ごとに呼び出されることを前提に、単純に「経過ms/30」ピクセルスクロールさせるようなことをしたら、前回からの呼び出しに1分かかったような時にはとんでもない位置にスクロールすることになります。

スリープなどによるsetTimeout()の呼び出し問題を解決するには、以下の点が重要です。

  1. 前回の処理時間との差(経過時間)を算出し、経過時間に応じた処理を行う
  2. 経過時間が基準値以上なら、前回から基準値の時間だけ経過したものとして扱う
  3. ゲームのステージ制限時間など「累計時間」の管理を行っている場合は、基準値を超えた経過時間の扱いに注意(「ステージ開始時からの経過時間」で処理をするなら、基準時からの超過分を差し引く、など)

こうした「Webブラウザがバックグラウンドに回ることで処理が止まる」問題は、PCでは起こりにくいので、特にPCで動かしていたWebシステムをAndroidやiOS向けに移植する際は、注意したい点ですね。