Cocos2d-x 教你从零开始构建:实现平滑控制人物左右移动

2015年03月18日 13:35 0 点赞 0 评论 更新于 2017-05-07 05:29

在许多游戏场景中,人物移动控制是常见需求。当玩家使用两手拇指分别点按左右屏幕来控制人物左右移动时,常出现点按频繁导致人物控制失灵的情况,这是因为两只手同时按触时,可能会出现一方触摸失效。本文将详细分享解决该问题的方法。

从零开始实现人物移动效果

1. 定义人物类

人物是移动的对象,我们创建一个 Man 类,将人物的所有功能封装其中。目前,我们仅需实现人物的移动功能。由于本文重点在于触摸功能的优化,因此 Man 类的创建过程不再赘述,直接给出代码:

// man.h
class Man : public cocos2d::Node {
public:
static Man *create(const std::string &textureFile);
virtual bool init(const std::string &textureFile);
void moveRight(float delta);

private:
cocos2d::Sprite *sprite;
};

// man.cpp
Man *Man::create(const std::string &textureFile) {
Man *p = new Man;
if (p && p->init(textureFile)) {
p->autorelease();
} else {
delete p;
p = nullptr;
}
return p;
}

bool Man::init(const std::string &textureFile) {
if (!Node::init())
return false;
// 根据纹理名称创建精灵
sprite = Sprite::create(textureFile);
addChild(sprite);
return true;
}

void Man::moveRight(float delta) {
setPositionX(getPositionX() + delta);
}

2. 创建人物并设定触摸监听

人物类创建完成后,我们需要设定触摸监听,以便在点击屏幕时,人物能根据点击方向移动。 首先,在 HelloWorldinit 函数中添加代码创建人物:

auto man = Man::create("man.png");
addChild(man);
man->setPosition(visibleSize / 2);

然后,同样在 init 函数中创建触摸监听,这里我们仅使用单点触摸:

auto listener = EventListenerTouchOneByOne::create();
listener->onTouchBegan = CC_CALLBACK_2(HelloWorld::touchBegan, this);
listener->onTouchMoved = CC_CALLBACK_2(HelloWorld::touchMoved, this);
listener->onTouchEnded = CC_CALLBACK_2(HelloWorld::touchEnded, this);
listener->setSwallowTouches(true);
_eventDispatcher->addEventListenerWithSceneGraphPriority(listener, man);

3. 构建细节:编写监听事件回调函数

我们的重点是编写监听事件的回调函数,实现触摸开始时人物开始移动,触摸结束时人物停止移动。这里采用额外设立一个数据成员来记录人物状态的方法,触摸回调函数决定该数据成员的值,再通过 schedule 检测该数据成员,从而控制人物的移动或静止。 首先,在 Man 类定义中新增一个成员 manState

enum {
STILL = 0,
RUN_LEFT = 1,
RUN_RIGHT = 2
};
int manState = STILL;

接下来,分两步实现人物的移动逻辑。第一步,在 Man 类中新增 run 函数,根据 manState 决定人物的运动:

void Man::run(float x) {
switch (manState) {
case STILL:
break;
case RUN_LEFT:
moveRight(-x);
break;
case RUN_RIGHT:
moveRight(x);
break;
}
}

第二步,编写触摸事件回调函数:

bool HelloWorld::touchBegan(cocos2d::Touch *touch, cocos2d::Event *event) {
// 获得目标
auto man = reinterpret_cast<Man*>(event->getCurrentTarget());
// 如果触摸点在屏幕右边,则往右移动,否则往左
if (touch->getLocation().x >= Director::getInstance()->getVisibleSize().width / 2) {
man->manState = Man::RUN_RIGHT;
} else {
man->manState = Man::RUN_LEFT;
}
return true;
}

void HelloWorld::touchMoved(cocos2d::Touch *touch, cocos2d::Event *event) {}

void HelloWorld::touchEnded(cocos2d::Touch *touch, cocos2d::Event *event) {
// 获得目标
auto man = reinterpret_cast<Man*>(event->getCurrentTarget());
// 触摸停止,人物停止移动
man->manState = Man::STILL;
}

4. 在 HelloWorld 中启用 schedule 并调用 run 函数

为了让 update 函数能够访问到 man 对象,我们需要将之前创建的 man 的声明放到类定义中:

class HelloWorld : public cocos2d::Layer {
//...
Man *man = nullptr;
virtual void update(float delta);
//...
};

bool HelloWorld::init() {
//...
scheduleUpdate();
//...
}

