[ANE][iPhone]AIR for iOSにて、スクリーンショットをカメラロールに保存するANEを作ってみた、がiOS6では機能しなかったという悲しい現実

以前書いた記事、Air for iOSにて、スクリーンショットをカメラロールに保存するANEを作ってみた、への追加メモです。

まず結論から、
iOS6では「スクリーンショットをカメラロールに保存するANE」は正常に機能しなくなりました。。
(iOS5では機能します)

そして色々検証してみたところ、
現状AIR for iOS(AIR3.4SDK)ではANE内でスクリーンショット(UIImageとして取得)することは無理じゃね?
という結論に達しました。

以下、無理じゃね?と思う理由が続きマス。
(解決策わかる方がいました教えてください。。)


■Objective-Cでのスクリーンショットを取るコードをiOS6上で動かしてみる

上記aneで使ったこちらのコード
これを使って、Xcodeを使ったObjective-Cプロジェクト(Xcode4.5のOpenGL Gameテンプレートを使用)でOpenGLで描画したもののキャプチャを取ってみようとしましたが、iOS6実機ではキャプチャが正常に取れません。
(Xcodeのシミュレーター上では正常にキャプチャできるんですが、iOS6実機ではキャプチャ画像は真っ白になってしまう)
※iOS5実機ではなんら問題なくキャプチャできます。

あら、そもそもiOS6ではOpenGL描画のキャプチャができなくなったの?
と思いましたが、そういうわけでもありません。


■Apple公式のキャプチャ手順

こちらにOpenGLの描画についてのキャプチャの取り方について記載されているのですが、やり方としては2種類あるようす。

1) Call glReadPixels before calling EAGLContext/-presentRenderbuffer

2) Set the CAEAGLLayer’s retained backing property to true to retain the contents of the layer. However, this can have adverse performance implications so you are cautioned to use this setting only when necessary.

上記2通りのやり方を、まずはObjective-Cプロジェクトを使って実装してみると、どちらを使ってもではOpenGL描画のキャプチャは取れました。

じゃあ、その2通りのどちらかをANE使って実装したらいいじゃん、ってことなんだけどこれがどうやっていいかわからず。

以下、それぞれの無理じゃねって理由です。


■やり方1)について

1) Call glReadPixels before calling EAGLContext/-presentRenderbuffer

これはglReadPixels()は、presentRenderbufferする前に呼んでね、ってことなんだと思います。
glReadPixelsはスクリーンショットを取るときに呼ぶメソッドなので、presentRenderbuffer()を実行する前のタイミングでスクリーンショットを取る処理を実行するようにしたいわけですが、presentRenderbuffer()はOpenGL描画の更新毎に呼ぶメソッドっぽい。
で、AIR for iOSでは描画更新処理というコアな部分の処理が具体的にどうなっているのかわからんのです。
(LLVMが具体的にどのようにコンパイル(AS3→ObjC)してるのかわからんので)

描画処理のコアな部分の処理をANEからオーバーライドなりして、修正することができればいけそうなんですけどね。どういう実装になってるのかわからんのでできません。
というわけで1)の案はANEで実装する方法がわからず。


■やり方2)について

2) Set the CAEAGLLayer’s retained backing property to true to retain the contents of the layer. However, this can have adverse performance implications so you are cautioned to use this setting only when necessary.

これは、CAEAGLLayer.drawablePropertiesにkEAGLDrawablePropertyRetainedBacking:YESを突っ込め、と。
ただそうするとパフォーマンス落ちるかもしれないから必要なときにだけYESにしてね、ということみたい。

CAEAGLLayerはANEからでも取得できました。

//UIWindowの下にCALayerがあってその下にCAEAGLLayerがありました。
UIWindow* win = [UIApplication sharedApplication].keyWindow;
UIView* uv = [[win subviews] objectAtIndex:0];
UIView* uv2 = [[uv subviews] objectAtIndex:0];
CAEAGLLayer* eagllayer = (CAEAGLLayer *) uv2.layer;

CAEAGLLayerが取得できればdrawablePropertiesにkEAGLDrawablePropertyRetainedBacking:YESを上書きするところまではできるのだけど、drawablePropertiesを変更した際にはEAGLContext.renderbufferStorage:fromDrawableを実行しないと反映されない。
で、これにはEAGLContextのインスタンスを取得しないとなのだけど、ここが無理っぽい。
先ほどと同様でLLVM後のコアな部分の処理が具体的にどうなっているのかわからんのです。
CAEAGLLayerをもつUIViewにEAGLContextは使われてると思うけど変数名がわからず、そもそも外部からアクセスできる変数かどうかもわからずです。

というわけで、Apple推奨のふたつの手順がAIR for iOSでは実装できないんじゃないかと思っているわけです。


というか、なんでiOS5ではできていたのかも謎です。
気になったのはTechnical Q&A QA1704 OpenGL ES View Snapshotに書かれたこの文章

Important: You must call glReadPixels before calling EAGLContext/-presentRenderbuffer: to get defined results unless you’re using a retained back buffer.

iOS6のベータが出回ったころに追記されたとかなら、iOS6で仕様が変わったのだと納得がいくんですけども。


これができないとiOSのTwitterAPIとかfacebookAPIのAPI使ってもアプリのキャプチャ画像添付ができないと思うんですよね。
Flash側で画面をBitmapDataでキャプチャしてそれをaneに送ってane側でBitmapDataをUIViewに変換することができればなんとかなりそうな気もするんですけども、Alternativa3Dで描画してる部分をBitmapDataにキャプチャしようとしたら(Web上ならできたのに)iOS上だとキャプチャとれず。

