如何基于Cocos2d-x v3.x实现A星寻路算法

本文详细介绍如何在Cocos2d-x中实现A星寻路算法。通过开发一款迷宫游戏,逐步引导读者理解并实践A星算法的核心概念,如开放列表、封闭列表及路径计算。文章还探讨了如何处理游戏逻辑、优化角色移动以及支持对角线移动等问题。
在学习本篇教程之前,如果你有cocos2d-x的开发经验,将会有所帮助。如果没有也没关系,因为你可以将这里讲解的例子迁移到其他的语言或者框架中。

找到到达你键盘的最短路径,开始吧!

Maze猫

首先介绍下我们将要在本篇教程中开发的简单游戏。

前往下载本篇教程的 工程代码 。编译运行工程,你将看到以下画面。


 

在这款游戏中,你扮演着一只小偷猫,在一个由危险的狗守护着的地牢里小心穿行。如果你试图穿过一只狗,他会把你吃掉 – 除非你可以用骨头去贿赂它!

所以在这款游戏中,你的任务是尝试以正确的顺序捡起骨头,然后 寻找路线 穿过狗逃离。

注意到猫只能水平或者垂直的移动(例如不能斜线移动),并且会从一个方块的中心点移动到另一个中心点。每个方块既可以是可通行的也可以是不可通行的。

尝试下这款游戏,看看你能否找到出路!建议你阅读代码以熟悉它的原理。这是一款相当普通的方块-地图式游戏,我们会在接下来的教程中修改它并使用上A星寻路算法。

Maze猫和A星概览

正如你所看到的,当你点击地图某处时,猫会沿着你点击的方向跳到相邻的方块上。

我们想对程序做修改,让猫持续的往你点击的方块方向前进,就像许多RPGs或者point-and-click冒险类游戏。

让我们看下控制触摸事件代码的工作原理。如果你打开HelloWorldScene.cpp文件,你将看到像下面这样去实现触摸操作:

复制代码
  1. auto listener = EventListenerTouchOneByOne::create();
  2. listener->setSwallowTouches(true);
  3. listener->onTouchBegan = [this](Touch *touch, Event *event){
  4. if (_gameOver)
  5.   {
  6. return false;
  7. }
  8. Point touchLocation = _tileMap->convertTouchToNodeSpace(touch);
  9. _cat->moveToward(touchLocation);
  10. return true;
  11. };
  12. _eventDispatcher->addEventListenerWithSceneGraphPriority(listener, this);


你可以看到这里只是对猫精灵调用了一个方法,让猫在方块地图上往你点击的地方移动。

我们现在要做的是修改在CatSprite.m文件中的以下方法,寻找到达该点的最短路径,并且开始前进:

复制代码
  1. void CatSprite::moveToward(const Point &target)
  2. {
  3.    // Figure out the shortest path to the target, and start following it!
  4. }


创建ShortestPathStep类

我们开始创建一个内部类,代表路径上的一步操作。在这种情况下,它是一个方块和由A星算法计算出来的的F,G和H scores。

复制代码
  1. class ShortestPathStep : public cocos2d::Object
  2. {
  3. public:
  4. ShortestPathStep();
  5. ~ShortestPathStep();
  6. static ShortestPathStep *createWithPosition(const cocos2d::Point &pos);
  7. bool initWithPosition(const cocos2d::Point &pos);
  8. int getFScore() const;
  9. bool isEqual(const ShortestPathStep *other) const;
  10. std::string getDescription() const;
  11. CC_SYNTHESIZE(cocos2d::Point, _position, Position);
  12. CC_SYNTHESIZE(int, _gScore, GScore);
  13. CC_SYNTHESIZE(int, _hScore, HScore);
  14. CC_SYNTHESIZE(ShortestPathStep*, _parent, Parent);
  15. };


现在添加以下代码到CatSprite.cpp文件的顶部。

