scheduleOnceについて

cocos2dxで◯◯秒後にメソッドを実行したい時は、scheduleOnceを用いると便利だがv2系の時と違って、呼び出すメソッドの引数はフロート型でなければならない。

例えば3秒後にあるメソッドを呼びたいときは以下のようでなければならない。

this->scheduleOnece(schedule_selector(HogeScene::FugaMethod),3.0f);

void FugaMethod(float delayTime)
{

}

複数の解像度への具体的な対応法

今回で三回目の複数解像度への対応の話。
実例(cocos2dxによるもの)とともに対応法を2つあげたいと思う。
(ちなみに方法名は僕が勝手につけたので正式名ではありません。)

1.はみ出してもいいよ法

 はみ出してもいいよ法とは、あらかじめはみ出しても良い部分を作っておきResolutionPolicyでNO_BORDERを選ぶことによって、サイズが合わない場合のみはみ出しても良い部分がカットされるという方法である。
実例)ブレイブフロンティア、ドランシアなど
f:id:charisuke:20150419220804p:plain
f:id:charisuke:20150419220821p:plain

利点
  1. 実装がシンプル
  2. ゆがみがない
  3. レイアウトのずれがない
欠点

特定の機種間での対応しかできない(iPhone5,6系とiPhone4系間など)

  1. 使えないスペースができてしまう
  2. バナー広告が使いにくいかも

2.DesignResolutionのアスペクト比をいじる法

 前回の設定ではDesignResolutionSizeは750x1334で固定だったが、それを画面サイズによって変えることで画面内におさめる方法。ResolutionPolicyはSHOW_ALLかEXACT_FIT。画像などのゆがみは発生しないまま画面内に全てを表示することができる。(背景などははみ出す)
実例)ドラクエスーパーライト、FFレコードキーパーなど(おそらく)

利点
  1. 見た目がとてもきれい
  2. ゆがみがない
欠点
  1. アスペクト比は変わるが画像のサイズの比は変わらないため、画像を配置する際に気をつけないとはみ出たり、ずれたり、重なってしまう
  2. 画面サイズによって見た目が異なる
  3. ずれないようにするにはプログラムを組む際にコツが必要


こんな風に画面を作っているつもりでも
f:id:charisuke:20150419230732p:plain




他のサイズで見るとこんな風になってたり。。。
f:id:charisuke:20150419230734p:plain



上の例は極端な場合だが、プレイ画面が小さくなってしまうことは事実である。
プログラムを組む際に細心の注意を払い、場合によっては画像のリサイズをしたり、リソースを分けたりしなければならない。基本的に絶対位置ではなく相対位置で画面をレイアウトする必要がある。また、異なるアスペクト比でどのような画面になってるかを細かくチェックする必要がある。

3,まとめ

とりあえず今のところ分かった方法はこれくらい。他に分かり次第追加したい。

どの端末でも全く同じ画面というのは不可能である。作りたいものに合わせて取捨選択していく必要がある。できるだけ違和感がなく実装も楽な方法を考えていきたい。

ローカライズ(多言語化)

cocos2dxでのローカライズ(多言語化)について

世界に向けてアプリを配信するのに欠かせないのがローカライズ。今回は特にアプリ内で使用するテキストを端末の言語に合わせて表示する方法をまとめた。
ちなみに、あまりローカライズする部分がない場合は以下の方法が役に立つ。tf.hateblo.jp

1,方向性

CSV形式で「"KEY","ENGLISH","JAPANESE"」のように保存したテキストデータをパースして連想配列に突っ込んでおく、という方向性にした。
突っ込む先はシングルトンクラスにすることでどこからでも呼び出せるようにした。

2,実装

LocalizeManager.h
#ifndef __LocalizeManager__
#define __LocalizeManager__

#include "cocos2d.h"
#include <unordered_map>

//短縮用のマクロ
#define TEXT(__KEY)(LocalizeManager::getInstance()->getStringForKey(__KEY))

USING_NS_CC;

class LocalizeManager
{
private:
    //コンストラクタ
    LocalizeManager();
    
    //自身のインスタンスを保存しておく
    static LocalizeManager* mLocalizeManager;
    
    //文字列を格納する連想配列
    std::unordered_map<std::string, std::string> stringMap;
    
