Search code examples
c++polymorphismcocos2d-xvirtual

Cocos2d-x Polymorphism


I am currently working on subdividing my cocos2dx-cpp game into a more modular system. I want to have one layer to receive all Touches and direct those touches to the affected CCSprite-derived objects.

The derived objects are stored in a CCArray in an EntityManager (which helps me create and manage the entities).

The problem I am facing is that I can't seem to access the correct virtual method for my derived CCSprites.

Here is the code from my Touch layer (called TouchManager):

void TouchManager::ccTouchesBegan( cocos2d::CCSet* pTouches , cocos2d::CCEvent* pEvents )
{
    cocos2d::CCSetIterator i;
    cocos2d::CCTouch* touch;
    cocos2d::CCPoint tap;

    auto entities = EntityManager::sharedManager()->getVisibleEntities();

    for ( i = pTouches->begin() ; i != pTouches->end() ; ++i )
    {
        touch = ( cocos2d::CCTouch* ) ( *i );
        if ( touch )
        {
            tap = touch->getLocation();

            for ( unsigned int entityIndex = 0 ; entityIndex < entities->size() ; ++entityIndex )
            {
                auto entity = entities->at( entityIndex );
                // OLD: auto entity = ( TouchableSprite* )entities->objectAtIndex( entityIndex );
                if ( entity->boundingBox().containsPoint( tap ) )
                {
                    entity->setTouch( touch );
                    entity->onTouch( tap );
                }
            }
        }
    }
}  

I want to have the TouchManager detect the entity that has been touched, and send the Touch to it. But there is my problem: it detects the touch but doesn't send it further. Either I have a crash or nothing at all.

I have created a Touchable interface class:

#include "cocos2d.h"

class Touchable : public cocos2d::CCSprite
{
    cocos2d::CCTouch* m_pTouch;
public:
    virtual cocos2d::CCTouch* getTouch();
    virtual void setTouch( cocos2d::CCTouch* touch );

    virtual void onTouch( cocos2d::CCPoint location ) = 0 ;
    virtual void onMoved( cocos2d::CCPoint location ) = 0 ;
    virtual void onEnded( cocos2d::CCPoint location ) = 0 ;

};

as well as a TouchableSprite base class:

#include "cocos2d.h"
#include "Touchable.h"

class TouchableSprite : public Touchable
{
    //cocos2d::CCTouch* m_pTouch;
public:
    //virtual cocos2d::CCTouch* getTouch();
    //virtual void setTouch( cocos2d::CCTouch* touch );

    static TouchableSprite* createSpriteWithFile( const char* fileName );

    void resetPosition( float positionX = 0.0f , float positionY = 0.0f );


    virtual void onTouch( cocos2d::CCPoint location ) ;
    virtual void onMoved( cocos2d::CCPoint location ) ;
    virtual void onEnded( cocos2d::CCPoint location ) ;


    TouchableSprite(void);
    ~TouchableSprite(void);
};

with simple implementation (TouchableSprite.cpp):

#include "TouchableSprite.h"


TouchableSprite::TouchableSprite(void)
{
}


TouchableSprite::~TouchableSprite(void)
{
}

TouchableSprite* TouchableSprite::createSpriteWithFile( const char* fileName )
{
    auto sprite = new TouchableSprite();

    if ( sprite && sprite->initWithFile( fileName ) )
    {
        sprite->autorelease();
        return ( TouchableSprite* )sprite;
    }

    CC_SAFE_DELETE( sprite );

    // should not reach this point
    return NULL;
}

void TouchableSprite::resetPosition( float positionX , float positionY )
{
    this->setPosition( ccp( positionX , positionY ) );
}

void TouchableSprite::onTouch( cocos2d::CCPoint location )
{
}

void TouchableSprite::onMoved( cocos2d::CCPoint location )
{
}

void TouchableSprite::onEnded( cocos2d::CCPoint location )
{
}

And finally, here's my derived class (in this case, ControlStickSprite):

#include "cocos2d.h"
#include "RenderSystem.h"
#include "EntityManager.h"
#include "TouchableSprite.h"

class ControlStickSprite : public TouchableSprite
{
    ControlStickSprite* m_sprite;

public:
    cocos2d::CCNode* create( cocos2d::CCNode* parent ); 

    void onTouch( cocos2d::CCPoint location ) ;
    void onMoved( cocos2d::CCPoint location ) ;
    void onEnded( cocos2d::CCPoint location ) ;

    ControlStickSprite(void);
    ~ControlStickSprite(void);
};

with simple implementation for testing (skipping the Create part because it works):

void ControlStickSprite::onTouch( cocos2d::CCPoint location )
{
    this->setScale( 0.5f );
}

void ControlStickSprite::onMoved( cocos2d::CCPoint location )
{
    this->setPosition( location );
}

