「Cocos2d-x」カテゴリーアーカイブ

【COCOS2D-X】アイコン画像を配置する。

画像を配置するのは簡単なんですが、

その配置位置を調整するのがめちゃくちゃ大変だったりします。

    auto Button = Sprite::create("btnChara.png");
    if (Button == nullptr)
    {
        problemLoading("'btnChara.png'");
    }
    else
    {
        Button->setPosition(Vec2(sprite->getPosition().x + sprite->getContentSize().width * scaleRate / 2 + origin.x, visibleSize.height + origin.y));
        Button->setAnchorPoint(Vec2(1.0,1.0));
        Button->setScale(3.0);
        this->addChild(Button, 1);
    }

まず、setAnchorPoint()についてですが、

これは画像位置の基準となるポイントを設定する関数ですね。

デフォルトでは、アンカーポイントは中心(0.5, 0.5)の位置にあり、これを左下にするには(0, 0)、右上にするには(1, 1)と設定します。

今回は画像の右上を、背景の右上に合わせたいので、(1.0 , 1.0)、すなわち、画像のアンカーポイントを右上に設定しています。

次に背景画像の右上の座標を調べる必要があるのですが、

getContentSize()でspriteのサイズがわかりますが、

これはsetScale()で拡大する前の値になっていました。

なので、getContentSize()の値に拡大率を計算式に入れることで、見事に位置が合いました。

あとは、ちょうど良い感じで画像の大きさを調整。

もう少し大きくしても良いかな?

用意したアイコンを全部配置して、バランスを取ってみたいと思います。

cocos2d-xのコーディングしてて思ったこと。(ガベコレの話)

C#やJavaにはガベージコレクションと言って、使用しなくなったオブジェクトを自動的に解放してくれる仕組みがありまして、

だからnewしたオブジェクトの使用後を意識しなくても使用できるんですが、

C++にはそんな機能は無く、

newしたオブジェクトは明示的にdeleteしないと、そのオブジェクトは消えません。

で、cocos2d-xはspriteなどを作成するときにnewを使用せずにcreate()メソッドを使用しているんです。

そして、サンプルを見る限りではdeleteは行っていない。

これはcocos2d-xのプラットフォームの中で独自にガベージコレクション的な動きをしています。

ただ、cocos2d-xのガベコレとC#やJavaのガベコレとは少しロジックが異なるようです。

詳しい内容はこの記事を見て欲しく

http://furicotech.blogspot.com/2015/03/memory-management-in-cocos2d-x.html

まぁ、この内容をまとめると、

cocos2d-xのspriteなどのオブジェクトは1回使い切り。(その都度createしなければならない。)

これ、自分もコーディングしててハマりました。

あると思っていたspriteのポインタが無効なポインタになっていた、と言うことが多々ありました。

C#と同じ感覚で使用することができないんですね。

なので、クラス設計をどうするか、少し悩んでいます。

spriteなどの部品はシーンクラスの中で全部処理させた方が良いかもしれない。

【cocos2d-x】タッチした場所にエフェクトを入れる

よくスマホゲームって、タッチした場所にアニメーションを入れてエフェクトかけてるじゃないですか。

あれを実装して見たいと思います。

今回もフリー素材のぴぽや倉庫さんから素材を拝借しました。

すでに、タッチ処理のやり方もアニメーションのやり方も知っているので、これを組み合わせます。

コードはこうなりました。

bool HelloWorld::init()
{

  中略

    auto listener1 = EventListenerTouchOneByOne::create();
    listener1->onTouchBegan = CC_CALLBACK_2(HelloWorld::onTouchBegan, this);
    _eventDispatcher->addEventListenerWithSceneGraphPriority(listener1, this);

    return true;
}