    //初期化
    void initialize();
public:
    //インスタンスを返すスタティックメソッド
    static LocalizeManager* getInstance();
    
    //keyを使って文字列を読み出す
    std::string getStringForKey(std::string key);
};

#endif 
LocallizeManager.cpp
#include "LocalizeManager.h"

using namespace std;

LocalizeManager* LocalizeManager::mLocalizeManager = NULL;

//コンストラクタ
LocalizeManager::LocalizeManager()
{
    
}

//自身のインスタンスを返す、もしインスタンスが存在しない場合はコンストラクタを呼び出しnewする
LocalizeManager* LocalizeManager::getInstance()
{
    if (mLocalizeManager == NULL) {
        mLocalizeManager = new LocalizeManager();
        mLocalizeManager->initialize();
    }
    
    return mLocalizeManager;
}

//値の初期化、テキストファイルの読み込み
void LocalizeManager::initialize()
{
    //言語の取得
    LanguageType lang = Application::getInstance()->getCurrentLanguage();
    if (lang != LanguageType::JAPANESE) {
        //日本語以外は英語として扱う
        lang = LanguageType::ENGLISH;
    }
    
    //テキストデータを読み込む
    auto fileUtiles = FileUtils::getInstance();
    string data = fileUtiles->getStringFromFile("language.csv");
    if (data.length() == 0) {
        log("can't get string data");
        return;
    }
    
    
    //パースを行う
    istringstream is(data); //テキストデータから文字列ストリームを作成
    string csvLine; //行
    while (std::getline(is, csvLine)) { //一行ずつ読み込む
        istringstream csvStream(csvLine);
        string csvCol; //列の項目
        int cntCol = 1; //項目番号
        string key; //keyを一時保存しておく
        while (std::getline(csvStream, csvCol, ',')) { //一項目ずつ読み込む
            switch (cntCol) {
                case 1:
                    key = csvCol;
                    if (stringMap.count(key) != 0) {
                        log("key:\"%s\"が重複しています", key.c_str());
                    }
                    break;
                case 2:
                    if (lang == LanguageType::ENGLISH) {
                        stringMap[key] = csvCol;
                    }
                    break;
                case 3:
                    if (lang == LanguageType::JAPANESE) {
                        stringMap[key] = csvCol;
                    }
                    break;
                default:
                    break;
            }
            cntCol++; //項目番号のincrement
        }
    }
}

//keyを使って文字列を読み込む
string LocalizeManager::getStringForKey(std::string key)
{
    if (stringMap.count(key) != 0) {
        return stringMap.at(key);
    }else
    {
        log("指定されたキーが存在しません");
        return "";
    }
}

3,使い方

language.csv
hello,Hello,こんにちは
bye,See you,さようなら

をリソースに追加しておく。

hogeScene.cpp
#include "LocalizeManager.h"

void makeLabel()
{
    string text = LocalizeManager::getInstance()->getStringForKey("hello");
    auto hogeLabel = Label::createWithSystemFont(text, "FONT_NAME", font_size);
    this->addchild(hogeLabel);
}

いちいちLocalizeManager::getInstance()->getStringForKey("key")と打つのはめんどくさいので、.hで定義したマクロを使って

    string text = TEXT("hello");
    auto fugaLabel = Label::createWithSystemFont(TEXT("bye"), "FONT_NAME", font_size);

とかすると楽。

4.まとめ

  • keyについては重複しないように注意しなければならない。重複しているとlogにメッセージが表示されるので確認すること。
  • 少しいじれば、他のテキストファイルをload、unloadできるはず。
  • CSVファイルとパース部分に新しい言語を追加すれば簡単に対応言語を増やせるはず。

複数の解像度に対応する〜ResolutionPolicy〜

前回に続いてcocos2dxでの解像度対応について。

前回では解像度に合わせて画像リソースを変更するやり方について書いたが、今回は異なるアスペクト比の機種に対しての対応の仕方についてまとめた。

1.主なアスペクト比

iOS端末のアスペクト比(横幅:縦幅)は

  • iPhone4系(2:3) = 1.5
  • iPad系(3:4) = 1.33

である。Android端末についてもほとんどがこのどれかだろう。

