cocos2d-x学习日志(13) --A星寻路算法demo

    你是否在做一款游戏的时候想创造一些怪兽或者游戏主角,让它们移动到特定的位置,避开墙壁和障碍物呢?如果是的话,请看这篇教程,我们会展示如何使用A星寻路算法来实现它!


A星算法简介:

A*搜寻算法俗称A星算法。这是一种在图形平面上,有多个节点的路径,求出最低通过成本的算法。常用于游戏中的NPC的移动计算,或线上游戏的BOT的移动计算上。


实现原理:

可参考这两篇文章:

http://www.raywenderlich.com/zh-hans/21503/a星寻路算法介绍 

http://www.raywenderlich.com/zh-hans/21315/如何使用cocos2d实现a星寻路算法


实现代码:


1.创建ShortestPathStep类,代表路径上的一步操作

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));
}

2.创建头文件resource,添加必要宏定义


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

3.创建AStar类,实现A星算法基本操作


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);
		}
	}
	
}

4.测试代码:


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)
{
}


效果图:


cocos2d-x学习日志(13) --A星寻路算法demo_第1张图片


备注:


     cocos2d-x学习日志(13) --A星寻路算法demo_第2张图片



你可能感兴趣的:(cocos2d-x)