游戏暂停是一个简单的功能,但只有在实际项目中才会用到。因此学了这么久,才在这里遇到了困难。书本中并未讲解,在网上查阅资料,得到了三点解决方案:
1.使用Director的pause方法;
2.先保存屏幕截图,然后push另外一个场景,该场景背景就是此截图。使用RenderTexture动态纹理类保存截图;
3.遍历节点,调用其onExit()接口。
先说下前两个解决方案。第一个不知道为什么,启用的时候会出现卡顿,不是我想要的暂停效果。第二个方案,要多写一个暂停场景类,对于游戏体验需求比较大的游戏,自然是必不可少了,但是不适合普通的按钮暂停方式。
这里我总结第三个方案,那就是使用onExit。根据官方文档,一个Node调用onExit接口,就是在它“退出舞台”时候调用。这个解释实在是很不具体,退出舞台时到底是做了什么?只好查看源代码了。在Node的onExit方法里面,有两个很重要的代码:
this->pause();
for( const auto &child: _children) child->onExit();
可以看到,一个节点onExit后,其子节点也全部onExit,而且它还调用了一个pause方法,那么pause方法又做了什么呢?看代码:
void Node::pause() { _scheduler->pauseTarget(this); _actionManager->pauseTarget(this); _eventDispatcher->pauseEventListenersForTarget(this); }
1.暂停了该节点的schedule;2.暂停了该节点的动作;3.暂停了该节点的事件监听。
也就是说,除了渲染,其余的所有动态功能几乎全都暂停了。现在就很清楚了:onExit的本质是一个回调函数,是节点在退出舞台时候调用的,功能是让它的节点“收工”。虽然它是一个回调函数,但完全可以手动调用。
回到问题:如何让游戏场景暂停?
场景中的主Layer是一个Node,只要主Layer调用了onExit,其所有子节点也会暂停不动了。但是,我们总有一些节点需要“加班”,比如按钮,如果把暂停按钮也onExit了,那么就没办法重新开启游戏了。解决这个办法也很简单,那就是通过遍历子节点,一个一个地onExit暂停它们,同时排除掉不需要暂停的子节点。这样,主Layer虽然没有暂停,但是其所有的该暂停的子节点已经全部暂停了。
见代码:
auto vec = this->getChildren(); for (auto &child : vec) { if (child->getTag() != PAUSE_MENU) child->onExit(); }
这样,基本就完成了暂停功能。但是还有两个非常重要的点,会对暂停产生致命影响,那就是:
1.主Layer的事件监听还没有关闭,仍然在接收事件,如触摸;
2.如果场景带有物理世界,那么物理世界还没有暂停,物理模拟还在继续。
因此,还需要多调用两行代码才能完成真正的暂停:
_eventDispatcher->pauseEventListenersForTarget(this); ((Scene*)this->getParent())->getPhysicsWorld()->setAutoStep(false);
这样才算完成了一个完整的暂停效果。而且看上去效率挺快,实际操作上也没出现任何卡顿。最后就是重启游戏了,很简单,反过来调用onEnter即可:节点们,开工了!