bool HelloWorld::onTouchBegan(cocos2d::Touch* touch, cocos2d::Event* event)
{
    auto anime = Sprite::create("pipo-btleffect007.png");
    if (anime == nullptr)
    {
        problemLoading("'pipo-btleffect007.png'");
    }
    else
    {
        this->addChild(anime, 0);
    }
    float frameHeight = anime->getContentSize().height;
    float frameWidth = anime->getContentSize().width / 14.0;
    Vector<SpriteFrame*> animFrames;
    animFrames.reserve(14);
    for(int i = 0; i < 14; i++) {
        animFrames.pushBack(SpriteFrame::create("pipo-btleffect007.png", Rect(frameWidth * i,0,frameHeight,frameWidth)));
    }
    Animation* animation = Animation::createWithSpriteFrames(animFrames, 0.02f);
    Animate* animate = Animate::create(animation);

    anime->setPosition(touch->getLocation().x, touch->getLocation().y);
    anime->runAction(animate);

    return true;
}

前回のタッチ処理でラムダ式を使いましたが、

C++のラムダ式は思った以上に使いづらいので

普通にコールバックとして関数を定義し、マクロで登録する形にしました。

アニメーション画像は14枚横に並んでいるので、この画像を横に14等分割にクリッピングして使用しています。

あとはアニメーション画像の位置をタップ位置に設定してアニメーションを実行します。

前回はanimateをforever(無限ループ)していましたが、今回は1回きりのアニメーションなので、animateをそのまま使用しています。

どちらも親クラスがActionクラスなので、問題ありません。

【COCOS2D-X】背景画像を設置。

画像はWeb検索して、見つけたフリー素材を使用しました。

で、これ多分、普通に表示するだけだと、画面からはみ出してしまうので。

これを上手く画面に収まるようにしたい。

見た感じ、画像の高さがはみ出ているので、高さの表示を画像に合うように拡大率を計算して、画像を縮小させたいと思います。

    auto sprite = Sprite::create("ID003_Western-Castle_noon.jpg");
    if (sprite == nullptr)
    {
        problemLoading("'ID003_Western-Castle_noon.jpg'");
    }
    else
    {
        // position the sprite on the center of the screen
        sprite->setPosition(Vec2(visibleSize.width/2 + origin.x, visibleSize.height/2 + origin.y));

        // add the sprite as a child to this layer
        this->addChild(sprite, 0);
    }
    auto scaleRate = visibleSize.height / sprite->getContentSize().height;
    sprite->setScale(scaleRate);

    auto str = String();
    str.appendWithFormat("width %f height %f", sprite->getContentSize().width, sprite->getContentSize().height);
    label->setString(str.getCString());

この画像の範囲をベースにして、画面を作っていきましょうか。

あ、画面をタップしたときにエフェクトかかるようにしたいなぁ。

次回やります。

【Cocos2d-x】Windowsとスマホで画像サイズが変わる件について

こちらの中で、スマホで表示すると画像サイズが変わるような事を書きましたが。

分かった。

sprite->getContentSize()で画像のサイズを取得することができるのですが、

Windowsとスマホではこの値が変わる。

なので、

クリッピングする時はPixel値を直接入力するのでは無くて、

このsprite->getContentSize()で取得した値を元にクリップするサイズを指定しなければならない。

    auto sprite = Sprite::create("pipo-charachip001b.png");
    if (sprite == nullptr)
    {
        problemLoading("'pipo-charachip001b.png'");
    }
    else
    {
        // position the sprite on the center of the screen
        sprite->setPosition(Vec2(visibleSize.width/2 + origin.x, visibleSize.height/2 + origin.y));

        // add the sprite as a child to this layer
        this->addChild(sprite, 0);
    }
    sprite->setScale(3.0);
    auto str = String();
    str.appendWithFormat("width %f height %f", sprite->getContentSize().width, sprite->getContentSize().height);
    label->setString(str.getCString());

    double partswidth = sprite->getContentSize().width / 3.0;
    double partsheight = sprite->getContentSize().height / 4.0;
    Vector<SpriteFrame*> animFrames;
    animFrames.reserve(12);
    animFrames.pushBack(SpriteFrame::create("pipo-charachip001b.png", Rect(0,0, partswidth, partsheight)));
    animFrames.pushBack(SpriteFrame::create("pipo-charachip001b.png", Rect(partswidth * 1,0, partswidth, partsheight)));
    animFrames.pushBack(SpriteFrame::create("pipo-charachip001b.png", Rect(partswidth * 2,0, partswidth, partsheight)));

    Animation* animation = Animation::createWithSpriteFrames(animFrames, 0.1f);
    Animate* animate = Animate::create(animation);

    sprite->runAction(RepeatForever::create(animate));

    return true;

