Cocos2d-x 地图行走的实现3:A*算法

上一节《Cocos2d-x
地图行走的实现2:SPFA算法
》:

http://blog.csdn.net/stevenkylelee/article/details/38440663

1.修改一下Dijkstra的实现

  回顾一下之前Dijkstra的实现。Dijkstra需要从一个表Q中选出一个路径代价最小的顶点。之前我们的实现是,一开始就把所有的顶点都放入这个表Q中。仔细想下就会发现,那些被初始化为路径代价最大值0x0FFFFFFF的顶点是不可能会被选中的,对于这些顶点不需要遍历。从表中取出的路径代价最小的顶点,取出一个就表示从起点找到了到这个顶点的最短路径,这些顶点不需要再放回列表中。

  我们可以对Dijkstra做这样一个小小的优化,虽然还是O(N^2),时间复杂度没有改变:

    一开始只把起始顶点放入表中。

    如果松弛成功,就把边终点指向的顶点放入表中。

  这样做的话,Relax就要返回结果了。

  

  实现代码如下:

void Dijkstra::Execute( const Graph& Graph , const string& VetexId  )
{
	m_Ret.PathTree.clear( ) ;

	const auto& Vertexes = Graph.GetVertexes( ) ;
	Vertex* pVertexStart = Vertexes.find( VetexId )->second ;
	vector< Vertex* > Q ; 

	// 初始化顶点
	for ( auto& it : Vertexes )
	{
		it.second->PathfindingData.Cost = 0x0FFFFFFF ;
		pVertexStart->PathfindingData.pParent = 0 ;
	}
	// 初始化起始顶点
	pVertexStart->PathfindingData.Cost = 0 ;
	pVertexStart->PathfindingData.pParent = 0 ;
	// 把起始顶点放入列表中
	Q.push_back( pVertexStart ) ;
	pVertexStart->PathfindingData.Flag = true ; 

	for ( ; Q.size() > 0 ; )
	{
		// 选出最小路径估计的顶点
		auto v = ExtractMin( Q ) ;
		v->PathfindingData.Flag = false ; 

		// 对所有的出边进行“松弛”
		const auto& EO = v->GetEdgesOut( ) ;
		for (  auto& it : EO )
		{
			Edge* pEdge = it.second ;
			Vertex* pVEnd = pEdge->GetEndVertex( ) ;

			bool bRet = Relax( v , pVEnd , pEdge->GetWeight( ) ) ;
			// 如果松弛成功,加入列表中。
			if ( bRet && pVEnd->PathfindingData.Flag == false )
			{
				Q.push_back( pVEnd ) ;
				pVEnd->PathfindingData.Flag = true ;
			}
		}
		// end for
	}
	// end for

}

  实践测试,修改后的Dijkstra运行得更快一些了。这个实现很类似BFS算法,都是拿出一个顶点出来扩展。但Dijkstra要比BFS聪明,BFS只是“盲目地”从队列中取出元素出来扩展,Dijkstra则知道每次应该选取路径代价最短的节点扩展。

2.A*算法

  Dijkstra比BFS聪明,A*则比Dijkstra更聪明,运行更快。A*通过一个叫“启发式函数”的东东来改进扩展规则,它会尽量避免扩展其他无用的顶点,它的目标就是朝着目的地直奔而去的。这样说,好像A*长了眼睛一样能看到当前位置距离目标点还有多远。A*和上面的Dijkstra最大的区别就是有“眼睛”:启发式函数。

  启发式函数会告诉A*应该优先扩展哪个顶点。启发式函数是怎么回事呢?公式表示是:F
= G + H。简单地说,就是:当前顶点的路径代价(G) + 当前顶点距离目标顶点估计花费的代价(F)

  之前对Dijkstra做修改优化,就是为了让它更加像A*算法。这里,把Dijkstra的启发式数据从选拥有最小路径代价的顶点改成选拥有最小的F(启发式函数的值)的顶点就变成了A*。估价函数H怎么设计呢?这里取顶点到目标顶点的距离即可。

  我们需要对上面的Dijkstra和数据结构做如下改造:

  1.顶点类的寻路数据结构体增加一个Heuristic字段。该字段用于A*算法,保存启发式函数计算出来的值。如下所示:

class Vertex
{
	// ... 省略了一些无关函数和字段
	// 和以前一样

public : 

	// 寻路算法需要的数据
	struct Pathfinding
	{
		// 顶点的前驱顶点。
		Vertex * pParent ;

		// 路径代价估计
		int Cost ; 

		// 标识符
		int Flag ;

		// 启发式函数的计算出来的值
		int Heuristic ; 

		Pathfinding( )
		{
			pParent = 0 ;
			Cost = 0 ;
			Flag = 0 ;
			Heuristic = 0 ;
		}
	}
	PathfindingData ;
}

  2.Dijkstra的Relax松弛函数,改成限制启发式函数F的值。如果计算出来的F值小于这个顶点原先的F值,就更新该顶点的父节点、实际路径代价、F值。

  3.每次循环都判断下,找出来的最小F值的顶点是不是目标顶点。如果是目标顶点,说明找到了路径,算法结束。

  和Dijkstra一样,A*算法从列表中找出来的具备最小F值的顶点,是不会再此进入列表了。

  下面是我实现的A*算法。

  AStar.h

#pragma once

#include "GraphPathfinding.h"
#include <functional>

class AStar : public GraphPathfinding
{
public:
	AStar( );
	~AStar( );

public : 

	// 估计顶点到目标顶点的代价
	std::function<int( const Vertex* pVCurrent , const Vertex* pVTarget ) > Estimate ; 

public:

	virtual void Execute( const Graph& Graph , const string& VetexId ) override ; 

private : 

	// 抽出最小路径估值的顶点
	inline Vertex* ExtractMin( vector< Vertex* >& Q ) ;

	// 松弛
	inline bool Relax( Vertex* v1 , Vertex* v2 , int Weight ) ;

public:

	void SetTarget( Vertex* pVTarget ) { m_pVTarget = pVTarget ; }

private: 

	Vertex* m_pVTarget ;

};

  AStar.cpp

#include "AStar.h"

AStar::AStar( )
{
}

AStar::~AStar( )
{
}

void AStar::Execute( const Graph& Graph , const string& VetexId )
{
	const auto& Vertexes = Graph.GetVertexes( ) ;
	Vertex* pVertexStart = Vertexes.find( VetexId )->second ;
	vector< Vertex* > Q ;

	// 初始化顶点
	for ( auto& it : Vertexes )
	{
		Vertex* pV = it.second ; 

		pV->PathfindingData.Cost = 0 ;
		pV->PathfindingData.pParent = 0 ;
		pV->PathfindingData.Heuristic = 0x0FFFFFFF ;
		pV->PathfindingData.Flag = false ;
	}
	// 初始化起始顶点
	pVertexStart->PathfindingData.pParent = 0 ;
	pVertexStart->PathfindingData.Cost = 0 ;
	pVertexStart->PathfindingData.Heuristic = Estimate( pVertexStart , m_pVTarget ) ;
	// 把起始顶点放入列表中
	Q.push_back( pVertexStart ) ;
	pVertexStart->PathfindingData.Flag = true ;

	for ( ; Q.size( ) > 0 ; )
	{
		// 选出最小路径估计的顶点
		auto v = ExtractMin( Q ) ;
		v->PathfindingData.Flag = false ;
		if ( v == m_pVTarget )
		{
			return ;
		}

		// 对所有的出边进行“松弛”
		const auto& EO = v->GetEdgesOut( ) ;
		for ( auto& it : EO )
		{
			Edge* pEdge = it.second ;
			Vertex* pVEnd = pEdge->GetEndVertex( ) ;

			bool bRet = Relax( v , pVEnd , pEdge->GetWeight( ) ) ;
			// 如果松弛成功,加入列表中。
			if ( bRet && pVEnd->PathfindingData.Flag == false )
			{
				Q.push_back( pVEnd ) ;
				pVEnd->PathfindingData.Flag = true ;

			}
		}
		// end for
	}
	// end for

}

