この記事は公開されてから1年以上経過しているため、情報が古い可能性がございます。
ご注意ください。

このWordPress pluginを配布しています

アニメーションクラス

私はcocos2d-x 2.xの書籍を参考に3.x系で開発してましたが、書籍の通りにAnimationというクラスを作成したところビルドエラーが発生するようになりました。

2.x系から3.x系のバージョンアップではクラスプリフィックスのCCがカットされましたが、その一環でCCAnimateAnimateにリネームされたために、クラス名が重複してしまったのですね。

自作したクラスをGameAnimationにリネームして回避します。
気づくまでに時間がかかってしまいました。

子ノードの検索

子ノードを検索するメソッドにGetChildByNamegetChildByTagがありますが、対象が自分の子のみということを見落としていました。
子供の子供を検索することはできないのですね。

子要素を再帰的に検索すると簡単に処理コストが上がってしますからね。

サウンド

サウンドのフェード

1作目アプリではSimpleAudioEngineをそのまま利用していますが、このクラスは名称通り機能が非常にシンプル。

タイトルからゲームに遷移する際にBGMがブツ切りの状態を回避するためフェードアウトを入れたかったのですが、とても簡単に実現することができない感じ。。。
そのため、以下のMusicFadeクラスを利用いたしました。
ありがとうございました。

Music Fader for Cocos2dx | fuguelike
http://www.fuguelike.com/music-fader-for-cocos2dx/

SEの連続再生

android端末でSEの同時再生数は5です。MAX_SIMULTANEOUS_STREAMS_DEFAULT’で定義されています。

1作目アプリTapShingyoではSEを連続で再生しており、連打すると音が途切れるようになってしまったため、対策を入れました。


TapShingyoのゲーム部分では以下のSEを再生しています。

  1. 木魚
  2. 読み上げ
  3. 鐘の音(3秒)

音が低くボイスにかき消されちゃってますが、木魚の音、入っているんですねー。
3. 鐘の音は一定数進んだ場合に再生するもので、ゴーーーンというお寺の鐘のようなSEです。

1タップごとに「木魚と読み上げボイス」の2SEが再生されるため、素早く3つタップするだけでデフォルトの5をオーバーします。
「木魚と読み上げボイス」が途切れても、もともとが短い音なのでそれほど違和感がありません。

しかし、長い音の「鐘の音」が「ゴー」くらいで途切れてしまい非常にかっこ悪くなってしまったため、ボイスの音を再生するためには直前のボイスをSTOPするような処理でごまかしました。

void VoiceManager::play(std::string word)
{
    if (m_seId > 0)
    {
        SimpleAudioEngine::getInstance()->stopEffect(m_seId);
    }
    std::string fileName = "voice/"+ word +".ogg";
    m_seId = SimpleAudioEngine::getInstance()->playEffect( fileName.c_str() );
}

ボイス再生時にIDをメンバに保持しておいて、次にボイスの再生指示があった場合にstopEffectに与える、というシンプルな処理です。

タップしやすくする

1作目アプリは「正解をタップする」アプリですが、デバッグ段階で「タップしているつもりなのに反応しない」状態が続き、イラつくことがありました。

art-cocos-tap-rect-extend

真ん中が「画像の多きさ==タップ可能領域」を表しており、実装書記はこの状態でタップを判定していました。

このアプリは「同時に1列しかタップできない」ルールです。
つまり上下のSpriteが反応する必要はないため、タップ領域が重複しても問題ないのですね。

そのため、タップ領域を拡張してほかのSpriteとかぶることを許容することで改善を図りました。

Rect CardSprite::getRect()
{
    int width  = this->getContentSize().width;
    int height = this->getContentSize().height;
    auto pos   = this->getPosition();

    // 領域を上方に広げ、タップしやすくする
    return Rect(
        pos.x - (width  / 2),
        pos.y - (height / 2),
        width,
        height + (height / 4)
    );
}

Spriteの拡張クラスを用意し、自分自身の大きさを返すgetRectを実装しました。
Nodeの位置とコンテンツの大きさからRectを生成します。

この時Rectの第4引数をちょっとだけ大きくすることで実装を行いました。

