最终效果
插件下载链接:
https://download.csdn.net/download/qq_36303853/87762376
可以前往官网下载和查看开发文档
https://arongranberg.com/astar/download
导入AI插件
新建空物体,添加PathFinder组件,用在地图导航
渲染导航路径,绘制出来的蓝色部分则为可行走区域,非蓝色区域是我配置的碰撞器区域,可自行修改,如果不满意可以修改Diameter的值,控制碰撞器区域多大的范围不可行走
给敌人添加碰撞器,碰撞区域自行调整
给敌人添加AIPath组件,我们这里的2d项目,记得orientation先选择YAxisForward(for 2D games)
,如果你不想敌人旋转,可以去掉勾选Enable Rotation
常用参数 | 解释 |
---|---|
can move | 表示能否移动 |
max speed | 表示移动速度 |
rotation speed | 表示旋转速度 |
slowdown distance | 表示减速距离 |
end reached distance | 表示停止距离,表示怪物距离玩家多远的适合会停止移动 |
pick next waypint dist | 表示距离下个路径点的距离 |
Gravity | 表示重力,这里不需要重力所以选择none |
再给敌人添加AI Destination setter组件,这个是设置敌人的目标,拖入我们的玩家即可
运行效果
代码控制敌人移动,并发起攻击
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Pathfinding;
public class Enemy : MonoBehaviour
{
public float startHealth = 100;//开始血量
public float health;//当前血量
public bool isDead;//是否死亡
public float damage = 10;//敌人伤害
public float hitRate = 1.0f;//攻速
private float _lastHit;//计时器
public LayerMask whatToHit;//可以攻击哪个图层
private float hitDistance = 2.0f;//攻击距离
[Header("ai导航属性")]
private AIPath aiPath;
private Transform target; //目标
private void Start()
{
aiPath = GetComponent<AIPath>();
target = GameObject.FindGameObjectWithTag("Player").transform;
health = startHealth;
}
private void Update()
{
if (target == null) return;
aiPath.destination = target.position;//敌人移动的目标位置
if (aiPath.reachedDestination)//是否抵达目标位置
{
//发起攻击
if (Time.time > _lastHit + 1 / hitRate)
{
Hit();
_lastHit = Time.time;
}
}
}
//攻击
void Hit()
{
//怪物朝向
Vector3 targetDirection = (target.position - transform.position).normalized;
//射线 aiPath.endReachedDistance表示抵达终点的距离
RaycastHit2D hit2D = Physics2D.Raycast(transform.position, targetDirection, aiPath.endReachedDistance + hitDistance, whatToHit);
if (hit2D.collider != null)
{
Debug.Log(damage);
//造成伤害
PlayerController playerController = hit2D.collider.GetComponent<PlayerController>();
playerController?.TakeDamage(damage);
}
}
}
更加复杂的代码控制
using UnityEngine;
using Pathfinding;
public class MyAIMove : MonoBehaviour
{
private Seeker mSeeker; // 寻路组件
private List<Vector3> mPathPointList; // 路径点列表
private int mCurrentIndex = 0; // 当前路径点索引
void Start()
{
mSeeker = GetComponent<Seeker>(); // 获取Seeker组件
}
void Update()
{
// 当鼠标左键点击时
if (Input.GetMouseButtonDown(0))
{
// 获取鼠标点击位置,并转换为世界坐标
Vector3 target = Camera.main.ScreenToWorldPoint(Input.mousePosition);
target.z = 0; // 保证z轴为0(2D场景)
// 创建路径
CreatePath(target);
// 移动角色
Move();
}
}
private void Move()
{
// 如果路径点列表为空或者当前索引超出范围,直接返回
if (mPathPointList == null || mCurrentIndex >= mPathPointList.Count)
return;
// 如果当前位置与目标路径点的距离大于0.2f,则继续移动
if (Vector2.Distance(transform.position, mPathPointList[mCurrentIndex]) > 0.2f)
{
// 计算移动方向
Vector3 dir = (mPathPointList[mCurrentIndex] - transform.position).normalized;
// 根据方向移动角色,这里的20是移动速度,Time.deltaTime是每帧的时间间隔
transform.position += dir * Time.deltaTime * 20;
}
else
{
// 如果已经到达当前路径点,则切换到下一个路径点
if (mCurrentIndex == mPathPointList.Count - 1)
return;
mCurrentIndex++;
}
}
private void CreatePath(Vector3 target)
{
mCurrentIndex = 0; // 重置当前路径点索引为0
// 开始寻路,从当前位置到目标位置
mSeeker.StartPath(transform.position, target, path =>
{
// 寻路完成后,将路径点存入路径点列表
mPathPointList = path.vectorPath;
});
}
}
目前人物的移动会非常生硬,在人物身上挂载一个Simple Smooth组件,不用做任何修改,我们的移动就变得顺滑了
A*插件实时添加障碍物
文档:https://arongranberg.com/astar/docs/graphupdates.html
对所有图形或仅几个图形进行完整的重新计算
Scan:直接更新整个场景的地图,地图复杂动画,时间会比较长,场景会卡至烘焙完成
这条语句的作用是:实时更新扫描网格,使得可以实时添加障碍物。具体应该在你在实例化一个障碍物的后面加上这一条语句。
// 重新计算所有图表
AstarPath.active.Scan();
// 仅重新计算第一个网格图表
var graphToScan = AstarPath.active.data.gridGraph;
AstarPath.active.Scan(graphToScan);
// 仅重新计算第一个和第三个图表
var graphsToScan = new[] { AstarPath.active.data.graphs[0], AstarPath.active.data.graphs[2] };
AstarPath.active.Scan(graphsToScan);
异步重新计算图表
你还可以异步重新计算图表(仅限专业版功能
)。这不能保证良好的帧率,但至少可以在处理时显示一个加载中的提示界面。
IEnumerator Start () {
foreach (Progress progress in AstarPath.active.ScanAsync()) {
Debug.Log("Scanning... " + progress.ToString());
yield return null;
}
}
小型图表更新
较小的图表更新可以通过以下两种方式之一完成:
- 使用
GraphUpdateScene
组件,该组件可以在Unity Inspector中编辑。 - 使用脚本编程,通过调用
AstarPath
类中的方法,并传入一个Bounds
对象或Pathfinding.GraphUpdateObject
(实际上,GraphUpdateScene
组件在后台也是这样做的)。
// 例如,使用附加的碰撞体的边界框
Bounds bounds = GetComponent<Collider>().bounds;
AstarPath.active.UpdateGraphs(bounds);
- 这段代码获取了附加到当前游戏对象的碰撞体的边界框,并将其传递给
AstarPath.active.UpdateGraphs()
方法,以仅更新该边界框范围内的路径查找图表。
using Pathfinding; // 放在脚本顶部
// 例如,使用附加的碰撞体的边界框
Bounds bounds = GetComponent<Collider>().bounds;
var guo = new GraphUpdateObject(bounds);
// 设置一些参数
guo.updatePhysics = true;
AstarPath.active.UpdateGraphs(guo);
- 这段代码创建了一个
GraphUpdateObject
实例guo
,并使用碰撞体的边界框进行初始化。然后设置了updatePhysics
参数为true
,这通常用于指示是否应该重新计算物理属性(如不可穿越区域)。最后,调用了AstarPath.active.UpdateGraphs(guo)
来应用这些更新。
详细解释
-
GraphUpdateScene
组件:这是一个可视化工具,允许你在Unity Inspector中指定哪些区域需要更新。它非常适合在编辑器中设置静态或半静态的更新区域。 -
使用脚本编程:对于动态变化或运行时更新,你可以编写脚本来触发特定区域的图表更新。这提供了更大的灵活性和控制力。
-
Bounds
对象:表示一个三维空间中的轴对齐包围盒,通常用来定义一个区域。在这个例子中,它是从游戏对象的碰撞体获取的。 -
GraphUpdateObject
类:这是一个更高级的类,允许你自定义图表更新的行为,比如是否更新物理属性、添加或移除节点等。
其他
还有一个Unity2d自动寻路插件NavMeshPlus,需要的可以去了解
git地址:https://github.com/h8man/NavMeshPlus
区别
A*Pathfinding插件和NavMeshPlus插件都是Unity的导航网格相关的插件。它们之间的区别主要体现在以下几个方面:
1.算法原理:A* Pathfinding插件使用的是A*算法来搜索最短路径,而NavMeshPlus插件使用的是Unity自带的导航网格系统来计算路径。
2.功能特点:A* Pathfinding插件提供了大量的路径搜索和寻路算法,同时还提供了寻路障碍物躲避、寻路优化等功能;而NavMeshPlus插件则主要关注于优化Unity的导航网格系统,提供了更高效更准确的表面剖分、NavMesh配置和障碍物遮蔽等功能。
3.使用成本:A* Pathfinding插件需要进行额外的配置和调试,而NavMeshPlus插件则更加易用,直接在Unity中就能够完成操作。
基于以上区别,推荐的使用场景为:如果您需要复杂的寻路算法、路线优化或存在大量的寻路单位,建议使用A*Pathfinding插件;如果您只需要简单的寻路算法,或需要优化现有导航网格的性能,建议使用NavMeshPlus插件。
总而言之,这两个插件都是非常优秀的导航网格相关插件,具体的使用需根据实际需求进行选择。
为啥大量的寻路单位推荐使用A*Pathfinding?
A* Pathfinding插件采用了基于图的最短路径搜索算法,相比于Unity的导航网格系统来说,对于大量的寻路单位能够更快速地搜索到最短路径,避免了因为单位数量增加而导致导航网格系统计算路径的效率下降的问题。
A* Pathfinding插件还提供了很多高级算法,如流形平滑、局部避障等算法,能够对路径进行优化或者避免行进时的碰撞,尤其适用于复杂的场景以及大量单位的游戏中。
此外,A* Pathfinding可以配置多个线程来计算寻路,能够进一步提高性能。而Unity的导航网格是单线程计算,一旦出现大量的寻路单位,计算时间会成倍增加。
因此,对于场景中存在大量的寻路单位的游戏,推荐使用A* Pathfinding插件。