“AS3.0高级动画编程”学习:第四章 寻路(AStar/A星/A*)算法 (下)

前一部分的最后,我们给出了一个寻路的示例,在大多数情况下,运行还算良好,但是有一个小问题,如下图:

很明显,障碍物已经把路堵死了,但是小球仍然穿过对角线跑了出来!

问题在哪里:我们先回顾一下AStar.as中用于判断的if语句

?
1
2
3
4
5
//如果是当前节点,或者是不可通过的,则跳过
if (test == node || !test.walkable)
{
     continue ;
}

在这个判断中,并没有规定说不允许走对象线。来看看如何修正:

在以node为中心考查四周节点时,如果遇到水平和垂直方向都是障碍物时,既使对角节点是可穿越的普通节点,也不能通过。所以只要再加二个条件判断即可

?
1
2
3
4
5
//如果是当前节点,或者是不可通过的,且排除水平和垂直方向都是障碍物节点时的特例情况
if (test == node || !test.walkable || !_grid.getNode(node.x, test.y).walkable || !_grid.getNode(test.x, node.y).walkable)
{
     continue ;
}

再运行一下:

一切正常了!

前面提到的这些示例,终点与目标点都是固定的,但在实际游戏中,正好相反,比如"星际",选定一个农民后,在地图上随便点击一下,农民就能自动找到去目标点的路径。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
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
package
{
     import flash.display.Sprite;
     import flash.display.StageAlign;
     import flash.display.StageScaleMode;
     import flash.events.Event;
     import flash.events.MouseEvent;
  
     [SWF(width= 600 ,height= 600 )]
     public class Game extends Sprite
     {
         private var _cellSize: int = 20 ;
         private var _grid:Grid;
         private var _player:Sprite;
         private var _index: int ;
         private var _path: Array ;
  
         public function Game()
         {
             stage.align=StageAlign.TOP_LEFT;
             stage.scaleMode=StageScaleMode.NO_SCALE;
             makePlayer();
             makeGrid();
             stage.addEventListener(MouseEvent.CLICK, onGridClick);
         }
  
         /** 生成一个player角色(简单起见,就是一个圈) */
         private function makePlayer(): void
         {
             _player= new Sprite();
             _player.graphics.beginFill( 0xff0000 );
             _player.graphics.drawCircle( 0 , 0 , 5 );
             _player.graphics.endFill();
             _player.x=Math.random() * 600 ;
             _player.y=Math.random() * 600 ;
             addChild(_player);
         }
  
         /** 生成网格,并随机放置一些障碍 */
         private function makeGrid(): void
         {
             _grid= new Grid( 30 , 30 );
             for ( var i: int = 0 ; i < 200 ; i++)
             {
                 _grid.setWalkable(Math.floor(Math.random() * 30 ), Math.floor(Math.random() * 30 ), false );
             }
             drawGrid();
         }
  
         /** 画网格线以及为障碍物填充颜色*/
         private function drawGrid(): void
         {
             graphics.clear();
             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);
                     graphics.lineStyle( 0 );
                     graphics.beginFill(getColor(node));
                     graphics.drawRect(i * _cellSize, j * _cellSize, _cellSize, _cellSize);
                 }
             }
         }
  
         /** 返回节点颜色 */
         private function getColor(node:Node): uint
         {
             if (!node.walkable)
                 return 0 ;
             if (node == _grid.startNode)
                 return 0xcccccc ;
             if (node == _grid.endNode)
                 return 0xff0000 ;
             return 0xffffff ;
         }
  
         /** 鼠标点击时随机设置终点,并以player当前位置做为起点 */
         private function onGridClick(event:MouseEvent): void
         {
             var xpos: int =Math.floor(mouseX / _cellSize);
             var ypos: int =Math.floor(mouseY / _cellSize);
             _grid.setEndNode(xpos, ypos);
             xpos=Math.floor(_player.x / _cellSize);
             ypos=Math.floor(_player.y / _cellSize);
             _grid.setStartNode(xpos, ypos);
             drawGrid();
             findPath();
         }
  
         /** 寻路 */
         private function findPath(): void
         {
             var astar:AStar= new AStar();
             if (astar.findPath(_grid))
             {
                 _path=astar.path;
                 _index= 0 ;
                 addEventListener(Event.ENTER_FRAME, onEnterFrame);
             }
         }
  
         /**每帧的动画处理*/
         private function onEnterFrame(event:Event): void
         {
             var targetX: Number =_path[_index].x * _cellSize + _cellSize / 2 ;
             var targetY: Number =_path[_index].y * _cellSize + _cellSize / 2 ;
              
             //把经过的点,涂上黄色
             var passedNode:Node=_path[_index];
             graphics.lineStyle( 0 );
             graphics.beginFill( 0xffff00 );
             graphics.drawRect(passedNode.x * _cellSize, passedNode.y * _cellSize, _cellSize, _cellSize);
              
             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++; //索引加1,即取一个路径节点
                 if (_index >= _path.length) //达到最后一个节点时,移除ENTER_FRAME监听
                 {
                     removeEventListener(Event.ENTER_FRAME, onEnterFrame);
                 }
             }
             else
             {
                 _player.x+=dx * . 5 ;
                 _player.y+=dy * . 5 ;
             }
         }
     }
}