复制代码
  1. CatSprite::ShortestPathStep::ShortestPathStep() :
  2.     _position(Point::ZERO),
  3.     _gScore(0),
  4.     _hScore(0),
  5.     _parent(nullptr)
  6. {
  7. }
  8. CatSprite::ShortestPathStep::~ShortestPathStep()
  9. {
  10. }
  11. CatSprite::ShortestPathStep *CatSprite::ShortestPathStep::createWithPosition(const Point &pos)
  12. {
  13.     ShortestPathStep *pRet = new ShortestPathStep();
  14.     if (pRet && pRet->initWithPosition(pos))
  15.     {
  16.         pRet->autorelease();
  17.         return pRet;
  18.     }
  19.     else
  20.     {
  21.         CC_SAFE_DELETE(pRet);
  22.         return nullptr;
  23.     }
  24. }
  25. bool CatSprite::ShortestPathStep::initWithPosition(const Point &pos)
  26. {
  27.     bool bRet = false;
  28.     do
  29.     {
  30.         this->setPosition(pos);
  31.         bRet = true;
  32.     } while (0);
  33.     return bRet;
  34. }
  35. int CatSprite::ShortestPathStep::getFScore() const
  36. {
  37.     return this->getGScore() + this->getHScore();
  38. }
  39. bool CatSprite::ShortestPathStep::isEqual(const CatSprite::ShortestPathStep *other) const
  40. {
  41.     return this->getPosition() == other->getPosition();
  42. }
  43. std::string CatSprite::ShortestPathStep::getDescription() const
  44. {
  45.     return StringUtils::format("pos=[%.0f;%.0f]  g=%d  h=%d  f=%d",
  46.                                this->getPosition().x, this->getPosition().y,
  47.                                this->getGScore(), this->getHScore(), this->getFScore());
  48. }


正如所见,这是一个很简单的类,记录了以下内容:

- 方块的坐标
- G值(记住,这是开始点到当前点的方块数量)
- H值(记住,这是当前点到目标点的方块估算数量)
- Parent是它的上一步操作
- F值,这是方块的和值(它是G+H的值)

这里定义了getDescription方法,以方便调试。创建了isEquals方法,当且仅当两个ShortestPathSteps的方块坐标相同时,它们相等(例如它们代表着相同的方块)。

创建Open和Closed列表

打开CatSprite.h文件,添加如下代码:

复制代码
  1. cocos2d::Vector<ShortestPathStep*> _spOpenSteps;
  2. cocos2d::Vector<ShortestPathStep*> _spClosedSteps;


检查开始和结束点

重新实现moveToward方法,获取当前方块坐标和目标方块坐标,然后检查是否需要计算一条路径,最后测试目标方块坐标是否可行走的(在这里只有墙壁是不可行走的)。打开CatSprite.cpp文件,修改moveToward方法,为如下:

复制代码
  1. void CatSprite::moveToward(const Point &target)
  2. {
  3.     Point fromTileCoord = _layer->tileCoordForPosition(this->getPosition());
  4.     Point toTileCoord = _layer->tileCoordForPosition(target);
  5.     if (fromTileCoord == toTileCoord)
  6.     {
  7.         CCLOG("You're already there! :P");
  8.         return;
  9.     }
  10.     if (!_layer->isValidTileCoord(toTileCoord) || _layer->isWallAtTileCoord(toTileCoord))
  11.     {
  12.         SimpleAudioEngine::getInstance()->playEffect("hitWall.wav");
  13.         return;
  14.     }
  15.     CCLOG("From: %f, %f", fromTileCoord.x, fromTileCoord.y);
  16.     CCLOG("To: %f, %f", toTileCoord.x, toTileCoord.y);
  17. }


编译运行,在地图上进行点击,如果不是点击到墙壁的话,可以在控制台看到如下信息:

复制代码
  1. From: 24.000000, 0.000000
  2. To: 20.000000, 0.000000


其中 **From** 就是猫的方块坐标,**To**就是所点击的方块坐标。

实现A星算法

根据算法,第一步是添加当前坐标到open列表。还需要三个辅助方法:

- 一个方法用来插入一个ShortestPathStep对象到适当的位置(有序的F值)
- 一个方法用来计算从一个方块到相邻方块的移动数值
- 一个方法是根据"曼哈顿距离"算法,计算方块的H值

打开CatSprite.cpp文件,添加如下方法:

