cocos2d-x学习日志(14) --A星寻路算法之45度地图

原创 2013年12月16日 12:03:54

一、A星搜索

      他就是一种启发性的算法,根据现在到达这个位置的步数及之后的“估计步数”,即f=g+h,f是整个从起点到终点的代价,g是从起点到我们目前位置的步数,h是从目前位置到终点的估计值,注意这里是估计值,所以我们得到解并不一定是最好的解,具体解“好”到什么程度呢?就是要根据h的估计的好坏,因此只是一个较优解。(以上的部分只能说是我对A星算法较为浅的理解,只能算是初探吧,有不足之处欢迎指正,这里只是为了更好的解释我的程序。)

我的程序的流程可以分为如下几步,

  • 有一个open表,有一个close表,open表首先存储我们的起点,然后由此出发。
  • 首先把起点放入close列表,并检测这点周围点的f值(g+h,h我们通过本点到终点的横纵索引差估计而来)。
  • 把剩下的点放入open列表中,并根据f值进行堆排序。
  • 把f值最小的点放入close列表中,
  • 继续上面的循环,继续处理,直到找到终点为止。

当然我们还要根据我们的游戏做一些处理即是处理地图中的碰撞等,我们要让我们的人物绕过碰撞。


二、代码

Astaritem.h

#pragma once
#include "cocos2d.h"
using namespace cocos2d;
class AstarItem:public CCNode
{
public:
    AstarItem(void);
	~AstarItem(void);
	void setpos(int col,int row);
	int getcol(){return id_col;};
	int getrow(){return id_row;};
	void setg(int g);
	int getg(){return id_g;};
	void seth(int h);
	int geth(){return id_h;};
	void setfid(int fid);
	int getfid(){return id_fid;};
	void setf(int f);
	int getf(){return id_f;};
private:
	int id_col;//列
	int id_row;//行
	int id_g;// 实际代价
	int id_h;// 估计代价
	int id_fid;// 父节点id
	int id_f;// 估价函数f = g + h
};

Astaritem.cpp

#include "Astaritem.h"
AstarItem::AstarItem(void)
{
}
AstarItem::~AstarItem(void)
{
}
void AstarItem::setpos(int col,int row)
{
	id_col = col;
	id_row = row;
}
void AstarItem::setg(int g)
{
	id_g = g;
}
void AstarItem::seth(int h)
{
	id_h = h;
}
void AstarItem::setfid(int fid)
{
	id_fid = fid;
}
void AstarItem::setf(int f)
{
	id_f = f;
}

Astar.h

#pragma once
#include "cocos2d.h"
#include "AstarItem.h"
using namespace cocos2d;
class Astar
{
private:
	int curCol, curRow, aimCol, aimRow;
	int AimX, AimY, AimW, AimH; 
	CCTMXTiledMap* map;
    CCArray *open;
    CCArray *close;
    CCArray *path;
public:
	Astar(void);
	~Astar(void);
	int getG(int col,int row,int id);
	int getH(int col,int row);
	void fromopentoclose();
	void removefromopen();
	void getpath();
	void starseach(int fid);
	void resetSort(int last);
	bool checkclose(int col,int row);
	void addtoopen(int col,int row,int id);
	bool checkmap(int col,int row);
	bool checkOpen(int col,int row,int id);
    CCArray *findPath(int curX, int curY, int aimX, int aimY, CCTMXTiledMap* passmap);
};

Astar.cpp

#include "Astar.h"
#include "Astaritem.h"

Astar::Astar(void)
{
}

