原文链接:http://arongranberg.com/astar/docs/getstarted.php
插件下载地址:http://pan.baidu.com/s/1eROqaB4
题外话:最近想学习一下A*插件,由于在网上没有发现什么比较详细的教程,所以就只能上官网了。这是第一次看这么长的英语文章,翻译得不好,请见谅!
概述:
A*插件的核心脚本就是”astarpath.cs”,所以如果你想使用A*插件进行寻路,那么场景中一定要有一个”astarpath.cs”(并且一个场景仅有一个)。可以通过Component->Pathfinding->Pathfinder添加”astarpath.cs”。
第二重要的脚本就是”seeker.cs”,每一个需要寻路的物体最好要有这个脚本(不是一定要添加,但是有了可以让寻路更加简单)。
然后就是一个名叫"SimpleSmoothModifier.cs"的脚本,它可以让路径更加圆滑、简单,它要跟"seeker"脚本一起添加到同一个物体上。
1.创建一个新场景,创建一个plane,位置(0,0,0),缩放(10,10,10)。创建一个新层(edit->project settings->tags and layers),命名为”Ground”,把plane设为”Ground”,创建一些cube作为障碍物,把这些cube设为新的层”Obstacles”。
2.创建一个新GameObject,命名为A*,添加"AstarPath"脚本。脚本中最重要的是两个东西是“Graphs”和下方的“Scan”按钮。“Graphs”包含了所有的寻路图,最多可以有16个,但是一般1到2个已经足够了。其中主要的两个是”Grid Graph”(以格子的方式生成节点)和”Navmesh”(在可行走的区域生成mesh)。“Scan”按钮用来更新寻路图。
3.点击”Grid Graph”,Grid graph会产生一系列的格子,大小为width * height,这个网格可以放在场景中的任何地方,也可以进行旋转。“Node Size”可以设置格子的大小,这里先设为1。注意有一个由5个点组成的正方形图标,点击它的左下方,此时图标那一行前面的文字会变为”Bottom-Left”,设为(-50,-0.1,-50)。其中y方向设置为-0.1是为了避免产生浮点错误,因为地面plane的y向坐标是0,如果寻路图也是y向坐标也为0的话,在进行高度检测的raycast的时候,会产生问题。所以确保寻路图的y要略小于地面的y。为了网格可以适应我们的场景,调整Width和Depth,均为100。(在实际设置中好像有点问题,总之就是让网格跟plane大小适应就是了,可以通过"Scan"进行刷新)
高度检测
4.为了把寻路的node放置到场景中的正确高度,一般向下发射一束射线来进行检测,寻路node会被放置到碰撞点的位置。我们将mask设置为Ground,因为只希望寻路节点与Ground进行检测。如果射线没有发生碰撞,说明检测的物体没有设置为“Ground”,又或者上面的第三点中y设为了0。
碰撞检测
5.当node被放置好后,它就会被用来检测是否可行走,一般可以使用sphere,capsule或ray来进行碰撞检测(这里指的是用Physics.CapsuleCast或者Physics.SphereCast模拟AI对象)。Capsule会使用和AI对象一样的半径和高度来进行碰撞。为了让AI对象和障碍物有一些边缘,这里将Capsule的半径设置为2.另外将碰撞检测的layer设置为Obstacles,因为不想让地面成为障碍。点击底部的Scan,我们就可以看到grid Graph的生成了。
添加AI
6.创建一个capsule,添加”Character Controller”部件,放到plane上。添加”Seeker”部件,Seeker脚本是一个帮助类的脚本,用来将其他脚本的寻路请求进行处理,它也可以处理Path modifier(一般是对寻路结果进行圆滑处理的脚本)。接下来将会写一个脚本用于寻路,当然你也可以用插件内置的脚本。A* 插件自带了两个AI脚本用于挂接到对象上进行寻路:AIPah可适用于任何类型的寻路图;而RichAI主要适用于NavMesh类型。
让物体寻路
7.seek脚本中有一个重要的方法:function StartPath (Vector3 start, Vector3 end, OnPathDelegate callback = null) : Path。参数是开始位置,结束位置,回调函数。其中OnPathDelegate是一个委托,被调用的函数类似于"void SomeFunction (Path p)"
脚本如下:
using UnityEngine; using System.Collections; //Note this line, if it is left out, the script won't know that the class 'Path' exists and it will throw compiler errors //This line should always be present at the top of scripts which use pathfinding using Pathfinding; public class AstarAI : MonoBehaviour { public Transform target; public void Start() { //Get a reference to the Seeker component we added earlier Seeker seeker = GetComponent<Seeker>(); //Start a new path to the targetPosition, return the result to the OnPathComplete function seeker.StartPath(transform.position, target.position, OnPathComplete); } public void OnPathComplete(Path p) { Debug.Log("Yay, we got a path back. Did it have an error? " + p.error); } }
把这个脚本添加到要寻路的物体上,给target赋值,点击"Play"后,你应该会看到一条绿色的线,这就是寻路的路径了,此时物体还不会动。
如果没有看到绿线,检查一下"Seek"的脚本的"Show Gizmos"有无勾上。又或者可能是unity版本的原因,使Gizmos画出来的线被隐藏在plane下面了。
可以看到画出来的线很不圆滑,后面会进行美化的。
设置回调的方式也可以是这样:
//OnPathComplete will be called every time a path is returned to this seeker seeker.pathCallback += OnPathComplete; //So now we can omit the callback parameter seeker.StartPath (transform.position,targetPosition);
public void OnDisable () { seeker.pathCallback -= OnPathComplete; }
计算后的路径有两个重要的list。
1.Path.vectorPath是一个Vector3的list,保存着每一个路径点的位置。
2.Path.path是一个node的list,保存着每一个路径点的node。
然后拓展一下上面的脚本:
using UnityEngine; using System.Collections; //Note this line, if it is left out, the script won't know that the class 'Path' exists and it will throw compiler errors //This line should always be present at the top of scripts which use pathfinding using Pathfinding; public class AstarAI : MonoBehaviour { //The point to move to public Transform target; private Seeker seeker; private CharacterController controller; //The calculated path public Path path; //The AI's speed per second public float speed = 100; //The max distance from the AI to a waypoint for it to continue to the next waypoint public float nextWaypointDistance = 3; //The waypoint we are currently moving towards private int currentWaypoint = 0; public void Start() { seeker = GetComponent<Seeker>(); controller = GetComponent<CharacterController>(); //Start a new path to the targetPosition, return the result to the OnPathComplete function seeker.StartPath(transform.position, target.position, OnPathComplete); } public void OnPathComplete(Path p) { Debug.Log("Yay, we got a path back. Did it have an error? " + p.error); if (!p.error) { path = p; //Reset the waypoint counter currentWaypoint = 0; } } public void FixedUpdate() { if (path == null) { //We have no path to move after yet return; } if (currentWaypoint >= path.vectorPath.Count) { Debug.Log("End Of Path Reached"); return; } //Direction to the next waypoint Vector3 dir = (path.vectorPath[currentWaypoint] - transform.position).normalized; dir *= speed * Time.fixedDeltaTime; controller.SimpleMove(dir); //Check if we are close enough to the next waypoint //If we are, proceed to follow the next waypoint if (Vector3.Distance(transform.position, path.vectorPath[currentWaypoint]) < nextWaypointDistance) { currentWaypoint++; return; } } }
然后运行游戏,物体就会移动了。注意路径点不是连续的,当物体离下一个路径点小于nextWaypointDistance时才会继续前进。最后物体会停在离目标前的一段距离,这是因为对于终点,我们需要进行特殊处理,但脚本还没有进行处理。
路径平滑
8.这时要用到内置的脚本"Path Modifiers",要加在有"seeker"脚本的物体上。(不过好像找不到这个名字的脚本,也可以通过Components–>Pathfinding–>Modifiers–>Simple Smooth添加脚本)。平滑的原理就是将一段段的小路径继续细分,让路径点之间的距离缩小。这里先将Max Segment Length设为1,Iterations设为5,Strength设为0.25。运行之后会发现绿线更加平滑了。
最后:因为使用上面的脚本进行寻路并不完美,而且每一次都要写脚本的话会很麻烦,所以直接为要寻路的物体添加"AIPath"脚本,赋值target,效果会更好!上面的脚本就当理解一下内部就好了!