复制代码
  1. void CatSprite::insertInOpenSteps(CatSprite::ShortestPathStep *step)
  2. {
  3.     int stepFScore = step->getFScore();
  4.     ssize_t count = _spOpenSteps.size();
  5.     ssize_t i = 0;
  6.     for (; i < count; ++i)
  7.     {
  8.         if (stepFScore <= _spOpenSteps.at(i)->getFScore())
  9.         {
  10.             break;
  11.         }
  12.     }
  13.     _spOpenSteps.insert(i, step);
  14. }
  15. int CatSprite::computeHScoreFromCoordToCoord(const Point &fromCoord, const Point &toCoord)
  16. {
  17.     // 这里使用曼哈顿方法,计算从当前步骤到达目标步骤,在水平和垂直方向总的步数
  18.     // 忽略了可能在路上的各种障碍
  19.     return abs(toCoord.x - fromCoord.x) + abs(toCoord.y - fromCoord.y);
  20. }
  21. int CatSprite::costToMoveFromStepToAdjacentStep(const ShortestPathStep *fromStep, const ShortestPathStep *toStep)
  22. {
  23.     // 因为不能斜着走,而且由于地形就是可行走和不可行走的成本都是一样的
  24.     // 如果能够对角移动,或者有沼泽、山丘等等,那么它必须是不同的
  25.     return 1;
  26. }


接下来,需要一个方法去获取给定方块的所有相邻可行走方块。因为在这个游戏中,HelloWorld管理着地图,所以在那里添加方法。打开HelloWorldScene.cpp文件,添加如下方法:

复制代码
  1. PointArray *HelloWorld::walkableAdjacentTilesCoordForTileCoord(const Point &tileCoord) const
  2. {
  3.     PointArray *tmp = PointArray::create(4);
  4.     // 上
  5.     Point p(tileCoord.x, tileCoord.y - 1);
  6.     if (this->isValidTileCoord(p) && !this->isWallAtTileCoord(p))
  7.     {
  8.         tmp->addControlPoint(p);
  9.     }
  10.     // 左
  11.     p.setPoint(tileCoord.x - 1, tileCoord.y);
  12.     if (this->isValidTileCoord(p) && !this->isWallAtTileCoord(p))
  13.     {
  14.         tmp->addControlPoint(p);
  15.     }
  16.     // 下
  17.     p.setPoint(tileCoord.x, tileCoord.y + 1);
  18.     if (this->isValidTileCoord(p) && !this->isWallAtTileCoord(p))
  19.     {
  20.         tmp->addControlPoint(p);
  21.     }
  22.     // 右
  23.     p.setPoint(tileCoord.x + 1, tileCoord.y);
  24.     if (this->isValidTileCoord(p) && !this->isWallAtTileCoord(p))
  25.     {
  26.         tmp->addControlPoint(p);
  27.     }
  28.     return tmp;
  29. }


可以继续CatSprite.cpp中的moveToward方法了,在moveToward方法的后面,添加如下代码:

