你是否在做一款游戏的时候想创造一些怪兽或者游戏主角,让它们移动到特定的位置,避开墙壁和障碍物呢?如果是的话,请看这篇教程,我们会展示如何使用A星寻路算法来实现它!
A*搜寻算法俗称A星算法。这是一种在图形平面上,有多个节点的路径,求出最低通过成本的算法。常用于游戏中的NPC的移动计算,或线上游戏的BOT的移动计算上。
可参考这两篇文章:
http://www.raywenderlich.com/zh-hans/21503/a星寻路算法介绍
http://www.raywenderlich.com/zh-hans/21315/如何使用cocos2d实现a星寻路算法
ShortestPathStep.h
#ifndef __AStarDemo__ShortestPathStep__ #define __AStarDemo__ShortestPathStep__ #include "cocos2d.h" USING_NS_CC; class ShortestPathStep:public CCNode { public: ShortestPathStep(void); ~ShortestPathStep(void); //CREATE_FUNC(ShortestPathStep); //m_pos表示方块的坐标; GScore表示这是开始点到当前点之间的方块数目; //HScore表示当前点到终点之前的方块估算值; m_Parent表示它自身的前继 CC_SYNTHESIZE(CCPoint, m_pos, Pos); CC_SYNTHESIZE(float,m_fGScore,GScore); CC_SYNTHESIZE(float,m_fHScore,HScore); CC_SYNTHESIZE(ShortestPathStep*,m_Parent,Parent); bool InitWithPosition(CCPoint point); bool IsEqual(ShortestPathStep* step); //方块的score值(它是F和G的和) float getFScore(); bool operator == (const ShortestPathStep& other); }; #endif /* defined(__AStarDemo__ShortestPathStep__) */
ShortestPathStep.cpp
#include "ShortestPathStep.h" ShortestPathStep::ShortestPathStep(void) { } ShortestPathStep::~ShortestPathStep(void) { } bool ShortestPathStep::InitWithPosition(CCPoint point) { bool pRet = false; if (CCNode::node()) { m_pos = point; m_fGScore = 0; m_fHScore = 0; m_Parent = NULL; pRet = true; } return pRet; } bool ShortestPathStep::IsEqual(ShortestPathStep *step) { return this->m_pos.equals(step->getPos()); } float ShortestPathStep::getFScore() { return (m_fGScore+m_fHScore); } bool ShortestPathStep::operator==(const ShortestPathStep &other) { return (other.getPos().equals(this->m_pos)); }
resource.h
#ifndef AStarDemo_resource_h #define AStarDemo_resource_h #define IDS_PROJNAME 100 #define IDR_ASTARDEMO 100 #define ID_FILE_NEW_WINDOW 32771 #ifdef APSTUDIO_INVOKED #ifndef APSTUDIO_READONLY_SYMBOLS #define _APS_NEXT_RESOURCE_VALUE 201 #define _APS_NEXT_CONTROL_VALUE 1000 #define _APS_NEXT_SYMED_VALUE 101 #define _APS_NEXT_COMMAND_VALUE 32775 #endif #endif
AStar.h
#ifndef __AStarDemo__AStar__ #define __AStarDemo__AStar__ #include "cocos2d.h" USING_NS_CC; class ShortestPathStep; typedef struct st_AStar_Coord_Info { CCPoint pointOrg; CCPoint point; int nType; }AStar_Coord_Info; class AStar:public CCNode { public: AStar(void); ~AStar(void); //创建open和closed列表 CC_SYNTHESIZE_RETAIN(CCArray*,openArray,OpenArray); CC_SYNTHESIZE_RETAIN(CCArray*,closeArray,CloseArray); //检查开始和结束点 void MoveToward(CCPoint fromPos,CCPoint toPos); void InsertInOpenArrays(ShortestPathStep *step); virtual CCPoint AStarCoordForPosition(CCPoint point); virtual bool IsValidPos(CCPoint point); void ConstructShortestPath(ShortestPathStep* step); void EndAStar(); int costToMoveFromStep(ShortestPathStep *fromStep ,ShortestPathStep *toStep); //实现对角线移动 void walkableAdjacentTilesCoordForTileCoord(CCPoint point, std::vector<CCPoint>& tmp); int computeHScoreFromCoord(CCPoint fromCoord ,CCPoint toCoord); void setBlockSize(int size = 32); int indexOfObject(CCArray *array,ShortestPathStep *st); bool ContainObject(CCArray *array,ShortestPathStep *st); int m_nBlockSize; CCArray *m_shortestPaths; public: virtual void SetMapSize(CCSize size,CCPoint orgPoint = CCPointZero); virtual void InitAStarCoord(); std::vector<AStar_Coord_Info> m_AstarCoordInfo; }; #endif /* defined(__AStarDemo__AStar__) */
AStar.cpp
#include "AStar.h" #include "ShortestPathStep.h" #include <algorithm> AStar::AStar(void) { m_nBlockSize = 32; m_shortestPaths = NULL; } AStar::~AStar() { } void AStar::setBlockSize(int size) { m_nBlockSize = size; } CCPoint AStar::AStarCoordForPosition(CCPoint point) { return CCPointMake(((int)point.x) / m_nBlockSize ,((int)point.y) / m_nBlockSize); } bool AStar::IsValidPos(CCPoint point) { std::vector<AStar_Coord_Info>::iterator it = m_AstarCoordInfo.begin(); for (; it!=m_AstarCoordInfo.end(); it++) { if ((*it).point.equals(point)) return ((*it).nType == 0); } return false; } void AStar::InsertInOpenArrays(ShortestPathStep *step) { int stepFScore = step->getFScore(); int count = openArray->count(); int i = 0; for (; i<count; i++) { if (stepFScore <= ((ShortestPathStep*)openArray->objectAtIndex(i))->getFScore()) { break; } } openArray->insertObject(step, i); } void AStar::ConstructShortestPath(ShortestPathStep *step) { m_shortestPaths = CCArray::create(); m_shortestPaths->retain(); do{ if (step->getParent() != NULL) { m_shortestPaths->insertObject(step, 0); } step = step->getParent(); }while (step != NULL); } void AStar::EndAStar() { openArray->removeAllObjects(); openArray->release(); closeArray->removeAllObjects(); closeArray->release(); } void AStar::walkableAdjacentTilesCoordForTileCoord(cocos2d::CCPoint point, std::vector<CCPoint> &tmp) { bool t = false; bool l = false; bool b = false; bool r = false; //Top CCPoint p = CCPointMake(point.x, point.y-1); if (IsValidPos(p)) { tmp.push_back(p); t = true; } // Left p = CCPointMake(point.x - 1, point.y); if (IsValidPos(p)) { tmp.push_back(p); l = true; } // Bottom p = CCPointMake(point.x, point.y + 1); if (IsValidPos(p)) { tmp.push_back(p); b = true; } // Right p = CCPointMake(point.x + 1, point.y); if (IsValidPos(p)) { tmp.push_back(p); r = true; } // Top Left p = CCPointMake(point.x - 1, point.y - 1); if (t && l && IsValidPos(p)) { tmp.push_back(p); } // Bottom Left p = CCPointMake(point.x - 1, point.y + 1); if (b && l && IsValidPos(p)) { tmp.push_back(p); } // Top Right p = CCPointMake(point.x + 1, point.y - 1); if (t && r && IsValidPos(p)) { tmp.push_back(p); } // Bottom Right p = CCPointMake(point.x + 1, point.y + 1); if (b && r && IsValidPos(p)) { tmp.push_back(p); } } void AStar::MoveToward(cocos2d::CCPoint fromPos, cocos2d::CCPoint toPos) { if (m_shortestPaths) { m_shortestPaths->removeAllObjects(); } CCPoint fromAStarCoor = AStarCoordForPosition(fromPos); CCPoint toAStarCoord = AStarCoordForPosition(toPos); if(fromAStarCoor.equals(toAStarCoord)) { return; } if(!IsValidPos(toAStarCoord)) { return; } openArray = CCArray::create(); openArray->retain(); closeArray = CCArray::create(); closeArray->retain(); ShortestPathStep *step = new ShortestPathStep(); step->retain(); step->InitWithPosition(fromAStarCoor); InsertInOpenArrays(step); do{ ShortestPathStep *curStep = (ShortestPathStep *)openArray->objectAtIndex(0); curStep->retain(); closeArray->addObject(curStep); openArray->removeObjectAtIndex(0); if (curStep->getPos().equals(toAStarCoord)) { ConstructShortestPath(curStep); EndAStar(); break; } std::vector<CCPoint> pointVec; walkableAdjacentTilesCoordForTileCoord(curStep->getPos(),pointVec); for (int i = 0; i<pointVec.size(); i++) { ShortestPathStep *st = new ShortestPathStep(); st->autorelease(); st->InitWithPosition(pointVec[i]); if (ContainObject(closeArray, st)) { st->release(); continue; } int moveCost = costToMoveFromStep(curStep, st); int index = indexOfObject(openArray, st); if (index == UINT_MAX) { st->setParent(curStep); st->setHScore(computeHScoreFromCoord(st->getPos(), toAStarCoord)); InsertInOpenArrays(st); st->release(); } else { st->release(); st = (ShortestPathStep*) openArray->objectAtIndex(index); if ((curStep->getGScore() + moveCost)<st->getGScore()) { st->setGScore(curStep->getGScore() + moveCost); st->retain(); openArray->removeObjectAtIndex(index); InsertInOpenArrays(st); st->release(); } } } }while (openArray->count() > 0); EndAStar(); } int AStar::costToMoveFromStep(ShortestPathStep *fromStep ,ShortestPathStep *toStep) { return ((fromStep->getPos().x != toStep->getPos().x) && (fromStep->getPos().y != toStep->getPos().y)) ? 14 : 10; } int AStar::computeHScoreFromCoord(CCPoint fromCoord ,CCPoint toCoord) { // Here we use the Manhattan method, which calculates the total number of step moved horizontally and vertically to reach the // final desired step from the current step, ignoring any obstacles that may be in the way return abs(toCoord.x - fromCoord.x) + abs(toCoord.y - fromCoord.y); } bool AStar::ContainObject(CCArray *array,ShortestPathStep *st) { if(array == NULL) return false; for(int i=0; i<array->count(); i++) { ShortestPathStep *t = (ShortestPathStep *)array->objectAtIndex(i); if(t->getPos().equals(st->getPos())) { return true; } } return false; } int AStar::indexOfObject(CCArray *array,ShortestPathStep *st) { if(array == NULL) return UINT_MAX; for(int i=0; i<array->count(); i++) { ShortestPathStep *t = (ShortestPathStep *)array->objectAtIndex(i); if(t->getPos().equals(st->getPos())) { return i; } } return UINT_MAX; } void AStar::SetMapSize(CCSize size,CCPoint orgPoint) { } void AStar::InitAStarCoord() { // CCSize size = CCDirector::sharedDirector()->getWinSize(); int AStarWidth = size.width/m_nBlockSize ; // int AStarHeight = size.height/m_nBlockSize; for (int w=-1; w <AStarWidth; w++) { for (int h=-1; h<AStarHeight; h++) { AStar_Coord_Info coordInfo; coordInfo.point = CCPointMake(w,h); coordInfo.pointOrg = CCPointMake(m_nBlockSize/2+w*m_nBlockSize,m_nBlockSize/2+m_nBlockSize*h); // if (w == -1 || h==-1 || w == AStarWidth-1 || h == AStarHeight-1) { coordInfo.nType = 1; } // else if ((w>3 && w <= 8) && h == 4) { coordInfo.nType = 1; } else if ((w>3 && w <= 6) && h == 2) { coordInfo.nType = 1; } else if ((h>4 && h <= 8) && w == 8) { coordInfo.nType = 1; }else { coordInfo.nType = 0; } m_AstarCoordInfo.push_back(coordInfo); } } }
HelloWorldScene.h
#ifndef __HELLOWORLD_SCENE_H__ #define __HELLOWORLD_SCENE_H__ #include "cocos2d.h" #include "AStar.h" #include "ShortestPathStep.h" USING_NS_CC; class HelloWorld : public cocos2d::CCLayer { public: // Method 'init' in cocos2d-x returns bool, instead of 'id' in cocos2d-iphone (an object pointer) virtual bool init(); // there's no 'id' in cpp, so we recommend to return the class instance pointer static cocos2d::CCScene* scene(); // a selector callback void menuCloseCallback(CCObject* pSender); // preprocessor macro for "static create()" constructor ( node() deprecated ) CREATE_FUNC(HelloWorld); void ClearPath(); void PaintPath(); private: AStar *m_AStar; CCArray *m_PathArray; CCLayerColor *m_colorFrom; CCLayerColor *m_colorEnd; public: //ccTouches virtual void ccTouchesBegan(CCSet *pTouches, CCEvent *pEvent); virtual void ccTouchesMoved(CCSet *pTouches, CCEvent *pEvent); virtual void ccTouchesEnded(CCSet *pTouches, CCEvent *pEvent); virtual void ccTouchesCancelled(CCSet *pTouches, CCEvent *pEvent); private: CCPoint m_FromPoint; // CCPoint m_ToPoint; // bool m_bSecondPoint; // }; #endif // __HELLOWORLD_SCENE_H__
HelloWorldScene.cpp
#include "HelloWorldScene.h" #include "SimpleAudioEngine.h" using namespace cocos2d; using namespace CocosDenshion; #define AStar_Coord_Block 32 CCScene* HelloWorld::scene() { // 'scene' is an autorelease object CCScene *scene = CCScene::create(); // 'layer' is an autorelease object HelloWorld *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 ( !CCLayer::init() ) { return false; } m_bSecondPoint = false; m_AStar = new AStar; m_AStar->InitAStarCoord(); m_PathArray = CCArray::create(); m_PathArray->retain(); std::vector<AStar_Coord_Info>::iterator it = m_AStar->m_AstarCoordInfo.begin(); for (; it != m_AStar->m_AstarCoordInfo.end(); it++) { CCLayerColor *color = CCLayerColor::create(ccc4(124, 124, 124, 124)); if ((*it).nType == 0) { color->setColor(ccc3(255, 0, 255)); } else { color->setColor(ccc3(125, 125, 0)); } color->setContentSize(CCSizeMake(AStar_Coord_Block,AStar_Coord_Block)); color->setPosition((*it).pointOrg); this->addChild(color); } PaintPath(); this->setTouchEnabled(true); return true; } void HelloWorld::menuCloseCallback(CCObject* pSender) { CCDirector::sharedDirector()->end(); #if (CC_TARGET_PLATFORM == CC_PLATFORM_IOS) exit(0); #endif } void HelloWorld::PaintPath() { if(m_AStar->m_shortestPaths != NULL) { for (int i=0; i<m_AStar->m_shortestPaths->count();i++) { CCLayerColor *color = CCLayerColor::create(ccc4(255,124,124,124),AStar_Coord_Block,AStar_Coord_Block); ShortestPathStep* s =(ShortestPathStep*) (m_AStar->m_shortestPaths->objectAtIndex(i)); CCPoint point = CCPointMake(s->getPos().x *AStar_Coord_Block +AStar_Coord_Block/2,s->getPos().y*AStar_Coord_Block+AStar_Coord_Block/2); color->setPosition(point); this->addChild(color); m_PathArray->addObject(color); } } } void HelloWorld::ClearPath() { CCObject* it = NULL; CCARRAY_FOREACH(m_PathArray, it) { CCLayerColor *color = (CCLayerColor *)it; this->removeChild(color,true); } m_PathArray->removeAllObjects(); this->removeChild(m_colorEnd, true); this->removeChild(m_colorFrom, true); } void HelloWorld::ccTouchesBegan(CCSet *pTouches, CCEvent *pEvent) { CCSetIterator it = pTouches->begin(); CCTouch *pTouch = (CCTouch*)(*it); CCPoint touchLocation = convertTouchToNodeSpace(pTouch); touchLocation = m_AStar->AStarCoordForPosition(touchLocation); touchLocation = CCPointMake(AStar_Coord_Block*touchLocation.x+AStar_Coord_Block/2,AStar_Coord_Block*touchLocation.y+AStar_Coord_Block/2); if (m_bSecondPoint) { m_ToPoint = touchLocation; // m_AStar->MoveToward(m_FromPoint,m_ToPoint); // m_colorEnd = CCLayerColor::create(ccc4(255,0,0,255),AStar_Coord_Block,AStar_Coord_Block); m_colorEnd->setPosition(m_ToPoint); this->addChild(m_colorEnd); // PaintPath(); m_bSecondPoint = false; } else { m_FromPoint = touchLocation; // ClearPath(); // m_colorFrom = CCLayerColor::create(ccc4(255,0,255,255),AStar_Coord_Block,AStar_Coord_Block); m_colorFrom->setPosition(m_FromPoint); this->addChild(m_colorFrom); m_bSecondPoint = true; } } void HelloWorld::ccTouchesMoved(CCSet *pTouches, CCEvent *pEvent) { CCSetIterator it = pTouches->begin(); CCTouch *pTouch = (CCTouch*)(*it); switch (pTouches->count()) { case 1: { CCPoint touchLocation = convertTouchToNodeSpace(pTouch); } break; case 2: { } break; } } void HelloWorld::ccTouchesEnded(CCSet *pTouches, CCEvent *pEvent) { } void HelloWorld::ccTouchesCancelled(CCSet *pTouches, CCEvent *pEvent) { }