Astar::~Astar(void)
{
    open->removeAllObjects();
    close->removeAllObjects();
    path->removeAllObjects();
}
CCArray *Astar::findPath(int curX, int curY, int aimX, int aimY, CCTMXTiledMap* passmap)
{
    //参数以及记录路径数组初始化
	curCol = curX;
    curRow = curY;
	aimCol = aimX;
	aimRow = aimY;
	map = passmap;
	path = new CCArray();
	open = new CCArray();
    AstarItem * temp = new AstarItem();
	open->addObject(temp);
	AstarItem * temp1 = new AstarItem();
	temp1->setpos(curCol,curRow);
	temp1->setg(0);
	int ag = getH(curCol,curRow);
	temp1->seth(ag);
	temp1->setfid(0);
	temp1->setf(ag);
	open->addObject(temp1);
	close = new CCArray();
    //遍历寻找路径
	while(open->count() > 1){
	   fromopentoclose();//open和close列表管理
	   int fatherid = close->count() - 1;
	   if(abs(aimCol - ((AstarItem *)close->objectAtIndex(fatherid))->getcol()) <= 1 && abs(aimRow - ((AstarItem *)close->objectAtIndex(fatherid))->getrow()) <= 1){
		   getpath();
		   break;
	   }else{
           //搜索
	       starseach(fatherid);
	   }
	}
	open->removeAllObjects();
	close->removeAllObjects();
    //获得路径
	if(path->count() == 0){
	   return NULL;
	}else{
		if(((AstarItem *)path->lastObject())->getcol() != aimCol || ((AstarItem *)path->lastObject())->getrow() != aimRow){
		   AstarItem * temp = new AstarItem();
	       temp->setpos(aimCol,aimRow);
		   path->addObject(temp);
		}
		return path;
	}
}
void Astar::starseach(int fid)
{
	int col = ((AstarItem *)close->objectAtIndex(fid))->getcol();
	int row = ((AstarItem *)close->objectAtIndex(fid))->getrow();
    //搜索目前点的上下左右四个方向
	int mycol = col;
	int myrow = row - 1;
	if(myrow >= 0 && checkmap(mycol,myrow)){
		if(checkOpen(mycol,myrow,fid) && checkclose(mycol,myrow)){
		   addtoopen(mycol,myrow,fid);
		}
	}
	mycol = col - 1;
	myrow = row;
	if(mycol >= 0 && checkmap(mycol,myrow)){
		if(checkOpen(mycol,myrow,fid) && checkclose(mycol,myrow)){
		   addtoopen(mycol,myrow,fid);
		}
	}
	mycol = col;
	myrow = row + 1;
	if(myrow < map->getMapSize().width && checkmap(mycol,myrow)){
		if(checkOpen(mycol,myrow,fid) && checkclose(mycol,myrow)){
		   addtoopen(mycol,myrow,fid);
		}
	}
	mycol = col + 1;
	myrow = row;
	if(mycol < map->getMapSize().height && checkmap(mycol,myrow)){
		if(checkOpen(mycol,myrow,fid) && checkclose(mycol,myrow)){
		   addtoopen(mycol,myrow,fid);
		}
	}
}
void Astar::addtoopen(int col,int row,int id)
{
    //向open列表中加入点
    AstarItem * temp = new AstarItem();
	temp->setpos(col,row);
	temp->setfid(id);
	int g = getG(col, row, id);
	int h = getH(col, row);
	temp->setg(g);
	temp->seth(h);
	temp->setf(g + h);
	open->addObject(temp);
	resetSort(open->count() - 1);

}
bool Astar::checkclose(int col,int row)
{
    //检查close列表
	for(int i = close->count() - 1;i > 0;i --){
        if(((AstarItem *)close->objectAtIndex(i))->getcol() == col && ((AstarItem *)close->objectAtIndex(i))->getrow() == row){
           return false; 
		}
	}
	return true;
}
bool Astar::checkOpen(int col,int row,int id)
{
    //检查open列表中是否有更小的步长,并排序
	for(int i = open->count() - 1;i > 0;i --){
		if(((AstarItem *)open->objectAtIndex(i))->getcol() == col && ((AstarItem *)open->objectAtIndex(i))->getrow() == row){
		    int tempG = getG(col,row,id);
			if(tempG < ((AstarItem *)open->objectAtIndex(i))->getg()){
			   ((AstarItem *)open->objectAtIndex(i))->setg(tempG);
			   ((AstarItem *)open->objectAtIndex(i))->setfid(id);
			   ((AstarItem *)open->objectAtIndex(i))->setf(tempG + ((AstarItem *)open->objectAtIndex(i))->geth());
			   resetSort(i);
			}
			return false;
		}
	}
	return true;
}
bool Astar::checkmap(int col,int row)
{
    //检查地图中是否有碰撞
   if(abs(aimCol - col) > 1 || abs(aimRow - row) > 1){
	  CCTMXLayer* layer = map->layerNamed("trees2");
      int tilegid = layer->tileGIDAt(ccp(col,row));
	  CCDictionary *tiledic = map->propertiesForGID(tilegid);
    
     
       if (tiledic)
       {
           CCDictionary *properties = map->propertiesForGID(tilegid);
           if (properties)
           {
               const CCString *collision = properties->valueForKey("conflict");
               if (collision && collision->compare("true") == 0)
               {
                   return false;
               }
           }
       }

       
       
//	  CCString *mvalue = (CCString *)tiledic->objectForKey("conflict");
//      int mv = mvalue->intValue();
//	  if(mv == 1){
//	     return false;
//	  } 
   }
   return true;
}
void Astar::getpath()
{
    //从整个close数组中找出路径
	if(path->count() == 0){
		path->addObject(close->objectAtIndex(close->count() - 1));
	}else{
	    path->insertObject(close->objectAtIndex(close->count() - 1),path->count() - 1);
	}
	while(true){
		if(((AstarItem *)path->objectAtIndex(0))->getg() == 0){
		   break;
		}
		path->insertObject(close->objectAtIndex(((AstarItem *)path->objectAtIndex(0))->getfid()),0);
	}
	curCol = aimCol;
	curRow = aimRow;
}
void Astar::fromopentoclose()
{
    //把open列表中的点放到close列表中
	AstarItem * temp = (AstarItem *)open->objectAtIndex(1);
	close->addObject(temp);
	removefromopen();
}
void Astar::removefromopen()
{
	int last = open->count() - 1;
	open->replaceObjectAtIndex(1,open->lastObject(),true);
	open->removeLastObject();
	last = open->count() - 1;
    //堆排序
	int head = 1;
	while((head * 2 + 1) <= last){
	   int child1 = head * 2;
	   int child2 = head * 2 + 1;
	   int childmin = (((AstarItem *)open->objectAtIndex(child1))->getf() < ((AstarItem *)open->objectAtIndex(child2))->getf() ? child1:child2);
	   if(((AstarItem *)open->objectAtIndex(head))->getf() <= ((AstarItem *)open->objectAtIndex(childmin))->getf()){
	      break;
	   }
       //AstarItem * temp = (AstarItem *)open->objectAtIndex(childmin);
	   open->replaceObjectAtIndex(childmin,open->objectAtIndex(head),false);
       //temp->release();
	}
}
void Astar::resetSort(int last)
{
    //根据步长排序,堆排序
	while(last > 1){
	   int half = last/2;
	   if(((AstarItem *)open->objectAtIndex(half))->getf() <= ((AstarItem *)open->objectAtIndex(last))->getf())
		   break;
	   AstarItem * temp = (AstarItem *)open->objectAtIndex(last);
	   open->replaceObjectAtIndex(last,open->objectAtIndex(half),false);
	   open->replaceObjectAtIndex(half,temp,true);
	   temp->release();
	}
}
int Astar::getG(int col,int row,int id)
{
    //获得该点的g函数值
	int fx = ((AstarItem *)close->objectAtIndex(id))->getcol();
	int fy = ((AstarItem *)close->objectAtIndex(id))->getrow();
	int fg = ((AstarItem *)close->objectAtIndex(id))->getg();
	if(fx - col != 0 && fy - row != 0){
	   return fg + 14;
	}else{
	   return fg + 10;
	}
}
int Astar::getH(int col,int row)
{
    //获得该点的h值
	return abs(aimCol - col) * 10 + abs(aimRow - row) * 10;
}