void ControlStickSprite::onEnded( cocos2d::CCPoint location )
{
}

Please help me get this working! I'm not too familiar with the usage of virtual methods so maybe I missed something there. I'm also relatively new to C++ and cocos2dx programming.

Thanks in advance!

EDIT: Thanks to @musikov for fixing the first part! I updated the above code to reflect the changes. I replaced the CCArray with std::vector< TouchableSprite* > to eliminate the need for casting the from CCObject*.

Now, I am facing the problem that when touched, ControlStickSprite::onTouch() is never chosen; it's always TouchableSprite::onTouch(). Added ControlStickSprite::create and EntityManager::createEntity methods:

My ControlStickSprite::create() method is like this:

ControlStickSprite* ControlStickSprite::create( cocos2d::CCNode* parent )
{
    // auto parent = this->getParent();
    auto entityType = "control-stick";
    auto scale = 6.0f;
    auto rotation = 0.0f;
    auto positionX = RenderSystem::sharedRenderSystem()->getScreenWidth() * 0.9f ;
    auto positionY = RenderSystem::sharedRenderSystem()->getScreenHeight() * 0.25f ;


    auto sprite = EntityManager::sharedManager()->createEntity( 
        parent , 
        entityType , 
        scale , 
        rotation , 
        positionX , 
        positionY 
        );
    m_sprite = ( ControlStickSprite* )sprite;

    return m_sprite;
}

which makes use of my EntityManager:

cocos2d::CCNode* EntityManager::createEntity( cocos2d::CCNode* parent , const char* entityType , float scale , float rotation , float positionX , float positionY )
{
    std::string extension = ".png";
    std::string fileName = entityType + extension;
    auto entity = TouchableSprite::createSpriteWithFile( fileName.c_str() );

    entity->setRotation( rotation );
    entity->setScale( scale );
    entity->resetPosition( positionX , positionY );

    parent->addChild( entity );
    // add to VisibleEntities vector
    this->addEntity( entity , true );

    return entity;
}

The only thing I can think of is that the createEntity() method creates a TouchableSprite* but returns a CCNode*, which I then cast to a ControlStickSprite*. Am I doing this wrong again? :)

Thanks for all your help!


Solution

  • You have wrong create method implementation in TouchableSprite and maybe in ControlStickSprite too. Your create method creates Sprite instance and casts it to TouchableSprite class. It's completely wrong :) That's why your program crashed on setTouch method call - because your calling this method in Sprite instance. You need to change your create method:

    TouchableSprite* TouchableSprite::createSpriteWithFile( const char* fileName )
    {
        auto sprite = new TouchableSprite();
    
        if ( sprite && sprite->initWithFile( fileName ) )
        {
            sprite->autorelease();
            return sprite;
        }
    
        CC_SAFE_DELETE( sprite );
    
        // should not reach this point
        return NULL;
    }
    

    Added controlstick implementation

    ControlStickSprite.h

    #include "cocos2d.h"
    #include "RenderSystem.h"
    #include "EntityManager.h"
    #include "TouchableSprite.h"
    
    class ControlStickSprite : public TouchableSprite
    {
    public:
        static ControlStickSprite* create();
    
        bool init();
    
        void onTouch( cocos2d::CCPoint location ) ;
        void onMoved( cocos2d::CCPoint location ) ;
        void onEnded( cocos2d::CCPoint location ) ;
    
        ControlStickSprite(void);
        ~ControlStickSprite(void);
    };
    

    ControlStickSprite.cpp

    bool ControlStickSprite::init()
    {
        auto entityType = "control-stick";
        std::string extension = ".png";
        std::string fileName = entityType + extension;
    
        if (initWithFile( fileName.c_str() )) {
            // auto parent = this->getParent();
            auto scale = 6.0f;
            auto rotation = 0.0f;
            auto positionX = RenderSystem::sharedRenderSystem()->getScreenWidth() * 0.9f ;
            auto positionY = RenderSystem::sharedRenderSystem()->getScreenHeight() * 0.25f ;
    
            sprite->setRotation( rotation );
            sprite->setScale( scale );
            sprite->resetPosition( positionX , positionY );
    
            return true;
        }
        return false;
    }
    
    ControlStickSprite* ControlStickSprite::create()
    {
        auto sprite = new ControlStickSprite();
    
        if ( sprite && sprite->init() )
        {
            sprite->autorelease();
    
            return sprite;
        }
    
        CC_SAFE_DELETE( sprite );
    
        // should not reach this point
        return NULL;
    }
    

    EntityManager:

    void EntityManager::addEntity( cocos2d::CCNode* parent , TouchableSprite* entity )
    {
        parent->addChild( entity );
        // add to VisibleEntities vector
        this->addEntity( entity , true );
    }
    

    Remember to call EntityManager::addEntity(parent, entity) after ControlStickSprite::create(), if you decide to use this solution