× [PR]上記の広告は3ヶ月以上新規記事投稿のないブログに表示されています。新しい記事を書く事で広告が消えます。 |
cocos2d-xでスクリーンショットを撮影する方法。
ios標準の機能だと、こんな感じになる。
参考 しかし、この方法だとcocos2d-xで描画している画面は映らない。(上記コードで言うと、[aWindow.layer renderInContext:context]; の部分) cocos2d-xの3.2以降だと、便利な機能がついている。 cocos2d::utils::captureScreen() がそれで、 以下コード例。
captureScreenの第1引数が完了時に実行するラムダ式。第2引数がファイル名。 キャプチャーが取れるとラムダ式の中が呼び出される。 ラムダ式の第2引数のfilePathは、保存したファイルの完全な絶対パスが入っている。 このパスを使って、スプライトをゲーム中に表示したり、Twitterにシェアしたりすることができる。 スプライトの場合は、通常通り Sprite::create(filePath); とすればよい。 ただし、そのままだと2回目以降、スクリーンショット画像が変化しない(1回目のスクリーンショットが表示されてしまう) これは、Sprite::create()の中で自動的にテクスチャがキャッシュされているからだ。
そこで、毎回テクスチャキャッシュを削除すればよい。
上記コードをSprite::create()の前に実行すればOK。 TwitterやFacebookにシェアする場合、UIImageインスタンスを作成する。
アセットではなくファイルパスなので、以下の書き方ではUIImageを作成できないので注意。 UIImage* image = [UIImage imageNamed:[NSString stringWithUTF8String:filePath]]; 総合すると、スクリーンショットを撮ってTwitterに投稿するのは以下のようなコードになる。
PR |
RPGとかノベルゲームでよくある、1文字ずつ表示する仕組み。
◯cocos2d-xの内部 文字を表示するのはLabelクラス。 これはSpriteBatchNodeの一種で、1文字を1つのSpriteで保持している。 文字数分だけ、子ノードにSpriteを持っているという構造になっている。 で、◯文字めのSpriteを返す getLetter() というメソッドがある。これを使う。
という感じ。 ◯注意点 labelは使い回しできない。 一度runActionして1文字ずつ表示した後、 label->setString("another string"); して、再度runActionすると、文字の表示位置がずれまくり、そして落ちるという謎現象が起きた。 その場合、label->removeFromParent(); してから再度 Label::create() すれば正しく動いた。 ◯注意点その2 int textLength = label->getStringLength(); の文字数が合っていない。textLengthの文字数だけでwhileをまわすと、たまに末尾の1〜2文字が表示されないことがあった。 どうやらcocos2d-x内部での文字の数え方に差異があるようだ。 面倒なので、textLength + 2 でループをまわしたりした。 ちゃんと調べたい |
白くフェードアウトする場合、スプライトに
と記述してある可能性がある。 SpriteBatchNodeを使用しているかどうかでも変わる(使用していると白くはならない)
|
ttfファイルをどこかからダウンロード。
PROD_DIR/proj.android/assets/fonts/XXX.ttf に配置。 EclipseのPackageExplorerで F5 を押してrefreshすると、表示される。 Label::createで "fonts/XXX.ttf" を指定。ファイル名やフォント名でなく、assets以下のフルパスで指定することに注意。 これで表示されるようになる。 簡単。 なお、iOSではフォント名での指定なので、OSによって変える必要がある。 イケてないね。 |
Xcodeで作っていたcocosプロジェクトを、androidで動かそうとしたときにエラー。
とか言われる。 調べてみると 参考 libcurlというライブラリが問題らしい。 コンパイルオプションをつけると解決できるとか、NDKのバージョンを戻すと治るとか治らないとか情報が交錯しているが、 cocos2d-x 3.3rc2 以降だとlibcurlの問題が解決しているらしく、 最新のcocos2d-xをダウンロードして、 cocos2d-x-3.3rc2/external/curl/ ディレクトリをまるっとコピペしたら治った。 |
cocos2d-xで、スクロールビューの中身にメニューを配置したい。
簡単なことのようだが、実は問題がある。 メニューのアイテムをタッチすると、画面がスクロールできないのである。 つまり、アイテムの配置されていない箇所に指を触れてからスワイプしないと、画面がスクロールさせられないのである。 これは不便だしかっこわるい。 どうしてこうなっているのかというと… メニュー(Menu)にtouchBeganイベントが処理されると、イベントが飲み込まれ、ScrollViewのtouchBeganイベントが処理されなくなるのである。 ◯優先度について cocos2d-xでは、EventDispatcherクラスにタッチイベントを登録する際(タッチ以外にも、マウスやキーボードイベントなども登録できる)、優先度を指定して登録する。 2種類の方法がある。
1つめの方法は、シーングラフの優先度と同じ優先度になる。 つまり、描画順序と同じく、シーン上の親から子への順序でイベントが処理される。 2つめの方法は、第2引数で指定した固定優先度で実行される。値が小さいほど優先度が高い。マイナス値も使えるので、-INT_MAXが一番優先度が高くなる。 混ぜて使う場合、 固定優先度がマイナス値のもの。-INT_MAX→-1 ↓ シーングラフ優先度が高いものから低いもの ↓ 固定優先度が0またはプラス値のもの。0→INT_MAX という順番で処理される。 実装はEventDispatcher::dispatchEventToListeners()にある。 さて、メニューとスクロールビュー、どちらのタッチイベントが先に処理されるかというと、この優先度順序に依る訳である。 実装部分を見てみると、
という風に、どちらもシーングラフの優先度通りに実行されるようになっている。 当然といえば当然なのだが、これだと困る。 どんなときでも常にメニューが優先されるからだ(スクロールビューよりメニューが手前に表示されるはずなので)。 だからメニューのアイテム上をタッチしたときは、touchBeganイベントはスクロールビューに回ってこない。 メニューをタップしたいときはこれでいいが、スクロールしたいときは困る。 「メニューをタップしてそのまま離したときは、メニューのtouchBegan,touchEndedを処理。 メニューをタップして指を滑らしたときは、スクロールビューのtouchBegan,touchMoved,touchEndedを処理」 というふうにしたいのだ。 ということは、touchBeganの時点では、スクロールビューとメニューの両方で処理を走らせる必要がある。 ◯swallow設定 cocos2d-xのイベントには、スワローというメンバ変数がある。 スワローの意味は「飲み込む」。 複数のイベントリスナーが登録されているとき、1つのイベントは上記で見た優先度順に処理されていく訳だが、 その途中でswallowがtrueになっているイベントリスナーがあると、そこで処理が終了してしまう。それ以降のイベントリスナーには処理がわたらないようになっている(これが飲み込む、の意) で、もう一度さっきの実装コードを見てみると、メニューは touchListener->setSwallowTouches(true); と描いている。 つまり、タッチイベントはメニューに処理されると、それ以降他のノードには渡されなくなるのだ。もちろんスクロールビューにも、である。 なるほど、だからメニュー上をタップするとスクロールしないのか。 逆に、スクロールビューではスワロー設定をしていないので、デフォルトのfalseのままである。 ということは、 「メニューより先にスクロールビューでタッチイベントを処理すればいい」 のである。 スクロールビューで処理しても、優先度が低いイベントリスナーにも処理を渡すからだ。 ということで、スクロールビューのイベント登録部分を改造する。
優先度値はマイナス値ならとりあえず何でもいい。 これで試してみると、メニューをタップしてもちゃんとスクロールするようになる!! ◯メニュータップのタッチ処理を止める しかし、ちょっと動かしてみると少し違和感がある。 確かにメニュー上をタップしてもスクロールするようになるのだが、 スクロールしたあと指を離したときに、メニューアイテムが押されるのである。 指をメニューアイテムの無い場所で離せば何も起きないが、 要するにメニューのタッチイベントがまだ生きているのである。 よく見てみれば、タップしたメニューアイテムはselected状態のスプライトが表示されたままに鳴っている。 メニューのtouchBeganで始まった処理が有効で、指を離したときにtouchEndedが発生すると、その通りメニューアイテムを選択してしまうのである。 これはちょっといやだ。 スクロールした時点で、メニューのタッチイベントは終わらせたい。 そういう処理はなかったか? もちろんある。 onTouchCancelledだ。 これはタッチイベントが中断されたときに呼び出されるメソッドだ。 本来はタップ中に電話がかかってきてアプリが中断されたときとかに呼ばれる想定だが、今回の目的はまさにこのメソッドを呼ぶことにある。 つまり、「スクロールビューのスクロールが発生した時点で、登録されている他のすべてのイベントリスナーのonTouchCancelledを呼び出す」 ということである。 onTouchMovedやonTouchEndedは呼び出させないのだ。 さてどうするか。 そもそもonTouch***系を呼び出している部分のコードを見てみる。
dispatchEventToListeners()の中で、優先度順のループが3つあり(固定優先度がマイナス値のもの、シーングラフ優先度のもの、固定優先度がプラス値のもの)、順番にonEventメソッドを呼んでいる。 onEventメソッドの実体はdispatchTouchEventの中のonTouchEvent というラムダ関数で、 この中でイベントリスナーを処理し、それぞれonTouchXXX関数を呼び出している。 スクロールビューのonTouchMovedの中でスクロールが発生したと判断したときに、 それ以降のイベントリスナーについてはすべてonTouchCancelledを呼び出すようにすればいい。 ということで、EventDispatcherクラスに_touchEventForceCancelled というフラグを用意し、以下のように改造する。
とする。 解説。ScrollViewのonTouchMovedで、スクロール発生と判断されたタイミングで、EventDispatcherに、「以降のタッチイベントをすべてキャンセルさせろ」というフラグを立てる。 すると、それ以降のEventDispatcher::dispatchTouchEvent()内の処理で、タッチイベントはすべてEventTouch::EventCode::CANCELLEDに強制的に書き換える、ということである。 これで、メニュー上をタップしても、指を滑らせてスクロールすると、メニューのタッチイベントはキャンセルされる。 スプライトは選択状態のものから通常状態のものに戻るし、指を離してもアイテムは選択されない。 ◯スクロールビューの無効化 スクロールビューにはもう一つバグがある。それは 「不可視状態でもタッチイベントが処理され、スクロールしている」 ということである。 正確なことを言うと、 スクロールビュー自体にsetVisible(false) を呼び出していれば処理されないのだが、 スクロールビューの祖先ノードでsetVisible(false)をしていて、スクロールビュー自体の_isVisibleがtrueのままの場合、処理されてしまう。 普通、他の種類のクラス(メニューとか)だと、親が_isVisible==falseなら処理しないようになっているので(普通に考えてそれが正しい)、スクロールビューがバグっている。 スクロールビューはextension扱いだし、どうもこういう不具合がまだ多い。 これに対処するのは割と簡単で、 onTouchXXX()系のすべての関数の先頭に以下のコードを書き足せば良い。
このコードは、Menu.cppからコピペしたものである。 |
cocos2d-xにあるSpriteBatchNodeクラス。
複数のスプライトを効率よく描画したいときに使う。 例えば同じ絵のキャラを大量にわらわらと表示させたいとき。 SpriteBatchNodeクラスのインスタンスのノードを作り、このクラスにテクスチャを登録する。 その子ノードに同じテクスチャを貼ったスプライトを大量に付ける。 すると、一度の描画命令で子ノードが全部描画できる。 ポイントは、SpriteBatchNodeクラスに登録したテクスチャと 「同じテクスチャ」を持つノードしか子ノードにできないこと。 テクスチャでなく、SpriteFrameでも良い。アニメーションする場合や、複数の絵を子ノードに並べたいときはSpriteFrameを使うことになるだろう。 実際に描画回数が減っているかどうかは、 画面左下に出ている。 GL calls が描画回数、 GL verts が頂点数。 GL callsがテクスチャ数より少なくなっていれば、効いている…はず 詳しいところはもう少しコードを追いかけてみる。 ちなみにLabelクラスはSpriteBatchNodeの子クラスである。 …といったところで、 v3.xでは標準でドローをまとめる機能がついたらしく、SpriteBatchNodeは非推奨になったとかなんとか。 参考 |
cocos2d-xで、LayerとかSpriteを継承して新しいクラスを作る場合がある。
たとえば画面を作りたいなら、Layerを継承すればいい。cocosプロジェクトを作ったときもコード中にHelloWorldSceneがそうして作られているし、それを真似すればいい。 基本的にゲーム中のオブジェクトなら、Layerを継承して作るのが良さそうだ。 前の記事も参照。 その他にも、たとえばキャラクターなら、Spriteを継承して作るなど、内容に応じていろいろなクラスを継承して作るのが良い。 以下はLayerを継承して作った例
こうすると、コード中にnewもdeleteも現れなくなる。 cocos2d-xの作法にあった形になる。 static関数のcreate() は、CREATE_FUNCマクロによって生成されている。 CREATE_FUNCマクロを使うためには、 ・引数無しコンストラクタがあること ・引数無しで戻り値がboolのinit()という関数を持っていること ・そのクラスがRefを継承していること(より正確には、autorelease()という関数を持っていること) が必要。
純粋なデータ構造を扱うクラスだから、SceneもNodeも継承する必要が無い、と思っても 「メモリ管理の方法は統一しておくべき」という観点から、最低でもRefは継承するのがよい。 Refは自動でメモリ管理をしてくれるクラス。使用されなくなったらreleaseすれば、裏でPoolManagerが自動的に破棄する。 <<注意!>> init()関数はNodeクラスに仮想関数として定義がある。 オーバーライドして、さらに親クラスのinit()を呼ぶのを忘れないように。 Refクラスを直接継承する場合は、オーバーライドではなく新規の定義になる(Refクラスにinit()がないので) Spriteクラスを継承して、親クラスのinit()の呼び出しを忘れた場合は、大抵バクる。 (glProgramStateがセットされないというアサートが出る。) もうひとつ。 create()メソッドは継承はしないが、 元々create()メソッドを持っているクラスから継承した場合、自クラスで定義しなくても呼び出せるわけだ。 例えばSpriteクラスを継承してMySpriteクラスを作った場合 auto sprite = MySprite::create(); とすると、 Sprite::create()が呼び出されることになる。 ただしその場合、戻り値型はSprite*なので、 MySprite* sprite = MySprite::create(); はコンパイルエラーになる。 ちゃんとMySpriteでcreate()を定義する必要がある(CREATE_FUNC(MySprite); と書くだけ) Spriteの場合、create()だけでなく create(const std::string& filename) create(const std::string& filename, const Rect& rect) createWithTexture(Texture2D *texture) createWithTexture(Texture2D *texture, const Rect& rect, bool rotated=false) createWithSpriteFrame(SpriteFrame *spriteFrame) createWithSpriteFrameName(const std::string& spriteFrameName) と言う具合に多くのcreate()系のstaticメソッドがある。 本来であれば、MySpriteクラスにすべての同名関数を用意すべきである。 ただし、戻り値がMySprite*でなくSprite*なので、コンパイルエラーで気付けるといえば気付ける。 |
|