注:checkmap(int col,int row) 方法中,获得该位置地图上的trees2层的"conflict"属性值,如果是碰撞的不能通过的位置,则返回false,否则返回true.


A星算法的逻辑部分基本完成。任何一个算法要在游戏开发的过程中使用,需要有特定的场景。这里设定一个简单的场景:45度角地图中,主角需要绕过树到达你点击的目标点。

MapScene.h

#ifndef __MAPSCENE_H__
#define __MAPSCENE_H__

#include "cocos2d.h"

#include "SimpleAudioEngine.h"

#include "Astar.h"
#include "AstarItem.h"

using namespace cocos2d;

class MapScene :public CCLayer
{
private:
	CCArray * path;
	int stepindex;
	int smallstepindex;
public:
	int vmove;
	int hmove;
	Astar * myastar;
	CCSprite*	m_tamara;
	virtual bool init(); 
	void update(float dt);
	//void MapScene::update1(ccTime dt);
	static cocos2d::CCScene* scene();
	CCPoint convertto2d(float x,float y);
	virtual void menuCloseCallback(CCObject* pSender);
	virtual void ccTouchesBegan(CCSet *pTouches, CCEvent *pEvent);
	CREATE_FUNC(MapScene);
};

#endif

MapScene.cpp

#include "MapScene.h"
#include "Astar.h"
#include "AstarItem.h"
#include <math.h>