拿鼠标在空白节点上随便点点,看看会发生些什么? 

考虑最后一个问题:实际游戏地图中有平地,有高坡,有沙地,有雪地...不同的路面状况,行走的难度(即代价)应该不同吧?而我们刚才的所有示例中,对所有可穿越的节点都是平等对待的。如何区分出不同情况的地形呢?

关注一下:Node.as中的

?
1
public var costMultiplier: Number = 1.0 ; //代价因子

以及AStar.as中的

?
1
2
//计算test节点的总代价                      
var g: Number =node.g + cost * test.costMultiplier;

聪明的你一定看出端倪了!没错,costMultiplier就是代价的权重因子,如果让每个节点的权重因子不同,就能体现出不同地形的行走难度程度。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
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
package
{
     import flash.display.Sprite;
     import flash.events.MouseEvent;
  
     public class GridView2 extends Sprite
     {
         private var _cellSize: int = 20 ;
         private var _grid:Grid;
                  
         public function GridView2(grid:Grid)
         {
             _grid = grid;
             for ( var i: int = 0 ; i < _grid.numCols; i++)
             {
                 for ( var j: int = 0 ; j < _grid.numRows; j++)
                 {
                     //为每个节点设置不同的“代价权重因子”
                     var mult: Number = Math.sin(i * . 50 ) + Math.cos(j * . 2 + i * . 05 );
                     _grid.getNode(i, j).costMultiplier = Math.abs(mult) + 1 ;
                 }
             }
             drawGrid();
             findPath();
             addEventListener(MouseEvent.CLICK, onGridClick);
         }
          
         //画网格
         public function drawGrid(): void
         {
             graphics.clear();
             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);
                     graphics.lineStyle( 0 );
                     graphics.beginFill(getColor(node));
                     graphics.drawRect(i * _cellSize, j * _cellSize, _cellSize, _cellSize);
                 }
             }
         }
          
         //取得单元格的颜色(与权重因子关联,costMultiplier越小,颜色越深)
         private function getColor(node:Node): uint
         {
             if (!node.walkable) return 0 ;
             if (node == _grid.startNode) return 0x666666 ;
             if (node == _grid.endNode) return 0x666666 ;
             var shade: Number = 300 - 70 * node.costMultiplier;
             return shade << 16 | shade << 8 | shade;
         }
          
         //单元格点击时,切换节点为普通节点或障碍物节点
         private function onGridClick(event:MouseEvent): void
         {
             var xpos: int = Math.floor(event.localX / _cellSize);
             var ypos: int = Math.floor(event.localY / _cellSize);
              
             _grid.setWalkable(xpos, ypos, !_grid.getNode(xpos, ypos).walkable);
             drawGrid();
             findPath();
         }
          
         //找路
         private function findPath(): void
         {
             var astar:AStar = new AStar();
             if (astar.findPath(_grid))
             {
                 //showVisited(astar);
                 showPath(astar);
             }
         }
          
          
         private function showVisited(astar:AStar): void
         {
             var visited: Array = astar.visited;
             for ( var i: int = 0 ; i < visited.length; i++)
             {
                 graphics.beginFill( 0xcccccc );
                 graphics.drawRect(visited[i].x * _cellSize, visited[i].y * _cellSize, _cellSize, _cellSize);
                 graphics.endFill();
             }
         }
          
          
         private function showPath(astar:AStar): void
         {
             var path: Array = astar.path;
             for ( var i: int = 0 ; i < path.length; i++)
             {
                 graphics.lineStyle( 0 );
                 graphics.beginFill( 0 );
                 graphics.drawCircle(path[i].x * _cellSize + _cellSize / 2 ,
                     path[i].y * _cellSize + _cellSize / 2 ,
                     _cellSize / 3 );
             }
         }
     }
}

跟上一部分里的GridView.as比较起来,GridView2.as在构造函数里根据sin与cos函数,为节点设置了不同的权重因子,而且在节点着色上,深色的代价要比浅色的代价大,测试一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package
{
     import flash.display.Sprite;
     import flash.display.StageAlign;
     import flash.display.StageScaleMode;
     import flash.events.MouseEvent;
  
     [SWF(backgroundColor= 0xffffff ,width= 440 ,height= 440 )]
     public class Pathfinding_2 extends Sprite
     {
         private var _grid:Grid;
         private var _gridView:GridView2;
  
         public function Pathfinding_2()
         {
             stage.align=StageAlign.TOP_LEFT;
             stage.scaleMode=StageScaleMode.NO_SCALE;
             _grid= new Grid( 20 , 20 );
             _grid.setStartNode( 1 , 1 );
             _grid.setEndNode( 18 , 18 );
              
              
              
              
             _gridView= new GridView2(_grid);
             _gridView.x= 20 ;
             _gridView.y= 20 ;
             addChild(_gridView);
         }
     }
}

可以看出,调整权重因子后,路径尽量在靠近浅色的区域前进!可能这样对比还不强烈,把上面测试代码中的GridView2换回GridView,对比看下没有权重因子干扰时的路径

当然,在具体游戏开发过程中,A*算法还要结合其它很多技术(比如加载地图,配合地图设置权重因子,把地图分配到网格单元等)才能最终做出不错的游戏,我们在这里只是讨论寻路算法的原理,其它方面留给大家自行去完善吧.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值