复制代码
  1. bool pathFound = false;
  2. _spOpenSteps.clear();
  3. _spClosedSteps.clear();
  4. // 首先,添加猫的方块坐标到open列表
  5. this->insertInOpenSteps(ShortestPathStep::createWithPosition(fromTileCoord));
  6. do
  7. {
  8.     // 得到最小的F值步骤
  9.     // 因为是有序列表,第一个步骤总是最小的F值
  10.     ShortestPathStep *currentStep = _spOpenSteps.at(0);
  11.     // 添加当前步骤到closed列表
  12.     _spClosedSteps.pushBack(currentStep);
  13.     // 将它从open列表里面移除
  14.     // 需要注意的是,如果想要先从open列表里面移除,应小心对象的内存
  15.     _spOpenSteps.erase(0);
  16.     // 如果当前步骤是目标方块坐标,那么就完成了
  17.     if (currentStep->getPosition() == toTileCoord)
  18.     {
  19.         pathFound = true;
  20.         ShortestPathStep *tmpStep = currentStep;
  21.         CCLOG("PATH FOUND :");
  22.         do
  23.         {
  24.             CCLOG("%s", tmpStep->getDescription().c_str());
  25.             tmpStep = tmpStep->getParent(); // 倒退
  26.         } while (tmpStep);                  // 直到没有上一步
  27.         _spOpenSteps.clear();
  28.         _spClosedSteps.clear();
  29.         break;
  30.     }
  31.     // 得到当前步骤的相邻方块坐标
  32.     PointArray *adjSteps = _layer->walkableAdjacentTilesCoordForTileCoord(currentStep->getPosition());
  33.     for (ssize_t i = 0; i < adjSteps->count(); ++i)
  34.     {
  35.         ShortestPathStep *step = ShortestPathStep::createWithPosition(adjSteps->getControlPointAtIndex(i));
  36.         // 检查步骤是不是已经在closed列表
  37.         if (this->getStepIndex(_spClosedSteps, step) != -1)
  38.         {
  39.             continue;
  40.         }
  41.         // 计算从当前步骤到此步骤的成本
  42.         int moveCost = this->costToMoveFromStepToAdjacentStep(currentStep, step);
  43.         // 检查此步骤是否已经在open列表
  44.         ssize_t index = this->getStepIndex(_spOpenSteps, step);
  45.         // 不在open列表,添加它
  46.         if (index == -1)
  47.         {
  48.             // 设置当前步骤作为上一步操作
  49.             step->setParent(currentStep);
  50.             // G值等同于上一步的G值 + 从上一步到这里的成本
  51.             step->setGScore(currentStep->getGScore() + moveCost);
  52.             // H值即是从此步骤到目标方块坐标的移动量估算值
  53.             step->setHScore(this->computeHScoreFromCoordToCoord(step->getPosition(), toTileCoord));
  54.             // 按序添加到open列表
  55.             this->insertInOpenSteps(step);
  56.         }
  57.         else
  58.         {
  59.             // 获取旧的步骤,其值已经计算过
  60.             step = _spOpenSteps.at(index);
  61.             // 检查G值是否低于当前步骤到此步骤的值
  62.             if ((currentStep->getGScore() + moveCost) < step->getGScore())
  63.             {
  64.                 // G值等同于上一步的G值 + 从上一步到这里的成本
  65.                 step->setGScore(currentStep->getGScore() + moveCost);
  66.                 // 因为G值改变了,F值也会跟着改变
  67.                 // 所以为了保持open列表有序,需要将此步骤移除,再重新按序插入
  68.                 // 在移除之前,需要先保持引用
  69.                 step->retain();
  70.                 // 现在可以放心移除,不用担心被释放
  71.                 _spOpenSteps.erase(index);
  72.                 // 重新按序插入
  73.                 this->insertInOpenSteps(step);
  74.                 // 现在可以释放它了,因为open列表应该持有它
  75.                 step->release();
  76.             }
  77.         }
  78.     }
  79. } while (_spOpenSteps.size() > 0);
  80. if (!pathFound)
  81. {
  82.     SimpleAudioEngine::getInstance()->playEffect("hitWall.wav");
  83. }


添加以下方法:

复制代码
  1. ssize_t CatSprite::getStepIndex(const cocos2d::Vector<CatSprite::ShortestPathStep *> &steps, const CatSprite::ShortestPathStep *step)
  2. {
  3.     for (ssize_t i = 0; i < steps.size(); ++i)
  4.     {
  5.         if (steps.at(i)->isEqual(step))
  6.         {
  7.             return i;
  8.         }
  9.     }
  10.     return -1;
  11. }


编译运行,在地图上进行点击,如下图所示:


 

复制代码
  1. From: 24.000000, 0.000000
  2. To: 23.000000, 3.000000
  3. PATH FOUND :
  4. pos=[23;3]  g=10  h=0  f=10
  5. pos=[22;3]  g=9  h=1  f=10
  6. pos=[21;3]  g=8  h=2  f=10
  7. pos=[20;3]  g=7  h=3  f=10
  8. pos=[20;2]  g=6  h=4  f=10
  9. pos=[20;1]  g=5  h=5  f=10
  10. pos=[21;1]  g=4  h=4  f=8
  11. pos=[22;1]  g=3  h=3  f=6
  12. pos=[23;1]  g=2  h=2  f=4
  13. pos=[24;1]  g=1  h=3  f=4
  14. pos=[24;0]  g=0  h=0  f=0


注意该路径是从后面建立的,所以必须从下往上看猫选择了哪条路径。

跟随路径前进

现在已经找到了路径,只需让猫跟随前进即可。需要创建一个数组去存储路径,打开CatSprite.h文件,添加如下代码:

复制代码
  1. cocos2d::Vector<ShortestPathStep*> _shortestPath;


打开CatSprite.cpp文件,更改moveToward方法,注释掉语句**bool pathFound = false**;,如下:

复制代码
  1. //bool pathFound = false;


替换语句**pathFound = true;**为如下:

复制代码
  1. //pathFound = true;
  2. this->constructPathAndStartAnimationFromStep(currentStep);


并且注释掉下方的调试语句:

复制代码
  1. //ShortestPathStep *tmpStep = currentStep;
  2. //CCLOG("PATH FOUND :");
  3. //do
  4. //{
  5. //    CCLOG("%s", tmpStep->getDescription().c_str());
  6. //    tmpStep = tmpStep->getParent(); // 倒退
  7. //} while (tmpStep);                  // 直到没有上一步


替换语句**if (!pathFound)**为如下:

复制代码
  1. //if (!pathFound)
  2. if (_shortestPath.empty())


现在创建一个方法,用来存储整个路径,并且负责动画的播放。添加方法如下:

复制代码
  1. void CatSprite::constructPathAndStartAnimationFromStep(CatSprite::ShortestPathStep *step)
  2. {
  3.     _shortestPath.clear();
  4.     do
  5.     {
  6.         // 起始位置不要进行添加
  7.         if (step->getParent())
  8.         {
  9.             // 总是插入到索引0的位置,以便反转路径
  10.             _shortestPath.insert(0, step);
  11.         }
  12.         step = step->getParent();   // 倒退
  13.     } while (step);                 // 直到没有上一步
  14.     for (const ShortestPathStep *s : _shortestPath)
  15.     {
  16.         CCLOG("%s", s->getDescription().c_str());
  17.     }
  18. }


编译运行,点击,就可以在控制台看到如下信息:

复制代码
  1. From: 24.000000, 0.000000
  2. To: 24.000000, 3.000000
  3. pos=[24;1]  g=1  h=2  f=3
  4. pos=[23;1]  g=2  h=3  f=5
  5. pos=[22;1]  g=3  h=4  f=7
  6. pos=[21;1]  g=4  h=5  f=9
  7. pos=[20;1]  g=5  h=6  f=11
  8. pos=[20;2]  g=6  h=5  f=11
  9. pos=[20;3]  g=7  h=4  f=11
  10. pos=[21;3]  g=8  h=3  f=11
  11. pos=[22;3]  g=9  h=2  f=11
  12. pos=[23;3]  g=10  h=1  f=11
  13. pos=[24;3]  g=11  h=0  f=11


这些信息跟之前的很类似,除了它是从开始到结束,而不是相反的,并且步骤都被很好的存储在数组中以供使用。最后要做的是遍历shortestPath数组,让猫沿着路径动画前进。为了实现这一点,创建一个方法,从数组中获取步骤,让猫移动到那个位置,然后添加一个回调函数去重复调用这个方法直到路径完成。添加方法如下:

复制代码
  1. void CatSprite::popStepAndAnimate()
  2. {
  3.     // 检查是否仍有路径步骤需要前进
  4.     if (_shortestPath.size() == 0)
  5.     {
  6.         return;
  7.     }
  8.     // 得到下一步移动的步骤
  9.     ShortestPathStep *s = _shortestPath.at(0);
  10.     // 准备动作和回调
  11.     MoveTo *moveAction = MoveTo::create(0.4f, _layer->positionForTileCoord(s->getPosition()));
  12.     CallFunc *moveCallback = CallFunc::create(CC_CALLBACK_0(CatSprite::popStepAndAnimate, this));
  13.     // 移除步骤
  14.     _shortestPath.erase(0);
  15.     // 运行动作
  16.     this->runAction(Sequence::create(moveAction, moveCallback, nullptr));
  17. }


在constructPathAndStartAnimationFromStep方法里的最下面添加如下代码:

复制代码
  1. this->popStepAndAnimate();


编译运行,点击地面,可以看到猫自动移动到所点击的位置了。如下图所示:


 

然而,会发现到以下问题:

- 猫看起来有点僵硬
- 猫没有带走骨头
- 猫可以穿过狗(没有带着骨头),而不被吃掉
- 当在猫走完路径之前,点击了一个新的路径的话,猫会有奇怪的行为

