「cocos2d-x」虚拟摇杆实现(2)

上一篇文章「cocos2d-x」虚拟摇杆实现(1)讲了如何实现一个虚拟摇杆,但是这种方法实现的虚拟摇杆存在以下问题:

  1. 适用于屏幕与背景相同的情况,如果屏幕比背景小,要实现滚屏时,咱们的控制器虚拟摇杆就滑出屏幕了,之后游戏完全失去了控制。

本次采用了分层设计,将虚拟摇杆放在了HudLayer层,在屏幕滚动时,虚拟摇杆的位置会保持不变。

以下的视频演示了2种方法实现虚拟摇杆的差异:

http://www.56.com/u50/v_OTE2ODY1MzU.html

程序设计最重要的思想和设计模式,而不是掌握了多少API和函数,这一点深有体会:

  • 初次在实现滚屏又不丢失摇杆时,我根据玩家的位置算出来摇杆的相对位置,然后实时地更改摇杆的位置,但是相对于屏幕来说,摇杆还是有滑出屏幕的可能,而且会在屏幕上浮动,位置不固定,用户体验极差;
  • 利用分层模式,一个层为游戏背景层,专门显示背景图,再加一个HUD层,HUD层上面放游戏菜单、玩家的血条,虚拟摇杆等
  • 在实现分层模式时,2个层之间要传递玩家的坐标,我在这里卡了很久,起初想在一个层中得到上层节点,然后再根据tag向下找,结果失败告终,几个类互相包含时,头文件错综复杂,用了#pragma once指令后,又引发了CC_SYNTHESIZE等宏定义的错误,总之,在2个CCLayer之间交换数据花了很多时间,VS中一堆莫名其妙的错误,以下为当时的代码。
//获取GameLayer层的实例_gamelayer,这种方法行不通,还是在2个类的父类中进行数据交换吧
    //GameScene * _gamescene = (GameScene *)(CCDirector::sharedDirector()->getRunningScene());
    //GameLayer * _gamelayer = _gamescene->getGameLayer();
    //_player->setPosition(_gamelayer->getHeroBorn());

2个节点之间的数据交互,建议将2个CCLayer封装为一个Scene或CCLayer类的数据成员,然后由父节点调用,这样可以很方便地完成数据交换。如下代码轻易地完成了hudlayer和gamelayer2个CCLayer之间玩家坐标的数据交换:

        _gamelayer = GameLayer::create();        //创建GameLayer
        this->addChild(_gamelayer, GameLayerTag, GameLayerTag);

        _hudlayer = HudLayer::create();            //创建HudLayer
        this->addChild(_hudlayer, GameLayerTag, HudLayerTag);
        
        //设置玩家出生位置
        _hudlayer->getPlayer()->setPosition(_gamelayer->getHeroBorn());

 

本次的例子进行了分模块设计,如下图所示:

GameLayer是游戏背景层,实现了游戏TMX地图的加载及地图滚动

HudLayer是玩家信息层,实现了虚拟摇杆实时控制玩家在地图中移动

GameScene是游戏场景层,把GameLayer和HudLayer作为实现变量封装进去

因为我们实现了自己的场景类,所以AppDelegate.cpp中默认的Helloword场景需要改成我们的。

// create a scene. it's an autorelease object
    //CCScene *pScene = HelloWorld::scene();
    CCScene *pScene = GameScene::create();

    // run scene
    pDirector->runWithScene(pScene);

 

GameScene类的声明代码

#include "HudLayer.h"
#include "GameLayer.h"
#include "cocos2d.h"


using namespace cocos2d;

class GameScene : public cocos2d::CCScene
{
public:
    virtual bool init();
    GameScene();
    ~GameScene();
    void update(float dt);

    CREATE_FUNC(GameScene);
    CC_SYNTHESIZE(GameLayer *, _gamelayer, GameLayer);
    CC_SYNTHESIZE(HudLayer *, _hudlayer, HudLayer);
    
};

GameScene类的实现代码

#include "GameScene.h"

GameScene::GameScene()
{
    _gamelayer = NULL;
    _hudlayer  = NULL;
}

GameScene::~GameScene()
{

}

void GameScene::update(float dt)
{
    _gamelayer->setViewpointCenter(_hudlayer->getPlayer()->getPosition());
}

bool GameScene::init()
{
    bool bRet = false;
    do
    {
        //先初始化父类,如果失败则break循环
        CC_BREAK_IF(! CCScene::init());

        _gamelayer = GameLayer::create();        //创建GameLayer
        this->addChild(_gamelayer, GameLayerTag, GameLayerTag);

        _hudlayer = HudLayer::create();            //创建HudLayer
        this->addChild(_hudlayer, GameLayerTag, HudLayerTag);
        
        //设置玩家出生位置
        _hudlayer->getPlayer()->setPosition(_gamelayer->getHeroBorn());

        this->scheduleUpdate();

        bRet = true;

    }while(0);

    return bRet;
}

