Cocos引擎的渲染工作在主线程完成
因此一旦主线程出现耗时模块产生了阻塞(大文件IO/复杂AI)
游戏画面就不可避免的出现了卡顿
引入多线程可以很好的解决这个问题
看过龙骨部分的代码CCDataReaderHelper
其中实现了纹理/动画文件的异步加载
经过一番学习之后实现了个性化的复杂AI的异步工作
以供参考
实例中使用了我之前的博文做例子
动画纹理来自
http://www.cnblogs.com/billyrun/articles/5577176.html
Astar寻路算法来自
http://www.cnblogs.com/billyrun/articles/5498802.html
本文转载请注明地址
http://www.cnblogs.com/billyrun/articles/5630965.html
1.开辟新的线程处理AI
void HelloWorld::findCallback(Ref* pSender) { //AStarMap* AStar = new AStarMap(map, WIDTH, HEIGHT); //auto path = AStarMap::findPath(AStar, Pos(0, 0), Pos(WIDTH - 1, HEIGHT - 1) , 8); //drawPath(path); if (m_pWorkThread == nullptr) { m_pWorkThread = new std::thread(&HelloWorld::findPathAsync, this); auto scheduler = cocos2d::Director::getInstance()->getScheduler(); scheduler->schedule(schedule_selector(HelloWorld::asyncUpdate), this, 0, false); } }
注释掉的部分是原有代码,同步计算AI路径
有兴趣可以试一下,明显卡顿,卡顿的时间主要就是Astar消耗的时间
现在我们开辟一个新的线程在HelloWorld::findPathAsync方法中计算路径
同时在主线程监听HelloWorld::asyncUpdate方法来等待计算结果
这样等待就是单纯的等待,主线程不会阻塞界面也不会卡顿
计算会发生在子线程中
2.子线程计算结果处理
void HelloWorld::findPathAsync() { AStarMap* AStar = new AStarMap(map, WIDTH, HEIGHT); auto path = AStarMap::findPath(AStar, Pos(0, 0), Pos(WIDTH - 1, HEIGHT - 1), 8); this->path = path; //drawPath(path); } void HelloWorld::asyncUpdate(float t) { if (this->path) { drawPath(this->path); this->path = nullptr; auto scheduler = cocos2d::Director::getInstance()->getScheduler(); scheduler->unschedule(schedule_selector(HelloWorld::asyncUpdate), this); m_pWorkThread->join(); CC_SAFE_DELETE(m_pWorkThread); } }
HelloWorld::findPathAsync方法完成了路径计算但没有绘制
因为cocos中绘制工作需要在主线程完成
多个线程异步调用OpenGL命令想想也是乱了
有一点很奇怪,在子线程中使用CCLOG可能会出现死锁!
尤其是常规的子线程函数是一个循环,循环内部将要sleep的时候
所以在子线程里最好还是做专一的事情
做加载的就纯做加载,做AI计算的就纯做AI计算
HelloWorld::asyncUpdate方法思路很简单
检查到path算出来了就画
同时十分简单粗暴的关闭了线程
如果需要再算路径,再重新开一遍线程
这样做应该是ok的,吧?
更常见的是子线程方法里做一个循环来处理事务,没有事务的时候sleep
有事务的时候主线程再notify_all/notify_one
具体到我这个例子我觉得没必要那么麻烦
3.总结
多线程可以解决需要阻塞引发的实际问题
然而同时也非常容易出错
更多的知识可见参考文献
http://www.cnblogs.com/haippy/p/3284540.html
http://www.cnblogs.com/zhuyp1015/archive/2012/04/08/2438288.html
https://www.chenlq.net/cpp11-faq-chs
完整代码如下
#ifndef __HELLOWORLD_SCENE_H__ #define __HELLOWORLD_SCENE_H__ #include "cocos2d.h" #include "AStar.h" #define WIDTH 128 #define HEIGHT 70 #define CELL_WIDTH 10 #define CELL_HEIGHT 10 class HelloWorld : public cocos2d::Layer { public: HelloWorld(); virtual ~HelloWorld(); // there's no 'id' in cpp, so we recommend returning the class instance pointer static cocos2d::Scene* createScene(); // Here's a difference. Method 'init' in cocos2d-x returns bool, instead of returning 'id' in cocos2d-iphone virtual bool init(); // a selector callback void findCallback(cocos2d::Ref* pSender); void restartCallback(cocos2d::Ref* pSender); // implement the "static create()" method manually CREATE_FUNC(HelloWorld); Node* root; short** map; void drawMap(); void drawPath(Step*); //多线程部分 Step* path; void asyncUpdate(float t); void findPathAsync(); std::thread *m_pWorkThread; }; #endif // __HELLOWORLD_SCENE_H__
#include "HelloWorldScene.h" #include "UVSprite.h" USING_NS_CC; HelloWorld::HelloWorld() :map(nullptr) , m_pWorkThread(nullptr) , path(nullptr) {} HelloWorld::~HelloWorld() { delete[] map; if (m_pWorkThread)m_pWorkThread->join(); CC_SAFE_DELETE(m_pWorkThread); } Scene* HelloWorld::createScene() { // 'scene' is an autorelease object auto scene = Scene::create(); // 'layer' is an autorelease object auto layer = HelloWorld::create(); // add layer as a child to scene scene->addChild(layer); // return the scene return scene; } // on "init" you need to initialize your instance bool HelloWorld::init() { // // 1. super init first if ( !Layer::init() ) { return false; } Size visibleSize = Director::getInstance()->getVisibleSize(); Vec2 origin = Director::getInstance()->getVisibleOrigin(); auto findItem = MenuItemImage::create( "CloseNormal.png", "CloseSelected.png", CC_CALLBACK_1(HelloWorld::findCallback, this)); findItem->setPosition(Vec2(origin.x + visibleSize.width - findItem->getContentSize().width / 2, origin.y + findItem->getContentSize().height / 2)); auto restartItem = MenuItemImage::create( "CloseSelected.png", "CloseNormal.png", CC_CALLBACK_1(HelloWorld::restartCallback, this)); restartItem->setPosition(Vec2(origin.x + visibleSize.width - restartItem->getContentSize().width / 2, origin.y + restartItem->getContentSize().height * 4/ 2)); // create menu, it's an autorelease object auto menu = Menu::create(findItem, restartItem, NULL); menu->setPosition(Vec2::ZERO); this->addChild(menu, 1); CCLOG(""); root = Node::create(); this->addChild(root); //drawMap(); UVSprite * sprite = UVSprite::create("ball.png"); sprite->setPosition(Vec2(visibleSize / 2)); addChild(sprite); sprite->setUVTexture("snowflake2.png"); sprite->setUvStretch(false); sprite->setUvVelocity(Vec2(0.0f, -0.01f)); sprite->setUvAlphaFilter(true); return true; } void HelloWorld::drawMap() { map = new short*[WIDTH]; //随机 for (int i = 0; i < WIDTH; i++) { map[i] = new short[HEIGHT]; for (int j = 0; j < HEIGHT; j++) { bool avaliable = CCRANDOM_MINUS1_1() > -0.6f; if (i <= 1 || i >= WIDTH - 2 || j <= 1 || j >= HEIGHT - 2) { //avaliable = true; } map[i][j] = avaliable ? 1 : 0; } } //墙 for (int i = 1; i < 16; i++) { for (int j = 0; j < HEIGHT - 10; j++) { auto x = WIDTH * i / 16; auto y = i % 2 == 0 ? j : HEIGHT - j - 1; map[x][y] = 0; } } map[0][0] = 1; map[WIDTH - 1][HEIGHT - 1] = 1; for (int i = 0; i < WIDTH; i++) { for (int j = 0; j < HEIGHT; j++) { bool avaliable = map[i][j] == 1; auto sprite = Sprite::create(avaliable ? "white.png" : "black.png"); sprite->setAnchorPoint(ccp(0, 0)); sprite->setPosition(ccp(i * CELL_WIDTH, j * CELL_HEIGHT)); root->addChild(sprite); } } } void HelloWorld::drawPath(Step* step) { if (step) { int steps = 0; while (step != nullptr) { auto x = step->x; auto y = step->y; Step* s = step; step = s->parent; delete s; auto sprite = Sprite::create("stars.png"); sprite->setAnchorPoint(ccp(0, 0)); sprite->setPosition(ccp(x * CELL_WIDTH, y * CELL_HEIGHT)); root->addChild(sprite); steps++; } CCLOG("HelloWorld::drawPath steps = %d", steps); } else { CCLOG("HelloWorld::drawPath step null , no path"); } } void HelloWorld::restartCallback(Ref* pSender) { root->removeAllChildren(); delete[] map; root->runAction(CCSequence::create( CCDelayTime::create(0.5f), CallFunc::create(CC_CALLBACK_0(HelloWorld::drawMap, this)), nullptr )); } void HelloWorld::findCallback(Ref* pSender) { //AStarMap* AStar = new AStarMap(map, WIDTH, HEIGHT); //auto path = AStarMap::findPath(AStar, Pos(0, 0), Pos(WIDTH - 1, HEIGHT - 1) , 8); //drawPath(path); if (m_pWorkThread == nullptr) { //线程创建好之后可以自动执行 //若要强行插在主线程前面,那么join //线程在结束前需要在主线程中对它调用join或者detach一次 //不能不调也不能重复!否则不能正确退出线程 //Cocos中工作线程里使用CCLog可能会导致死锁! m_pWorkThread = new std::thread(&HelloWorld::findPathAsync, this); auto scheduler = cocos2d::Director::getInstance()->getScheduler(); scheduler->schedule(schedule_selector(HelloWorld::asyncUpdate), this, 0, false); } } void HelloWorld::findPathAsync() { AStarMap* AStar = new AStarMap(map, WIDTH, HEIGHT); auto path = AStarMap::findPath(AStar, Pos(0, 0), Pos(WIDTH - 1, HEIGHT - 1), 8); this->path = path; //drawPath(path); } void HelloWorld::asyncUpdate(float t) { if (this->path) { drawPath(this->path); this->path = nullptr; auto scheduler = cocos2d::Director::getInstance()->getScheduler(); scheduler->unschedule(schedule_selector(HelloWorld::asyncUpdate), this); m_pWorkThread->join(); CC_SAFE_DELETE(m_pWorkThread); } }