我只想让精灵一直向前走,为什么这么难?
一切故事发生的背景
同济大学软件学院每个学期会要求学生独立或者组队完成一个大项目,于是2016年大项目是用cocos2d-x这款引擎制作一个自己的游戏。
有幸能和<stroke>大神</stroke>组到了一起,形成了两人小分队。
C++语言方面我还真的没有可以拿得出手的东西,但是在cocos2d-x的学习中,还是略有收获的。今天想谈谈通过监听键盘事件从而让精灵一直走路,松开按键精灵就停下的功能。就好像骑车一样,按住一颗按键,就会向前行驶,不按就停下来。
一个简单的Sprite
先在Demo.h里声明出一个sprite成员作为我们今天的主角,顾名思义我叫它hero,因为是简单的例子,就不兴师动众创建一个严谨的主角类了。
# Demo.h
class HelloWorld : public cocos2d::Layer
{
public:
static cocos2d::Scene* createScene();
virtual bool init();
cocos2d::Sprite *hero;
// a selector callback
void menuCloseCallback(cocos2d::Ref* pSender);
void update(float delta) override;
// implement the "static create()" method manually
CREATE_FUNC(HelloWorld);
};
添加一个"僵尸"sprite
再在Demo.cpp中实实在在创建出这个带有纹理的sprite,设置居中位置,并添加到场景中。
就像开头贴出的效果图,我们能够控制的精灵纹理是一张《植物大战僵尸》中僵尸的贴图,而背景其实也是一个精灵对象,相关代码就不在此列出了。
# Demo.cpp
hero = Sprite::create("zombie.png");
// position the sprite on the center of the screen
hero->setPosition(Vec2(visibleSize.width/2 + origin.x, visibleSize.height/2 + origin.y));
// add the sprite as a child to this layer
this->addChild(hero, 0);
添加键盘事件监听
我相信你一定会一些基本的事件监听(Event Dispatcher)使用方法了吧,如果不是,请参照官方文档中的该章节。
当然其中有一些鼠标监听之类的方法不是我们今天需要的,EventListenerKeyboard才是今天的重头戏。于是我们在Demo.cpp 中创建一个监听器,并赋予一个lambda函数给它,让它能知道监听了键盘事件后该做什么事情。
# Demo.cpp
auto listener = EventListenerKeyboard::create();
listener->onKeyPressed = [=](EventKeyboard::KeyCode keyCode, Event* event){
log("key pressed");
};
_eventDispatcher->addEventListenerWithSceneGraphPriority(listener, this);
这样一来,按任意键,控制台就会输出"key pressed"字样的信息了,这为我们下一步做了良好的铺垫。传进来的参数是什么意思?不必解释了吧...
记录一共按过哪些键,以及其状态
我们还要记录一下一共按过了哪些键,以及现在它们是否还被按着。通俗地讲,就是用KeyPressed事件将当期按下的键信息保存起来,表示它现在被按下了。再判断触发KeyReleased事件时,将触发的按键信息去除掉,表示现在这颗键已经不再处于被按下的状态了。
于是我们在头文件里创建一个map类型的成员,取名为keys,用以保存我们按键记录。
# Demo.h
class HelloWorld : public cocos2d::Layer
{
public:
static cocos2d::Scene* createScene();
virtual bool init();
cocos2d::Sprite *hero;
// a selector callback
void menuCloseCallback(cocos2d::Ref* pSender);
void update(float delta) override;
// implement the "static create()" method manually
CREATE_FUNC(HelloWorld);
private:
std::map<cocos2d::EventKeyboard::KeyCode, bool> keys;
};
map键为每个按键所对应的keycode, 值为现在这颗按键是不是处于被按下的状态,正在被按为true, 没被按(即 松开)为false。
当然,我们还要为键盘监听器添加一个KeyReleased事件,用以监听任意按键被松开那一刻的状态。
# Demo.cpp
listener->onKeyReleased = [=](EventKeyboard::KeyCode keyCode, Event* event){
log("key release");
};
简简单单一些在控制台的输出是远远不够的,现在开始改进我们的俩监听事件,让它们能增加或改变keys里的记录。
# Demo.cpp
listener->onKeyPressed = [=](EventKeyboard::KeyCode keyCode, Event* event){
keys[KeyCode] = true;
};
listener->onKeyReleased = [=](EventKeyboard::KeyCode keyCode, Event* event){
keys[KeyCode] = false;
};
你问我为何可以写得如此草率简单,然而事实就是如此。在松开事件发生的时候,根本不需要判断在keys是不是已经存在了这个按键,因为,被松开就代表着,肯定被按下了。当然,我们排除在游戏之外的窗口按下,再切换到游戏内松开的情况,对于这种非法份子,今天网开一面。
Sprite show me your hands
老朋友update事件
每一个由Node类继承下来的子类中,无论你是精灵类,场景类,都有一个update方法,当判断发生改变时,能够做出一些反应。
要使用update,先要了解三种调度器Scheduler
我们为Demo类添加一个默认调度器,表示它要使用update方法。并且添加一些简单的代码到update事件中去。
# Demo.cpp
bool HelloWorld::init() {
...
this->scheduleUpdate();
}
void HelloWorld::update(float delta) {
// Register an update function that checks to see if the CTRL key is pressed
// and if it is displays how long, otherwise tell the user to press it
Node::update(delta);
log("update");
}
打开游戏以后,就会看见控制台正在疯狂地输出"update",说明我们的默认调度器监听了每一帧的改变并执行了我们自定义的操作。
你真的...一直在按着我吗?
我们创建一个方法,叫做isKeyPressed,用来判断一个按键是否当前是否处于被按下的状态,如果是,返回true,交给update来处理后续动作。
# Demo.cpp
bool HelloWorld::isKeyPressed(EventKeyboard::KeyCode keyCode) {
if(keys[keyCode]) {
return true;
} else {
return false;
}
}
时时刻刻判断特定的按键是否被按下
除了上下左右键,其他的我们都不需要理会,这个需求如何实现呢?
我们要让之前创建好的hero左右动,那就要在update事件中时时刻刻判断着KEY_LEFT_ARROW或者KEY_RIGHT_ARROW是否被按下,如果是,就对主角执行动作(action),没有,就忽略。
我们将update方法进行改写:
# Demo.cpp
void HelloWorld::update(float delta) {
Node::update(delta);
auto leftArrow = EventKeyboard::KeyCode::KEY_LEFT_ARROW, rightArrow = EventKeyboard::KeyCode::KEY_RIGHT_ARROW;
if(isKeyPressed(leftArrow)) {
keyPressedDuration(leftArrow);
} else if(isKeyPressed(rightArrow)) {
keyPressedDuration(rightArrow);
}
}
等等,keyPressedDuration是什么东西,内置方法吗?我们接着看。
Hero: 我该做些什么呢?
差不多都快要搞定的时候,我们的hero发现了一个严重的问题 --- 它该做些什么。
我们新创建一个方法叫做keyPressedDuration,表示着按下/按着对应按键后,hero应该做些什么。
# Demo.cpp
void HelloWorld::keyPressedDuration(EventKeyboard::KeyCode code) {
int offsetX = 0, offsetY = 0;
switch (code) {
case EventKeyboard::KeyCode::KEY_LEFT_ARROW:
offsetX = -5;
break;
case EventKeyboard::KeyCode::KEY_RIGHT_ARROW:
offsetX = 5;
break;
default:
offsetY = offsetX = 0;
break;
}
// 0.3s代表着动作从开始到结束所用的时间,从而显得不会那么机械。
auto moveTo = MoveTo::create(0.3, Vec2(hero->getPositionX() + offsetX, hero->getPositionY() + offsetY));
hero->runAction(moveTo);
}
我们判断code变量从而得知用户按了左右键中的哪一个,再执行对应的动作。
烂尾
最后的最后,我们将代码一整合,按住左键或者右键,就可以看见hero自由地走动啦。
本来作为一名cocos2d-x的初学者,遇到这种问题,我通常就面向csdn和stackoverflow编程了,通过他们来解决我的问题。可无奈,我竟然难以找到令我真正看得懂的教程,英文尚有几篇,而中文关于这方面的根本没有。
既然没有,虽然我才刚刚学习,但本着不逃避的态度,那就我来写一篇吧。
下一章就该讲讲我和物理引擎的一些故事了,下期再见。
以上。