using namespace cocos2d;

enum 
{
	kTagTileMap = 1,
};
CCScene* MapScene::scene()
{
    CCScene *scene = CCScene::create();
    MapScene *layer = MapScene::create();
    scene->addChild(layer);
    return scene;
}

// on "init" you need to initialize your instance
bool MapScene::init()
{
    if ( !CCLayer::init() )
    {
        return false;
    }
    //初始化地图
	CCTMXTiledMap *map = CCTMXTiledMap::create("iso-test-zorder.tmx");
	addChild(map, 0, kTagTileMap);
	CCSize s = map->getContentSize();
	CCSize winSize = CCDirector::sharedDirector()->getWinSize();
	////----UXLOG("ContentSize: %f, %f", s.width,s.height);
	map->setPosition(ccp(-s.width/2 + winSize.width/2,0));
	//初始化人物
	m_tamara = CCSprite::create("Icon.png");
	map->addChild(m_tamara, map->getChildren()->count() );
	m_tamara->retain();
	int mapWidth = map->getMapSize().width * map->getTileSize().width;
	int mapHeight = map->getMapSize().height * map->getTileSize().height;
	m_tamara->setPosition(ccp( mapWidth/2,112));
	m_tamara->setAnchorPoint(ccp(0.5f,0));
	setTouchEnabled(true);
	scheduleUpdate();
	vmove = -1;
	hmove = -1;
	stepindex = -1;
	myastar = new Astar();
    return true;
}
//坐标与地图位置的转换
CCPoint MapScene::convertto2d(float x,float y){
	CCTMXTiledMap* map = (CCTMXTiledMap*) getChildByTag(kTagTileMap);
	int mapWidth = map->getMapSize().width * map->getTileSize().width;
	int mapHeight = map->getMapSize().height * map->getTileSize().height;
	double distanse;
	double sin1;
	double sin11;
	double sin22;
	double cos11;
	double cos1;
	int d2x;
	int d2y;
	double mystatic5 = sqrt(5.0);//«Û∏˘∫≈5
	double mystatic = 16 * mystatic5;//–°ÕºøÈ¿‚≥§
	//char mch[256];
	if(x > mapWidth/2){
	   distanse = sqrt((x - mapWidth/2) * (x - mapWidth/2) + (mapHeight - y) * (mapHeight - y));
	   sin1 = (mapHeight - y)/distanse;
	   cos1 = (x - mapWidth/2)/distanse;
	   sin11 = (sin1 * 2 - cos1) / mystatic5;
	   cos11 = (sin1 + cos1 * 2) / mystatic5;
	   d2y = distanse * 5 / 4 * sin11 / mystatic;
	   sin22 = (2 * sin1 + cos1) / mystatic5;
	   d2x = distanse * 5 / 4 * sin22 / mystatic;
	   return ccp(d2x,d2y);
	}else{
	   distanse = sqrt((mapWidth/2 - x) * (mapWidth/2 - x) + (mapHeight - y) * (mapHeight - y));
	   sin1 = (mapHeight - y)/distanse;
	   cos1 = (mapWidth/2 - x)/distanse;
	   sin11 = (sin1 * 2 - cos1) / mystatic5;
	   cos11 = (sin1 + cos1 * 2) / mystatic5;
	   d2x = distanse * 5 / 4 * sin11 / mystatic;
	   //sin22 = 4.0 * cos11 / 5 + 3.0 * sin11 / 5;
	   sin22 = (2 * sin1 + cos1) / mystatic5;
	   d2y = distanse * 5 / 4 * sin22 / mystatic;
	   return ccp(d2x,d2y);
	}
}
void MapScene::update(float dt)
{
	CCPoint herop = m_tamara->getPosition();
	CCPoint mapindex = convertto2d(herop.x,herop.y);
    //根据路径移动
	if(stepindex >= 1){
	   if(smallstepindex == 0){
		   int ncol = ((AstarItem *)path->objectAtIndex(stepindex))->getcol();
		   int nrow = ((AstarItem *)path->objectAtIndex(stepindex))->getrow();
		   int pcol = ((AstarItem *)path->objectAtIndex(stepindex - 1))->getcol();
		   int prow = ((AstarItem *)path->objectAtIndex(stepindex - 1))->getrow();
		   if(pcol == ncol){
			   if(prow > nrow){
			      vmove = 2;
			   }else if(prow < nrow){
                  vmove = 3;
			   }else{
			      vmove = -1;
			   }
		   }else if(prow == nrow){
			   if(pcol > ncol){
			      vmove = 1;
			   }else if(pcol < ncol){
                  vmove = 0;
			   }else{
			      vmove = -1;
			   }
		   }else{
               vmove = -1;
		   }
	   }
	   if(vmove == 0){
	      herop.x += 1;
	      herop.y -= 0.5;
	   }else if(vmove == 1){
	      herop.x -= 1;
	      herop.y += 0.5;
	   }else if(vmove == 2){
	      herop.x += 1;
	      herop.y += 0.5;
	   }else if(vmove == 3){
	      herop.x -= 1;
	      herop.y -= 0.5;
	   }else if(vmove == 4){
	      herop.x += 1;
	   }else if(vmove == 5){
	      herop.x -= 1;
	   }else if(vmove == 6){
	      herop.y += 0.5;
	   }else if(vmove == 7){
	      herop.y -= 0.5;
	   }
	   smallstepindex ++;
	   if(smallstepindex >= 32){
	      smallstepindex = 0;
		  if(stepindex >= path->count() - 1){
		     stepindex = -1;
			 vmove = -1;
		  }else{
		     stepindex ++;
			 vmove = -1;
		  }
	   }
	}
	m_tamara->setPosition(herop);
    //地图随主角移动
	CCTMXTiledMap* map = (CCTMXTiledMap*) getChildByTag(kTagTileMap);
	CCSize s = map->getContentSize();
	int mapWidth = map->getMapSize().width * map->getTileSize().width;
	int mapHeight = map->getMapSize().height * map->getTileSize().height;
	float deltax = herop.x - mapWidth/2;
	float deltay = herop.y - 112;
	CCSize winSize = CCDirector::sharedDirector()->getWinSize();
    map->setPosition(ccp(-s.width/2 + winSize.width/2 - deltax,-deltay));
}
void MapScene::ccTouchesBegan(CCSet *pTouches, CCEvent *pEvent)
{
	CCSetIterator it = pTouches->begin();
    CCTouch* touch = (CCTouch*)(*it);

    CCPoint m_tBeginPos = touch->getLocationInView();    
    m_tBeginPos = CCDirector::sharedDirector()->convertToGL( m_tBeginPos );
    char mch[256];
	CCTMXTiledMap* map = (CCTMXTiledMap*) getChildByTag(kTagTileMap);
	CCPoint mapp = map->getPosition();
    //获得触摸点位置在地图上的索引(行列)
	CCPoint aimmapindex = convertto2d(m_tBeginPos.x - mapp.x,m_tBeginPos.y - mapp.y);
	if(aimmapindex.x < 0 || aimmapindex.y < 0 || aimmapindex.x >= map->getMapSize().width || aimmapindex.y >= map->getMapSize().height)
	{
		return;
	}
	CCPoint herop = m_tamara->getPosition();
	CCPoint mapindex = convertto2d(herop.x,herop.y);
	CCTMXLayer* layer = map->layerNamed("trees2");
	int tilegid = layer->tileGIDAt(ccp(aimmapindex.x,aimmapindex.y));
    if (tilegid)
    {
        CCDictionary *properties = map->propertiesForGID(tilegid);
        if (properties)
        {
            const CCString *collision = properties->valueForKey("conflict");
            if (collision && collision->compare("true") == 0)
            {
                return;
            }
        }
    }
    
    //A星搜索
	path = myastar->findPath(mapindex.x,mapindex.y,aimmapindex.x,aimmapindex.y,map);
	stepindex = 1;
	smallstepindex = 0;
}
void MapScene::menuCloseCallback(CCObject* pSender)
{
    // "close" menu item clicked
    CCDirector::sharedDirector()->end();
}

