上一篇添加了地图和英雄,但是英雄只是傻傻的站着动,而且还不听话,现在我们来控制他,这里采用摇杆的方式来操纵英雄。
为了可以灵活更换操纵方式,这里把操作层抽离出来,创建一个OperateLayer类来实现英雄的操作方式,现在来把摇杆在屏幕上绘制出来:

1. 添加摇杆

OperateLayer.h
class OperateLayer : public cocos2d::Layer
{
public:
 OperateLayer();
 ~OperateLayer();
 virtual bool init();
 CREATE_FUNC(OperateLayer);
 void onTouchesBegan(const std::vector<cocos2d::Touch*>& touches, cocos2d::Event *unused_event);
 void onTouchesMoved(const std::vector<cocos2d::Touch*>& touches, cocos2d::Event *unused_event);
 void onTouchesEnded(const std::vector<cocos2d::Touch*>& touches, cocos2d::Event *unused_event);
private:
 void showJoystick(cocos2d::Point pos);
 void hideJoystick();
 void updateJoystick(cocos2d::Point direction, float distance);
 cocos2d::Sprite *m_pJoystick;
 cocos2d::Sprite *m_pJoystickBg;
};

OperateLayer.cpp
OperateLayer::OperateLayer():
 m_pJoystick(NULL),
 m_pJoystickBg(NULL)
{
}
OperateLayer::~OperateLayer()
{
}
bool OperateLayer::init()
{
 bool ret = false;
 do {
  CC_BREAK_IF( !Layer::init() );
  m_pJoystick = Sprite::create("joystick.png");
  m_pJoystickBg = Sprite::create("joystick_bg.png");
  this->addChild(m_pJoystick);
  this->addChild(m_pJoystickBg);
  this->hideJoystick();
  auto listener = EventListenerTouchAllAtOnce::create();
  listener->onTouchesBegan = CC_CALLBACK_2(OperateLayer::onTouchesBegan, this);
  listener->onTouchesMoved = CC_CALLBACK_2(OperateLayer::onTouchesMoved, this);
  listener->onTouchesEnded = CC_CALLBACK_2(OperateLayer::onTouchesEnded, this);
  _eventDispatcher->addEventListenerWithSceneGraphPriority(listener, this);
  ret = true;
 } while(false);
 return ret;
}
void OperateLayer::showJoystick(Point pos)
{
 m_pJoystick->setPosition(pos);
 m_pJoystickBg->setPosition(pos);
 m_pJoystick->setVisible(true);
 m_pJoystickBg->setVisible(true);
}
void OperateLayer::hideJoystick()
{
 m_pJoystick->setPosition(m_pJoystickBg->getPosition());
 m_pJoystick->setVisible(false);
 m_pJoystickBg->setVisible(false);
}
void OperateLayer::updateJoystick(Point direction, float distance)
{
 Point start = m_pJoystickBg->getPosition();
 if(distance < 33)
 {
  m_pJoystick->setPosition(start + (direction * distance));
 }else if(distance > 78) {
  m_pJoystick->setPosition(start + (direction * 45));
 }else {
  m_pJoystick->setPosition(start + (direction * 33));
 }
}
void OperateLayer::onTouchesBegan(const vector<Touch*>& touches, Event *unused_event)
{
 Size winSize = Director::getInstance()->getWinSize();
 vector<Touch*>::const_iterator touchIter = touches.begin();
 while(touchIter != touches.end())
 {
  Touch *pTouch = (Touch*)(*touchIter);
  Point p = pTouch->getLocation();
  if(p.x <= winSize.width / 2)
  {
   this->showJoystick(p);
  }else {

  }
  ++ touchIter;
 }
}
void OperateLayer::onTouchesMoved(const vector<Touch*>& touches, Event *unused_event)
{
 Size winSize = Director::getInstance()->getWinSize();
 std::vector<Touch*>::const_iterator touchIter = touches.begin();
 Touch *pTouch = (Touch*)(*touchIter);
 Point start = pTouch->getStartLocation();
 if(start.x > winSize.width / 2)
 {
  return;
 }
 Point dest = pTouch->getLocation();
 float distance = start.getDistance(dest);
 Point direction = (dest - start).normalize();
 this->updateJoystick(direction, distance);
}
void OperateLayer::onTouchesEnded(const vector<Touch*>& touches, Event *unused_event)
{
 this->hideJoystick();
}