void HelloWorld::update(float delta) {
man->run(5); // 5 表示每次调用移动 5 个像素,一秒钟算 60 次调用 update 的话就是 300 个像素每秒
}

编译成功后,即可看到人物移动的效果。

实现思路总结

  1. 创建人物:创建 Man 类并实例化人物对象。
  2. 创建监听:为人物对象创建触摸监听。
  3. 使用变量中转:使用 manState 变量表示人物方向。
  4. 改变中转值:监听事件根据点击屏幕的位置改变 manState 的值。
  5. 决定移动方向:人物根据 manState 的值决定自身移动方向(通过 run 函数)。
  6. 激活动作:通过 schedule 激活动作。

优化触摸体验

目前实现的触摸效果较为生硬,下面将介绍如何让触摸更加流畅。

现有问题分析

当前游戏运行时,如果先按住左边,然后不放开左手,再按住右边,人物会往右走。但当放开右手,左手不放开时,人物不会再往左走,而是静止不动。

优化思路

优化的重点在于三个监听回调函数。我们需要对左右手指的触摸状态进行注册和取消注册,以实现更流畅的触摸体验。具体思路是:左右手指触摸开始时进行注册,离开时分别取消注册。当一只手松开时,检查另一只手是否仍在触摸,若仍在触摸,则人物继续向该方向移动。

具体实现

首先,在 Man 类中创建两个布尔变量,用于表示左右的注册状态:

class Man : public cocos2d::Node {
//...
public:
bool registerLeft = false;
bool registerRight = false;
//...
};

接着,修改触摸回调函数:

bool Man::touchBegan(cocos2d::Touch *touch, cocos2d::Event *event) {
// 获得目标
auto man = reinterpret_cast<Man*>(event->getCurrentTarget());
// 如果触摸点在屏幕右边,则往右移动,否则往左
if (touch->getLocation().x >= Director::getInstance()->getVisibleSize().width / 2) {
man->manState = Man::RUN_RIGHT;
man->registerRight = true;
} else {
man->manState = Man::RUN_LEFT;
man->registerLeft = true;
}
return true;
}

void Man::touchMoved(cocos2d::Touch *touch, cocos2d::Event *event) {}

void Man::touchEnded(cocos2d::Touch *touch, cocos2d::Event *event) {
// 获得目标
auto man = reinterpret_cast<Man*>(event->getCurrentTarget());
if (touch->getLocation().x >= Director::getInstance()->getVisibleSize().width / 2) {
// 如果右手松开,则检查是否左手仍在触摸,如果是,则往左走,如果不是,则停止
if (man->registerLeft) {
man->manState = Man::RUN_LEFT;
} else {
man->manState = Man::STILL;
}
// 取消右手的注册
man->registerRight = false;
} else {
// 如果左手松开,则检查是否右手仍在触摸,如果是,则往右走,如果不是,则停止
if (man->registerRight) {
man->manState = Man::RUN_RIGHT;
} else {
man->manState = Man::STILL;
}
// 取消左手的注册
man->registerLeft = false;
}
}

为了方便管理,我们将监听功能和 schedule 功能都移到了 Man 类中,并将一些不需要公开的成员从 public 改为 protected。此时,HelloWorld 类只需创建一个 Man 对象即可实现人物的移动:

// create the man
man = Man::create("man.png");
addChild(man);
man->setPosition(visibleSize / 2);

解决新问题

当单只手指从左滑向右,或者从右滑向左时,人物会不停地往左或往右移动,且无法停止,只有再次点击左或右边的屏幕,人物才会停止。这是由于注册机制存在 bug。解决方法是在 touchMoved 回调函数中加入代码,限制移动范围:

void Man::touchMoved(cocos2d::Touch *touch, cocos2d::Event *event) {
auto man = reinterpret_cast<Man*>(event->getCurrentTarget());
if (touch->getLocation().x >= Director::getInstance()->getVisibleSize().width / 2 && man->registerLeft && !man->registerRight) {
// 如果从左向右滑动超过了屏幕的一半,则停止,并取消左手注册
man->manState = Man::STILL;
man->registerLeft = false;
} else if (touch->getLocation().x < Director::getInstance()->getVisibleSize().width / 2 && man->registerRight && !man->registerLeft) {
// 如果从右向左滑动超过了屏幕的一半,则停止,并取消右手注册
man->manState = Man::STILL;
man->registerRight = false;
}
}

通过以上步骤,我们实现了平滑控制人物左右移动的功能,同时优化了触摸体验。

作者信息

boke

boke

共发布了 1025 篇文章