Cocos2d-x RU‎ > ‎

Статья 4

Статья 4

Всем привет, в 4-ой статье я продолжу рассказывать о базовых понятиях движка.
Рассмотрим поближе класс CCLayer — как уже говорилось, это объект, который порожден от CCNode и представляет собой слой, который мы можем добавить на сцену. Давайте поговорим о событиях. Но сначала пару слов о переключении сцен. В своих проектах , для переключения сцен я пользуюсь вот таким вызовом: 

Код
pDirector->runWithScene(pScene);

Скажем, когда пользователь из режима заставки переходит к игре и т.п.
Важно понять, что все данные со старой сцены надо полностью почистить, а на новой создать. Есть другие методы переключения сцен, типа popScene и pushScene, но я никогда ими не пользовался и ничего путного здесь не скажу...
Теперь о событиях:
У CCLayer есть несколько событий:
onEnter — Событие срабатывает когда CCLayer получает управление. Иными словами, когда создается сцена, создаются слои, добавляются на сцену и директор запускает на выполнение эту сцену, то у всех слоев на ней срабатывает событие onEnter. Скорее всего, в том порядке, в котором они добавлялись. Но не факт, сами проверте. onEnter, естественно, срабатывает после конструктора слоя.
В этом методе можно проинициализировать данные для слоя, добавить спрайты, создать меню и т. д. 
onEnterTransitionDidFinish() - Это событие похоже на onEnter и срабатывает когда закончился эффект перехода на новую сцену... CCDirector позволяет запускать сцены с различными эффектами, например при переходе на другую сцену можно создать эффект перелистывания страницы и т. д. Эффектов много,я позже приведу пример.


onExit — событие срабатывает когда мы покидаем слой, например переходим на другую сцену. Здесь важно, то что onExit срабатывает до деструктора CCLayer. В этом методе надо чистить за собой (удалять объекты со слоя и все созданные объекты)

Каждый слой может принимать события нажатия на экран.
Чтобы слой мог реагировать на эти события надо вызвать метод setTouchEnabled(true); 
Событий бывает несколько типов:
Пользователь нажал на экран, сработал метод ccTouchesBegan
Пользователь убрал палец с экрана, сработал метод:ccTouchesEnded
Пользователь провел пальцем по экрану, сработал метод:ccTouchesMoved


Я ознакомился с парочкой книг по cocos2d-x, но мне таки не удалось найти примера, как правильно чистить память при переходах с одной сцены, на другую. В этой статье я покажу метод очистки, которым пользуюсь я.

Еще важно знать, что у движка есть свой цикл. В слое можно переопределить метод update(float dt), который будет вызываться на каждом такте движка. В переменной dt — время прошедшее с предыдущего вызова метода. Чтобы включить цикл для слоя во время инициализации необходимо вызвать метод scheduleUpdate();
например в методе onEnter()

Перейдем к кодированию.

Давайте добавим пару файлов в наш проект HelloWorld: 1) Layer1.h, 2) Layer1.cpp. В этих файлах опишем новый слой - Layer1. Классы надо добавлять в группу «Classes», а ресурсы (каринки, звуки и т. д.) надо добавлять в группу Resources. В нашем классе HelloWorld, из прошлой статьи, добавим пункт меню в середину экрана, по нажатию на который мы перейдем к новому слою. А на новом слое Layer1 мы в центр экрана добавим спрайт, по нажатию на который будет переход обратно на слой HelloWorld.
Я подготовил 3 изображения: 


Спрайт в виде кнопки, для возврата на слой HelloWorld
Прикрепленное изображение

Копка для перехода на слой Layer1, когда она не нажата
Прикрепленное изображение

Копка для перехода на слой Layer1, когда на нее тапнули
Прикрепленное изображение


Кстати, забыл сказать про макрос CREATE_FUNC(class_layer_name), - это макрос, на месте которого будет статическая функция для создания слоя, о таких я писал в первой статье. Этот макрос мы видим в объявлении класса — слоя. Вот как он выглядит:
Код
/**
* define a create function for a specific type, such as CCLayer
* @__TYPE__ class type to add create(), such as CCLayer
*/
#define CREATE_FUNC(__TYPE__) \
static __TYPE__* create() \
{ \
    __TYPE__ *pRet = new __TYPE__(); \
    if (pRet && pRet->init()) \
    { \
        pRet->autorelease(); \
        return pRet; \
    } \
    else \
    { \
        delete pRet; \
        pRet = NULL; \
        return NULL; \
    } \
}



Теперь код для Layer1.h

Код
#ifndef HelloWorld_Layer1_h
#define HelloWorld_Layer1_h

#include "cocos2d.h"
using namespace cocos2d;