あとはプラットフォームに合わせて拡大すれば良い。

解決。

【Cocos2d-x】フリック処理を行う

もう一つやりたいのはフリック処理。

使用するハンドラはonTouchBeganとonTouchEndedの二つ。

タッチ開始の座標(sx,sy)とタッチリリースの座標(ex,ey)から、フリックの方向を求めるのですが、

上下左右の4方向だけだったら値の大小だけで良いかもしれませんが、

今回はアナログスティックの様な360°フリックの方向を出したいので、

ここでは三角関数を使用します。

アークタンジェント(tanの逆関数)を使用すれば、y/xから求めることができます。

関数名はatan2()です。

class SampleScene : public cocos2d::Scene
{
private:
    cocos2d::Label* label;
    cocos2d::Vec2 startPos, endPos;
    auto listener1 = EventListenerTouchOneByOne::create();
    listener1->onTouchBegan = [this](Touch* touch, Event* event)->bool
    {
        startPos = touch->getLocation();
        return true;
    };
    listener1->onTouchEnded = [this](Touch* touch, Event* event)->bool
    {
        endPos = touch->getLocation();
        double angle = atan2(endPos.y - startPos.y,endPos.x - startPos.x) * 180.0 / M_PI;
        auto str = String();
        str.appendWithFormat("angle %f", angle);
        label->setString(str.getCString());
        return true;
    };
    _eventDispatcher->addEventListenerWithSceneGraphPriority(listener1, this);

atan2が返す値の単位はラジアンなので、これに×180/PIとすることで角度に変換できます。

よし、これで必要なことは一通りできたかな。

【COCOS2D-X】タッチ処理を使用する。

さて、今回はタッチ処理を試してみたいと思います。

タッチ処理はスマホ専用の処理なので、Windowsでは動かないので、スマホで動作確認します。

で、タッチした座標を画面に表示させたいと思います。

    auto listener1 = EventListenerTouchOneByOne::create();
    listener1->onTouchBegan = [this](Touch* touch, Event* event)->bool
    {
        auto str = String();
        str.appendWithFormat("Touch (%f %f)", touch->getLocation().x, touch->getLocation().y);
        label->setString(str.getCString());
        return true;
    };
    _eventDispatcher->addEventListenerWithSceneGraphPriority(listener1, this);

タッチ処理はイベントリスナーを使用するのですが、

EventListenerTouchOneByOneとEventListenerTouchAllAtOnceがあるのですが、前者はシングルタッチ、後者はマルチタッチのようです。

使用するハンドラ名が若干違うようです。

今回はシングルタッチを使用します。

で、今回初めてC++のラムダ式を使ったのですが、

[]はキャプチャといって、ラムダ式内に持っていく変数を指定するみたいなんですよね。

座標を表示するラベルなんですが、

ローカルにLabelオブジェクトを定義して[&label]って書いてもエラーで動かなかったので、

今回はサンプルコードに従って、labelはprivate変数に定義し、キャプチャには[this]とします。

こうすることでprivateのlabelオブジェクトを使用することができました。

あとは、引数touchからx,y座標をラベルに表示させる。

スマホだからか、画像のクリッピング処理がWindowsとは動きが違ってたんですよね。

ちょっとここは要調査ですわ。

もしかしたら、Windows用とスマホ用に画像を2種類用意しないといけないかもしれん。

【Cocos2d-x】画像を使用する

今回は画像を使ってみます。

画像はスプライトと呼ばれる単位で扱われます。

例えば、ただ画面に画像を表示する場合は、

bool SampleScene::init()
{
    //////////////////////////////
    // 1. super init first
    if ( !Scene::init() )
    {
        return false;
    }

    auto visibleSize = Director::getInstance()->getVisibleSize();
    Vec2 origin = Director::getInstance()->getVisibleOrigin();

    // add "HelloWorld" splash screen"
    auto sprite = Sprite::create("pipo-charachip001b.png");
    if (sprite == nullptr)
    {
        problemLoading("'pipo-charachip001b.png'");
    }
    else
    {
        // position the sprite on the center of the screen
        sprite->setPosition(Vec2(visibleSize.width/2 + origin.x, visibleSize.height/2 + origin.y));

        // add the sprite as a child to this layer
        this->addChild(sprite, 0);
    }
    return true;
}

画像をResources/res配下に設置し、

Sprite::create(“[画像ファイル名]”)スプライトの作成。

setPosition()で表示位置を決めてaddChild()でスプライトを設置すれば表示されます。

でも大抵は一枚の絵の中にいくつものパーツがまとめられているのが普通です。

なので、画像をクリッピングして、一部だけを表示させます。