因此,为了解决猫的僵硬行为,还有游戏逻辑(胜利/失败,狗,骨头,等等......),必须加上之前实现的旧游戏逻辑。

重新添加游戏逻辑

为了修复这些问题,替换popStepAndAnimate方法为如下:

复制代码
  1. void CatSprite::popStepAndAnimate()
  2. {
  3.     Point currentPosition = _layer->tileCoordForPosition(this->getPosition());
  4.     if (_layer->isBoneAtTilecoord(currentPosition))
  5.     {
  6.         SimpleAudioEngine::getInstance()->playEffect("pickup.wav");
  7.         _numBones++;
  8.         _layer->showNumBones(_numBones);
  9.         _layer->removeObjectAtTileCoord(currentPosition);
  10.     }
  11.     else if (_layer->isDogAtTilecoord(currentPosition))
  12.     {
  13.         if (_numBones == 0)
  14.         {
  15.             _layer->loseGame();
  16.             return;
  17.         }
  18.         else
  19.         {
  20.             _numBones--;
  21.             _layer->showNumBones(_numBones);
  22.             _layer->removeObjectAtTileCoord(currentPosition);
  23.             SimpleAudioEngine::getInstance()->playEffect("catAttack.wav");
  24.         }
  25.     }
  26.     else if (_layer->isExitAtTilecoord(currentPosition))
  27.     {
  28.         _layer->winGame();
  29.         return;
  30.     }
  31.     else
  32.     {
  33.         SimpleAudioEngine::getInstance()->playEffect("step.wav");
  34.     }
  35.     // 检查是否仍有路径步骤需要前进
  36.     if (_shortestPath.size() == 0)
  37.     {
  38.         return;
  39.     }
  40.     // 得到下一步移动的步骤
  41.     ShortestPathStep *s = _shortestPath.at(0);
  42.     Point futurePosition = s->getPosition();
  43.     Point diff = futurePosition - currentPosition;
  44.     if (abs(diff.x) > abs(diff.y))
  45.     {
  46.         if (diff.x > 0)
  47.         {
  48.             this->runAnimation(_facingRightAnimation);
  49.         }
  50.         else
  51.         {
  52.             this->runAnimation(_facingLeftAnimation);
  53.         }
  54.     }
  55.     else
  56.     {
  57.         if (diff.y > 0)
  58.         {
  59.             this->runAnimation(_facingForwardAnimation);
  60.         }
  61.         else
  62.         {
  63.             this->runAnimation(_facingBackAnimation);
  64.         }
  65.     }
  66.     // 准备动作和回调
  67.     MoveTo *moveAction = MoveTo::create(0.4f, _layer->positionForTileCoord(s->getPosition()));
  68.     CallFunc *moveCallback = CallFunc::create(CC_CALLBACK_0(CatSprite::popStepAndAnimate, this));
  69.     // 移除步骤
  70.     _shortestPath.erase(0);
  71.     // 运行动作
  72.     Sequence *moveSequence = Sequence::create(moveAction, moveCallback, nullptr);
  73.     moveSequence->setTag(1);
  74.     this->runAction(moveSequence);
  75. }


这里只是对原来的代码进行重构。接着在moveToward方法里面的最上面添加如下代码:

复制代码
  1. this->stopActionByTag(1);


编译运行,可以看到一切正常了,如下图所示:


 

如何实现对角线移动

在A星算法中实现对角线移动十分简单,只需要更改以下两个方法:

- walkableAdjacentTilesCoordForTileCoord:更改以便包括对角线方块
- costToMoveFromStepToAdjacentStep:更改以让对角线移动跟水平/垂直移动有不一样的成本

如何计算出在对角线方向上的成本值?使用简单的数学即可。猫从一个方块的中心移动到另一个方块的中心,并且因为方块是正方形,A、B和C形成了一个三角形,如下图所示:


 


 

所以对角线的移动成本等于1.41,这低于向左移动再向上移动的成本值2(1+1)。正如所知的,使用整型计算远比浮点型更高效,所以不是使用浮点型来标示对角线移动的成本值,而是简单地对成本值乘以10,然后四舍五入,所以水平/垂直移动的成本值为10,而对角线移动的成本值为14。更改costToMoveFromStepToAdjacentStep方法为如下:

复制代码
  1. int CatSprite::costToMoveFromStepToAdjacentStep(const ShortestPathStep *fromStep, const ShortestPathStep *toStep)
  2. {
  3.     return ((fromStep->getPosition().x != toStep->getPosition().x)
  4.             && (fromStep->getPosition().y != toStep->getPosition().y)) ? 14 : 10;
  5. }


更改walkableAdjacentTilesCoordForTileCoord方法为如下:

复制代码
  1. PointArray *HelloWorld::walkableAdjacentTilesCoordForTileCoord(const Point &tileCoord) const
  2. {
  3.     PointArray *tmp = PointArray::create(8);
  4.     bool t = false;
  5.     bool l = false;
  6.     bool b = false;
  7.     bool r = false;
  8.     // 上
  9.     Point p(tileCoord.x, tileCoord.y - 1);
  10.     if (this->isValidTileCoord(p) && !this->isWallAtTileCoord(p))
  11.     {
  12.         tmp->addControlPoint(p);
  13.         t = true;
  14.     }
  15.     // 左
  16.     p.setPoint(tileCoord.x - 1, tileCoord.y);
  17.     if (this->isValidTileCoord(p) && !this->isWallAtTileCoord(p))
  18.     {
  19.         tmp->addControlPoint(p);
  20.         l = true;
  21.     }
  22.     // 下
  23.     p.setPoint(tileCoord.x, tileCoord.y + 1);
  24.     if (this->isValidTileCoord(p) && !this->isWallAtTileCoord(p))
  25.     {
  26.         tmp->addControlPoint(p);
  27.         b = true;
  28.     }
  29.     // 右
  30.     p.setPoint(tileCoord.x + 1, tileCoord.y);
  31.     if (this->isValidTileCoord(p) && !this->isWallAtTileCoord(p))
  32.     {
  33.         tmp->addControlPoint(p);
  34.         r = true;
  35.     }
  36.     // 左上
  37.     p.setPoint(tileCoord.x - 1, tileCoord.y - 1);
  38.     if (t && l && this->isValidTileCoord(p) && !this->isWallAtTileCoord(p))
  39.     {
  40.         tmp->addControlPoint(p);
  41.     }
  42.     // 左下
  43.     p.setPoint(tileCoord.x - 1, tileCoord.y + 1);
  44.     if (b && l && this->isValidTileCoord(p) && !this->isWallAtTileCoord(p))
  45.     {
  46.         tmp->addControlPoint(p);
  47.     }
  48.     // 右上
  49.     p.setPoint(tileCoord.x + 1, tileCoord.y - 1);
  50.     if (t && r && this->isValidTileCoord(p) && !this->isWallAtTileCoord(p))
  51.     {
  52.         tmp->addControlPoint(p);
  53.     }
  54.     // 右下
  55.     p.setPoint(tileCoord.x + 1, tileCoord.y + 1);
  56.     if (b && r && this->isValidTileCoord(p) && !this->isWallAtTileCoord(p))
  57.     {
  58.         tmp->addControlPoint(p);
  59.     }
  60.     return tmp;
  61. }


重要提示:添加对角线方块的代码和添加水平/垂直方块的代码有些不同。事实上,例如,只有当顶部和左侧的方块被添加时,左上对角线才能够被添加。这是为了防止猫穿过墙壁的角落。以下是所有的详细情况处理:

- O = Origin
- T = Top
- B = Bottom
- L = Left
- R = Right
- TL = Top – Left
- …


 

就拿上面图像的左上部分来进行举例。猫想要从原始点(O)到左下对角线方块(BL)。如果在左侧或者底部(或者都有)有一面墙,然后尝试走对角线,算法将会封掉墙壁的角落(或者两面墙壁的角落)。所以只有当左侧和底部没有墙壁时,左下对角线方块才可行走。如下图所示:


 

完整源码请下载 CatMazeStarter

引用:
 
本文改编自iOS Tutorial Team的成员 Johann Fradj 的文章 如何使用coocs2d-x 3.x中实现A*寻路算法 。以及博主 无幻 的部分资源。

[ 此帖被ivenyang在2014-04-30 11:46重新编辑 ]

http://www.cocoachina.com/bbs/read.php?tid=199994

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值