Cocos2d-x 教你从零开始构建:实现平滑控制人物左右移动
在许多游戏场景中,人物移动控制是常见需求。当玩家使用两手拇指分别点按左右屏幕来控制人物左右移动时,常出现点按频繁导致人物控制失灵的情况,这是因为两只手同时按触时,可能会出现一方触摸失效。本文将详细分享解决该问题的方法。
从零开始实现人物移动效果
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. 创建人物并设定触摸监听
人物类创建完成后,我们需要设定触摸监听,以便在点击屏幕时,人物能根据点击方向移动。
首先,在 HelloWorld
的 init
函数中添加代码创建人物:
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 个像素每秒
}
编译成功后,即可看到人物移动的效果。
实现思路总结
- 创建人物:创建
Man
类并实例化人物对象。 - 创建监听:为人物对象创建触摸监听。
- 使用变量中转:使用
manState
变量表示人物方向。 - 改变中转值:监听事件根据点击屏幕的位置改变
manState
的值。 - 决定移动方向:人物根据
manState
的值决定自身移动方向(通过run
函数)。 - 激活动作:通过
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;
}
}
通过以上步骤,我们实现了平滑控制人物左右移动的功能,同时优化了触摸体验。