Vertex* AStar::ExtractMin( vector< Vertex* >& Q )
{
	Vertex* Ret = 0 ;

	Ret = Q[ 0 ] ;
	int pos = 0 ;
	for ( int i = 1 , size = Q.size( ) ; i < size ; ++i )
	{
		if ( Ret->PathfindingData.Heuristic > Q[ i ]->PathfindingData.Heuristic )
		{
			Ret = Q[ i ] ;
			pos = i ;
		}
	}

	Q.erase( Q.begin( ) + pos ) ;

	return Ret ;
}

bool AStar::Relax( Vertex* v1 , Vertex* v2 , int Weight )
{
	// 这里就是启发式函数
	int G = v1->PathfindingData.Cost + Weight ;	// 取得从V1到V2的实际路径代价
	int H = Estimate( v2 , m_pVTarget ) ;	// 估计V2到目标节点的路径代价
	int nHeuristic = G + H ;	// 实际 + 估算 = 启发式函数的值

	// 如果从此路径达到目标会被之前计算的更短,就更新
	if ( nHeuristic < v2->PathfindingData.Heuristic )
	{
		v2->PathfindingData.Cost = G ;
		v2->PathfindingData.pParent = v1 ;

		v2->PathfindingData.Heuristic = nHeuristic ;

		return true ;
	}

	return false ;
}

  

  H函数(估计当前顶点到目标顶点的代价)”外包“到外部执行了。因为AStart类是不知道MapWalkVertex顶点类的存在的。为什么要”外包“执行,而不是在AStar类中做呢?如果要在AStar类中做,就需要知道每个顶点的几何位置,而顶点的几何位置是Cocos2D-x的Node类的属性。AStar类不应该和其他东西耦合,为了”独立“,”通用“,计算H就用观察者模式思想,”外包“执行了。

  AStar类的使用,如下:

			// A*的H估价函数
			auto Estimate = [ ]( const Vertex* pVCurrent , const Vertex* pVTarget )->int
			{
				MapWalkVertex * pMwv1 = ( MapWalkVertex* )pVCurrent->UserData.find( "mwv" )->second ;
				MapWalkVertex * pMwv2 = ( MapWalkVertex* )pVTarget->UserData.find( "mwv" )->second ;
				Point v = pMwv1->getPosition( ) - pMwv2->getPosition( ) ;
				int H = v.getLength( ) ;
				return H ; 

			} ; 

			AStar AStar ;
			// 设置目的顶点
			AStar.SetTarget( pVertexTarget ) ;
			// 设置H估价函数
			AStar.Estimate = Estimate ;
			// 开始执行
			AStar.Execute( *m_pGraph , pMwvStart->GetGraphVertex( )->GetId( ) ) ; 

  OK ,A* 完成了。测试运行一下:

  经过测试。我们的A*能找到最短路径。并且执行速度比Dijkstra和Spfa都快。

3.本文源代码工程下载:

  http://download.csdn.net/detail/stevenkylelee/7734787

Cocos2d-x 地图行走的实现3:A*算法,布布扣,bubuko.com

时间: 2024-08-02 23:49:06

Cocos2d-x 地图行走的实现3:A*算法的相关文章

Cocos2d-x 地图行走的实现:图论与Dijkstra

本文乃Siliphen原创,转载请注明出处:http://blog.csdn.net/stevenkylelee 本文的实现基于Cocos2d-x 3.2. 本文,我们最终实现的地图行走效果如下2图: 下面是2张屏幕录制的gif动画图,有点大,看不到的话,耐心等待一下,或者刷新页面试试. 地图行走用于现实的地图上. 1.什么是地图行走 很多游戏会有一个"世界"的概念.玩家在这个世界中行走,到达不同的地方去执行的任务,打怪或者是触发剧情等.下图是<锁链战记>的世界地图的截图.

Cocos2d-x 地图行走的实现2:SPFA算法

上一节<Cocos2d-x 地图行走的实现1:图论与Dijkstra算法> http://blog.csdn.net/stevenkylelee/article/details/38408253 本节实践另一种求最短路径算法:SPFA 1.寻路算法实现上的优化 上一节我们实现的Dijkstra用了一个哈希表来保存搜索到的路径树.如果能用直接的访问的方式,就不要用哈希表,因为直接访问的方式会比哈希表更快.我们修改一下图顶点的数据结构.如下: /* 图顶点 */ class Vertex { fr