なんとかなる方法ないかなぁ・・・

p.s
iOS5以上から追加されたDLKViewクラス(CAEAGLLayerをもつUIView系クラス)にはsnapshotってメソッドがあって、これを実行するとUIImageがさくっととれちゃうんですよね。
AIR for iOSはiOS4もサポートしてるからDLKViewは使われてないんでしょうけど。
(ちなみにAIR for iOSではCAEAGLLayerをもつUIView系クラスはCTStageViewというクラスで定義されてるっぽい)
CTStageViewにもsnapshotみたいなAPI仕込まれていないかなぁ。。。

[iPhone][Alternativa3D][AIR]AIR for iOSでシンプルな3Dゲームをリリースしました。

メモ記事を書いてましたが先週やっとiPhoneアプリとしてリリースしました。

Live with the Wind.
support site
itunesStore

初めてFlash:Air for iOS(AIR3.3SDK)で作ってみたよ。
3DエンジンにはAlternativa3D(8.17.0)を使用したよ。

もろもろ苦戦したことは以前公開したStage3D勉強会#2の資料に書いたので興味ある方は読んでみてください。


■実装以外でのゲーム性について
▼気持ちよさの工夫
BGMのBPMに連動するような動きを意識
→風を連続で取ったときの効果音がBPMと連動するようにスピードを調整
→風車の首を降り始めは曲調が変わるタイミング
→あと風車が首を振るときのスピードもBPMと連動するような

▼飽きさせない工夫
上昇気流だけを取り続けて飛ばれるとゲームオーバーになりにくいが緊張感なくなりゲームが単調になる
追い風も取らせて緊張感のあるゲーム展開にしたい
→上昇気流だけを取りつづけた場合は無理ゲーになるような設定に。
 (風車の背がどんどん伸びていく)
→2種類のコンボ要素の追加
 (コンボを続けると風車の背が伸びにくくなるようにしている)
※「上昇気流を取る→風車がのびる」という部分がルール的には判りにくくなってしまったのでヒントの項目に記載したけど理解できてない人も多いかも。。

▼操作性について
iPhoneアプリというと傾きセンサーだけど今回は無しで。
プレイヤーが学習してやりこみ性あるゲームにしたかった。
傾きセンサーでは細かいコントロールができないので運要素が増えてしまう
個人的に思うようにコントロールできないのは嫌い、というのが一番の理由
あと電車でプレイしてる姿も少し恥かしくなるしね。


8月中は無料で9月から170円になりますので是非試してみてくださいまし。

※Flash製なので簡単にWEBおためしプレイ版も用意できますよ。
>FlashPlayer11.0
(音鳴ります)

[ANE][iPhone]Air for iOSにて、スクリーンショットをカメラロールに保存するANEを作ってみた

aneを使ってAir for iOS(AIR3.3SDK)でスクリーンショットを撮るお話。

ありそうでなかった、というかあったけどもうまく動かなかったので作ってみたらちょっとハマりました。


まず、タイトルどおりのaneのコードが公開されてました。
abhisek-mishra / ScreenShot

が、実装してみても想定通りに動作しない。。
Objective-C部分のコードを見てみると、UIGetScreenImage()というapple非公開APIを使用してる。
非公開APIを使うとリジェクト対象だそうでこれは使えないと。


とりあえず下の記事を参考にスタンダードな実装に変更してみる。
iPhoneアプリから画面のキャプチャー画像を取得する2つの方法

aneファイルをビルドしなおして再テスト。
が、画像はカメラロールに保存されたのは真っ白の画像。。

ちなみにObjective-CでiOSプロジェクトを作って、Xib上に適当にボタンとか画像とか配置した状態で上記の方法を実行すればスクリーンショットは問題なく取れるから根本的に間違ってるわけではなさげ。


肝はFlashの描画領域がどこにあるのか、ということ。
上記の記事のコードだと、

[app.keyWindow.layer renderInContext:UIGraphicsGetCurrentContext()];

UIWindowのkeyWindowのレイヤーをキャプチャしてるのだからFlashで言えばStage上のメインタイムラインをキャプチャしてるようなものなはずだから良さげな感じだけども結果は前述したとおりに真っ白。


で、結論。
上記の方法は使えないみたい。
app.xmlでのrenderMode要素をdirectを指定する場合はObjective-C側ではOpenGLESでのスクリーンショットを撮るような処理をしないといけないっぽい。
具体的なコードはここに書かれてるものを参考にしました。
GLPaint save image

このコードで取得したUIImageをキャプチャすることでとスクリーンショットを撮ることができました。


では、renderModeをcpu指定にすれば最初のコードでもスクリーンショットは撮れるのではないか?と思いましたが結果は否。
renderMode:cpuでは、上述のどちらのコードでもスクリーンショットを撮ることはできませんでした。
謎。。。
まぁrenderModeをcpuにする理由なんてない気もするのでまぁいいか。


各コードの詳細はQuiitaにあげてあります。
Qiita:AIR for iOSでスクリーンショットをカメラロールに保存するANEを作ってみたけど真っ白な画面しか取れない
(コメント欄で自己解決してます)

p.s.
ちなみに今回スクリーンショットを撮るのが最終目的ではなく、iOS5からのネイティブなTwitterAPIを使ってのスクリーンショットつきのTweet機能を実装したかったのでした。
これも無事にうまく実装できたので、また後で記事を書きます。