GameLayer的声明

#include "cocos2d.h"

using namespace cocos2d;

class GameLayer : public cocos2d::CCLayer
{
    public:
    GameLayer();            //构造函数声明
    ~GameLayer();            //析构函数声明
    CREATE_FUNC(GameLayer);    //类似于cocos2d中的node方法,返回一个autorelease对象
    virtual bool init();    //本类对象初始化
    bool InitTmxMap();        //初始化TMX地图

    //设置视图位置
    void setViewpointCenter(cocos2d::CCPoint position);        
    
    //cocos2d::CCTMXTiledMap *_TiledMap;
    CC_SYNTHESIZE(cocos2d::CCPoint , _heroborn, HeroBorn);
    CC_SYNTHESIZE(cocos2d::CCTMXTiledMap *, _tiledmap, TiledMap);
    CC_SYNTHESIZE(cocos2d::CCTMXLayer*, _background, Background);
    
};

GameLayer的实现

#include "GameLayer.h"

GameLayer::GameLayer()
{
    _tiledmap = NULL;
    _background = NULL;
}

GameLayer::~GameLayer()
{

}

bool GameLayer::init()
{
    bool bRet = false;
    do
    {
        //先初始化父类,如果失败则break循环
        CC_BREAK_IF(! CCLayer::init());

        InitTmxMap();

        bRet = true;

    }while(0);

    return bRet;
}

bool GameLayer::InitTmxMap()
{
    bool bRet = false;
    do
    {
        //创建瓦片地图
        this->setTiledMap(CCTMXTiledMap::create("pd_tilemap1.tmx"));
        _tiledmap->setAnchorPoint(ccp(0.0f, 0.0f));        //设置瓦片地图的锚点为屏幕正中间
        _tiledmap->setPosition(ccp(0, 0));                //设置瓦片地图的位置为屏幕左下角
        this->setBackground(_tiledmap->layerNamed("Layer1"));
        this->addChild(_tiledmap, 2);        //加入场景层

        //获取Objects对象层
        CCLOG("_tileMap %d", _tiledmap->retainCount());
        CCTMXObjectGroup *objects = _tiledmap->objectGroupNamed("Objects");
        CCLOG("_tileMap %d", _tiledmap->retainCount());
        CC_BREAK_IF(!objects);    //如果失败,退出

        //获取SpawnPoint的字典(包含坐标,高和宽等信息)
        CCDictionary *spawnPoint = objects->objectNamed("SpawnPoint");
        CC_BREAK_IF(!spawnPoint);    //如果失败,退出

        //分别取x和y坐标的值
        int x = spawnPoint->valueForKey("x")->intValue();
        int y = spawnPoint->valueForKey("y")->intValue();

        this->setHeroBorn(ccp(x, y));    //设置玩家出生坐标

        bRet = true;

    }while(0);

    return bRet;
}

void GameLayer::setViewpointCenter(cocos2d::CCPoint position)
{
    CCSize winSize = CCDirector::sharedDirector()->getWinSize();
    int x = MAX(position.x, winSize.width / 2);
    int y = MAX(position.y, winSize.height / 2);
    x = MIN(x, (_tiledmap->getMapSize().width * _tiledmap->getTileSize().width) - winSize.width / 2);
    y = MIN(y, (_tiledmap->getMapSize().height * _tiledmap->getTileSize().height) - winSize.height / 2);
    CCPoint actualPosition = ccp(x, y);

    CCPoint centerOfView = ccp(winSize.width / 2, winSize.height / 2);
    CCPoint viewPoint = ccpSub(centerOfView, actualPosition);

    this->setPosition(viewPoint);
}

HudLayer的声明

#include "cocos2d.h"

typedef enum
{
    HudLayerTag = 2,
    GameLayerTag =1,
    
}GameLayersTags;

using namespace cocos2d;

class HudLayer : public cocos2d::CCLayer
{
public:
    HudLayer();                //构造函数声明
    ~HudLayer();            //析构函数声明
    CREATE_FUNC(HudLayer);    //类似于cocos2d中的node方法,返回一个autorelease对象
    virtual bool init();    //本类对象初始化
    void InitJoystick();    //初始化摇杆
    void InitHero();            //初始化主角精灵
    void menuCloseCallback(CCObject* pSender);

    //触摸事件处理
    virtual void ccTouchesBegan(CCSet *pTouches, CCEvent *pEvent);
    virtual void ccTouchesMoved(CCSet *pTouches, CCEvent *pEvent);
    virtual void ccTouchesEnded(CCSet *pTouches, CCEvent *pEvent);

