cocos2d-x 4.0 学习之路(十四)动作监听(CallFunc)

今天我们来学习另一个动作–CallFunc,它是一种特殊的动作,它是看不见的,不像之前我们用的动作都非常具体,比如移动、跳跃、旋转等。CallFunc的作用就是回调一个函数。
用CallFunc我们可以实现动作监听。比如,我们想让一个精灵移动到目的之后,通知我们它到了(显示一个Label)。用CallFunc就很容易实现。

bool HelloWorld::init() {
    auto moveBy = MoveBy::create(1.0f, Vec2(500, 0));
    auto arrived = [&]() {
        auto visibleSize = Director::getInstance()->getVisibleSize();
        auto label = Label::createWithTTF("*** I arrived! ***", "fonts/arial.ttf", 24);
        label->setPosition(visibleSize.width / 2, visibleSize.height / 2 + 100);
        this->addChild(label);
    };
    auto callFunc = CallFunc::create(arrived);
    sprite1->runAction(Sequence::create(moveBy, callFunc, nullptr));
    return true;
}

我们看到,当精灵结束移动后,arrived()函数就会被调用,Label就会被创建显示。效果:
在这里插入图片描述

auto arrived = [](){};这个回调函数其实就是Lambda函数。这是C++的,与cocos2dx无关。从cocos2dx3.0才开始支持这种类型的函数回调。
和java里面的Lambda类似,它是一个匿名函数。它的结构是:[] () {}
[] :表示要开始一个Lambda函数
() :里面可以填写函数的参数
{} :里面是函数的内容

我们写一个最简单的例子:

auto func = [](){
	log("test");
};
auto callFunc = CallFunc::create(func);
this->runAction(callFunc);

这样可以在输出里面看到test。有人会问,还可以this->runAction?之前不都是sprite->runAction吗?
其实,只要是继承于Node的类,都是可以使用runAction的。Sprite是直接继承Node的,而this(也就是我们HelloWorld类)是继承于Scene,Scene继承于Node的。所以可以直接用this->runAction来让它执行动作。

最上面的例子里,有人会观察到中括号里面有个&,这个符号是用来干啥的呢?你可以把它去掉试一下,是不是直接就编译错误了。这里涉及到一个概念,就是变量的捕获模式。
说人话就是,在Lambda函数中,你想利用Lambda函数外面的变量时,应该怎么办?你不要以为这个Lambda函数写在你当前运行的函数中(比如当前我们运行在init中),就可以随便访问这个函数中的局部变量或者访问类的全局变量,那是不可能的。为什么呢?因为Lambda函数,其本质也是一个函数,计算机创建它的时候是另外开辟一段内存空间给它的,和你在哪写的它没有关系。也就是说,HelloWorld里面的this(全局变量),还有sprite(局部变量)等,对于Lambda函数来说是完全看不到的。

那么,我们还想用this, sprite它们,怎么办?就需要在[]中加上指定的符号,就能指定变量的捕获模式,常用的捕获模式如下:
[]:不截取任何变量
[&]:截取外部作用域中的所有变量,并且作为引用在Lambda函数中使用。只要外部作用域中的变量没有别释放,在Lambda函数中就可以使用。所以局部变量时不能使用的,因为局部变量会被释放。
[=]:截取外部作用域中的所有变量,并且拷贝一份在Lambda函数中使用。即使外部变量的值改变了,在Lambda函数执行的时候,依旧是复制时候的值。
[=, &myvar]:和[=]作用一样,但是对myvar变量使用引用(也就是[&]方式)。
[myvar]:和[=]作用一样,但只针对于myvar一个变量,其他变量忽略。

估计大家已经晕了。还是用代码解释一下。
在下面的Lambda函数中,我们建立一个label,然后用this把它添加到HelloWorldScene上,那么这个this就是外部变量,我们要使用它,设定捕获模式[&],就是用this了。

bool HelloWorld::init() {
    auto arrived = [&]() {
        auto visibleSize = Director::getInstance()->getVisibleSize();
        auto label = Label::createWithTTF("*** I arrived! ***", "fonts/arial.ttf", 24);
        label->setPosition(visibleSize.width / 2, visibleSize.height / 2 + 100);
        this->addChild(label);
    };
    this->runAction(arrived);
    return true;
}

那我们再改一下这个代码,把Label的定义放到Lambda函数外面:

bool HelloWorld::init() {
	auto visibleSize = Director::getInstance()->getVisibleSize();
    auto label = Label::createWithTTF("*** I arrived! ***", "fonts/arial.ttf", 24);
    label->setPosition(visibleSize.width / 2, visibleSize.height / 2 + 100);
    auto arrived = [&]() {
        this->addChild(label);
    };
    this->runAction(arrived);
    return true;
}

这样,对于Lambda函数arrived来讲,this和Label都是外部变量。那还是使用[&]可以吗?编译不过了。因为this是HelloWorld的全局变量,label是init()函数的局部变量。当init()函数执行完,this是不会被释放的,而label是会被释放了(不存在了,你还访问个啥)。这个地方有点微妙,其实Label这个对象本身是没有被释放的,只不过是label这个局部变量被释放了。换句话说label这个指针被释放了,但是它指向的对象没有被释放。(实在不明白,也不用纠结这块)
这时候,我们得使用[=]。也就是说,给lambda函数拷贝一份label指针,就OK了。既然Lambda函数是匿名函数,我们当然就可以把函数名省略,直接写在runAction里面。

bool HelloWorld::init() {
	auto visibleSize = Director::getInstance()->getVisibleSize();
    auto label = Label::createWithTTF("*** I arrived! ***", "fonts/arial.ttf", 24);
    label->setPosition(visibleSize.width / 2, visibleSize.height / 2 + 100);
    this->runAction([=]() {
        this->addChild(label);
    });
    return true;
}

只要理解了[&]和[=],其他的模式也就好理解了。

关于Lambda函数就说这么多,对于一般的需求应该是够用了。如果想精益求精,可以自己找资料多研究研究C++的知识。

发布了104 篇原创文章 · 获赞 8 · 访问量 21万+

猜你喜欢

转载自blog.csdn.net/sunnyboychina/article/details/105338241