效果图:



资源下载:http://download.csdn.net/detail/my183100521/6721821

参考书藉:《Cocos2D-X权威指南》


版权声明:本文为博主原创文章,未经博主允许不得转载。

自制45度2D引擎之坐标转换更新版

自制45度2D引擎之坐标转换
  • stophin
  • stophin
  • 2015年05月23日 10:43
  • 1086

2D 45度斜角地图原理

  • 2009年01月14日 16:18
  • 39KB
  • 下载

2D斜45度SLG游戏地图转换规则

需要注意如果单元格的长宽是不等的,那么在转换过程中width和height的单位要分开计算而不是统一为一个单位运算了...
  • Sengoo545
  • Sengoo545
  • 2017年03月18日 17:39
  • 233

45度角地图的A*算法。

地图为45度角的斜地图,依旧是寻找最短路径,在前面的文章中,有一篇正常地图的A*算法,算法思想一致,只是走路方向变成可以横-竖-斜。 直接贴代码吧,若有不懂,请回顾正常地图的A* 算法,或提问。 ...
  • zhangzhenyuaf
  • zhangzhenyuaf
  • 2014年11月27日 15:19
  • 218

cocos2dx使用TiledMap创建斜45度地图场景

         做游戏,场景是一个很重要的部分,如果缺少这一步,很难做出好的游戏,对于cocos2dx来说,有很多2D的地图编辑器可以用,效果都还可以,其中Tiled是支持的比较好...
  • zhanghefu
  • zhanghefu
  • 2014年03月30日 10:04
  • 11254

