Cocos2d-x多线程异步AI寻路

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__
View Code
#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);
    }
    
}
View Code

 

转载于:https://www.cnblogs.com/billyrun/articles/5630965.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值