iPad系とiPhone5,6系ではおよそ1.3倍もの差がある。iPhoneiPadを同時に開発する際には注意しないとかなりのずれやゆがみができてしまう。

2.ResolutionPolicy

異なるアスペクト比に対してどのように画面サイズを合わせるかを設定するのがResolutionPolicyである。ResolutionPolicyの設定は前回AppDelegate.cpp内のapplicationDidFinishLaunching()内で追加した、

glview->setDesignResolutionSize(designResolutionSize.width, designResolutionSize.height, ResolutionPolicy::SHOW_ALL);

で行う。第1,2引数では、どのアスペクト比で画面を作成するかを設定する。今回ではiPhone6の解像度を用いている。
第3引数のResolutionPolicy::SHOW_ALLの部分は主に3種類ある。例を挙げてそれぞれを説明する。今回は以下の750x1334iPhone6サイズの画像をiPhone4Sで表示した。
f:id:charisuke:20150418210036p:plain


1 SHOW_ALL

SHOW_ALLはアスペクト比を保ったまま、画面内に全てが表示できるように調節する。ゆがみがなく、はみ出す部分もないが何も表示されない黒いエリアが左右にできてしまう。
f:id:charisuke:20150418210124p:plain



2 NO_BORDER

NO_BORDERはアスペクト比を保ったまま、画面に黒いふちが出ないように調節する。ゆがみがなく、画面全体に表示されているが、よく見ると上下が画面外にはみ出てしまっている。
f:id:charisuke:20150418210456p:plain



3 EXACT_FIT

EXACT_FITは画面全体に表示できるよう画像を引き延ばして表示する。はみ出す部分もなく黒い縁もないが、左右に引き延ばされてしまっている。
f:id:charisuke:20150418210700p:plain


3.まとめ

ResolutionPolicyにはそれぞれ一長一短がある。様々な機種で同じような画面を表示するには、歪ませてもいいのか、はみ出してもいいのかなどを開発を始める前に決めておくことが重要だと思う。開発が進んだ後からどうにかしようとすると、画像の配置などを直さなくては行けなくなり困ることになる。

複数の解像度へ対応する

cocos2dxでの複数の解像度への対応のやり方
(今回はiPhoneについてのみ触れているが、Android機種にも対応できるはずである)

1.解像度に合わせて複数の大きさの画像を用意する

 一種類の画像を引き延ばして解像度対応を行うとiPhone6plusなどの解像度が大きい機種では画像がぼやけてしまう。かといってiPhone6plusの解像度に合わせた画像をiPhone5の解像度に合わせて縮小して表示するとメモリがもったいない。
 そこで、iPhone6以下で使用する画像と、iPhone6 plusやiPadで使用する画像の2種類を用意する。
*なおiPhone3G,iPhone3GSはシェアがほとんどないと考えたため今回は考慮しない。

各機種の解像度については以下のサイトを参考にした。
iOS - iPhone/iPad解像度早見表 - Qiita


  1. ProjectのResourcesディレクトリに新しく「M」ディレクトリと「L」ディレクトリを作成する。
  1. 「L」ディレクトリにはiPhone6Plusの解像度を基準とした画像を追加する。(iPhone6Plusは実際には1080x1920サイズにリサイズされるので、1080x1920サイズで作成する。)
  1. 「M」ディレクトリにはiPhone6の解像度を基準とした画像を追加する。(この際、Lサイズの画像を縮小させて画像を作ると楽。)
2.作成したディレクトリをXcode上で追加する。

Xcodeのproject navigator上でResourcesに「L」「M」ディレクトリを追加する。
この際、create folder referenceにチェックをつけておく。つけておけば青色のフォルダが追加されるはずである。
(create folder referenceはディレクトリの階層構造を参照できるようにするためである)

3.Classesにヘッダーファイルを追加する

 先ほど作ったディレクトリを使うためにClassesに新しく「AppMacro.h」を追加する。
追加したAppMacro.hを以下のように書き換える。

#ifndef _AppMacro_H_
#define _AppMacro_H_

#include "cocos2d.h"

typedef struct tagResource
{
    cocos2d::Size size;
    char directory[100];
}Resource;

