× [PR]上記の広告は3ヶ月以上新規記事投稿のないブログに表示されています。新しい記事を書く事で広告が消えます。 |
やることは大して変わらない。
まず、ダウンロードページへ行く 最新のバージョンをダウンロードしたら解凍。 cocos2d-x-*.* という名前のディレクトリができる <cocos2d-x-*.*>/README.md に基本的に書いてあるが $ ./setup.py $ source ~/.bash_profile $ cocos new MyGame -p com.your_company.mygame -l cpp -d NEW_PROJECTS_DIR で作れる。 PR |
アプリ内でNSTimerを発行したいときがある。
◯記述場所 NSTimerは、アプリが起動している間だけ生きていれば良かったので、Model層では扱う必要が無い。 だからViewControllerで完結させるように記述した。 ◯破棄 さて、NSTimerはキャンセルすることもできるようにしたい。 ということは、作成したNSTimerのインスタンスを保持しておく必要がある。 NSTimerインスタンスの生成・破棄及び参照保持と削除のタイミングは、 ・作成ボタン押下 インスタンス化、参照保持 ・キャンセルボタン押下 無効化、参照削除 ・タイマー発動 参照削除(←忘れがちだがここでもちゃんと削除しておく。そうでないとvalidでないNSTimerインスタンスの参照がずっと残ることになる) ということになる。 さてインスタンスの保持の方法だが、 [NSTimer] Array<NStimer> Dictionary NSMutableSet といろいろあるが、試してみて一番便利なのは NSMutableSet だった。 なぜなら、削除したいインスタンスを直接 set.removeObject(timer) という形で指定できるからである。 それ以外の型だと、削除していいインスタンスかどうかの判定のロジックを書かなければならなくなる。 ◯別のViewControllerから生成・削除させる キャンセルは実は別の画面から行われることもある、という場合。 あるViewControllerから別のViewControllerにどうやって情報を伝達するか。 NotificationCenterを使う。 ・通知を発信する側 var notification = NSNotification(name: "RemoveInAppNotification", object: self, userInfo: ["data": declareData!]) NSNotificationCenter.defaultCenter().postNotification(notification) なおuserInfoは必ず Dictionary? 型でないといけない ・通知を受け取る側 NSNotificationCenter.defaultCenter().addObserver(self, selector: "removeInAppNotifications:", name: "RemoveInAppNotification", object: nil) func removeInAppNotifications(notification: NSNotification) { //notification.userInfo などを使って処理 } となる。 nameはもちろん揃っている必要がある。 通知を受け取る側は、addObserver()やremoveObserver()を正しく書いておく必要がある。 画面に出入りしたときにadd/removeObserverしたいところだが、この場合はいけない。 「別の画面から呼び出されたとき」に通知を受け取りたいからである。 もちろん、該当のViewControllerが常にメモリ上に存在している保証が必要だ(そんなことできるのか?) 以上まとめるとコードは以下のようになる
|
cocos2d-xには、グローバルZ値を設定できる。
これによって、親子関係に依らず表示順序を設定できる。 グローバルZは、設定したそのノードだけに効く。 例えばノードAの子ノードBにグローバルZを設定し、一番手前に表示したとする。 ノードBの子ノードCがあったとき、ノードCは、ノードBより手前には表示されない。 ノードAの子として通常のローカルZによって描画順序が決定される。 ノードCをノードBより手前に表示したいときは、ノードCにもグローバルZを指定する必要がある。 なお、グローバルZは、値が小さい方が手前に表示される。 この点、ローカルZと逆になっている。 なぜこんなことになっているのか…。 ちなみにdouble型。 さて、グローバルZが子ノードに伝播しないと、 Labelが困ったことになる。 Labelは内部的にSpriteを子ノードに持っているため、 label->setGlobalZ() を設定しても、実際の文字にはグローバルZの値が効かないのだ! 参考 大変困る話だ。 表示文字列が決まっているなら、文字全体を一枚のテクスチャで作ってしまっておけばSpriteで表示するといった手で逃げられるが。 |
++i;
i++; を比べると ++i; のほうが速い。 違いはもちろん 前置インクリメントは、インクリメントを先に行う。 後置インクリメントは、インクリメントを後に行う。 ということだ。 前置の場合、自身のインスタンスをインクリメントし、自身を戻り値として返せば良い。 なぜなら、インクリメントが先だから。インクリメント後の値をその次の計算で使うので、インクリメントしたものをそのまま戻り値にすればよい。 後置の場合、そうはいかない。 インクリメントするが、戻り値として返すのはインクリメントする前の元の値である。 ということで、「インクリメントするまえに自身のコピーインスタンスを作成している」のだ。 そして、戻り値として返すのはこのコピーインスタンスなのである。 このコピーの手間分だけ、後置インクリメントは遅くなる。 クラスインスタンスなどはもとより、intなどのプリミティブ型でもその差は出てくる。 試しに以下の処理を実行してみる その結果は以下のようになった。 何度もやると時間は変化するし、まれに逆転することもあるが、 後置は前置より遅くなる。 (この処理の前置と後置の順番を逆にしてもやはり後置が遅い) というわけで、インクリメント処理は理由が無い限り前置を使うこと。 |
以下のコマンドを実行する
$ cd <eclipseのworkspace> $ cd .metadata $ find . -name .markers -exec rm {} \; $ path/to/adt-bundle-mac-x86_64-YYYYMMDD/eclipse/Eclipse.app/Contents/MacOS/eclipse -clean -refresh 参考 |
以下のリジェクト理由が来た
3.3 - Apps with names, descriptions, screenshots, or previews not relevant to the content and functionality of the App will be rejected さらに後ろにその詳細に付いても書いてある。 3.3 Details Also, we noticed your icon includes the following image, which is not relevant to the application content and functionality: We've attached the screenshot for your reference. というわけで添付されていたスクショを見ると、それはcocos2d-xのアイコンであった。 なるほど。 さっそくアイコンを削除。 バージョン番号、ビルド番号を上げ、アーカイブを作成してサブミット! …と思ったら、サブミットでエラー発生。 とのこと。 で、見てみる。 プロジェクトビューからプロジェクトを選択。 Infoタブを見てみると、確かに IconFile という項目が。その値が icon57.png になっている。 これはxcassetsが導入されてから不要になったはず。 というわけでそれを項目ごと削除。 これでOK、通った |
Androidで、ビューをpx指定で表示したいことがある。
当たり前のやり方のような気がするが、意外と方法が用意されていない。 iOSなら、view.frameやview.boundsで矩形を指定することができるのだが、Androidはそれがない。 Androidは、ビューに加えて、レイアウト(FrameLayout,LinearLayout,RelativeLayoutなど)という概念がある。 (正確にはレイアウトもビューなのだが。) ビューはレイアウトの中に配置される。 そのビューを、レイアウトから上下左右のマージンいくつ離すか、という指定をすることになる。 本来これでいいはずなのだが、 実際にビューを配置してみると、狙った位置と大きさも位置もずれていることがある。 なぜか。 マージンを指定することによって、矩形の大きさが決まる。 例えば、幅640,高さ240のLayoutの中に、左右マージン各20,上下マージン各10を取ると、 ビューのRect(x,y,w,h)=(20,10,600,220)になる。 しかし、ビューが実際に正確にその大きさで配置されるとは限らない。 たとえば、ビューの幅、高さの指定がWRAP_CONTENTだと、矩形のサイズはあまり関係なくビューの「見た目の大きさ」は決まってしまう。 また、MATCH_PARENTだとしても、 たとえばImageViewなら、元画像の縦横比を崩さない範囲で最大サイズ表示、のようなことになるからだ。 また、広告SDKで表示するアイコンなど、実際の表示サイズが不定になるものの場合もある。 そのようなとき、ビューは大抵左上寄せの位置に表示される。 これは嫌だ。指定座標を中心とする位置に収まってほしい。 じゃあ、gravityをCENTERに指定すれば…? としたいが、gravity指定はビューにはできず、Layoutに指定する。 gravityは、そのレイアウト全体の重力場を指定するためのものだからだ。 ビューを中央寄せにする指定をビュー自体に対してできないか…となる。 もちろんその指定はあって、xmlでいうと centerHorizontal などの指定がそれにあたる。これは、 「自分の親のレイアウトの左右方向中心に配置」 ということだ。 ……ということは、自分の親のレイアウトが必要になる。 何も考えずそのままcenterHorizontal,centerVerticalを指定したら、大本のレイアウト(おそらく画面全体を覆うRelativeLayoutになっているだろう)の中心に行ってしまう。それはスクリーンの中心だ。 そうじゃない。 さっき指定した矩形の中心にいてほしいのだ! …………ということは、さっきの指定の矩形のレイアウトを作成すればいいんだ。
という二重構造にすればよい。
こういう感じになる。 中間レイアウトはRelativeLayoutでなければダメで、他のレイアウトだとマージン指定が効かない。 FrameLayoutやLinearLayoutにすると表示位置が左上固定になってしまう。 RelativeLayoutなので、レイアウトパラメータもRelativeLayout.LayoutParamsにする。 ビューのレイアウトパラメータもRelativeLayout.LayoutParamsにする。 でないとaddRule()メソッドが使えない。(だからCENTER_HORIZONTAL/VERTICAL指定ができない) FrameLayoutには lp2.gravity = Gravity.CENTER; のような指定もできるが、これは自分の子孫に対して効かせるパラメータなので、ビュー自身を中央寄せしたい場合は意味が無い。 というわけで、 ================================= 中間レイアウトはRelativeLayout 中間レイアウトのパラメータはRelativeLayout.LayoutParams ビューのパラメータもRelativeLayout.LayoutParams ================================= となる。 ちなみにレイアウトパラメータ類の子孫関係は以下
どのレイアウトパラメータも、コンストラクタの基本はwidthとheightの指定(int)である。 lp1のように、幅と高さを直に設定することもできるが、 多くの場合はlp2のように MATCH_PARENT(-1) か WRAP_CONTENT(-2) という特殊な値を入れて使うことになる。 |
こんな感じ。
AndroidManifest.xmlには
と記述のこと。 ◯内部クラスを使う場合の注意 ・内部クラスはstaticをつけて定義する Javaでは、内部クラスは基本的にstaticをつけて宣言することが推奨。 staticにしていないと、「no empty constructor」という実行時エラーになる。 なぜなら、staticでない内部クラスは、それを包含するクラスからしかインスタンス化できないからだ。 ちなみに、staticでない内部クラスは、自身を包含するクラスへの参照を暗黙的に持つ。アクセスできて便利だが、当然gcを阻害することになる ・AndroidManifest.xmlでは、内部クラスは $ で繋ぐ。 これもJava的な決まり事。 |
cocos2d-xでandroidで、同じBGMが2度目以降再生されないバグがある。
(cocos2d-xトップディレクトリ)/cocos/platform/android/java/src/org/cocos2dx/lib/Cocos2dxMusic.java の中。 stopBackgroundMusic() のあと、同じファイルで playBackgroundMusic() すると音が鳴らない。 結論言うと、 prepare()をしていないから。 cocosの音声周りの実装は、SimpleAudioEngineを介して、Cocos2dxMusic.java を呼び出している。 Cocos2dxMusic は、メンバにAndroidSDKのMediaPlayerを保持し、これを操作している。 初回のplayBackgroundMusicは、createMediaPlayerメソッドを実行し、内部で mediaPlayerのインスタンス化、 setDataSource prepare setVolume を行っている。 その後playを実行し、音が鳴り始める。 さて、2回目にplayBackgroundMusicが呼ばれたとき。 直前に鳴らした音声の情報はmediaPlayerの中に残っており、(stopBackgroundMusicを実行しても、stopするだけでreleaseはしていない)、同ファイルだった場合は初期化処理を丸ごと飛ばす。 ここが問題。 このとき、本来実行すべきprepare()が抜けているのだ。 そこで、同ファイルのplayBackgroundMusicのとき、 prepare seekTo(0)…曲の先頭に移動 を行うようにした。
try{ this.mBackgroundMediaPlayer.prepare(); this.mBackgroundMediaPlayer.seekTo(0); } catch (final Exception e) { Log.e(Cocos2dxMusic.TAG, "error: " + e.getMessage(), e); } } : これでOK。 ちなみにlibcocos2dxプロジェクトのjavaファイルを書き換えてビルドすれば ゲームプロジェクトにもちゃんと反映される。 |
JNIで
というエラーが出た。 ググってみると、無効になったクラスにアクセスしてるとか、解放された変数に参照しているみたいなことが書いてある。 参考 参考 でも、そんな単純ミスはしていない。 おかしい。 その少し前のワーニングで、 trying to work around app JNI bugs, but didn't find 0xXXXXXXXX in table! DeleteLocalRef(0xXXXXXXXX) failed to find entry というようなのが出ている。 で、このポインタが示す変数はなんぞや、というのを出力して見てみたところ、 C++変数から変換したJNIオブジェクトだった。 特に何の問題もなさそう。 さらに発見したのが、
の戻り値のobjResultが0x1になっている。 エラーはこの変数にアクセスした瞬間起きたようだ。 しかし、メソッドシグネチャーも合っているし、なぜ戻り値がこんなことになるのか。 このコードのどこに問題があるのか。 こういう不可解なことが起きるときはスレッドが絡んでいるもの。 そうすると真の原因はここでないまったく別の場所にある可能性がある。 ということで、ともかく例外を出力させてみる。
すると…
案の定、スレッドのエラーだった。 発生箇所とはこことは無関係の場所。 ここを直したら、無事に動いた。 |