要注意的是cocos2d-x 3.0监听屏幕触摸事件跟以前版本改动了很多,用了c++ 11新特性std::bind和std::function,参数也由之前的set改成了vector。这里来解释下updateJoystick函数,根据手指或者鼠标滑动的距离来更新摇杆,看下面三个图:

  
33是中间摇杆的半径,45是摇杆底盘的半径,当摇杆内边缘要移出底盘外圆时,需要把摇杆中心固定在底盘外圆边缘。可以利用摇杆位于底盘的内圆边缘或外圆边缘来设置英雄是慢走还是疾走。
为了简便,这里把屏幕分成两半, 当点击在左半部分则显示摇杆,当点击右半部分时则触发英雄攻击(暂时先不实现)。
现在把OperateLayer添加到游戏场景中去,修改GameScene.cpp,在createScene函数中添加下面的代码:

 auto operateLayer = OperateLayer::create();
 scene->addChild(operateLayer, 1);

现在摇杆有了,可以让英雄听话了,让英雄动起来吧!

2. 控制英雄移动
给精灵添加速度属性,修改BaseSprite类,BaseSprite.h中添加下面的代码:

CC_SYNTHESIZE(cocos2d::Point, m_fVelocity, Velocity); 
 std::function<void(void)> attack;

在Hero.h中添加:

 std::function<void(cocos2d::Point, float)> walk;
 std::function<void(void)> stop;

修改OperateLayer类,让它能控制英雄,OperateLayer.h中添加:

CC_SYNTHESIZE(Hero*, m_pHero, Hero);

OperateLayer.cpp中以下函数中添加执行英雄的相关函数:

void OperateLayer::onTouchesBegan(const vector<Touch*>& touches, Event *unused_event)
{
//.....
  if(p.x <= winSize.width / 2)
  {
   this->showJoystick(p);
  }else {
   m_pHero->attack();
  }
  ++ touchIter;
 }
}
void OperateLayer::onTouchesMoved(const vector<Touch*>& touches, Event *unused_event)
{
//.....
 m_pHero->walk(direction, distance);
}
void OperateLayer::onTouchesEnded(const vector<Touch*>& touches, Event *unused_event)
{
//.....
 m_pHero->stop();
}

在GameLayer.h中添加:

void onHeroWalk(cocos2d::Point direction, float distance);
 void onHeroAttack();
 void onHeroStop();
 void update(float dt);
 void updateHero(float dt);

 float m_fTileWidth;
 float m_fTileHeight;

这几个onHeroXXX函数是在OperateLayer类中执行XXX函数时调用,在GameLayer.cpp中进行绑定。m_fTileWidth和m_fTileHeight表示地图每个瓦片的宽高,用来控制英雄的行走区域。
在GameLayer.cpp的init函数中添加:

Size tileSize = m_pTiledMap->getTileSize();
  m_fTileWidth = tileSize.width;
  m_fTileHeight = tileSize.height;

  m_pHero->attack = CC_CALLBACK_0(GameLayer::onHeroAttack, this);
  m_pHero->stop = CC_CALLBACK_0(GameLayer::onHeroStop, this);
  m_pHero->walk = CC_CALLBACK_2(GameLayer::onHeroWalk, this);

  this->scheduleUpdate();

实现GameLayer类新增加的几个函数:

