第三讲 Touch
前面两篇我们学习的内容,足够我们做一款简单的小游戏。也可以说,我们已经入门了,可以蹒跚的走路了。
本篇将讲解cocos2dx中很重要的touch回调机制。你肯定记得第一章做定时器时间的时候用过CC_CALLBACK_1宏定义,它让我们回调一个只有一个形参的函数来执行定时操作。
回调函数的实现(Lambda表达式)
学习本篇前请仔细学习一下C++11的特性,std::function和lambda表达式。C++11还引入了很多boost库的优秀代码,使我们在使用的时候不必再加boost::,比如将要使用的std::bind;
学习地址如下:
简单的说一下function的使用。统一的函数定义格式:function<int(int,float)>。就相当于一种数据类型。只不过int是定义一个整形数,而function是定义一个函数。
function<int(int,float)>func = [](int a ,float b){return a+b;};//定义一个函数,名为func,它的第一个形参是int,第二个形参是float,返回值是int类型。
int ret = func(3, 1.2f);
首先让我们来看一下宏CC_CALLBACK_的定义
// new callbacksbased on C++11
#defineCC_CALLBACK_0(__selector__,__target__, ...)std::bind(&__selector__,__target__, ##__VA_ARGS__)
#defineCC_CALLBACK_1(__selector__,__target__, ...)std::bind(&__selector__,__target__, std::placeholders::_1, ##__VA_ARGS__)
#defineCC_CALLBACK_2(__selector__,__target__, ...)std::bind(&__selector__,__target__, std::placeholders::_1,std::placeholders::_2, ##__VA_ARGS__)
#defineCC_CALLBACK_3(__selector__,__target__, ...)std::bind(&__selector__,__target__, std::placeholders::_1,std::placeholders::_2, std::placeholders::_3, ##__VA_ARGS__)
其实它们只是封装了bind的用法。可以看到有几个参数,bind后面就跟几个_*。
要使用bind和function需要引入头文件#include <functional>,bind的参数类型需要引入命名空间using namespace std::placeholders;
#include"stdafx.h"
#include<iostream>
#include<string>
#include<functional>
using namespace std;
using namespacestd::placeholders;
struct Foo {
Foo(int num) : num_(num) {}
void print_add(int i) const { std::cout<< num_ + i << '\n'; }
int num_;
};
int _tmain(int argc,_TCHAR* argv[])
{
const Foo foo(123);
function<void(int)> f_add_display =bind(&Foo::print_add, foo, _1);
return0;
}
注意:bind要绑定一个类函数的时候,第二个参数必须是类对象。
所以我们在菜单子项绑定回调函数的时候,可以不使用CC_CALLBACK_1:
auto item =MenuItemLabel::create(Label::createWithBMFont("fonts/futura-48.fnt",itemName->getCString()));
item->setCallback(CC_CALLBACK_1(KT0618::change, this));
等价于 auto item =MenuItemLabel::create(Label::createWithBMFont("fonts/futura-48.fnt",itemName->getCString()));
item->setCallback(std::bind(&KT0618::change, this,std::placeholders::_1));
也等价于
auto item =MenuItemLabel::create(Label::createWithBMFont("fonts/futura-48.fnt",itemName->getCString()));
item->setCallback([=](Ref *ref)->void{//lambd表达式
……
});
如何正确定义和声明回调函数:
当我们create一个精灵的时候,往往记不住这种类型的精灵的回调函数有几个参数,参数类型是什么。这里很重要的方法就是阅读api。我们追踪进去看create方法源码,在create方法中查看需要的回调函数返回值是什么类型,形参是什么类型,复制过来即可。
在练习过程中,我们要尽量不使用CC_CALLBACK_*,而是自己书写bind函数或者lambda表达式。
***************************************************************************************************************
反向传值
一个简单的应用场景:游戏主界面展示最高分,切换到游戏场景完成游戏后,要向主场景返回成绩判断是否刷新纪录,如果是的话就更新主界面的最高分。
前面我们学习了正向传值,使用子场景的成员函数可以向子场景传值。所谓反向传值可以理解为子场景传值回主场景。
根据我们上面学习的function,我们应该把主场景的函数指针利用子场景的成员函数传递给子场景存储起来,然后在子场景中可以调用它的成员变量来调用主场景的函数。 这样我们切换场景的时候只能pushScene,子场景使用popScene。否则主场景的对象都不存在了还如何实现回调呢?!
新手可能会想,再在子场景中实例化一个主场景的类对象这么就可以传递值了,然后使用replace切换场景,而不需要这么麻烦的传递一个函数指针。如果按这种做法,主场景和子场景要相互引用头文件实例化对方,违反了低耦合的原则。
在子场景头文件中这么定义:
Public:
std::function<void(int)> func;
我们就可以在主场景切换到子场景的时候这样来注册回调函数:
auto scene =HomeWorkSnowFight::createScene();
HomeWorkSnowFight*layer = (HomeWorkSnowFight*)scene->getChildren().at(0);
layer->func = std::bind(&HomeWorkSnow::callback1,this,std::placeholders::_1 );//绑定回调函数到子场景
Director::getInstance()->pushScene(TransitionCrossFade::create(1,scene));
这样我们在子场景中调用func(99);就相当于调用的主场景的callback1(99)了。
三 touch事件
陀螺仪
Device::setAccelerometerEnabled(true);
// auto ac =EventListenerAcceleration::create(CC_CALLBACK_2(KT0618::accelerationc, this));
auto ac =EventListenerAcceleration::create([&](Acceleration* acc, Event* e){
sp->setPositionX(acc->x+sp->getPositionX());
});
_eventDispatcher->addEventListenerWithSceneGraphPriority(ac, this);
书写函数的时候仔细查看api,看create有几个参数。
比如陀螺仪create函数的形参格式如下:
const std::function<void(Acceleration*, Event*)>& callback
这就说明需要传入的参数应该是有两个参数的void类型的函数对象。
陀螺仪的代码只能在真机上测试了。
键盘事件
在xcode下是无法模拟的,只有在VS下才能测试。下面的这些代码都是要牢记于心的,动手实现一下就明白了!
auto keyboardLs =EventListenerKeyboard::create();
keyboardLs->onKeyPressed =[=](EventKeyboard::KeyCode code, Event*event){
if(code==EventKeyboard::KeyCode::KEY_A)
{
CCLOG("AAA");
}
};
keyboardLs->onKeyReleased =[](EventKeyboard::KeyCode code, Event*event){
CCLOG("BBB");
};
_eventDispatcher->addEventListenerWithSceneGraphPriority(keyboardLs,this);
鼠标事件 单点触控
auto listen =EventListenerTouchOneByOne::create();
listen->onTouchBegan =CC_CALLBACK_2(KT0618::onTouchBegan, this);
listen->onTouchMoved =CC_CALLBACK_2(KT0618::onTouchMoved, this);
listen->onTouchEnded =CC_CALLBACK_2(KT0618::onTouchEnded, this);
listen->setSwallowTouches(true);
Director::getInstance()->getEventDispatcher()->addEventListenerWithSceneGraphPriority(listen,this);
动手实现一下拖动一个物体到另一个物体上,只需要拖动到目的的边缘松开鼠标,物体会自动放到中心处。
b
oolKT0618::onTouchBegan(Touch *t, Event*e)
{
Point p = t->getLocation();
Rect rect = spMove->getBoundingBox();
if (rect.containsPoint(p))
{
return true;
}
else
{
return false;
}
return true;
}
voidKT0618::onTouchMoved(Touch *t, Event*e)
{
Point p = t->getLocation();
Rect rect = spMove->getBoundingBox();
if (rect.containsPoint(p))
{
spMove->setPosition(p);
}
}
voidKT0618::onTouchEnded(Touch *t, Event*e)
{
Point p = t->getLocation();
Rect rect = spBase->getBoundingBox();
if (rect.containsPoint(p))
{
spMove->setPosition(spBase->getPosition());
}
}
多点触控
ios下AppController.mm加一句[eaglView setMultipleTouchEnabled:YES];在模拟器按住alt可以调试多点。
windows就不用想了,surface除外。
auto touchMore =EventListenerTouchAllAtOnce::create();
touchMore->onTouchesBegan =CC_CALLBACK_2(KT0618::onTouchesBegan, this);
touchMore->onTouchesMoved =CC_CALLBACK_2(KT0618::onTouchesMoved, this);
touchMore->onTouchesEnded =CC_CALLBACK_2(KT0618::onTouchesEnded, this);
_eventDispatcher->addEventListenerWithSceneGraphPriority(touchMore,this);
onTouchesBegan跟单点触控返回值不同,请具体的根据api来写。
void KT0618::onTouchesBegan(conststd::vector<Touch*>& touches, Event* events)
{
for (auto v : touches )
{
v->getLocation();
}
}
voidKT0618::onTouchesMoved(const std::vector<Touch*>& touches, Event*unused_event)
{
}
void KT0618::onTouchesEnded(conststd::vector<Touch*>& touches, Event *unused_event)
{
}
添加自定义消息响应EventListenerCustom
init()里面添加如下代码,那么这个层就会响应标志位shutdown的消息。
auto listenCustom =EventListenerCustom::create("shutdown",CC_CALLBACK_1(KT0618::popupLayerCustom, this));
Director::getInstance()->getEventDispatcher()->addEventListenerWithFixedPriority(listenCustom,1);
这样可以在需要发送shutdown消息的地方这样添加,第二个参数是这条消息的自定义参数。
_eventDispatcher->dispatchCustomEvent("shutdown", (void*)"Go!DongGuan!");
这样就往外分发了一个名字是shutdown的消息。
这个消息可以在不同的层中接收到,利用第二个参数可以做到数据传递。可以是任何类型的数据,比回调方便。
其他层init也照上面添加,并添加对应的响应函数。
voidPopupLayer::shutdown(EventCustom * event)
{
char * str =(char *)event->getUserData();
CCLOG("Do not toucheme,bullshit!");
CCLOG(str);
}
添加音效
#include<SimpleAudioEngine.h>
usingnamespace CocosDenshion;
CocosDenshion::SimpleAudioEngine::sharedEngine()->playBackgroundMusic(MUSIC_BG,true);
CocosDenshion:
:SimpleAudioEngine::sharedEngine()->playEffect(MUSIC_ENEMY1);
音效init的时候预加载,但是要注意切换场景的时候要释放掉预加载的音效。
CocosDenshion::SimpleAudioEngine::sharedEngine()->preloadBackgroundMusic(MUSIC_BG);
CocosDenshion::SimpleAudioEngine::sharedEngine()->preloadEffect(MUSIC_BULLET);