    // add "HelloWorld" splash screen"
    auto sprite = Sprite::create("pipo-charachip001b.png", Rect(0,0,32,32));
    if (sprite == nullptr)
    {
        problemLoading("'pipo-charachip001b.png'");
    }
    else
    {
        // position the sprite on the center of the screen
        sprite->setPosition(Vec2(visibleSize.width/2 + origin.x, visibleSize.height/2 + origin.y));

        // add the sprite as a child to this layer
        this->addChild(sprite, 0);
    }

    auto sprite2 = Sprite::create("pipo-charachip001b.png", Rect(0,32,32,32));
    if (sprite2 == nullptr)
    {
        problemLoading("'pipo-charachip001b.png'");
    }
    else
    {
        // position the sprite on the center of the screen
        sprite2->setPosition(Vec2(visibleSize.width/2 + origin.x + sprite->getContentSize().width, visibleSize.height/2 + origin.y));

        // add the sprite as a child to this layer
        this->addChild(sprite2, 0);
    }
    return true;

じゃあ、これを使って、アニメーションさせます。


    auto sprite = Sprite::create("pipo-charachip001b.png");
    if (sprite == nullptr)
    {
        problemLoading("'pipo-charachip001b.png'");
    }
    else
    {
        // position the sprite on the center of the screen
        sprite->setPosition(Vec2(visibleSize.width/2 + origin.x, visibleSize.height/2 + origin.y));

        // add the sprite as a child to this layer
        this->addChild(sprite, 0);
    }

    Vector<SpriteFrame*> animFrames;
    animFrames.reserve(12);
    animFrames.pushBack(SpriteFrame::create("pipo-charachip001b.png", Rect(0,0,32,32)));
    animFrames.pushBack(SpriteFrame::create("pipo-charachip001b.png", Rect(32,0,32,32)));
    animFrames.pushBack(SpriteFrame::create("pipo-charachip001b.png", Rect(64,0,32,32)));
    animFrames.pushBack(SpriteFrame::create("pipo-charachip001b.png", Rect(0,32,32,32)));
    animFrames.pushBack(SpriteFrame::create("pipo-charachip001b.png", Rect(32,32,32,32)));
    animFrames.pushBack(SpriteFrame::create("pipo-charachip001b.png", Rect(64,32,32,32)));
    animFrames.pushBack(SpriteFrame::create("pipo-charachip001b.png", Rect(0,64,32,32)));
    animFrames.pushBack(SpriteFrame::create("pipo-charachip001b.png", Rect(32,64,32,32)));
    animFrames.pushBack(SpriteFrame::create("pipo-charachip001b.png", Rect(64,64,32,32)));
    animFrames.pushBack(SpriteFrame::create("pipo-charachip001b.png", Rect(0,96,32,32)));
    animFrames.pushBack(SpriteFrame::create("pipo-charachip001b.png", Rect(32,96,32,32)));
    animFrames.pushBack(SpriteFrame::create("pipo-charachip001b.png", Rect(64,96,32,32)));