百度地图API位置偏移的校准算法

转自极客人原文 百度地图API位置偏移的校准算法 在开始使用百度地图API进行开发时可能会遇到一件相当奇怪的事情,使用百度定位的经纬度在地图上显示相当不准确,这一问题我在微信开发和安卓开始时都遇到过.第一次使用百度地图api获取位置并在地图上显示是在微信开发的时候,那是不知道具体原因无奈在微信获取的地理位置上加了一个偏移量进行校准,虽能勉强解决,但是不太准确.后来在安卓开始也同样遇到了这个问题,才发现百度地图API定位偏移已经不是一个偶然问题了. 百度地图API定位偏移的原因 以下来自互联网:

Cocos2D实现RPG游戏人物地图行走的跟随效果

大熊猫猪·侯佩原创或翻译作品.欢迎转载,转载请注明出处. 如果觉得写的不好请多提意见,如果觉得不错请多多支持点赞.谢谢! hopy ;) 在一些RPG游戏中,人物队列在地图中行走的时候有时需要实现一个跟随效果,比如大家都玩过的FC游戏<<吞食天地>>. 效果为当队列只有一个人时,Ta可以自由在地图中行走,当队列多于一人时,我们让其他角色跟随在游戏主角之后行走,达到一种"萌萌的"拖尾效果. 如上图,可以看到游戏队列中有4位角色,但我们设定只显示后2位,当然后面跟着

Cocos2d-x 2地图步行实现:SPFA算法

本文乃Siliphen原创,转载请注明出处:http://blog.csdn.net/stevenkylelee 上一节<Cocos2d-x 地图行走的实现1:图论与Dijkstra算法> http://blog.csdn.net/stevenkylelee/article/details/38408253 下一节<Cocos2d-x 地图行走的实现3:A*算法> http://blog.csdn.net/stevenkylelee/article/details/38456419

TileMap地图

参考资料: http://8287044.blog.51cto.com/5179921/1045274 TileMap编辑器使用 1.认识TileMap TileMap是一款开源的地图编辑器,为什么要开发地图编辑器呢,我们就用整张图做地图不就好了吗? 这里简单回答一下,好处有两个: 第一个是极大的减少用图的面积,这样就减少了在运行时系统占用的内存,具体原理问你们的boss或者度娘. 第二个好处是可以通个打散的地图方便在格子中做很多事件,方便判断,比如做地图行走障碍判断,做触发事件判断. 1.新建

卡马克算法(地图重复利用,跑酷类游戏)

----------------------------下面是理论知识-------------------------- 卡马克算法:由约翰·卡马克(John Carmack)开发的一种游戏地图处理方法,被广泛运用到2D卷轴式游戏和手机游戏中.约翰·卡马克:id Software创始人之一,技术总监.享誉世界的著名程序员,以卡马克算法和3D游戏引擎开发而闻名世界,被奉为游戏行业偶像.同时他也是个全面型的技术天才,现在致力于民用航天器开发,是民用航天器开发小组Armadillo Aerospac

cocos2d-x 使用tmx地图总结

首先我们需要知道,tmx地图的坐标为格子坐标,左上角为原点(0,0),而cocos里面一般使用opengl坐标系,即左下角为原点(0,0). 我们可以这样子来转换tmx地图和opengl的坐标: Point HelloLayer::tiledCoorForPosition(const Point& position) //转成格子坐标 { Size mapSize = _tiledMap->getMapSize(); Size tileSize = _tiledMap->getTile

通过sqlview动态发布地图图层

1.SQL Views简介 Geoserver+postgresql+openlayers(leaflet)是目前主流的gis开发工具.Postgresql用于存储地图数据,geoserver用于发布地图数据,openlayers或者leaflet用于访问发布地图.正常情况下当shapefile格式的数据导入postgresql数据库中之后,我们需要通过geoserver把所有的数据发布出去,这样才能访问.常规情况下这种操作方式是没有问题的,因为地图作为基础服务,一旦发布出去是不会变化的.但是对