ゲームの高速化

TapShingyoは「親ノードとして空のSpriteを作成し、その中にSpriteを敷き詰める」実装を行っています。

実装当初、親ノードをMoveByで移動した場合に「子Spriteの判定がついてこなくて、タップ位置がずれる」という意図しない挙動になってしまいました。

これは、以下にも記載しましたね。

[cocos2d-x]WindowsでAndroidアプリ開発日誌4:Sprite関係の処理
TapShingyoは多数のSpriteから正解のものをタップしていく単純なゲーム次世代お経アプリです。 続編アプリです...

そのため子Spriteをループで1つずつ移動するという処理を行っていました。
コードは以下です。

Sprite* spr;
for (auto child : parent->getChildren()) {
    spr = (Sprite*)child;
    spr->runAction(MoveBy::create(0.1f, Point(0, -1 * cardSize.height)) );
}

当然、子Spriteの数が多ければパフォーマンスが悪化しますので、動作にもたつきが感じられるようになりました。

タップ判定は前述のとおり「子Sprite->getPosition()」を行っていましたが、親要素内で移動していませんのでgetPositionの値が変化しないことは当然のことでした。

こちらは、「子Sprite->getPosition()」に親ノードの位置を加味することで解決しました。

SpriteSheetシートの作成

日本語に対応していない端末で問題なく日本語が出るのか」を検証することができず、Spriteを工夫することで対処しました。
Spriteを工夫、、、つまり地道に1文字1文字を画像化しSpriteSheetを利用することで対処しました。

※正直SpriteSheetについてはもっと楽にで対応できるのだと思いますが、アプリ実装については知識不足のために愚直に作業をこなしました。。。。

SpriteSheet

SpriteSheetは1枚の画像と、画像に対応する.plistから成っています。
.plistはXMLに、画像のキー名、座標を定義しているファイルです。

以下に.plistのサンプルを示します。

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
    <dict>
        <key>frames</key>
        <dict>
            <key>char/E3/E38182.png</key>
            <dict>
                <key>frame</key>
                <string>{{0,0},{96,96}}</string>
                <key>offset</key>
                <string>{0,0}</string>
                <key>rotated</key>
                <false />
                <key>sourceSize</key>
                <string>{96,96}</string>
            </dict>
            <key>char/E3/E38184.png</key>
            <dict>
                <key>frame</key>
                <string>{{96,0},{96,96}}</string>
                <key>offset</key>
                <string>{0,0}</string>
                <key>rotated</key>
                <false />
                <key>sourceSize</key>
                <string>{96,96}</string>
            </dict>

手作業でこれを用意ことはほぼ無理のため、おとなしくツールを使います。

画像を用意

Phothoshop(Elements)でこんな画像を作ります。

art-cocos-ts-charsheet1

フォントに青柳衡山フォントTを利用していますが、般若心経でも出力されない文字がありましたので、似た漢字から手作業で合成しました。

青柳衡山フォントT
http://opentype.jp/aoyagikouzanfontt.htm

画像をSpriteSheet化する

1枚の画像をSpriteSheet化する方法が全く分からず試行錯誤しました。

画像をカットする

今回はじめに1枚の画像に文字を敷き詰めた画像を用意しました。

CocosStudioで1枚の画像からSpriteSheetを作成する方法が不明(たぶんできない)でしたため、バラバラの画像を用意することに。

PhotoShopのアクション(マクロ)を使えば、1文字1枚の画像を作成することはたぶん簡単なんですよね?
Elementsではアクションの作成に対応していませんでしたので、人力で何とかします。

E-cutterというWindows用のフリーソフトを利用して、1枚画像に座標を指定して1つずつ切り出します。

art-cocos-ts-e-cutterE-cutterの画面イメージ

切り出し後の画像は元の画像名_[y]_[x]というパターンで命名されますが、これだと文字と画像の関連が把握が難しいため、任意の画像名にリネームしました。
これも地道な作業です。

SpriteSheet作成

バラ画像が準備できれば、あとはCocosStudioを利用してSpriteSheet化するだけです。
CocosStudioでのSpriteSheet作成は以下に記事をアップ済みです。
ぜひご覧ください。