void GameLayer::onHeroWalk(Point direction, float distance)
{
 m_pHero->setFlippedX(direction.x < 0 ? true : false);
 m_pHero->runWalkAction();
 Point velocity = direction * (distance < 78 ? 1 : 3);
 m_pHero->setVelocity(velocity);
}
void GameLayer::onHeroAttack()
{
 m_pHero->runAttackAction();
}
void GameLayer::onHeroStop()
{
 m_pHero->runIdleAction();
}
void GameLayer::update(float dt)
{
 this->updateHero(dt);
}
void GameLayer::updateHero(float dt)
{
 if(m_pHero->getCurrActionState() == ACTION_STATE_WALK)
 {
  float halfHeroFrameHeight = (m_pHero->getDisplayFrame()->getRect().size.height) / 2;
  Point expectP = m_pHero->getPosition() + m_pHero->getVelocity();
  Point actualP = expectP;
  //can not walk on the wall or out of map
  if(expectP.y < halfHeroFrameHeight || expectP.y > (m_fTileHeight * 3 + halfHeroFrameHeight) )
  {
   actualP.y = m_pHero->getPositionY();
  }
  float mapWidth = m_pTiledMap->getContentSize().width;
  float halfWinWidth = m_fScreenWidth / 2;
  float halfHeroFrameWidth = (m_pHero->getDisplayFrame()->getRect().size.width) / 2;
  if(expectP.x > halfWinWidth && expectP.x <= (mapWidth - halfWinWidth))
  {
   this->setPositionX(this->getPositionX() - m_pHero->getVelocity().x);
  }else if(expectP.x < halfHeroFrameWidth || expectP.x >= mapWidth - halfHeroFrameWidth)
  {
   actualP.x = m_pHero->getPositionX();
  }
  m_pHero->setPosition(actualP);
  m_pHero->setZOrder(m_fScreenHeight - m_pHero->getPositionY());
 }
}

onHeroWalk是英雄行走时调用的,setFlippedX实现切换英雄的朝向,并根据摇杆的位置改变英雄的速度。updateHero函数对英雄的行走范围进行了控制,不能走出屏幕,也不能走到墙上去。最后修改GameScene.cpp的createScene函数,添加:
operateLayer->setHero(gameLayer->getHero());
好的,编译运行项目,英雄终于听话了。

现在屏幕上就只有一个英雄,感觉太孤独了,下一篇来给它创造几个敌人吧。

cocos2d-x手游性能优化总结

近段时间在使用cocos2d-x开发2D手游,技术方案使用的是cocos2d-x+lua,因为游戏使用的是cocos2d-x 2.1.5版本,有些优化方案在最新版的cocos2d-x版本已经实现...

阅读全文

cocos2dx-html5 实现网页版flappy bird游戏

我也是第一次使用cocos2d_html5,对js和html5也不熟,看引擎自带的例子和引擎源码,边学边做,如果使用过cocos2d-x的话,完成这个游戏还是十分简单的。游戏体...

阅读全文

【cocos2d-x开发实战 特训99-终结篇】移植到android平台和添加admob广告

