Cocos2d-x Ver 3.x 画像のドラッグ[ Touch Move](リファレンスカウンタ対応版)


ボタンを押して画像移動をさせます。

このような一連の動きとプロパティマクロを利用してメモリリークを解放するリファレンスカウンタをどのように記述すればよいか画像移動の参考例で基礎をまとめてみました。


Cocos2dx 3.x C++言語

MoveTopScene.hを次のように変更してみてください。

#ifndef __MoveSprite__MoveTopSprite__

#define __MoveSprite__MoveTopSprite__


#include "cocos2d.h"


class MoveTopSprite :public cocos2d::Layer

{

protected:

    // コンストラクタ 初期化

    MoveTopSprite();

    // デストラクタ メモリーリークを解放

    virtual ~MoveTopSprite();

    // メソッド CREATE_FUNCとの連携

    bool init() override;

    

public:

    static cocos2d::Scene* createScene();

    

    CREATE_FUNC(MoveTopSprite);

    

    // _/_/_/ プロパティー _/_/_/

    // 頻繁に他の場所から参照するためメンバー変数としてシーンクラスに保持させる

    // _Player変数と、getPlayer()メソッド、setPlayer(Sprite *)メソッドが自動的に実装される

    CC_SYNTHESIZE_RETAIN(cocos2d::Sprite*, _player, Player);

};



#endif /* defined(__MoveSprite__MoveTopSprite__) */


 

ActionTopScene.cppを次のように変更してみてください。

#include "MoveTopSprite.h"


USING_NS_CC;


// _/_/_/ コンストラクタ プロパティー _/_/_/

MoveTopSprite::MoveTopSprite()

: _player(NULL) // 初期化

{

    

}


// MainScene デストラクタで解放 メモリーリークを防ぐ

// Objective-C deallocと同じ様なもの

MoveTopSprite::~MoveTopSprite()

{

    CC_SAFE_RELEASE_NULL(_player); // _spritereleaseしてメモリーリークを防ぎます

}


// createSceneLayerSceneに貼り付けて返すクラスメソッドです。

// 自分自身(MoveTopSprite)を生成し、空のSceneに貼り付けて返す簡単な処理を行っているだけです。

// これでほかのシーンからの遷移が楽に行えます。

Scene* MoveTopSprite::createScene()

{

    auto scene = Scene::create();

    auto layer = MoveTopSprite::create();

    scene->addChild(layer);

    return scene;

}


bool MoveTopSprite::init()

{

    if (!Layer::init()) {

        return false;

    }

    

    // 初期化処理

    

    // Directorを取り出す

    auto director = Director::getInstance();

    

    //画面サイズを取得

    auto size = Director::getInstance()->getVisibleSize();

    // バックグランドカラー(ブルー)

    auto background = LayerColor::create(Color4B::BLUE,

                                         size.width,

                                         size.height);

    // Layerにバックグランドを追加

    this->addChild(background);

    

    //_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/

    

    // Playerをスプライトとして生成し、setPlayer()を使いメンバー変数にしています

    this->setPlayer(Sprite::create("Icon-304.png"));

    

    //位置を設定

    _player->setPosition(Vec2(680,400));

    

    //画面に追加をしています。

    this->addChild(_player);

    

    //_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/

    

    // シングルタッチモード

    auto listener = EventListenerTouchOneByOne::create();

    

    // ラムダ式

    // onTouchBeganは必須

    // 画面がタッチされたときに呼び出されます

    listener->onTouchBegan = [](Touch* touch, Event* event){

        

        // タッチされた時の処理

        

        // タッチ位置のログ出力

        log("Touch at (%f, %f)", touch->getLocation().x, touch->getLocation().y);

        

        return true; // イベントを実行する falseが返されたときは以後のイベントは実行されません。

    };

    

    

    // タッチされた点が移動したときに毎フレームが呼び出されます

    // []thisが追加されています。ラムダキャプチャ ラムダの中で_player変数を利用している

    listener->onTouchMoved = [this](Touch* touch, Event* event){

        

        /* ラムダキャプチャ

         [=] : 全てのオブジェクトのコピーがラムダ式に渡されます。

         [&] : 全てのオブジェクトの参照がラムダ式に渡されます。

         [obj] :objのコピーがラムダ式に渡されます。

         [&obj]:objの参照がラムダ式に渡されます。

        */

        

        // タッチ中に動いた時の処理

        

        // タッチ位置が動いた時

        // 前回とのタッチ位置との差をベクトルで取得する

        Vec2 delta = touch->getDelta();

        

        // 現在のかわずたんの座標を取得する

        Vec2 position = _player->getPosition();

        

        // 現在座標 + 移動量を新たな座標にする

        Vec2 newPosition = position + delta;

        

        /////////////////////////////////////////

        // キャラクターが画面から飛び出してしまうのを防ぐ

        // 画面サイズを取り出す

        auto winsiz = Director::getInstance()->getWinSize();

        


        if (newPosition.x < 0) { //もし、新しい位置が左端より左だったら


            newPosition.x = 0;   //x座標を0にする


        } else if(newPosition.x > winsiz.width){ //もし、右端より右だったら


            newPosition.x = winsiz.width;   //x座標を画面の横幅にする

        }


        if (newPosition.y < 0) { //もし、新しい位置が左端より左だったら

            

            newPosition.y = 0;   //y座標を0にする

            

        } else if(newPosition.y > winsiz.height){ //もし、右端より右だったら

            

            newPosition.y = winsiz.height;   //x座標を画面の縦幅にする

        }

        

        // "player.png"

        _player->setPosition(newPosition);


    };

    

    // EventListenerEventDispatcherに登録してイベントの処理は完了

    // directorからEventDispatcherを取り出し、EventListenerを登録しています。

    // addEventListenerWithSceneGraphPriorityは、イベントの実行の優先順位を

       //  ノードの重なり順に依存させる登録方法です。

    // 複数のEventListenerが登録されているとき、ノードの重なり順が上の方から優先的

       //  に実行されていきます。

    // 今回はLayer全体でタッチを受け付けているため、thisを指定しています。

    director->getEventDispatcher()->addEventListenerWithSceneGraphPriority

                                    (listener, this);

    return true;

}