    Animation* animation = Animation::createWithSpriteFrames(animFrames, 0.1f);
    Animate* animate = Animate::create(animation);

    sprite->runAction(RepeatForever::create(animate));

    auto moveBy = MoveBy::create(2, Vec2(50, 0));
    sprite->runAction(moveBy);

    return true;

SpriteFrameを組み合わせてAnimationを作成し、それからAnimateを作成します。

これからアクションを作成して、実行させます。

なかなか良い感じじゃないですか?

【cocos2d-x】シーンを追加する。

今日はもう一個やったので、まとめておく。

シーンを追加する場合。

Cocos2d-xでは1シーンにつき1クラスと考えまして、

今回はSampleSceneを追加してみます。

C++の場合はクラスを記入したヘッダーファイルと、処理本体を記述したソースファイル本体を作成します。

中身は、とりあえずHelloWorldとほぼ同じにして、最初にSampleSceneを表示するようにします。

class SampleScene : public cocos2d::Scene
{
public:
    static cocos2d::Scene* createScene();

    virtual bool init();
    
    // a selector callback
    void menuCloseCallback(cocos2d::Ref* pSender);
    
    // implement the "static create()" method manually
    CREATE_FUNC(SampleScene);
};
#include "SampleScene.h"
#include "SimpleAudioEngine.h"

USING_NS_CC;

Scene* SampleScene::createScene()
{
    return SampleScene::create();
}

// Print useful error message instead of segfaulting when files are not there.
static void problemLoading(const char* filename)
{
    printf("Error while loading: %s\n", filename);
    printf("Depending on how you compiled you might have to add 'Resources/' in front of filenames in HelloWorldScene.cpp\n");
}

// on "init" you need to initialize your instance
bool SampleScene::init()
{
    //////////////////////////////
    // 1. super init first
    if ( !Scene::init() )
    {
        return false;
    }

    auto visibleSize = Director::getInstance()->getVisibleSize();
    Vec2 origin = Director::getInstance()->getVisibleOrigin();

    // add "HelloWorld" splash screen"
    auto sprite = Sprite::create("HelloWorld.png");
    if (sprite == nullptr)
    {
        problemLoading("'HelloWorld.png'");
    }
    else
    {
        // position the sprite on the center of the screen
        sprite->setPosition(Vec2(visibleSize.width/2 + origin.x, visibleSize.height/2 + origin.y));

        // add the sprite as a child to this layer
        this->addChild(sprite, 0);
    }
    return true;
}


void SampleScene::menuCloseCallback(Ref* pSender)
{
    //Close the cocos2d-x game scene and quit the application
    Director::getInstance()->end();

    /*To navigate back to native iOS screen(if present) without quitting the application  ,do not use Director::getInstance()->end() as given above,instead trigger a custom event created in RootViewController.mm as below*/

    //EventCustom customEndEvent("game_scene_close_event");
    //_eventDispatcher->dispatchEvent(&customEndEvent);


}
    // create a scene. it's an autorelease object
    auto scene = SampleScene::createScene();

    // run
    director->runWithScene(scene);

    return true;
}

ただ、C言語やっている人なら分かると思いますが、これだけじゃあビルドしてくれません。

通常はmakefileを弄るのですが、WindowsはVisualStudio、AndroidはAndroid Studioでビルドするので、

VisualStudioの場合

プロジェクト名\proj.win32\プロジェクト名.vcxproj

