背景:
上在一篇文章路径优化中对于A *传统寻路的结果不平滑的问题,为了解决生成折线Ž型路线,我们尝试了一种判断两点间是否存在障碍物的算法,并在生成寻路。路径时候先判断起终点间是否存在障碍物,若不存在障碍物,则路径数组中将只具有一个终点节点;否则调用A *寻路运算大致过程可用下面代码表示:
- //判断起终点间是否存在障碍物,若存在则调用A *算法进行寻路,通过A *寻路得到的路径是一个个所要经过的节点数组;否不存在障碍则直接把路径设置为只含有一个终点元素的数组
- var hasBarrier:Boolean = _grid.hasBarrier(startPosX,startPosY,endPosX,endPosY);
- if(hasBarrier)
- {
- _grid.setStartNode(startPosX,startPosY);
- _grid.setEndNode(endPosX,endPosY);
- findPath();
- }
- 其他
- {
- _path = [_grid.getNode(endPosX,endPosY)];
- _index = 0 ;
- addEventListener(Event.ENTER_FRAME,onEnterFrame); //开始行走
- }
但是实际应用中发现仍然存在一些问题。
就是当起终点间不存在障碍物时,我们不使用导航寻路,而是直接设置目标点然后移动,因此,若目标点有时候会是路径网格外的某一点,会导致主角“飞”起来了,或是角色莫名其妙到达终点而不是一步步走过去......为了避免这个问题的发生,必须使用A *算法去计算路径,即使起终点间没有障碍物也必须调用寻路方法进行寻路。上一篇中第一种策略弃用。
但是使用A *寻路算法计算出来的路径,会发现路径并不平滑,尤其是导航网格为方格时候,路径会呈现一种阶梯状情况,尤其是在路径开始和路径结束的地方,这种情况会更明显,造成结果角色移动会频繁转向而发生抖动。
本文我们主要讨论A *寻路算法的路径平滑处理办法:弗洛伊德路径平滑算法。
一,什么是弗洛伊德算法
从图中的某个顶点出发到达另外一个顶点的所经过的边的权重和最小的一条路径,称为最短路径
求解最短路径的算法主要有三种:
- 迪杰斯特拉算法(Dijkstra算法算法)
- 弗洛伊德算法(弗洛伊德算法)
- SPFA算法
1,弗洛伊德算法
算法的特点:
弗洛伊德算法是解决任意两点间的最短路径的一种算法,可以正确处理有向图或有向图或负权(但不可存在负权回路)的最短路径问题,同时也被用于计算有向图的传递闭包。算法的思路
通过弗洛伊德计算图G =(V,E)中各个顶点的最短路径时,需要引入两个矩阵,矩阵小号中的元素A [i] [j]表示顶点I(第I个顶点)到顶点Ĵ(第Ĵ个顶点)的距离。矩阵P中的元素b [i] [j],表示顶点我到顶点Ĵ经过了b [i] [j]记录的值所表示的顶点。
假设图ģ中顶点个数为N,则需要对矩阵d和矩阵P进行Ñ次更新初始时,矩阵d中顶点A [i] [j]的距离为顶点我到顶点Ĵ的权值;如果i和j不相邻,则a [i] [j] =∞,矩阵P的值为顶点b [i] [j]的j的值。接下来开始,对矩阵D进行N次更新。第1次更新时,如果“a [i] [j]的距离”>“a [i] [0] + a [0] [j]”(a [i] [0] + a [0] [j]表示”我与Ĵ之间经过第1个顶点的距离”),则更新A [i] [j]为” A [1] [0] + A [0] [j]的”,更新b [i]于[j] = b [i] [0]。同理,第k次更新时,如果“a [i] [j]的距离”>“a [i] [k-1] + a [k-1 ] [j]的”,则更新A [i] [j]为” A [1] [K-1] + A [K-1] [j]的”,b [i] [j] = b [i]于[K-1]。更新ñ次之后,操作完成!
2,弗洛伊德算法的实例过程
上面,我们已经介绍了算法的思路,如果,你觉得还是不理解,那么通过一个实际的例子,把算法的过程过一遍,你就明白了,如下图,我们求下图的每个点对之间的最短路径的过程如下:
第一步,我们先初始化两个矩阵,得到下图两个矩阵:
第二步,以v1为中阶,更新两个矩阵:
发现,a [1] [0] + a [0] [6] <a [1] [6]和a [6] [0] + a [0] [1] <a [6] [1],所以我们只需要矩阵D和矩阵P,结果如下:
通过矩阵P,我发现V2-V7的最短路径是:V2-V1-V7
第三步:以V2作为中介,来更新我们的两个矩阵,使用同样的原理,扫描整个矩阵,得到如下图的结果:
OK,到这里我们也就应该明白弗洛伊德算法是如何工作的了,他每次都会选择一个中介点,然后,遍历整个矩阵,查找需要更新的值,下面还剩下五步,就不继续演示下去了,理解了方法,我们就可以写代码了。
3,弗洛伊德算法的代码实现
- Floyd.h文件代码
//@尽量写出完美的程序
#pragma once
//#pragma once是一个比较常用的C/C++杂注,
//只要在头文件的最开始加入这条杂注,
//就能够保证头文件只被编译一次。
/*
本博客开始对Floyd算法的使用邻接矩阵实现的
*/
#include<iostream>
#include<string>
using namespace std;
class Graph_DG {
private:
int vexnum; //图的顶点个数
int edge; //图的边数
int **arc; //邻接矩阵
int ** dis; //记录各个顶点最短路径的信息
int ** path; //记录各个最短路径的信息
public:
//构造函数
Graph_DG(int vexnum, int edge);
//析构函数
~Graph_DG();
// 判断我们每次输入的的边的信息是否合法
//顶点从1开始编号
bool check_edge_value(int start, int end, int weight);
//创建图
void createGraph(int);
//打印邻接矩阵
void print();
//求最短路径
void Floyd();
//打印最短路径
void print_path();
};
- 1
- 2
- 3
- 4
- 五
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 三十
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- Floyd.cpp文件代码
#include"Floyd.h"
//构造函数
Graph_DG::Graph_DG(int vexnum, int edge) {
//初始化顶点数和边数
this->vexnum = vexnum;
this->edge = edge;
//为邻接矩阵开辟空间和赋初值
arc = new int*[this->vexnum];
dis = new int*[this->vexnum];
path = new int*[this->vexnum];
for (int i = 0; i < this->vexnum; i++) {
arc[i] = new int[this->vexnum];
dis[i] = new int[this->vexnum];
path[i] = new int[this->vexnum];
for (int k = 0; k < this->vexnum; k++) {
//邻接矩阵初始化为无穷大
arc[i][k] = INT_MAX;
}
}
}
//析构函数
Graph_DG::~Graph_DG() {
for (int i = 0; i < this->vexnum; i++) {
delete this->arc[i];
delete this->dis[i];
delete this->path[i];
}
delete dis;
delete arc;
delete path;
}
// 判断我们每次输入的的边的信息是否合法
//顶点从1开始编号
bool Graph_DG::check_edge_value(int start, int end, int weight) {
if (start<1 || end<1 || start>vexnum || end>vexnum || weight < 0) {
return false;
}
return true;
}
void Graph_DG::createGraph(int kind) {
cout << "请输入每条边的起点和终点(顶点编号从1开始)以及其权重" << endl;
int start;
int end;
int weight;
int count = 0;
while (count != this->edge) {
cin >> start >> end >> weight;
//首先判断边的信息是否合法
while (!this->check_edge_value(start, end, weight)) {
cout << "输入的边的信息不合法,请重新输入" << endl;
cin >> start >> end >> weight;
}
//对邻接矩阵对应上的点赋值
arc[start - 1][end - 1] = weight;
//无向图添加上这行代码
if(kind==2)
arc[end - 1][start - 1] = weight;
++count;
}
}
void Graph_DG::print() {
cout << "图的邻接矩阵为:" << endl;
int count_row = 0; //打印行的标签
int count_col = 0; //打印列的标签
//开始打印
while (count_row != this->vexnum) {
count_col = 0;
while (count_col != this->vexnum) {
if (arc[count_row][count_col] == INT_MAX)
cout << "∞" << " ";
else
cout << arc[count_row][count_col] << " ";
++count_col;
}
cout << endl;
++count_row;
}
}
void Graph_DG::Floyd() {
int row = 0;
int col = 0;
for (row = 0; row < this->vexnum; row++) {
for (col = 0; col < this->vexnum; col++) {
//把矩阵D初始化为邻接矩阵的值
this->dis[row][col] = this->arc[row][col];
//矩阵P的初值则为各个边的终点顶点的下标
this->path[row][col] = col;
}
}
//三重循环,用于计算每个点对的最短路径
int temp = 0;
int select = 0;
for (temp = 0; temp < this->vexnum; temp++) {
for (row = 0; row < this->vexnum; row++) {
for (col = 0; col < this->vexnum; col++) {
//为了防止溢出,所以需要引入一个select值
select = (dis[row][temp] == INT_MAX || dis[temp][col] == INT_MAX) ? INT_MAX : (dis[row][temp] + dis[temp][col]);
if (this->dis[row][col] > select) {
//更新我们的D矩阵
this->dis[row][col] = select;
//更新我们的P矩阵
this->path[row][col] = this->path[row][temp];
}
}
}
}
}
void Graph_DG::print_path() {
cout << "各个顶点对的最短路径:" << endl;
int row = 0;
int col = 0;
int temp = 0;
for (row = 0; row < this->vexnum; row++) {
for (col = row + 1; col < this->vexnum; col++) {
cout << "v" << to_string(row + 1) << "---" << "v" << to_string(col+1) << " weight: "
<< this->dis[row][col] << " path: " << " v" << to_string(row + 1);
temp = path[row][col];
//循环输出途径的每条路径。
while (temp != col) {
cout << "-->" << "v" << to_string(temp + 1);
temp = path[temp][col];
}
cout << "-->" << "v" << to_string(col + 1) << endl;
}
cout << endl;
}
}
- 1
- 2
- 3
- 4
- 五
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 三十
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
- 130
- 131
- 132
- 133
- 134
- 135
- 136
- 137
- 138
- 139
- main.cpp中文件的代码
#include"Floyd.h"
//检验输入边数和顶点数的值是否有效,可以自己推算为啥:
//顶点数和边数的关系是:((Vexnum*(Vexnum - 1)) / 2) < edge
bool check(int Vexnum, int edge) {
if (Vexnum <= 0 || edge <= 0 || ((Vexnum*(Vexnum - 1)) / 2) < edge)
return false;
return true;
}
int main() {
int vexnum; int edge;
cout << "输入图的种类:1代表有向图,2代表无向图" << endl;
int kind;
cin >> kind;
//判读输入的kind是否合法
while (1) {
if (kind == 1 || kind == 2) {
break;
}
else {
cout << "输入的图的种类编号不合法,请重新输入:1代表有向图,2代表无向图" << endl;
cin >> kind;
}
}
cout << "输入图的顶点个数和边的条数:" << endl;
cin >> vexnum >> edge;
while (!check(vexnum, edge)) {
cout << "输入的数值不合法,请重新输入" << endl;
cin >> vexnum >> edge;
}
Graph_DG graph(vexnum, edge);
graph.createGraph(kind);
graph.print();
graph.Floyd();
graph.print_path();
system("pause");
return 0;
}
- 1
- 2
- 3
- 4
- 五
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 三十
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
1,两点的直达距离最短。(如下图<v,x>)
2,两点间只通过一个中间点而距离最短。(图<v,u>)
3,两点间用通过两各以上的顶点而距离最短(图<v,W>)。
对于第一种情况:在初始化的时候就已经找出来了且以后也不会更改到。
对于第二种情况:弗洛伊德算法的基本操作就是对于每一对顶点,遍历所有其它顶点,看看可否通过这一个顶点让这对顶点距离更短,也就是遍历了图中所有的三角形(算法中对同一个三角形扫描了九次,原则上只用扫描三次即可,但要加入判断,效率更低)。
对于第三种情况:如下图的五边形,可先找一点(比如x,使<v, U> = 2),就变成了四边形问题,再找一点(比如Y,使<U,W> = 2),可变成三角形问题了(v,U,W),也就变成第二种情况了,由此对于ñ边形也可以一步步转化成四边形三角形问题。(这里面 用担心哪个点要先找哪个点要后找,因为找了任一个点都可以使其变成第(n-1)边形的问题)。
使用前
使用后
其本思路:
- 使用A *得出基本路径
- 删除路径中方向相同的节点比如[0,1],[0,2],[0,3],[1,2]可表现为[0,1] [0,3] [1,2]
- 把余下的节点做为转角,代入flody算法进行计算,最后得出最简洁的方法。
- 包 {
- / **
- * ......
- * @author sliz http://game-develop.net/blog/
- * /
- import flash.display.Bitmap;
- import flash.display.BitmapData;
- import flash.display.Sprite;
- import flash.display.StageAlign;
- import flash.display.StageScaleMode;
- import flash.events.Event;
- import flash.events.MouseEvent;
- import flash.geom.Point;
- import flash.geom.Rectangle;
- import flash.text.TextField;
- import flash.utils.getTimer;
- import sliz.miniui.Button;
- import sliz.miniui.Checkbox;
- import sliz.miniui.Label;
- import sliz.miniui.LabelInput;
- import sliz.miniui.layouts.BoxLayout;
- import sliz.miniui.Window;
- 公共 类 Game2 扩展 Sprite {
- private var _cellSize:int = 5 ;
- private var _grid:Grid;
- private var _player:Sprite;
- private var _index:int ;
- private var _path:Array;
- private var tf:Label;
- private var astar:AStar;
- private var path:Sprite = new Sprite();
- 私人 VAR图像:位图= 新 位图(新 的BitmapData(1, 1));
- private var imageWrapper:Sprite = new Sprite();
- 公共 功能Game2(){
- stage.align = StageAlign.TOP_LEFT;
- stage.scaleMode = StageScaleMode.NO_SCALE;
- 的addChild(imageWrapper);
- imageWrapper.addChild(图像);
- makePlayer();
- VAR瓦特:窗口= 新 窗口(此, 20, 20, “工具”);
- numCols = new LabelInput(“numCols”, “numCols”);
- numCols.setValue(“50”);
- w.add(数numCols);
- numRows = new LabelInput(“numRows”, “numRows”);
- w.add(numRows行);
- numRows.setValue(“50”);
- cellSize = new LabelInput(“cellSize”, “cellSize”);
- cellSize.setValue(“10”);
- w.add(CELLSIZE);
- density = new LabelInput(“密度”, “密度”);
- density.setValue(“0.1”);
- w.add(密度);
- isEight = new Checkbox(“是否8方向”);
- isEight.setToggle(true);
- w.add(isEight);
- tf = 新 标签(“信息”);
- w.add(TF);
- w.add(new sliz.miniui.Link(“作者sliz”));
- w.add(new sliz.miniui.Link(“source”, “http://code.google.com/p/actionscriptiui/”));
- VAR BTN:按钮= 新 按钮(“新建”, 0, 0, 空,newMap);
- w.add(btn, null, 0.8);
- w.setLayout(新 的BoxLayout(瓦特, 1, 5));
- w.doLayout();
- imageWrapper.addEventListener(MouseEvent.CLICK,onGridClick);
- addEventListener(Event.ENTER_FRAME,onEnterFrame);
- imageWrapper.addChild(路径);
- makeGrid();
- }
- private function newMap(e:Event):void {
- makeGrid();
- }
- private function changeMode(e:Event):void {
- / * if(_grid.getType()== 1){
- _grid.calculateLinks(0);
- (e.currentTarget as Button).text =“四方向”;
- } else {
- _grid.calculateLinks(1);
- (e.currentTarget as Button).text =“八方向”;
- } * /
- }
- 私有 函数makePlayer():void {
- _player = new Sprite();
- _player.graphics.beginFill(0xff00ff);
- _player.graphics.drawCircle(0, 0, 2);
- _player.graphics.endFill();
- imageWrapper.addChild(_player);
- }
- 私有 函数makeGrid():void {
- var rows:int = int(numRows.getValue());
- var cols:int = int(numCols.getValue());
- _cellSize = int(cellSize.getValue());
- _grid = new Grid(cols,rows);
- for (var i:int = 0 ; i <rows * cols * Number(density.getValue()); i ++){
- _grid.setWalkable(Math.floor(Math.random()* cols),Math.floor(Math.random()* rows), false);
- }
- _grid.setWalkable(0, 0, 真);
- _grid.setWalkable(cols / 2,rows / 2, false);
- if (isEight.getToggle())
- _grid.calculateLinks();
- 其他
- _grid.calculateLinks(1);
- astar = 新的 AStar(_grid);
- drawGrid();
- isClick = false ;
- _player.x = 0 ;
- _player.y = 0 ;
- path.graphics.clear();
- }
- private function drawGrid():void {
- image.bitmapData = new BitmapData(_grid.numCols * _cellSize,_grid.numRows * _cellSize, false, 0xffffff);
- for (var i:int = 0 ; i <_grid.numCols; i ++){
- for (var j:int = 0 ; j <_grid.numRows; j ++){
- var node:Node = _grid.getNode(i,j);
- if (!node.walkable){
- image.bitmapData.fillRect(new Rectangle(i * _cellSize,j * _cellSize,_cellSize,_cellSize),getColor(node));
- }
- }
- }
- }
- private function getColor(node:Node):uint {
- if (!node.walkable)
- 返回 0 ;
- if (node == _grid.startNode)
- return 0xcccccc ;
- if (node == _grid.endNode)
- return 0xcccccc ;
- return 0xffffff ;
- }
- private 函数onGridClick(event:MouseEvent):void {
- var xpos:int = Math.floor(mouseX / _cellSize);
- var ypos:int = Math.floor(mouseY / _cellSize);
- xpos = Math.min(xpos,_grid.numCols - 1);
- ypos = Math.min(ypos,_grid.numRows - 1);
- _grid.setEndNode(xpos,ypos);
- xpos = Math.floor(_player.x / _cellSize);
- ypos = Math.floor(_player.y / _cellSize);
- _grid.setStartNode(xpos,ypos);
- findPath();
- //path.graphics.clear();
- //path.graphics.lineStyle(0,0xff0000,0.5);
- //path.graphics.moveTo(_player.x,_player.y);
- }
- private function findPath():void {
- var time:int = getTimer();
- if (astar.findPath()){
- _index = 0 ;
- isClick = true ;
- astar.floyd();
- _path = astar.floydPath;
- time = getTimer() - 时间;
- tf.text = time + “ms length:” + astar.path.length;
- 迹(astar.floydPath);
- path.graphics.clear();
- for (var i:int = 0 ; i <astar.floydPath.length; i ++){
- var p:Node = astar.floydPath [i];
- path.graphics.lineStyle(0, 为0xFF0000);
- path.graphics.drawCircle((px + 0.5)* _cellSize,(py + 0.5)* _cellSize, 2);
- path.graphics.lineStyle(0, 为0xFF0000, 0.5);
- path.graphics.moveTo(_player.x,_player.y);
- }
- } else {
- time = getTimer() - 时间;
- tf.text = time + “ms找不到” ;
- }
- }
- private var isClick:Boolean = false ;
- private var numCols:LabelInput;
- private var numRows:LabelInput;
- private var cellSize:LabelInput;
- private var density:LabelInput;
- private var isEight:Checkbox;
- private function onEnterFrame(event:Event):void {
- if (!isClick){
- 回归 ;
- }
- var targetX:Number = _path [_index] .x * _cellSize + _cellSize / 2 ;
- var targetY:Number = _path [_index] .y * _cellSize + _cellSize / 2 ;
- var dx:Number = targetX - _player.x;
- var dy:Number = targetY - _player.y;
- var dist:Number = Math.sqrt(dx * dx + dy * dy);
- if (dist < 1){
- _index ++;
- if (_index> = _path.length){
- isClick = false ;
- }
- } else {
- _player.x + = dx *。5 ;
- _player.y + = dy *。5 ;
- path.graphics.lineTo(_player.x,_player.y);
- }
- }
- }
- }
- import flash.geom.Point;
- class AStar {
- // private var _open:Array;
- private var _open:BinaryHeap;
- private var _grid:Grid;
- private var _endNode:Node;
- private var _startNode:Node;
- private var _path:Array;
- private var _floydPath:Array;
- public var heuristic:Function;
- private var _straightCost:Number = 1.0 ;
- private var _diagCost:Number = Math.SQRT2;
- private var nowversion:int = 1 ;
- public function AStar(grid:Grid){
- 这个 ._grid = grid;
- heuristic = euclidian2;
- }
- private function justMin(x:Object,y:Object):Boolean {
- return xf <yf;
- }
- public function findPath():Boolean {
- _endNode = _grid.endNode;
- nowversion ++;
- _startNode = _grid.startNode;
- // _ open = [];
- _open = new BinaryHeap(justMin);
- _startNode.g = 0 ;
- return search();
- }
- public function floyd():void {
- if (path == null)
- 回归 ;
- _floydPath = path.concat();
- var len:int = _floydPath.length;
- if (len> 2){
- VAR向量:节点= 新 节点(0, 0);
- VAR tempVector:节点= 新 节点(0, 0);
- floydVector(vector,_floydPath [len - 1 ],_floydPath [len - 2 ]);
- for (var i:int = _floydPath.length - 3 ; i> = 0 ; i - ){
- floydVector(tempVector,_floydPath [i + 1 ],_ floydPath [i]);
- if (vector.x == tempVector.x && vector.y == tempVector.y){
- _floydPath.splice第(i + 1, 1);
- } else {
- vector.x = tempVector.x;
- vector.y = tempVector.y;
- }
- }
- }
- len = _floydPath.length;
- for (i = len - 1 ; i> = 0 ; i - ){
- for (var j:int = 0 ; j <= i - 2 ; j ++){
- if (floydCrossAble(_floydPath [i],_ floydPath [j])){
- for (var k:int = i - 1 ; k> j; k - ){
- _floydPath.splice(k, 1);
- }
- i = j;
- len = _floydPath.length;
- 打破 ;
- }
- }
- }
- }
- private function floydCrossAble(n1:Node,n2:Node):Boolean {
- var ps:Array = bresenhamNodes (new Point(n1.x,n1.y), new Point(n2.x,n2.y));
- for (var i:int = ps.length - 2 ; i> 0 ; i - ){
- if (!_grid.getNode(ps [i] .x,ps [i] .y).walkable){
- 返回 虚假 ;
- }
- }
- 返回 true ;
- }
- private function bresenhamNodes(p1:Point,p2:Point):Array {
- var steep:Boolean = Math.abs(p2.y - p1.y)> Math.abs(p2.x - p1.x);
- if (陡){
- var temp:int = p1.x;
- p1.x = p1.y;
- p1.y = temp;
- temp = p2.x;
- p2.x = p2.y;
- p2.y = temp;
- }
- var stepX:int = p2.x> p1.x? 1 :(p2.x <p1.x? - 1 : 0);
- var stepY:int = p2.y> p1.y? 1 :(p2.y <p1.y? - 1 : 0);
- var deltay:Number =(p2.y - p1.y)/ Math.abs(p2.x - p1.x);
- var ret:Array = [];
- var nowX:Number = p1.x + stepX;
- var nowY:Number = p1.y + deltay;
- if (陡){
- ret.push(new Point(p1.y,p1.x));
- } else {
- ret.push(new Point(p1.x,p1.y));
- }
- while (nowX!= p2.x){
- var fy:int = Math.floor(nowY)
- var cy:int = Math.ceil(nowY);
- if (陡){
- ret.push(new Point(fy,nowX));
- } else {
- ret.push(new Point(nowX,fy));
- }
- if (fy!= cy){
- if (陡){
- ret.push(new Point(cy,nowX));
- } else {
- ret.push(new Point(nowX,cy));
- }
- }
- nowX + = stepX;
- nowY + = deltay;
- }
- if (陡){
- ret.push(new Point(p2.y,p2.x));
- } else {
- ret.push(new Point(p2.x,p2.y));
- }
- 返回 ;
- }
- private function floydVector(target:Node,n1:Node,n2:Node):void {
- target.x = n1.x - n2.x;
- target.y = n1.y - n2.y;
- }
- public function search():Boolean {
- var node:Node = _startNode;
- node.version = nowversion;
- while (node!= _endNode){
- var len:int = node.links.length;
- for (var i:int = 0 ; i <len; i ++){
- var test:Node = node.links [i] .node;
- var cost:Number = node.links [i] .cost;
- var g:Number = node.g + cost;
- var h:Number = heuristic(test);
- var f:Number = g + h;
- if (test.version == nowversion){
- if (test.f> f){
- test.f = f;
- test.g = g;
- test.h = h;
- test.parent = node;
- }
- } else {
- test.f = f;
- test.g = g;
- test.h = h;
- test.parent = node;
- _open.ins(测试);
- test.version = nowversion;
- }
- }
- if (_open.a.length == 1){
- 返回 虚假 ;
- }
- node = _open.pop()as Node;
- }
- 构建路径();
- 返回 true ;
- }
- private function buildPath():void {
- _path = [];
- var node:Node = _endNode;
- _path.push(节点);
- while (node!= _startNode){
- node = node.parent;
- _path.unshift(节点);
- }
- }
- public function get path():Array {
- return _path;
- }
- public function get floydPath():Array {
- return _floydPath;
- }
- 公共 功能曼哈顿(节点:节点):号码{
- 返回 Math.abs(node.x - _endNode.x)+ Math.abs(node.y - _endNode.y);
- }
- public function manhattan2(node:Node):Number {
- var dx:Number = Math.abs(node.x - _endNode.x);
- var dy:Number = Math.abs(node.y - _endNode.y);
- return dx + dy + Math.abs(dx - dy)/ 1000 ;
- }
- public function euclidian(node:Node):Number {
- var dx:Number = node.x - _endNode.x;
- var dy:Number = node.y - _endNode.y;
- 返回 Math.sqrt(dx * dx + dy * dy);
- }
- private var TwoOneTwoZero:Number = 2 * Math.cos(Math.PI / 3);
- public function chineseCheckersEuclidian2(node:Node):Number {
- var y:int = node.y / TwoOneTwoZero;
- var x:int = node.x + node.y / 2 ;
- var dx:Number = x - _endNode.x - _endNode.y / 2 ;
- var dy:Number = y - _endNode.y / TwoOneTwoZero;
- return sqrt(dx * dx + dy * dy);
- }
- private function sqrt(x:Number):Number {
- 返回 Math.sqrt(x);
- }
- public function euclidian2(node:Node):Number {
- var dx:Number = node.x - _endNode.x;
- var dy:Number = node.y - _endNode.y;
- return dx * dx + dy * dy;
- }
- public function diagonal(node:Node):Number {
- var dx:Number = Math.abs(node.x - _endNode.x);
- var dy:Number = Math.abs(node.y - _endNode.y);
- var diag:Number = Math.min(dx,dy);
- var straight:Number = dx + dy;
- return _diagCost * diag + _straightCost *(直 - 2 * diag);
- }
- }
- class BinaryHeap {
- public var a:Array = [];
- public var justMinFun:Function = function(x:Object,y:Object):Boolean {
- return x <y;
- };
- 公共 函数BinaryHeap(justMinFun:Function = null){
- a.push( - 1);
- if (justMinFun!= null)
- 这个 .justMinFun = justMinFun;
- }
- public function ins(value:Object):void {
- var p:int = a.length;
- a [p] =值;
- var pp:int = p >> 1 ;
- while (p> 1 && justMinFun(a [p],a [pp])){
- var temp:Object = a [p];
- a [p] = a [pp];
- a [pp] = temp;
- p = pp;
- pp = p >> 1 ;
- }
- }
- public function pop():Object {
- var min:Object = a [ 1 ];
- a [ 1 ] = a [a.length - 1 ];
- a.pop();
- var p:int = 1 ;
- var l:int = a.length;
- var sp1:int = p << 1 ;
- var sp2:int = sp1 + 1 ;
- while (sp1 <l){
- if (sp2 <l){
- var minp:int = justMinFun(a [sp2],a [sp1])?sp2:sp1;
- } else {
- minp = sp1;
- }
- if (justMinFun(a [minp],a [p])){
- var temp:Object = a [p];
- a [p] = a [minp];
- a [minp] = temp;
- p = minp;
- sp1 = p << 1 ;
- sp2 = sp1 + 1 ;
- } else {
- 打破 ;
- }
- }
- 回归 分钟;
- }
- }
- class Grid {
- private var _startNode:Node;
- private var _endNode:Node;
- private var _nodes:Array;
- private var _numCols:int ;
- private var _numRows:int ;
- private var type:int ;
- private var _straightCost:Number = 1.0 ;
- private var _diagCost:Number = Math.SQRT2;
- public function Grid(numCols:int,numRows:int){
- _numCols = numCols;
- _numRows = numRows;
- _nodes = new Array();
- for (var i:int = 0 ; i <_numCols; i ++){
- _nodes [i] = new Array();
- for (var j:int = 0 ; j <_numRows; j ++){
- _nodes [i] [j] = 新 节点(i,j);
- }
- }
- }
- / **
- *
- * @param type 0四方向1八方向2跳棋
- * /
- public function calculateLinks(type:int = 0):void {
- 这个 .type = type;
- for (var i:int = 0 ; i <_numCols; i ++){
- for (var j:int = 0 ; j <_numRows; j ++){
- initNodeLink(_nodes [i] [j],type);
- }
- }
- }
- public function getType():int {
- 返回 类型;
- }
- / **
- *
- * @param节点
- * @param type 0八方向1四方向2跳棋
- * /
- private function initNodeLink(node:Node,type:int):void {
- var startX:int = Math.max(0,node.x - 1);
- var endX:int = Math.min(numCols - 1,node.x + 1);
- var startY:int = Math.max(0,node.y - 1);
- var endY:int = Math.min(numRows - 1,node.y + 1);
- node.links = [];
- for (var i:int = startX; i <= endX; i ++){
- for (var j:int = startY; j <= endY; j ++){
- var test:Node = getNode(i,j);
- if (test == node ||!test.walkable){
- 继续 ;
- }
- if (type!= 2 && i!= node.x && j!= node.y){
- var test2:Node = getNode(node.x,j);
- if (!test2.walkable){
- 继续 ;
- }
- test2 = getNode(i,node.y);
- if (!test2.walkable){
- 继续 ;
- }
- }
- var cost:Number = _straightCost;
- if (!((node.x == test.x)||(node.y == test.y))){
- if (type == 1){
- 继续 ;
- }
- if (type == 2 &&(node.x - test.x)*(node.y - test.y)== 1){
- 继续 ;
- }
- if (type == 2){
- cost = _straightCost;
- } else {
- cost = _diagCost;
- }
- }
- node.links.push(new Link(test,cost));
- }
- }
- }
- public function getNode(x:int,y:int):Node {
- return _nodes [x] [y];
- }
- public function setEndNode(x:int,y:int):void {
- _endNode = _nodes [x] [y];
- }
- public function setStartNode(x:int,y:int):void {
- _startNode = _nodes [x] [y];
- }
- public function setWalkable(x:int,y:int,value:Boolean):void {
- _nodes [x] [y] .walkable = value;
- }
- public function get endNode():Node {
- return _endNode;
- }
- public function get numCols():int {
- return _numCols;
- }
- public function get numRows():int {
- return _numRows;
- }
- public function get startNode():Node {
- return _startNode;
- }
- }
- class Link {
- public var node:Node;
- public var cost:Number;
- public function Link(node:Node,cost:Number){
- 这个 .node = node;
- 这个 .cost =成本;
- }
- }
- class Node {
- public var x:int ;
- public var y:int ;
- public var f:Number;
- public var g:Number;
- public var h:Number;
- public var walkable:Boolean = true ;
- public var parent:Node;
- // public var costMultiplier:Number = 1.0;
- public var version:int = 1 ;
- public var links:Array;
- // public var index:int;
- public function Node(x:int,y:int){
- 这个 .x = x;
- 这个 .y = y;
- }
- public function toString():String {
- 返回 “x:” + x + “y:” + y;
- }
- }
弗洛伊德路径平滑算法应在通过A *寻路算法得出路径后进行,它的步骤分为两步:一,合并路径数组中共线的节点;二,尽可能地去掉多余拐点这个过程如下图所示:
原始A *寻路路径
去掉共线点
去掉多余拐点
可以看到,使用弗洛伊德路径平滑处理后的路径正如我们期望的那样,而且大大削减了路径数组中的节点数目。
那么接下来来讲讲实现思路吧。首先,不难发现,若存在三点A(1,1),B(2,2),C(3,3),若B与A的横,纵坐标差值分别等于C与B的横,纵坐标差值,则A, B,C三点共线,使用代码来表示就是:
- if((bx -ax == cx - bx)&&(by-ay == cy - by))
- {
- //三点共线
- }
由上式可知去掉路径中共线节点的方法。接下来讨论如何去掉多余的拐点。
仔细观察第三幅图你会发现,若路径中存在节点A,B,C,d,E,F,G,如果A与G之间的连线所经过的节点中没有一个节点是不可移动节点,则我们称A与G之间是不存在障碍物的。如两节点间不存在障碍物,则可以去掉此两点间其他所有节点。如上例中AG这些节点,若A与G之间不存在障碍物,则我们可以去掉A与G之间的B,C,D,E,F节点,最终路径数组中只剩下A与G两个节点。那么如何判断两个点之间存不存在障碍物呢?在上一篇的教程中我已详细解释过方法,列位道友若不记得了,可以。回头再仔细再去一二研读
那么求最后我们使用代码来实现弗洛伊德路径平滑处理的算法:
- / **弗洛伊德路径平滑处理* /
- public function floyd():void {
- if (path == null)
- 回归 ;
- _floydPath = path.concat();
- var len:int = _floydPath.length;
- if (len> 2)
- {
- VAR向量:阳极= 新 阳极(0, 0);
- VAR tempVector:阳极= 新 阳极(0, 0);
- //遍历路径数组中全部路径节点,合并在同一直线上的路径节点
- //假设有1,2,3,三点,若2与1的横,纵坐标差值分别与3与2的横,纵坐标差值相等则
- //判断此三点共线,此时可以删除中间点2
- floydVector(vector,_floydPath [len - 1 ],_floydPath [len - 2 ]);
- for (var i:int = _floydPath.length - 3 ; i> = 0 ; i--)
- {
- floydVector(tempVector,_floydPath [i + 1 ],_ floydPath [i]);
- if (vector.x == tempVector.x && vector.y == tempVector.y)
- {
- _floydPath.splice第(i + 1, 1);
- }
- 其他
- {
- vector.x = tempVector.x;
- vector.y = tempVector.y;
- }
- }
- }
- //合并共线节点后进行第二步,消除拐点操作算法流程如下:
- //如果一个路径由1-10十个节点组成,那么由节点10从1开始检查
- //节点间是否存在障碍物,若它们之间不存在障碍物,则直接合并
- //此两路径节点间所有节点。
- len = _floydPath.length;
- for (i = len - 1 ; i> = 0 ; i--)
- {
- for (var j:int = 0 ; j <= i - 2 ; j ++)
- {
- if (_grid.hasBarrier(_floydPath [i] .x,_floydPath [i] .y,_floydPath [j] .x,_floydPath [j] .y)== false )
- {
- for (var k:int = i - 1 ; k> j; k--)
- {
- _floydPath.splice(k, 1);
- }
- i = j;
- len = _floydPath.length;
- 打破 ;
- }
- }
- }
- }
接下来再讲一讲第二个棘手的问题,就是A *寻路会当你点击一个不可移动点之后返回假的寻路结果,即告知你无路可走。这不是我们想要的结果,我们想要的是点击一个不可移动点后玩家应该走到离此不可移动点最近的一个可移动点位上面。那么如果你阅读过我的上一篇帖子,你会看到我解决此问题的一个方式是使用“将不可移动点设置超大代价法”。不过使用此方法的一个弊病在于当你在选择一个不可移动点作为终点后,A *寻路算法会遍历大量的点,造成性能的低下。那么在经过另一番研究后发现还有一种办法能解决这个问题,就是“寻找替代点法”。
先解释一下什么是“寻找替代点法”,当点击一个不可移动点U之后我们将寻找此不可移动点外围一个离起点距离最短的可移动点- [R作为ü的替代点,替代ü来完成寻路且玩家将最终移动到此ř点位置 如下图所示
那么,为了尽可能地降低寻找替代点的时间,我们提出了一种“埋葬深度”的概念先看下图:
这种情况下,U点由一圈甚至两圈或更多圈不可移动点包围着,若我们采用传统的方式以辐射型遍历ü点外圈节点(从û点外围第一圈开始遍历,遍历完毕没有找到替代点,继续再遍历更外边的圈直到找到为止),将会在多次点击同一点时产生冗余遍历时间。那么此时我们为了降低重复遍历的次数,就引入了“埋葬深度的概念“。若一个节点为不可移动点,则其埋葬深度为1;在此基础上,若其周围一圈全部为不可移动点,则其埋葬深度加一,为2;若更外围一圈依然全部为不可移动点,埋葬深度再加一,依次类推,下图列出了埋葬深度为1-3的情况:
在为某一个节点第一次寻找替代点时以辐射型遍历此点周围节点以计算出此节点的埋葬深度并记录于每个节点对象节点类中新增的一个埋葬深度属性buriedDepth中,下一次为此点寻找替代点时就可以根据埋葬深度从存在可移动点的那一圈遍历
在遍历原始终点ü周围存在可移动点的那一圈之后把所有可移动点都存到一个数组中,之后比较此数组中全部候选点与起点的距离,选出距离最短的一个点作为替代点[R即可。
好吧,寻找替代点的过程大致就是这样,最后来看代码呗先看节点类阳极类中一些要用到的新增属性和方法:
- 公共 类 ANode
- {
- .....
- / **埋葬深度* /
- public var buriedDepth:int = - 1 ;
- / **距离* /
- public var distance:Number;
- .....
- / **得到此节点到另一节点的网格距离* /
- public function getDistanceTo(targetNode:ANode):Number
- {
- var disX:Number = targetNode.x - x;
- var disY:Number = targetNode.y - y;
- distance = Math.sqrt(disX * disX + disY * disY);
- 返回 距离;
- }
- }
再看节点网格NodeGrid类中新增方法。
- / **当终点不可移动时寻找一个离原终点最近的可移动点来替代之* /
- public function findReplacer(fromNode:ANode,toNode:ANode):ANode
- {
- var结果:ANode;
- //若终点可移动则根本无需寻找替代点
- if(toNode.walkable)
- {
- result = toNode;
- }
- //否则遍历终点周围节点以寻找离起始点最近一个可移动点作为替代点
- 其他
- {
- //根据节点的埋葬深度选择遍历的圈
- //若该节点是第一次遍历,则计算其埋葬深度
- if(toNode.buriedDepth == - 1 )
- {
- toNode.buriedDepth = getNodeBuriedDepth(toNode,Math.max(_numCols,_numRows));
- }
- var xFrom:int = toNode.x - toNode.buriedDepth < 0 ? 0 :toNode.x - toNode.buriedDepth;
- var xTo:int = toNode.x + toNode.buriedDepth> numCols - 1 ?numCols - 1 :toNode.x + toNode.buriedDepth;
- var yFrom:int = toNode.y - toNode.buriedDepth < 0 ? 0 :toNode.y - toNode.buriedDepth;
- var yTo:int = toNode.y + toNode.buriedDepth> numRows - 1 ?numRows - 1 :toNode.y + toNode.buriedDepth;
- var n:ANode; //当前遍历节点
- for(var i:int = xFrom; i <= xTo; i ++)
- {
- for(var j:int = yFrom; j <= yTo; j ++)
- {
- if((i> xFrom && i <xTo)&&(j> yFrom && j <yTo))
- {
- 继续 ;
- }
- n = getNode(i,j);
- if(n.walkable)
- {
- //计算此候选节点到起点的距离,记录离起点最近的候选点为替代点
- n.getDistanceTo(fromNode);
- if(!result)
- {
- result = n;
- }
- 否则 if(n.distance <result.distance)
- {
- result = n;
- }
- }
- }
- }
- }
- 返回 结果;
- }
- / **计算一个节点的埋葬深度
- * @param node欲计算深度的节点
- * @param loopCount计算深度时沿历此节点外围圈数。默认值为10 * /
- private 函数getNodeBuriedDepth(node:ANode,loopCount:int = 10 ):int
- {
- //如果检测节点本身是不可移动的则默认它的深度为1
- var result:int = node.walkable? 0 : 1 ;
- var l:int = 1 ;
- while(l <= loopCount)
- {
- var startX:int = node.x - l < 0 ? 0 :node.x - l;
- var endX:int = node.x + l> numCols - 1 ?numCols - 1 :node.x + l;
- var startY:int = node.y - l < 0 ? 0 :node.y - l;
- var endY:int = node.y + l> numRows - 1 ?numRows - 1 :node.y + l;
- var n:ANode;
- //遍历一个节点周围一圈看是否周围一圈全部是不可移动点,若是,则深度加一,
- //否则返回当前累积的深度值
- for(var i:int = startX; i <= endX; i ++)
- {
- for(var j:int = startY; j <= endY; j ++)
- {
- n = getNode(i,j);
- if(n!= node && n.walkable)
- {
- 返回 结果;
- }
- }
- }
- //遍历完一圈,没发现一个可移动点,则埋葬深度加一。接着遍历下一圈
- 结果++;
- 升++;
- }
- 返回 结果;
- }
那么最后,看看实际应用这个方法进行寻路的部分吧。
- 私有 函数onGridClick(event:MouseEvent):void
- {
- var startTime:int = getTimer();
- var startPosX:int = Math.floor(_player.x / _cellSize);
- var startPosY:int = Math.floor(_player.y / _cellSize);
- var startNode:ANode = _grid.getNode(startPosX,startPosY);
- var endPosX:int = Math.floor(event.localX / _cellSize);
- var endPosY:int = Math.floor(event.localY / _cellSize);
- var endNode:ANode = _grid.getNode(endPosX,endPosY);
- if(endNode.walkable == false )
- {
- replacer = _grid.findReplacer(startNode,endNode);
- if(替换者)
- {
- endPosX = replacer.x;
- endPosY = replacer.y;
- }
- }
- _grid.setStartNode(startPosX,startPosY);
- _grid.setEndNode(endPosX,endPosY);
- findPath();
- }
使用“弗洛伊德路径平滑处理”结合“寻找替代点法”做寻路,不论效率还是结果都还算如人意啦。不过,“寻找替代点法”的弊端在于无法在存在“孤岛”和“回”字形的路径网格中找到正确的替代点,如下图:
所以为了避免此情况,要么就用上一篇帖子中提到的“极大代价法”来进行寻路(结果准确,效率过低),要么就在布置路径网格时刻意避免编出这种形式的路径网格。
二、解决导航沿着不可行走区域走的问题
有时候我们会发现角色会沿着不可行走区域走,而事实上某两点之间是有直线可以走的。
这其实是因为我们在分治过程中,这两个可直线行走的点被分到两段中,就不会去检测他们是否可以直线行走了。
这时经过上面针对微观抖动的过滤后,路径上的点已经比较少了。所以我们可以采用遍历路径上所有路点,两两之间是否可以直线行走,每次过滤掉的点接着的判断就不纳入计算,复杂度为O(N ^ 2)。
当然可能还是有点沿着不可行走区域,因为整个过滤过程还是基于A *的,A *给出的路径本身就是有点沿着不可行走区域。
这样我们就可以获得比较不错的角色寻路体验了。
-
私有函数过滤器(route:Array,wall:Wall):void {
-
GameStage.itemLayer.graphics.clear();
-
GameStage.itemLayer.graphics.beginFill( 0xffff00 );
-
for (var i: int = 0 ; i <route.length; i ++){
-
//GameStage.itemLayer.graphics.drawRect(route [i] [0],route [i] [1],5,5);
-
}
-
-
var i: int = 0 ;
-
var j: int = route.length- 1 ;
-
-
而 (ji> 1 ){
-
-
而 (j> i + 1 ){
-
var xDistance: int = route [j] [ 0 ] - route [i] [ 0 ];
-
var yDistance: int = route [j] [ 1 ] - route [i] [ 1 ];
-
var distance: int = Math.round(Math.pow(xDistance * xDistance + yDistance * yDistance, 1 / 2 ));
-
var tempX: int ,tempY: int ;
-
var flag:Boolean = false ;
-
for (var k: int = 1 ; k <distance; k + = KEY_POINT_LENGTH){
-
tempX = route [i] [ 0 ] + k * xDistance / distance;
-
tempY = route [i] [ 1 ] + k * yDistance / distance;
-
-
if (wall.isWall(Math.floor(tempX),Math.floor(tempY))
-
&& wall.isWall(Math.floor(tempX),Math.ceil(tempY))
-
&& wall.isWall(Math.ceil(tempX),Math.ceil(tempY))
-
&& wall.isWall(Math.ceil(tempX),Math.floor(tempY))){
-
GameStage.itemLayer.graphics.beginFill(0xff00ff);
-
GameStage.itemLayer.graphics.drawRect(tempX,tempY,5,5);
-
flag = true ;
-
打破 ;
-
}
-
}
-
if (!flag){
-
GameStage.itemLayer.graphics.beginFill( 0x00ffff );
-
for (var d: int = i + 1 ; d <j; d ++){
-
//GameStage.itemLayer.graphics.drawRect(route [d] [0],route [d] [1],5,5);
-
}
-
route.splice(i + 1 ,ji- 1 );
-
打破 ;
-
} else {
-
j--;
-
}
-
}
-
我++;
-
j = route.length- 1 ;
-
}
-
//GameStage.itemLayer.graphics.endFill();
-
}