Cocos2d-xで2本目をリリースしました。前回の英語パズルの続編『英単語ギズモ2』です。前回のアプリはなかなかダウンロードされず、スマホゲーム市場の厳しさをヒシヒシ感じますが、懲りず2本目の投入であります。
せっかくスマホ開発の技術を習得したからという思いと、その厚い壁とのシーソーゲームがモチベに絡みついてきますが、大作ゲーム市場のスキマをいかに狙うかがポイントとなりそうです。
1作目は右も左も分からなかったので、ゲームというよりはツールっぽい完成形を目指しましたが、今回はゲーム色が強いです。それに従い、処理が重くなったり、様々な問題が浮かび上がってきました。今回は高速化について。
現実的には高速化が必須
今回のアプリは2作目で、それなりに画面内のスプライトも増えましたが、まだまだ大規模とは言えません。それでも高速化なしだと激重でゲームとして成り立つ速度では動かせませんでした。
最初は単純にシーンにスプライトを追加していくわけですが、オブジェクトが増えると動きがカクカクしてきます。フレームレートが下がるんですね。Cocos2d-xは、デフォルトで左下にフレームレートが表示されています。
例:50 | 1フレームのオブジェクト描画数 |
例:0.001 | SPF(秒/1フレーム) 1フレームの描画時間 |
例:30 | FPS(フレーム数/秒)フレームレート |
フレームレートを下げない=高速化する手法としては、各所のアルゴリズムを高速化するほかに、できるだけ画面内の描画を減らすことです。そのため1フレームのオブジェクト描画数を減らして高速化していきます。Cocos2d-xには高速化の仕組みが既に用意されているので、それを利用します。
ちなみにデバッグ情報はsetDisplayStatsで制御します。
- AppDelegate.cpp
CCDirector::sharedDirector()->setDisplayStats(1);
具体的な例
ボール関係
今回のアプリに施した具体的な高速化手順です。
ゲーム画面にはアルファベットのボールが6〜7個表示されます。ボールにはそれぞれ影が落ちており、さらにボール同士が隣接すると矢印が表示され白く光ります。ボール1つにつき、本体と影と矢印とグローの4つが表示される可能性があります。それらを単純にシーンに追加すると、ボール6つで24個のオブジェクトが表示されます。
Cocos2d-xには、同じスプライトを一つのオブジェクトにまとめて高速化するCCSpriteBatchNodeという仕組みがあります。これを利用することで、ボールがいくつでも、本体や影を一回で表示できます。
CCSpriteBatchNode – 同じ画像なら複数でも一回で描画してくれる
CCSpriteBatchNode* glowBatch = CCSpriteBatchNode::create("test.png"); CCSprite* glow = CCSprite::createWithTexture(glowBatch->getTexture()); glowBatch->addChild(glow); addChild(glowBatch);
これでボール関係の描画を24→4つに減らせました。
単語リスト
最初は何も考えずCCLabelTTFを使って単語を表示しました。しかしCCLabelTTFは、作成時にその都度TTFファイルからスプライトを作成するので処理が重く、最初から画像として文字を描画するCCLabelBMFontの方が高速です。フォント用の画像と.fntファイルを作成するには、フリーのShoeBoxが便利です。
CCLabelTTF* label = CCLabelTTF::create("test", "Ariel", 32); CCLabelBMFont* label = CCLabelBMFont::create("test", "list_font.fnt");
CCLabelBMFontを使うことで若干の処理速度の向上に繋がりましたが、それでも単語毎にオブジェクトを作っていくので、このステージの場合、全ての単語が表示されると38個のCCLabelBMFontオブジェクトが表示されることになります。
同じ画像限定で高速化できるCCSpriteBatchNodeは、テクスチャアトラスを利用することで複数の画像に対応することができます。テクスチャアトラスは複数の画像を一つにまとめた物です。テクスチャアトラスで使用する.plistと画像もShoeBoxで作成できます。
CCSpriteFrameCache – テクスチャアトラス
CCSpriteFrameCache* frameCache = CCSpriteFrameCache::sharedSpriteFrameCache(); frameCache->addSpriteFramesWithFile("list_sheet.plist"); CCSpriteBatchNode* batch = CCSpriteBatchNode::create("list_sheet.png"); layer->addChild(batch);
この結果、38個のCCLabelBMFontを一つのオブジェクトにまとめることができました。
こちらのサイトが大変参考になりました。
【Cocos2d-x】CCTextureCacheとCCSpriteFrameCacheを理解してキャッシュを自在にあやつる | Cocoa部
ランキングリスト
ランキングリストも単語リストと同様にCCSpriteBatchNodeで高速化が適用できます。しかし、ゲーム画面の単語リストで表示されうる文字は大文字のA-Zのみですが、ランキングはユーザー名が表示されるので文字数も多く、国旗や偶数列の背景変化など、単語リストに比べやや複雑です。それらを全てCCSpriteBatchNodeで表示しても良いのですが、ランキングはオフスクーンレンダリングという手法で表示することにしました。
オフスクーンレンダリングは、CCLabelBMFontやCCSpriteなどを複数まとめてフレームバッファやテクスチャにレンダリングできます。 この仕組みを利用すれば、複数のオブジェクトを一つにまとめて表示できるので、手軽に高速化できます。
ならば単語リストもオフスクーンレンダリングでいいのでは。という気もしますが、「単語は最初は隠れているが発見されたら表示」というような動的変化がある場合は、オフスクーンレンダリングではやや処理が複雑になるので、今回は動的変化のないランキングで使用しました。
CCRenderTexture – オフスクーンレンダリング
CCRenderTexture* renderTexture = CCRenderTexture::create(size.width, size.height); renderTexture->setPosition(ccp(size.width*0.5, size.height*0.5)); target->addChild(renderTexture); renderTexture->beginWithClear(0,0,0,0); CCSprite* sprite = CCSprite::create("test.png"); sprite->visit(); renderTexture->end();