この記事は「Adobe AIR Advent Calendar 2013」の11日目の記事です。
すでに11日目過ぎているっていうツッコミはナシで。
さて、小ネタばかりだったので、ちょっと違うやつやりましょう。
AIR 3.7らへんから、ゲームのコントローラをFlashPlayer及びAIRで扱う機能が追加されました。これは、PCで認識されるコントローラであればFlashのコンテンツで扱うことが可能になるというものです。まぁ使い方とか癖とかはICSの川勝さんが詳細な記事を書かれていますので、こちらを参考にしていただければと。
http://ics-web.jp/lab/archives/512
で。
上記の記事に上がってない情報として、「複数のデバイスを同時に扱う」「GameInputDeviceのキャッシュ機能」があります。前者は、「東京てらこ」と「東京ひよこの会」で発表したので置いておいて、後者の「GameInputDeviceのキャッシュ機能」を今回は少しお話しましょう。何気に情報があまりないので。
GameInputDeviceのキャッシュ機能
さて。まずは上記の記事で書かれていることの復習をしましょう。GameInputDeviceでのボタン操作をAS側で受け取るには、3つの方法がありますが、そのうちの2つが書かれています。
- GameInputContorlのEvent.CHANGEをリッスンして、event.target(GameInputContorl)からvalueプロパティを取得する
- 必要なタイミングでGameInputContorlのvalueプロパティをチェックする
まぁ、時々しか扱わないのであれば前者、割と頻繁に操作する必要があるのであれば後者、ってところでしょうか。
ただ、これって、ボタンの情報の更新が、AS側のフレームレートにどうしても依存してしまうんですね。フレームとフレームの間でボタンを押されて検知できなかった場合、キー入力のシビアなゲームでは致命的になります。なんか音ゲーとかだと、60fpsの間が見える人がいるとかなんとか?
まぁ、なんにしても、そんな時に使えるのが、3つ目の取得方法。今回の「GameInputDeviceのキャッシュ機能」です。
結局なんなのさ
まずは、Flash側で、どうやってゲームパッドの情報を取得・保持しているかを知る必要があります。なお、多分大丈夫だと思いますけど、理解が違ったらツッコミいただければと・・・。
GameInputDeviceにはsampleIntervalプロパティというものが有り、これはデフォルトは0になっています。これは、1フレームごとに1回、ゲームパッドの状態のスクリーンショットを取るということです。そして、僕らはそのスクリーンショットを元にvalueを取得しています。
なので、僕らは、毎フレーム、その時のゲームパッドの情報を知ることができるわけです。但しデフォルトでは、そのスクリーンショットは1フレームに1回しか取られていないので、その間のスクリーンショットは取られません。
そこで、sampleIntervalをミリ秒単位で指定してやると、そのタイミングでスクリーンショットを取ることができます。ですので、フレームとフレームの間のスクリーンショットを取ることが可能です。但し、これは毎回上書きされますので、結局、フレーム毎に僕らが取得できるのは、その時の最新のスクリーンショットということになります。
ですので、これを上書きをせずにキャッシュするようにして、取得段階になった時に、その間に取ったスクリーンショットを全部参照しようというのが、この機能になります。
扱い方
まず、キャッシュ取得を開始するためにGameInputDeviceインスタンスのメソッドを実行します。
startCachingSamples(numSamples:int, controls:Vector.<String>):void
第1引数はサンプルをどれだけキャッシュできるようにするかっていう器の大きさを設定します。この大きさを超えるキャッシュが貯められてしまった場合、ランタイムエラーが発生するので気をつけてください。GameInputDeviceの定数として、「MAX_BUFFER_SIZE : int = 32000」が定義されていますので、これを使用することも可能です。
で、大事なのは第2引数です。ここにチェックしたいボタンのIDの配列を設定します。つまり、必ず全部のボタンのスクリーンショットを取る必要はないということです。例えば、特定のボタンの連打とかだったら、特定のボタンだけ指定してあげればよいです。
この配列を元にスクリーンショットが作成されますので、次に述べる「getCachedSamples」で取り出したキャッシュは、ここで設定した順番になってます。予めこういった配列を変数として作っておいて、両方の関数で同じ配列を参照するように構築すれば間違いないでしょう。
さて、これでキャッシュされるようになりました。指定した秒数に従ってガツガツとキャッシュされます。
次はキャッシュしたスクリーンショットを取り出しましょう。
getCachedSamples(data:ByteArray, append:Boolean = false):int
キャッシュの中から一番古いスクリーンショットが取り出せます。第1引数は、書き込みを受け付けるためのByteArrayで、ここにスクリーンショットが格納されています。第2引数は、受け取り側のByteArrayに新しいスクリーンショットを付け足すかどうかです。まぁfalseにしておいたほうが無難でしょう。ASDocによると、これをtrueにする理由としては、以下の様なことがあるようです。
- 特定の数の値を使用できるように維持する必要がある場合。
- キャッシュのサイズを超える履歴を維持する必要がある場合。
まぁ、これも、ByteArrayでずっと保持せずに別の情報で保持しても良いかと思います。
で、上にも書いた通り、このByteArrayにはstartCachingSamplesの第2引数で指定したボタンの順序で値が倍精度浮動小数点数で書き込まれています。ですので、ByteArrayのreadDouble()で、その数の分だけ取得します。
もしキャッシュする必要がなくなった時は、
stopCachingSamples():void
でキャッシュするのを停止しましょう。そうしないと、getCachedSamplesしない限りずっとキャッシュが溜まり続けることになるので、途中で書いたとおり、ランタイムエラーを引き起こします。
まとめ
こんなかんじで。面倒なんでAdobeの例を引用。この例だと、開始してすぐにキャッシュを取得していますが、本来であれば、フレームのタイミングとか、その他のイベントをトリガーとするとか、何かのタイミングで取得したほうがよいでしょう。
var device:GameInputDevice = GameInput.getDeviceAt(0); // キャッシュ開始 var vec:Vector.<String> = new Vector.<String>(); vec[0] = device.getControlAt(0).id; device.sampleInterval = 10; device.enabled = true; device.startCachingSamples(5, vec); // numSamples = 5 // キャッシュ取得 var ba:ByteArray = new ByteArray(); var num:int = device.getCachedSamples(ba, false); for(var i:int=0; i<num; i++) { trace("samplevalue " + ba.readDouble()); }
これで、格ゲーとかもバッチリ?
と言いたいところですが、これ組み込む前にちゃんと検証したほうが良いです。この前作ったOUYAControllerManagerでは、最初はこの方法で取得しようとしていたのですが、いろいろ不具合が出てて公式で謳ってる挙動になってなかったので、実装やめました。苦労の割に報われない可能性もあるので、よっぽど必要でない限りはやめたほうがいいかもしれません。
というわけで
今まで書いたことを最後に否定した感じで、今回の記事を終わります。お疲れ様でした。
参考資料
GameInputDevice – AS3
http://help.adobe.com/ja_JP/FlashPlatform/reference/actionscript/3/flash/ui/GameInputDevice.html
Game controllers on Adobe AIR
http://www.adobe.com/devnet/air/articles/game-controllers-on-air.html
Adobe AIR の GameInput クラス
http://cuaoar.jp/2013/04/adobe-air-gameinput.html
ゲームパッドでブラウザゲームが遊べる時代が来た!話題の GameInput API を使ってみよう
http://ics-web.jp/lab/archives/512