|
本系列的教程文章基于 A*Pathfinding Project 4.2.8的官网教程翻译,每一章节的原文地址都会在教程最下方给出。
本篇教程将阐述一下如何创建一个简单的自定义寻路脚本。
下面我们将正式开始创建一个属于我们自己的寻路脚本,打开脚本编辑器跟着做吧。
不管你创建的是3D还是2D游戏,本篇教程都适用哈。但在某些地方,会阐述2D和3D类型的不同之处。这些不同的地方将会用注释指明。但是,我们前一篇所讲述的工程其实是一个3D的项目,所以如果想要了解如何创建一个2D项目,后面的教程中会有讲解。 寻路的第一件事肯定是计算路径。为了达到目的我们只要调用Seeker组件里的StartPath方法即可!并且函数调用及其简单,只有3个参数,起始位置,结束位置和一个callback函数。(callback函数必须是一个 OnPathDelegate 类型的代理函数,函数定义参加下面代码区域的 void OnPathComplete (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 targetPosition;
public void Start () {
// Get a reference to the Seeker component we added earlier
Seeker seeker = GetComponent<Seeker>();
// Start to calculate a new path to the targetPosition object, return the result to the OnPathComplete method.
// Path requests are asynchronous, so when the OnPathComplete method is called depends on how long it
// takes to calculate the path. Usually it is called the next frame.
seeker.StartPath(transform.position, targetPosition.position, OnPathComplete);
}
public void OnPathComplete (Path p) {
Debug.Log(&#34;Yay, we got a path back. Did it have an error? &#34; <span class="p">+ p.error);
}
}
创建一个C#脚本,复制以上代码进去,并把脚本命名为AstarAI,然后将此脚本绑定到一个AIGameObject上。
创建一个GameObject当做Target,把它的坐标设置为(-20,0,22),然后将它拖拽到刚才AstarAI的targetPosition参数位置,这个位置就是AI将要寻找路径前往的位置。
点击Play按钮吧,你会看到一些日志信息,并且路径也会在Scene窗口用绿色的线显示出来。(Seeker组件会用Gizmos功能把最新计算出来的路径画出来。)
如果你看不到绿色的线,那么就打开Gizmos的面板,查看一下Seeker组件是否勾选让了。因为Unity的一些版本也会进行一些深度测试,所以也有可能是被隐藏在了某些物体之后,所以点击场景窗口上方的Gizmos按钮,然后把3D Icons的勾选项去掉就好。
如果你碰到了error报错,那么要确保Seeker组件真的被绑定到了和AstarAI脚本相同的物体上了。如果是目标点不可到达,那么也要稍稍改变一下目标点的位置。
一些常见的错误信息可以参见如下页面:
那么再来看下这个寻路路径,它看起来歪歪扭扭的一点也不圆润,怎么办呢?盘他!
那么问题出在哪里呢?脚本其实只调用了Seeker&#39;s的StartPath方法。而这个方法将会创建一个ABPath的实例然后发送给AstarPath脚本。AstarPath会把路径存放在一个队列里,然后尽快的按格子搜索直到找到结束点为止。
如果需要了解更多关于搜索步骤的 可以参考这里:
路径计算完成之后,会返回给Seeker,如果它同时绑定了修饰符类型的脚本将会进行后处理,然后会调用寻路完成的callback函数通知AI。当然你也可以直接给Seeker指定一个固定的回调函数,这样就不用每次调用StartPath的时候传入回调函数了。代码如下:
// 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);当禁用或者销毁脚本的时候,回调引用并不会移除,所以为了防止回调异常,最好在OnDisable里将回调移除,像这样: public void OnDisable () {
seeker.pathCallback -= OnPathComplete;
}当计算结果返回了之后,我们怎么拿到路径信息呢?
一个路径的示例包含了2个Lists列表,其中Path.VectorPath是一个Vector3类型的路径集合,如果使用了修饰符的话,这个路径点的数据可能会在后处理的时候被修改,当然这个是推荐拿数据的方式。第二个列表是Path.path,这里存的是GraphNode的元素,他保存了路径访问过的所有节点,这对于获取更多的附加信息来说是比较有用的。
不过,你应该先检查一下Path.error。如果这个变量为true,表示寻路失败了,你可以从Path.errorLog里获取更多的错误信息来排查问题。
如果扩展AI脚本的话,需要加上一些移动的脚本来表现:
<ul>3D:一般使用Unity内置的CharacterController组件即可。2D:使用一个简单的位置移动修改的脚本来移动 Transform组件即可。</u>这个脚本需要保持朝着路径列表最近的路点移动,并在接近的时候快速切换至下一个路点。所以,每帧都需要做如下事情:
首先是要确认路径是否已经生成了。因为路径请求是异步的,所以得到计算结果可能会需要几帧的时间(通常会在一帧内的)。然后我们要检查移动的AI是否已经接近了我们正在靠近的路点,如果是的话,就要切换到下一个路点,然后重复这个动作直到接近目标点。为了让移动准确,我们需要用当前接近的路点坐标减掉我们现在的坐标来得到一个向量,将向量的单位归整到1,然后用这个向量来确定目标的移动方向,若不然你可能反而离目标路点越来越远。用向量乘以移动速度值,得到一个带方向的速度。最后使用CharacterController.SimpleMove(3D)或者修改transform.position(2D)的方法来移动AI。
using UnityEngine;
// Note this line, if it is left out, the script won&#39;t know that the class &#39;Path&#39; 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 targetPosition;
private Seeker seeker;
private CharacterController controller;
public Path path;
public float speed = 2;
public float nextWaypointDistance = 3;
private int currentWaypoint = 0;
public bool reachedEndOfPath;
public void Start () {
seeker = GetComponent<Seeker>();
// If you are writing a 2D game you can remove this line
// and use the alternative way to move sugggested further below.
controller = GetComponent<CharacterController>();
// Start a new path to the targetPosition, call the the OnPathComplete function
// when the path has been calculated (which may take a few frames depending on the complexity)
seeker.StartPath(transform.position, targetPosition.position, OnPathComplete);
}
public void OnPathComplete (Path p) {
Debug.Log(&#34;A path was calculated. Did it fail with an error? &#34; + p.error);
if (!p.error) {
path = p;
// Reset the waypoint counter so that we start to move towards the first point in the path
currentWaypoint = 0;
}
}
public void Update () {
if (path == null) {
// We have no path to follow yet, so don&#39;t do anything
return;
}
// Check in a loop if we are close enough to the current waypoint to switch to the next one.
// We do this in a loop because many waypoints might be close to each other and we may reach
// several of them in the same frame.
reachedEndOfPath = false;
// The distance to the next waypoint in the path
float distanceToWaypoint;
while (true) {
// If you want maximum performance you can check the squared distance instead to get rid of a
// square root calculation. But that is outside the scope of this tutorial.
distanceToWaypoint = Vector3.Distance(transform.position, path.vectorPath[currentWaypoint]);
if (distanceToWaypoint < nextWaypointDistance) {
// Check if there is another waypoint or if we have reached the end of the path
if (currentWaypoint + 1 < path.vectorPath.Count) {
currentWaypoint++;
} else {
// Set a status variable to indicate that the agent has reached the end of the path.
// You can use this to trigger some special code if your game requires that.
reachedEndOfPath = true;
break;
}
} else {
break;
}
}
// Slow down smoothly upon approaching the end of the path
// This value will smoothly go from 1 to 0 as the agent approaches the last waypoint in the path.
var speedFactor = reachedEndOfPath ? Mathf.Sqrt(distanceToWaypoint/nextWaypointDistance) : 1f;
// Direction to the next waypoint
// Normalize it so that it has a length of 1 world unit
Vector3 dir = (path.vectorPath[currentWaypoint] - transform.position).normalized;
// Multiply the direction by our desired speed to get a velocity
Vector3 velocity = dir * speed * speedFactor;
// Move the agent using the CharacterController component
// Note that SimpleMove takes a velocity in meters/second, so we should not multiply by Time.deltaTime
controller.SimpleMove(velocity);
// If you are writing a 2D game you may want to remove the CharacterController and instead use e.g transform.Translate
// transform.position += velocity * Time.deltaTime;
}
}
如果你点击播放按钮,AI就会按照计算好的路线移动了,看到没?
如果你绑定了SimpleSmoothModifier脚本,就会得到一个比较平缓的路径。
给一个3D的示例参考一下:
https://www.zhihu.com/video/1124632192822898688
如果需要更进阶的方法(比如定期修改路径等),可以查看插件内置的AI脚本。
本篇教程到这里就结束了,你可以继续阅读入门教程的其他章节。
文章原地址: |
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有账号?立即注册
×
|