    float heronsformula(float x1, float y1, float x2, float y2, float x3, float y3);  
    bool triangleContainPoint(float x1, float y1, float x2, float y2, float x3, float y3, float px, float py);  

    void setPlayerPosition(cocos2d::CCPoint position);        //设置玩家位置

    CC_SYNTHESIZE(cocos2d::CCSprite*, _player, Player);

    //CC_SYNTHESIZE();
private:
    void Moving(float dt);
    cocos2d::CCSprite *joystick;
    cocos2d::CCSprite *joystick1;
    cocos2d::CCSize size;
  
    cocos2d::CCPoint O;        //中心点O  
    float R;                //大圆半径  
    
    //精灵移动的速度分量值  
    float speedX;  
    float speedY;  

    //是否移动的标志  
    bool IsMoving;  
    
};

HudLayer的实现

#include "Hudlayer.h"

HudLayer::HudLayer()
{
    //初始化需要的变量
    IsMoving = false;
    speedX = speedY = 0;
}

HudLayer::~HudLayer()
{

}

bool HudLayer::init()
{
    bool bRet = false;
    do
    {
        //先初始化父类,如果失败则break循环
        CC_BREAK_IF(! CCLayer::init());

        //创建一个关闭按钮菜单置于屏幕右下角
        CCMenuItemLabel *txtmenItem = CCMenuItemLabel::create(CCLabelTTF::create("Exit", "Arial", 24), this, menu_selector(HudLayer::menuCloseCallback));
        CC_BREAK_IF(! txtmenItem);
        txtmenItem->setPosition(ccp(CCDirector::sharedDirector()->getWinSize().width - txtmenItem->getContentSize().width/2, 20));
        CCMenu* pMenu = CCMenu::create(txtmenItem, NULL);
        pMenu->setPosition(CCPointZero);
        CC_BREAK_IF(! pMenu);

        // Add the menu to HelloWorld layer as a child layer.
        this->addChild(pMenu, 10);

        this->InitHero();                //初始化精灵
        this->setTouchEnabled(TRUE);    //打开触摸事件处理
        this->InitJoystick();            //初始化摇杆

        //每帧要执行的函数  
        this->schedule(schedule_selector(HudLayer::Moving));

        bRet = true;

    }while(0);

    return bRet;

}

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

void HudLayer::InitJoystick()
{
    //创建摇杆下面部分 
    joystick1 = CCSprite::create("joystick1.png");  
    //设置透明度,锚点,位置
    joystick1->setOpacity(150);
    joystick1->setAnchorPoint(ccp(0, 0));
    joystick1->setPosition(ccp(0, 0));
    joystick1->setColor(ccYELLOW);
    //大圆半径
    R=joystick1->getContentSize().width/2;  
    //中心点  
    O = ccp(R, R);  
    //添加进布景  
    this->addChild(joystick1, 3);

    //创建摇杆上面圆圈部分 
    joystick = CCSprite::create("joystick2.png");
    //设置位置为摇杆中心点并添加进布景
    joystick->setPosition(ccp(O.x, O.y));
    joystick->setColor(ccRED);
    joystick->setScale(0.8f);
    this->addChild(joystick, 4);

}
void HudLayer::InitHero()
{
    CCSize size = CCDirector::sharedDirector()->getWinSize();
    this->setPlayer(CCSprite::create("hartnett.jpg"));
    _player->setPosition(ccp(size.width/2, size.height/2));

    //获取GameLayer层的实例_gamelayer,这种方法行不通,还是在2个类的父类中进行数据交换吧
    //GameScene * _gamescene = (GameScene *)(CCDirector::sharedDirector()->getRunningScene());
    //GameLayer * _gamelayer = _gamescene->getGameLayer();
    //_player->setPosition(_gamelayer->getHeroBorn());
    _player->setScale(0.3f);
    this->addChild(_player);
}
void HudLayer::Moving(float dt)  
{  
    if (IsMoving && (speedX != 0 || speedY != 0))
    {  
        //精灵移动
        CCPoint position=ccp(_player->getPosition().x + speedX, _player->getPosition().y + speedY);
        CCSize size=CCDirector::sharedDirector()->getWinSize();  
        CCRect rect=CCRectMake(0, 0, size.width, size.height);  
  
        //_player->setPosition(position);  

        //判断触摸点是否在屏幕内  
        if(rect.containsPoint(position))
        {  
            _player->setPosition(position);  
        }  

    }  
}  