[cocos2d-x]Cocos Studioを使おう
Webでもアプリでも、レイアウト調整って地味で生産性が低く、どちらかというとあまりやりたくない作業だったりしますね。 だからこそ...

もう一工夫

SpriteSheetは「1辺の最大が4096」ですが、4096に対応しておらず読み込めない端末が存在するようで、実質の最大サイズは2048×2048です。

1作目アプリは画像が膨大な数でになってしまったために、1シート2048×2048では収まらなくなってしまいました。
何とかして画像を分類し、複数のSpriteSheetに分離していく必要がありましたが、その基準はUTF8の文字コードとしました。

文字コードは「あ」なら「E38182」です。
文字コードの先頭2文字でグループ分けしたところ、点数は増えましたが、すべてのSpriteSheetを2048×2048に収めることができました。

幸い、内部ではすべて文字コードで文字を管理していたため、この作業の負担は少なかったです。

Unicode対応 文字コード表
http://ash.jp/code/unitbl21.htm

UnityでSpriteSheetが簡単に作成できる?

art-cocos-ts-charsheet22

こんな画像を用意して、Unityにドロップします。
また、文字同士の隙間を透明にしておくと、自動で抽出できますよ。

次にInspectorのSpreite ModeをMultipleに変更し、Sprite Editorからスライスを実行します。

art-cocos-ts-unity-slice

スライス後、1文字ずつに分かれていることが確認できます。
次に、Unityを閉じ、assetsディレクトリに移動してカット元の画像を枠ナシのものに差し替えます。

ディレクトリを覗くと.plistもしっかり出力されていて、簡単に1枚画像からSpriteSheetを作成することができました。

しかし、.plistがCocosStudioで生成したものと差があったり、画像のキーを任意にできなかったため、「これじゃあダメだ」と気づき、実際にcocos2d-xで使用できるかの検証は行いませんでした。
ごめんなさい。

なお、最初に枠あり(文字の下に四角)を用意しておく理由は、文字以外透明の状態ですと、Unity側でギリギリまで画像の大きさを詰めるため、大きさがマチマチになってしまいきれいな出力にならないためです。

引数付きでシーンを生成する

シーン作成時にレベルなどの引数を受け取ることができるようにします。

GameScene.h

public:
    virtual bool init(int level);
    static cocos2d::Scene* createScene(int level);
    static GameScene* create(int level);

GameScene.cpp

Scene* GameScene::createScene(int level)
{
    auto scene = Scene::create();
    scene->addChild(GameScene::create(level));

    return scene;
}

GameScene* GameScene::create(int level)
{
    GameScene *pRet = new GameScene();
    if (pRet && pRet->init(level))
    {
        pRet->autorelease();
        return pRet;
    }
    else
    {
        delete pRet;
        pRet = NULL;
        return NULL;
    }
}

bool GameScene::init(int level)
{

ちょっと面倒ですね。

広告の実装

広告にAdMobを実装します。
既にたくさんの記事がありますが、私は以下を参考にしてその通りに実装しました。

cretia studio
Cocos2d-x 3.5 AdMobのバナー広告を実装する(iOS, Android)
http://studio.cretia.net/blog/344

AdMobを組み込むと必然的に対象AndroidOSバージョンがアップしてしまいます。
Android2.3以降でいいかなと思っていたのですが、手元に端末もなくデバッグのできないのでAndroid4以降を対象に変更します。

以下を参考に変更を加えることで解決しました。

Slowly Days
AndroidでAdmobを使用してエラーが出る場合の対処法
http://www.slowlydays.net/wordpress/?p=30

Twitterに投稿する

SDKがあるようですが、それを読み解く余裕がありませんでしたので、ツイート文言を指定したURLを作成してTwitterのWebページに飛ばすという実装を行いました。

std::string url = "https://twitter.com/intent/tweet?url=[url]&hashtags=[tag]&text=[text]";
Application::getInstance()->openURL(url);
スポンサーリンク
ad_336
ad_336
  • このエントリーをはてなブックマークに追加
  • Evernoteに保存Evernoteに保存