Html5斜45度地图+3D模型ARPG系列教程(6)-- 寻路算法及平滑处理

目录: 1.A星寻路算法 2.弗洛伊德路径平滑算法 3.源码下载 1.A星寻路算法 讲A星算法原理的文章实在是太多了,我这里就不重复造轮子了。本文采用的算法是9ria算法大赛挑选...
  • leeveel
  • leeveel
  • 2017年05月23日 10:55
  • 421

用cocos2dx开发斜45度社交游戏(一)地图的设计

 无论什么项目的开发都要有一定的技术积累和实施步骤,就像楼房的建设也是从最基本的地基开始的,地基打不好,大楼盖装饰的再漂亮也只是一个失败的作品。好吧,闲话还是少说,来点实际的,先来介绍一下此系列...
  • zhanghefu
  • zhanghefu
  • 2014年03月23日 15:52
  • 2164

编写简易斜45度地图编辑器

最近在研究cocos2dx的地图,最开始使用的是Tiled,这个编辑器做比较小的地图还是比较强大的,不过做大地图的时候,有一些功能不太方便并且有缺陷(包括刷图繁琐以及坐标体系过于复杂,导致寻路比较看起...
  • ycg514230
  • ycg514230
  • 2014年01月09日 16:04
  • 5452

斜45度地图简介、坐标系转换以及数据碰撞

手机平台上开发斜45度地图系统的游戏,相信做惯了正面俯视的开发者刚接触总很不习惯。所谓斜45度游戏,也就是常说的2.5D游戏,用斜方向俯视的角度来增强立体感的一种技术。这种技术在PC平台上早就流行了,...
  • ybhjx
  • ybhjx
  • 2016年01月14日 23:17
  • 1653

45度地图遮挡问题解决方案(cocos2d-x)

最近一直在做45度斜视角游戏,也就是isometric等容地图,俗称2.5D。地图上物体的前后遮挡是我遇到的第一个问题,总结一下处理方法。 遮挡问题(不知道术语),就是比如一个角色站在树后面,那...
  • u013565351
  • u013565351
  • 2014年01月27日 14:01
  • 1175
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:cocos2d-x学习日志(14) --A星寻路算法之45度地图
举报原因:
原因补充:

(最多只允许输入30个字)