「cocos2d-x」虚拟摇杆实现(2)
上一篇文章「cocos2d-x」虚拟摇杆实现(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; }