※重要

 

リファレンスカウンタ


Cocos2d-xでの開発では、プログラマが手動でメモリー管理を行っていかなくてはなりません。
そのためには、このリファレンスカウンタの仕組みの理解が不可欠です。

autoreleaseの方法

Cocos2d-xにはクラスメソッドとして定義されている、createから始まる関数が用意されています。

これを使用します。

auto node = Node::create();
auto sprite = Sprite::create("image.png");
auto label1 = Label::createWithSystemFont("Action 回転 移動 拡大", "Arial", 80);

auto node = Node::create(); // カウンタ1(autorelease);
// メイングループ終了時にnodeはreleaseされる。

Cocos2d-xでは、標準で実施されているクラスはこの方法でオブジェクトを生成していくことになります。

 

※重要


一時的に使用する変数はプログラマの解放忘れを防ぐため、autoreleaseされたオブジェクトを使用したほうが良いことを示しました。

メイングループを抜けるタイミングでオブジェクトが削除されると他のところでメンバー変数を参照できなくなってしまいます。

実際のコードでは、プログラマがretainを呼び出すことなく、プロパティマクロを利用します。
メンバー変数をどのタイミングでタイミングすればよいか?
ほとんどの場合は、メンバー変数はクラスのデストラクタでreleaseします。

Cocos2d-xには、Refの解放のための便利なマクロが用意されており、一般的にはそれを使用します。

// MainScene デストラクタで解放 メモリーリークを防ぐ
// Objective-C deallocと同じ様なもの
MoveTopSprite::~MoveTopSprite()
{
    CC_SAFE_RELEASE_NULL(_player); // _spriteをreleaseしてメモリーリークを防ぎます
}

CC_SAFE_RELEASE_NULLは、引数を渡した変数がNULLでなければ、releaseを呼び出した後、変数にNULLを格納するマクロです。このマクロを利用することで、安心してオブジェクトを解放することができます。

 

メンバー変数を追加したいときはこのようにプロパティの設定と、初期化構文での初期化、
デストラクタでの解放をワンセットで行ってください

/////////////// ワンセットの記述 ///////////////

MoveTopScene.h

// 他のクラスからnewを使って生成できないようにするためにprotectedに宣言しています。
protected:


    // コンストラクタ 初期化

    MoveTopSprite();

    // デストラクタ メモリーリークを解放
    virtual ~MoveTopSprite();


    // _/_/_/ プロパティー _/_/_/
    // 頻繁に他の場所から参照するためメンバー変数としてシーンクラスに保持させる
    // _Player変数と、getPlayer()メソッド、setPlayer(Sprite *)メソッドが自動的に実装される
    CC_SYNTHESIZE_RETAIN(cocos2d::Sprite*, _player, Player);


ActionTopScene.cpp

// _/_/_/ コンストラクタ プロパティー _/_/_/
MoveTopSprite::MoveTopSprite()
: _player(NULL) // 初期化
{

}

// MainScene デストラクタで解放 メモリーリークを防ぐ
// Objective-C deallocと同じ様なもの
MoveTopSprite::~MoveTopSprite()
{
    CC_SAFE_RELEASE_NULL(_player); // _spriteをreleaseしてメモリーリークを防ぎます
}


//_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/

    // Playerをスプライトとして生成し、setPlayer()を使いメンバー変数にしています
    this->setPlayer(Sprite::create("Icon-304.png"));

    //位置を設定
    _player->setPosition(Vec2(680,400));

    //画面に追加をしています。
    this->addChild(_player);

//_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/

▫️参考にしたページ

【Cocos2d-x】画像のドラッグ判定