游戏开发基础:A*算法(转)

转自:http://www.codeguru.com/csharp/csharp/cs_misc/designtechniques/article.php/c12527/

A-Star (A*) Implementation in C# (Path Finding, PathFinder)

Introduction

Some time ago, I had to make a project where I need to find the shorted path inside a matrix and I though "nothing better than use path finding for this."

There is lots of links and explanation about Path Finding, but Ididn't find a version written in C# that could meet my expectatations. So, I decided to make the A-star implementation in C#. This code was really useful for me and I bet it can be useful for many people too.

I won't explain the algorithm implementation too much, because just typing "pathfinding algorithm a-star" in Google brings 25,000 documents where you can find every single detail about it.

About the Application

A* is a generic algorithm and there are no perfect parameters to be set. The algorithm has many things that can be set, so the best way to know the best parameters that will fit in your project is to test the different combinations.

Note: Usually, a good A* implementation does not use a standard ArrayList or List for the open nodes. If a standard List is used, the algorithm will spend a huge amount of time searching for nodes in that list; instead, a priority queue should be used.

I borrow code from BenDi (http://www.codeproject.com/csharp/PriorityQueue.asp) for the priority queue implementation. I changed it a little bit and I changed the implementation to use generics instead ArrayList to make it run faster than before.

 

[PathFinder.png]

This project is really useful for two reasons:

  1. I followed the A* implementation and I tried to implement it to run with good performance, so the algorithm itself can be easily reused in any project.
  2. The front end gives a full chance to experiment with many variables where you can really watch and learn how it works. You also can change the heuristic, formula, or options to analyze the best setting for your project.

The call in the source code that does the entire job is the following:

public List<PathFinderNode> FindPath(Point start, Point end,
                                     byte[,] grid);

This method takes as parameters a start, end point, and the grid; it will return the path as a list of nodes with the coordinates. I made this call on a separate thread; this gives the chance to keep control of the application when PathFinder is working.

Algorithm Settings

Speed

This is the rendering speed. Reducing the speed changes the look of how the algorithm opens and closes the nodes in real time.

Diagonals

This hint will tell the algorithm whether it is allowed to process diagonals as a valid path. If this checkbox is not set, the algorithm will process just four directions instead of eight.

Heavy Diagonals

If this check box is set, the cost of the diagonals will be bigger. That will make the algorithm avoid using diagonals.

Punish Change Direction

If this check box is set, every time the algorithm changes direction it will have a bigger cost. The end result is that, if the path is found, it will be a smoother path without too many changes in directions and look more natural. The con is that it will take more time because it searches for extra nodes.

Heuristic

This is a constant that will affect the estimated distance from the current position to the goal destination. A heuristic function creates this estimate of how long it will take to reach the goal state because the better the estimate, the better a short path will be found.

Formula

This is the equation used to calculate the heuristic. Different formulas give different results. Some will be faster and others slower. The end may vary, and the formula to be used depends strongly on what the A-star algorithm will be used for.

Use Tie Breaker

Sometimes when A* is finding the path, it will find many possible choices with the same cost and basically all of them could reach the destination. A tie breaker setting is a hint to tell the algorithm that when it has multiple choices to research, instead keeping going, to change those costs a little bit and apply a second formula to determinate what could be the best "guess" to follow. Usually, this formula increments the heuristic from the current position to the goal by multipling by a constant factor.

Heuristic = Heuristic + Abs(CurrentX * GoalY - GoalX * CurrentY)
                      * 0.001
Search Limit

This is the limit of closed nodes to be researched before returning a "Path not found." This is a useful parameter because sometimes the grid can be too big and you need to know a path only if the goal is near from the current position or it can't spend too much time calculating the path.

Grid Size

This parameter just affects the front end. It will change the grid size; reducing the grid size gives a chance to create a bigger test, but it will take longer to render.

Fast PathFinder

When unchecked, the implementation used is the algorithm as it usually appears in the books. When checked, it will use my own PathFinder implementation. It requires more memory. but it is about 300 to 1500 times faster or more, depending on the map complexity (See PathFinder Optimization below).

Show Progress

This allows seeing how the algorithm works in real time. If this box is checked, the completed time will be the calculation plus rendering time.

Completed Time

This is the time that the algorithm took to calculate the path. To know the real value, uncheck 'Show Progress'.

Path Finder Optimization

After I implemented the original algorithm, I got frustrated because of the amount of time it took to resolve paths, especially for bigger grids and also when there was no solution to the path. Basically. I noticed that the open and closing lists were killing the algorithm. The first optimization was to replace the open list by a sorted list and the close list by a hashtable. This made a good improvement in the calculation time, but still was not what I expected. Later, I replaced the open list from a sorted list to a priority queue; it made a change, but still was not a big difference.

The big problem was that when the grid was big (1000x1000), the number of open and close nodes in the list was huge and searching in those lists a took long time, whatever method I used. At first, I thought to use classes to keep the nodes' information, but that was going to make the garbage collection crazy between the nodes' memory allocation and releasing the memory for the nodes that were disposed. Instead of classes, I used structs and re-use them in the code. I got more improvements, but still nothing close to what StarCraft does to handle eight hundred units in real time.

My current application is like a Viso application where I need to find a path between objects. The objects are not moving, so I didn't need really a super-fast algorithm, but I needed something that can react in less than one second.

I needed to find a way to get nodes from the open list in O(1) or closer to that. I though the only way to get that was not having an open list at all. There is when I thought to use a calculation grid to store the nodes. If I knew the (X/Y) location, I could reach the node immediately O(1).

Because of this, I could get rid of the close list. I could mark a closed node directly in the calculation grid. Every node has a link to the parent node, so I did not need a close list at all. However, I could not get rid of the open list because I needed to keep getting the nodes with less total cost (F), and the second grid didn't help with that.

This time I kept using the open list (priority queue), but it was just to push and get the node with lowest cost. Priority queues are excellent to do that; they require no linear access at all.

The only cons are that the memory consumption was huge to keep a second grid, because every cell stores a node, and every node was 32 bytes long. Basically, for a map (1000x1000), I needed 32 Mb of RAM. Now I was accessing the second grid by coordinates (X/Y), so I didn't need those values in the node anymore. That reduced eight bytes, multiplied by 1,000,000. I saved 8 Mb of RAM.

Every node has a link to the parent nodes and those were int (32 bits). Because the grid can never be too big, I replaced them for ushort (16 bits). That saved another 2 bytes by node; that meant another 2 Mb of savings.

Also, I realize that the heuristic (H) is calculated for every node on the fly and it is not used any more for the algorithm, so I removed it from the node structure too. Heuristic was a int (4 bytes), so I saved another 4 Mb. Finally, I got a minimum node that took 13 bytes but, because of memory alignment, they took 16 bytes at run-time. I had to set the struct to use memory alignment for every byte.

[StructLayout(LayoutKind.Sequential, Pack=1)]

About the Author

started with programming about 19 years ago as a teenager, from my old Commodore moving to PC/Server environment Windows/UNIX SQLServer/Oracle doing gwBasic, QBasic, Turbo Pascal, Assembler, Turbo C, BC, Clipper, Fox, SQL, C/C++, Pro*C, VB3/5/6, Java, and today loving C#. Currently working on VOIP/SIP technology. Passion for most programming languages and my son Aidan.
参考文章
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 非常感谢您的问题,C 语言是一种通用的、面向过程的计算机编程语言,它由计算机科学家美国计算机科学家贝尔·约翰逊(Bell Johnson)和美国计算机科学家丹尼斯·里奇(Dennis Ritchie)开发,并以1969年发布的“编译程序设计语言”(CPL)为基础。它是一种可移植的语言,可以用于多种不同的硬件平台和操作系统,并且功能强大。 ### 回答2: C语言是一种通用的高级编程语言,广泛应用于嵌入式系统、操作系统和应用程序的开发中。它具有简洁、高效、可移植等特点,适合用于开发一些对性能要求较高的应用。 与其他高级语言相比,C语言的语法较为简单,便于学习和理解。它提供了丰富的基本数据类型和运算符,可以方便地进行各种数学计算和逻辑操作。同时,C语言还提供了强大的控制结构,如条件语句、循环结构和函数,使得程序的编写更加灵活和高效。 C语言的可移植性也是它的一个重要特点。通过使用标准库和标准头文件,可以实现对不同平台上的应用程序的开发。而且C语言的编译器也非常丰富,可以运行在各种操作系统上,如Windows、Linux、Mac OS等。 在嵌入式系统和操作系统的开发中,C语言是首选的开发语言。由于它具有较低的层次感和较高的执行效率,可以更好地控制底层硬件和操作系统资源。此外,C语言还提供了指针操作和内存管理功能,可以更好地处理底层系统的内存布局和访问问题。 总之,C语言作为一种通用的高级编程语言,具有简洁、高效、可移植等优点,适用于各种开发场景。无论是开发嵌入式系统、操作系统还是应用程序,都可以选择C语言作为开发语言,以实现高效、可靠的程序编写。 ### 回答3: 语言编程的好处和挑战。 使用C语言进行编程有许多好处。首先,C语言是一种通用的编程语言,广泛应用于系统开发、嵌入式系统、游戏开发等领域。其简单而高效的语法使得编写代码更加容易,C语言也具备了底层的硬件和内存控制能力,使得程序开发者可以更好地控制程序的性能和效率。此外,C语言具备了丰富的标准库和函数,方便程序员利用已有的资源进行开发。 然而,使用C语言进行编程也面临一些挑战。首先,C语言是一种底层的编程语言,需要程序员对计算机底层的原理和机器语言有一定的了解。这对于一些新手来说可能会造成困扰。其次,C语言的代码相对较长,需要较多的代码行数来完成一些任务。这增加了程序的复杂性和容错难度。此外,由于C语言没有自动内存管理的机制,程序员需要手动管理内存的分配和释放,这可能导致内存泄漏和崩溃等问题。 在总体来说,使用C语言进行编程具有许多优点和挑战。对于有一定编程基础开发者来说,C语言是一个强大的工具,可以实现高性能的程序。然而,对于新手和初学者来说,可能需要一些时间和经验来适应C语言的语法和特点。总之,C语言作为一种经典的编程语言,在编程领域仍然具有重要的地位。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值