static Resource mediumResource = { cocos2d::Size( 750, 1334), "M" };
static Resource largeResource  = { cocos2d::Size(1080, 1920), "L" };

static cocos2d::Size designResolutionSize = cocos2d::Size(750, 1334);

//画面サイズに応じてフォントのサイズを変更する
#define FONT_SIZE (cocos2d::Director::getInstance()->getOpenGLView()->getDesignResolutionSize().width / mediumResource.size.width * 48)

#endif
4.AppDelegate.cppにL、Mどちらのリソースを使用するかを選択するコードを追加する

AppDelegate.cppに先ほど作ったAppMacro.hをincludeしてからapplicationDidFinishLaunching()に以下のようにコードを追加する。

bool AppDelegate::applicationDidFinishLaunching() {
    // initialize director
    auto director = Director::getInstance();
    auto glview = director->getOpenGLView();
    if(!glview) {
        glview = GLViewImpl::create("My Game");
        director->setOpenGLView(glview);
    }
    

    //↓追加分/////////////
    //デザインサイズの設定
    glview->setDesignResolutionSize(designResolutionSize.width, designResolutionSize.height, ResolutionPolicy::NO_BORDER);
    
    auto frameSize = glview->getFrameSize();
    
    std::vector<std::string> searchPath;
    
    if (frameSize.width > mediumResource.size.width) {
        //「L」ディレクトリのリソースを使用
        searchPath.push_back(largeResource.directory);
        director->setContentScaleFactor(MIN(largeResource.size.height / designResolutionSize.height, largeResource.size.width / designResolutionSize.width));
    }
    else
    {
        //「M」ディレクトリのリソースを使用
        searchPath.push_back(mediumResource.directory);
        director->setContentScaleFactor(MIN(mediumResource.size.height / designResolutionSize.height, mediumResource.size.width / designResolutionSize.width));
    }
    
    //リソースディレクトリを指定
    FileUtils::getInstance()->setSearchPaths(searchPath);
    
    //↑追加分///////

    
    // turn on display FPS
    director->setDisplayStats(true);

    // set FPS. the default value is 1.0/60 if you don't call this
    director->setAnimationInterval(1.0 / 60);
    
    // create a scene. it's an autorelease object
    auto scene = GameScene::createScene();

    // run
    director->runWithScene(scene);

    return true;
}
5.以上の手順で解像度対応ができる

後は以下のように解像度を意識しないでスプライトやラベルを使える。fontsizeの指定は48pointを基準に倍率で指定する。

auto hogeSprite = Sprite::create("hoge.png");

auto fugaLabel = Label::create("text", "Font Name", FONT_SIZE * 1.5);

ResolutionPolicyについては後日記事を書くことにする。

Actionの再生スピードを変更する

cocos2dxでActionの再生スピードを変更したいときは、Speedクラスを使用する。
cocos2d-x: Speed Class Reference

使い方
    auto hogeAction = MoveBy::create(1.0f, Vec2(100,100));
    
    //Speed::create(スピードを変更したいAction, float 再生速度);
    auto speed = Speed::create(hogeAction, 1.0f);
    
    auto fuganode = Node::create();

    fuganode->runAction(speed);

    //setSpeed(float speed) で再生スピードをsetできる
    speed->setSpeed(2.0f);
    //getSpeed()で現在の再生スピードをgetできる
    float currentSpeed = speed->getSpeed();

speed > 1.0 でより速く
speed < 1.0 でゆっくり再生できる。

SpeedクラスはIntervalActionではないので、Speedクラスのアクションと他のアクションとをつなげて再生するSequenceは使えないらしいので注意。


キャラクターの走るスピードに合わせて、走るアニメーションの再生スピードを動的に変更するなどすれば、より表現を増すことができると思う。

LayerGradientを動的にグラデーションさせる

LayerGradientクラスを継承して、時間とともにグラデーションが変わっていくDynamicGradientLayerクラスを作成した。

 

f:id:charisuke:20150416012115p:plain

f:id:charisuke:20150416012118p:plain

f:id:charisuke:20150416012117p:plainf:id:charisuke:20150416012114p:plain

時間とともに少しずつ色が変わっていく。

メンバ変数の値を変更すれば色の濃さや、変わる早さを調節できる。

 

gist826a8570f11678c0cb12