のここに追加するファイルを記載する。

  <ItemGroup>
    <ClCompile Include="..\Classes\AppDelegate.cpp" />
    <ClCompile Include="..\Classes\HelloWorldScene.cpp" />
    <ClCompile Include="..\Classes\SampleScene.cpp" />
    <ClCompile Include="main.cpp" />
  </ItemGroup>
  <ItemGroup>
    <ClInclude Include="..\Classes\AppDelegate.h" />
    <ClInclude Include="..\Classes\HelloWorldScene.h" />
    <ClInclude Include="..\Classes\SampleScene.h" />
    <ClInclude Include="main.h" />
  </ItemGroup>

Androidの場合

プロジェクト名\CMakeLists.txt

のここに追加するファイルを記載する。

# add cross-platforms source files and header files 
list(APPEND GAME_SOURCE
     Classes/AppDelegate.cpp
     Classes/HelloWorldScene.cpp
     Classes/SampleScene.cpp
     )
list(APPEND GAME_HEADER
     Classes/AppDelegate.h
     Classes/HelloWorldScene.h
     Classes/SampleScene.h
     )

Mac/iOSはXcodeのメニューから追加すれば良いと思う。(知らんけど)

こういうシーンの追加って、cocosコマンドでなんとかできないんですかね?

シーンに限らず、クラス追加するときも同じか。

めんどくさい。

【Cocos2d-x】ラベルでディスプレイ情報などを表示する。

前回の記事でシーンの内容は大体分かった。

ちょっと気になるのはvisibleSizeとoriginの値。

じゃあ、visibleSizeとoriginの値をラベルに表示させちゃおう。

    auto str = String();
    str.appendWithFormat("visible (%f %f)", visibleSize.width, visibleSize.height);
    auto label = Label::createWithTTF(str.getCString(), "fonts/msgothic.ttc", 24);
    if (label == nullptr)
    {
        problemLoading("'fonts/msgothic.ttc'");
    }
    else
    {
        // position the label on the center of the screen
        label->setPosition(Vec2(origin.x + visibleSize.width/2,
                                origin.y + visibleSize.height - label->getContentSize().height));

        // add the label as a child to this layer
        this->addChild(label, 1);
    }

    auto str2 = String();
    str2.appendWithFormat("origin (%f %f)", origin.x, origin.y);
    auto label2 = Label::createWithTTF(str2.getCString(), "fonts/msgothic.ttc", 24);
    if (label2 == nullptr)
    {
        problemLoading("'fonts/msgothic.ttc'");
    }
    else
    {
        // position the label on the center of the screen
        label2->setPosition(Vec2(origin.x + visibleSize.width/2,
                                origin.y + visibleSize.height - label->getContentSize().height * 2));

        // add the label as a child to this layer
        this->addChild(label2, 2);
    }

フォントについて

フォントはResource/fontsフォルダに拡張子ttfファイルが置いてあると思いますが、

Label::createWithTTF()をコールするときにフォントファイルを指定します。

Windowsだったらフォントはc:\windows\fontsがあるので、ここにあるttf/ttcファイルをここに置けば使用することができます。

文字列について

文字列はcocos2dx::Stringというクラスが存在するらしい。

これを使った方がいろいろと便利なので、これを使用する。

addChild()の第二パラメータ

これはzIndexとあったので、重ねて表示する場合、上に表示する順番を示すパラメータですね。

数字が大きい方が上に表示されるみたいです。

Windowsでの表示結果。

visibleは画面のサイズ、originは原点の座標のようです。

Windowsはこれでいいのですが、Android(pixel4a)の場合はこうなりました。

Pixel4aはちょっと横長なので、heightが少し小さいようです。

アスペクト比が異なり、アスペクト比は長辺が基準なので、その分heightが小さいのですね。

あと、左下にピンホールカメラがあるので、その分だけ、originのy座標が少し上になっていますね。

特殊ディスプレイ、嫌い。

まぁ、この点はどうするか後で考えよう。