class Layer1 : public CCLayerColor
{
public:
    static CCScene* scene();
    CREATE_FUNC(Layer1);
    virtual bool init();
private:
    CCSprite* sprButton;
    virtual void onEnter();
    virtual void onExit();
    virtual void onEnterTransitionDidFinish();
    
    virtual void ccTouchesEnded(cocos2d::CCSet* touches, cocos2d::CCEvent* event);
    virtual void ccTouchesBegan(cocos2d::CCSet* touches, cocos2d::CCEvent* event);
    virtual void ccTouchesMoved(cocos2d::CCSet* touches, cocos2d::CCEvent* event);
};

#endif


Посмотрите, думаю в объявлении все понятно? А вот и реализация

Код
#include "Layer1.h"
#include "HelloWorldScene.h"

CCScene* Layer1::scene()
{
    CCScene *scene = CCScene::create();
    Layer1 *layer = Layer1::create();
    scene->addChild(layer,1);
        return scene;
}
bool Layer1::init()
{
    if( !CCLayerColor::initWithColor(ccc4(255, 255, 255, 255)) ) //RGBA
    {
        return false;
    }
    CCSize winSize = CCDirector::sharedDirector()->getWinSize();
    setColor(ccc3(0xf0,0xf0,0xf0));
    sprButton = CCSprite::create("Ris4.png");
    sprButton->setPosition(ccp(winSize.width/2,winSize.height/2));
    addChild(sprButton);   
}

void Layer1::onEnter()
{
    CCLayerColor::onEnter();
    setTouchEnabled(true);
}

void Layer1::onExit()
{
    CCLayerColor::onExit();
    // Удаляем все что добавлено на слой, в том числе и спрайт кнопки
    removeAllChildrenWithCleanup(true);

    // Чистим все что можно
    CCDirector::sharedDirector()->purgeCachedData();
    CCLabelBMFont::purgeCachedData();
    CCAnimationCache::purgeSharedAnimationCache();
    CCSpriteFrameCache::purgeSharedSpriteFrameCache();
    CCTextureCache::purgeSharedTextureCache();
    CCTextureCache::sharedTextureCache()->removeAllTextures();
    CCUserDefault::purgeSharedUserDefault();
}

void Layer1::onEnterTransitionDidFinish()
{
    CCLayerColor::onEnterTransitionDidFinish();
}

void Layer1::ccTouchesBegan(CCSet *touches, CCEvent *event)
{
    //Add a new body/atlas sprite at the touched location
    CCSetIterator it;
    CCTouch* touch;
    
    for( it = touches->begin(); it != touches->end(); it++)
    {
        touch = (CCTouch*)(*it);
        if(!touch) break;
        CCPoint location = touch->getLocationInView();
        location = CCDirector::sharedDirector()->convertToGL(location);
        if (sprButton->boundingBox().containsPoint(location))
        {
            sprButton->setScale(1.2f);
        }
    }
}

void Layer1::ccTouchesMoved(CCSet *touches, CCEvent *event)
{
    CCSetIterator it;
    CCTouch* touch;
    for( it = touches->begin(); it != touches->end(); it++)
    {
        touch = (CCTouch*)(*it);
        if(!touch) break;
        CCPoint location = touch->getLocationInView();
        location = CCDirector::sharedDirector()->convertToGL(location);
    }
}
void Layer1::ccTouchesEnded(CCSet* touches, CCEvent* event)
{
    CCSetIterator it;
    CCTouch* touch;
    for( it = touches->begin(); it != touches->end(); it++)
    {
        touch = (CCTouch*)(*it);
        if(!touch) break;
        CCPoint location = touch->getLocationInView();
        location = CCDirector::sharedDirector()->convertToGL(location);
        
        //  Если мы нажали на кнопку, то уходим на другую сцену
        if (sprButton->boundingBox().containsPoint(location))
        {
            sprButton->setScale(1.0f);
            CCScene* scene = HelloWorld::scene();
            CCDirector::sharedDirector()->replaceScene(scene);

        }
    }
}


Обратите внимание на метод ccTouchesBegan,в нём мы обрабатываем момент нажатия на кнопку, просто увеличиваем её в размере. Методе ccTouchesEnded срабатывает, когда пользователь убирает палец с экрана, здесь мы восстанавливаем размер кнопки, создаем и переходим на сцену для слоя HelloWorld
Еще посмотрите на метод onExit, в нём происходит очистка памяти. Удаляется спрайт кнопки, а затем чистим все кэши движка. 
Теперь модифицируем наш прежний класс HelloWorld
в объявление добавим следующие строки кода:

Код
    void menuGotoLayer(CCObject* pSender);
    virtual void update(float dt);