上一篇已经完成特性99在win32平台下的开发,现在把它移植到android上,首先修改Android.mk文件,内容如下: LOCAL_PATH := $(call my-dir) include $(CLEAR_...

阅读全文

14 条评论

  1. cookiezby :
    你好,楼主我看了你的教程,有个地方老是报错,就是std::function那里,其他地方应该是没问题的。我想问一下楼主,GameLayer和OperateLayer中都有一个Hero对象,在实际运行的时候,OperateLayer将触摸的信息传给自己的Hero对象,而并不是传给GameLayer中的Hero对象,而GameLayer中的Hero对象必须获得触摸的信息,楼主到底是如何将触摸信息从OperateLayer传到GameLayer中,并调用GameLayer中Hero对象的onHeroWalk的方法的?

    实在太感谢你的提醒了。应该在GameScene::init()最后添加一句: _operateLayer->setHero(_gameLayer->m_pHero);

  2. 我的要在 Operate::onTouchesBegan() 里面添加一个 p*=0.5 ,否则那个位置有偏移,能看懂吧。为什么会这样的

    1. Hi Mary, this pumpkin roll looks wonderful… I will have to try and make it for the holidays..what can I substitute the eggs with since I have a grand allergic to eggs???Thanks for sharing this cake roll… Have a good day. hugs, Baba

  3. Point direction = (dest – start).normalize(); 在cocos2dx 3.2 版本里面

    为甚么错呢呢 1 IntelliSense: 对象包含与成员函数不兼容的类型限定符
    对象类型是: const cocos2d::Vec2 c:\Users\Unity\Desktop\project\PompaDroid\Classes\OperateLayer.cpp 103 20 PompaDroid

  4. 你好,楼主我看了你的教程,有个地方老是报错,就是std::function那里,其他地方应该是没问题的。我想问一下楼主,GameLayer和OperateLayer中都有一个Hero对象,在实际运行的时候,OperateLayer将触摸的信息传给自己的Hero对象,而并不是传给GameLayer中的Hero对象,而GameLayer中的Hero对象必须获得触摸的信息,楼主到底是如何将触摸信息从OperateLayer传到GameLayer中,并调用GameLayer中Hero对象的onHeroWalk的方法的?

  5. lalu :
    0x0026888E 处有未经处理的异常(在 MyGame.exe 中): 0xC0000005: 读取位置 0xCDCDCDCD 时发生访问冲突。
    定位错误是
    m_pHero->attack = CC_CALLBACK_0(GameLayer::onHeroAttack, this);
    m_pHero->stop = CC_CALLBACK_0(GameLayer::onHeroStop, this);
    m_pHero->walk = CC_CALLBACK_2(GameLayer::onHeroWalk, this);

    再说一句,用的是最新的3.1

  6. 0x0026888E 处有未经处理的异常(在 MyGame.exe 中): 0xC0000005: 读取位置 0xCDCDCDCD 时发生访问冲突。

    定位错误是
    m_pHero->attack = CC_CALLBACK_0(GameLayer::onHeroAttack, this);
    m_pHero->stop = CC_CALLBACK_0(GameLayer::onHeroStop, this);
    m_pHero->walk = CC_CALLBACK_2(GameLayer::onHeroWalk, this);

  7. 那么这样,如果2个手指在左半屏滑动,遍历到第一个touch(start.x < winSize.width / 2),设置一次位置,当遍历到第二个touch(start.x < winSize.width / 2)又按照第二个touch方向移动,我猜测,在第二个touch位置出现摇杆,但是先按照第一个touch方向移动,然后在按照第二个touch方向移动 ,人物可能出现方向不分的状况。我没真机,没法调试,只是提出我的疑问。

  8. void OperateLayer::onTouchesMoved(const vector& touches, Event *unused_event)
    {
    Size winSize = Director::getInstance()->getWinSize();
    vector::const_iterator touchIter = touches.begin();
    while(touchIter != touches.end())
    {
    Touch *pTouch = (Touch*)(*touchIter);
    Point start = pTouch->getStartLocation();
    if(start.x > winSize.width / 2)
    {
    ++ touchIter;
    continue;
    }
    Point dest = pTouch->getLocation();
    float distance = start.getDistance(dest);
    Point direction = (dest – start).normalize();
    this->updateJoystick(direction, distance);

    m_pHero->walk(direction, distance);
    ++ touchIter;
    }
    }

    果然和我说的一样,改成这样之后没有问题了

  9. void OperateLayer::onTouchesMoved(const vector& touches, Event *unused_event)

    你好我想问一下,在这个方法中为什么没有遍历所有的touches,仅仅取了touches.begin()?

    假如我用两根手指分别在左右两半屏幕里划动(理论上主角还是会动),这样岂不是可能只取到右半屏幕的touch指针(主角动不了?

欢迎留言