float HudLayer::heronsformula(float x1,float y1,float x2,float y2,float x3,float y3)  
{  
    //求边长a  
    float a = sqrt(pow(x1 - x2, 2) + pow(y1 - y2, 2));  
    //求边长b  
    float b = sqrt(pow(x2 - x3, 2) + pow(y2 - y3, 2));  
    //求边长c  
    float c = sqrt(pow(x3 - x1, 2) + pow(y3 - y1, 2));  
    //求半周长s  
    float s = (a + b + c) / 2;  
  
    //根据海伦公式返回三角形面积  
    return sqrt(s * (s - a) * (s - b) * (s - c));  
}  
  
//判断三个新三角形面积和是否等于原先三角形面积的函数的实现  
bool HudLayer::triangleContainPoint(float x1,float y1,float x2,float y2,float x3,float y3,float px,float py)  
{  
    //求S1的面积  
    float s1=heronsformula(x1,y1,x2,y2,px,py);  
    //求S2的面积  
    float s2=heronsformula(x2,y2,x3,y3,px,py);  
    //求S3的面积  
    float s3=heronsformula(x3,y3,x1,y1,px,py);  
    //求S的面积  
    float s=heronsformula(x1,y1,x2,y2,x3,y3);  
  
    //返回S是否等于S1,S2,S3的和  
    return abs(s-(s1+s2+s3))<0.001f;  
}  


void HudLayer::ccTouchesBegan(CCSet *pTouches, CCEvent *pEvent)
{
    CCTouch *touch = (CCTouch*)pTouches->anyObject();  
    CCPoint location = touch->getLocation();  
  
    CCRect rect = joystick->boundingBox();  
    if (rect.containsPoint(location))  
    {  
        IsMoving = true;  
    }  
}

void HudLayer::ccTouchesMoved(CCSet *pTouches, CCEvent *pEvent)
{
    CCTouch *touch = (CCTouch*)pTouches->anyObject();  
    CCPoint location = touch->getLocation();  
  
    //判断触摸滑动点是否在摇杆范围内  
    bool inRange = pow(O.x - location.x, 2) + pow(O.y - location.y, 2) < pow(R, 2);  
    if (IsMoving && inRange)
    {  
        CCPoint position=_player->getPosition();  
        joystick->setPosition(location);  
  
        float r = R * 2 / 6;  
        float d = R * 2 / 3;  

        //上,区域2或5  
        if(triangleContainPoint(O.x, O.y, O.x - r, O.y + r, O.x  +r, O.y + r, location.x, location.y)  
            || CCRectMake(O.x - r, O.y + r, d, d).containsPoint(location))
        {  
            speedX = 0;  
            speedY = 1;  
        }  

        //下,区域6或11  
        else if (triangleContainPoint(O.x, O.y, O.x - r, O.y - r, O.x + r, O.y - r, location.x, location.y)  
            || CCRectMake(O.x - r, O.y - r - d, d, d).containsPoint(location))
        {  
            speedX = 0;  
            speedY = -1;  
        }  

        //左,区域4或7  
        else if (triangleContainPoint(O.x, O.y, O.x - r, O.y + r, O.x - r, O.y - r, location.x, location.y)  
        || CCRectMake(O.x - r - d, O.y - r, d, d).containsPoint(location))
        {  
            speedX = -1;  
            speedY = 0;  
        }  

        //右,区域9或8  
        else if (triangleContainPoint(O.x, O.y, O.x + r, O.y + r, O.x + r, O.y - r, location.x, location.y)  
        || CCRectMake(O.x + r, O.y - r, d, d).containsPoint(location))
        {  
            speedX = 1;  
            speedY = 0;  
        }  

        //右上,区域3  
        else if (location.x - (O.x + r) > 0 && location.y - (O.y + r) > 0)
        {  
            speedX = 0.7f;  
            speedY = 0.7f;  
        }  

        //左上,区域1  
        else if (location.x - (O.x - r) < 0 && location.y - (O.y + r) > 0)
        {  
            speedX =- 0.7f;  
            speedY = 0.7f;  
        }  

        //左下,区域10  
        else if (location.x - (O.x - r) < 0 && location.y - (O.y - r) < 0)
        {  
            speedX = -0.7f;  
            speedY = -0.7f;  
        }  

        //右下,区域12  
        else if (location.x - (O.x + r) > 0 && location.y - (O.y - r) < 0)
        {  
            speedX = 0.7f;  
            speedY = -0.7f;  
        }
    }
}

void HudLayer::ccTouchesEnded(CCSet *pTouches, CCEvent *pEvent)
{
    IsMoving = false;  
    joystick->setPosition(O);  
    speedX = speedY = 0;  
}

 

posted @ 2013-05-16 17:34  netxfly  阅读(488)  评论(0编辑  收藏  举报