первый метод, - это функция обратного вызова, у нее должна быть именно такая сигнатура, это событие срабатывает, когда пользователь выбирает пункт меню для перехода на другую сцену. Второй метод, - это такт движка. О нем я писал ранее, в update обычно описываются действия, происходящие в цикле. Например движение и поворот спрайтов, выполнение шага физического движка box2D и.д. 
Вот так выглядит объявление класса HelloWorld

Код
#ifndef __HELLOWORLD_SCENE_H__
#define __HELLOWORLD_SCENE_H__
#include "cocos2d.h"

using namespace cocos2d;

class HelloWorld : public cocos2d::CCLayer
{
public:
    // Method 'init' in cocos2d-x returns bool, instead of 'id' in cocos2d-iphone (an object pointer)
    virtual bool init();
    // there's no 'id' in cpp, so we recommend to return the class instance pointer
    static cocos2d::CCScene* scene();
    // a selector callback
    void menuCloseCallback(CCObject* pSender);
    void menuGotoLayer(CCObject* pSender);
    virtual void update(float dt);
    CCSprite* pBack;
    CCSprite* pWood;
    CCSprite* pFence;    
    void displayPicture();
    // preprocessor macro for "static create()" constructor ( node() deprecated )
    CREATE_FUNC(HelloWorld);
};
#endif // __HELLOWORLD_SCENE_H__


И реализация, с учётом сказанного:
Код
#include "HelloWorldScene.h"
#include "Layer1.h"
#include "SimpleAudioEngine.h"

using namespace cocos2d;
using namespace CocosDenshion;

CCScene* HelloWorld::scene()
{
    // 'scene' is an autorelease object
    CCScene *scene = CCScene::create();
    
    // 'layer' is an autorelease object
    HelloWorld *layer = HelloWorld::create();

    // add layer as a child to scene
    scene->addChild(layer);

    // return the scene
    return scene;
}
void HelloWorld::displayPicture()
{
    CCSize winSize = CCDirector::sharedDirector()->getWinSize();


    pBack = CCSprite::create("back.png");
    pWood = CCSprite::create("wood.png");
    pFence = CCSprite::create("fence.png");
    pBack->setPosition(ccp(winSize.width/2,winSize.height/2));
    pWood->setPosition(ccp(winSize.width/2,pWood->getContentSize().height/2));
    pFence->setPosition(ccp(winSize.width/2,pFence->getContentSize().height/2));

    addChild(pBack,0);
    addChild(pWood,1);
    addChild(pFence,2);

}


// on "init" you need to initialize your instance
bool HelloWorld::init()
{
    CCSize winSize = CCDirector::sharedDirector()->getWinSize();
    //////////////////////////////
    // 1. super init first
    if ( !CCLayer::init() )
    {
        return false;
    }
    displayPicture();
    CCMenuItemImage *pCloseItem = CCMenuItemImage::create(
                                        "CloseNormal.png",
                                        "CloseSelected.png",
                                        this,
                                        menu_selector(HelloWorld::menuCloseCallback) );
    
    CCMenuItemImage *pGotoLayerItem = CCMenuItemImage::create(
                                                          "Ris5.png",
                                                          "Ris6.png",
                                                          this,
                                                          menu_selector(HelloWorld::menuGotoLayer) );
    
    pCloseItem->setPosition( ccp(CCDirector::sharedDirector()->getWinSize().width - 20, 20) );
    pGotoLayerItem->setPosition(ccp(winSize.width/2,winSize.height/2));

    // create menu, it's an autorelease object
    CCMenu* pMenu = CCMenu::create(pCloseItem,pGotoLayerItem, NULL);
    pMenu->setPosition( CCPointZero );
    this->addChild(pMenu, 1);
    
    scheduleUpdate();
    
    return true;
}

void HelloWorld::update(float dt)
{
    CCLog("dt=%f",dt);
}

void HelloWorld::menuCloseCallback(CCObject* pSender)
{
    CCDirector::sharedDirector()->end();

#if (CC_TARGET_PLATFORM == CC_PLATFORM_IOS)
    exit(0);
#endif
}

void HelloWorld::menuGotoLayer(CCObject* pSender)
{
    CCScene* scene = Layer1::scene();
    CCTransitionScene *trans = CCTransitionSlideInR::create(0.3f, scene);//CCTransitionPageTurn::create(0.3f, scene,false);
    CCDirector::sharedDirector()->replaceScene(trans);
   // CCDirector::sharedDirector()->replaceScene(scene);
}


Обратите внимание на метод menuGotoLayer, как раз в нём мы переключаем сцену с эффектом, одна сцена наезжает на другую. Эффектов не мало в движке, есть очень интересные. О них почитайте сами. В методе update просто вывод в консоль значения dt. В методе init создается пункт меню, для перехода.
На этом статья закончена, при наличии времени будет готова следующая.
Comments