Unity中多边形寻路(A*寻路的优化)(待续)

多边形寻路实现  

2013-09-21 15:58:52|  分类: Unity教程 |  标签:unity3d  网格寻路  多边形寻路  |举报|字号 订阅

Unity3d本身自带有了NavMesh寻路功能。但用过这个功能的人,都会有各种的抱怨。比如,必须使用编辑器去烘焙、动态加载不方便、不能在服务器使用、不能随意的编辑,等等。
这里我自己做了一个多边形寻路的功能。这个功能纯粹是通过预存的数据生成一个模拟的多边形网格,然后通过坐标来计算的。所以这套东西是不限平台使用的,包括了在as3、在java或者c++的服务器、2d或者3d游戏都可以使用。不过针对不同的语言环境,肯定是需要做一定的修改的了。

为了测试我这套东西,我做了以下这个可行走的范围:点击鼠标,会在可行走范围内生成一条路径,然后代表着人物的cube就会根据路线来行走。
多边形寻路实现 - 阿赵 - 有爪的小羔羊阿赵
 
在寻路过程中,如果能直着走,那么我计算出来的路径就绝对不会是弯的。
多边形寻路实现 - 阿赵 - 有爪的小羔羊阿赵
 
如果必须是要拐弯,我计算的路径就会是拐一个最小的弯来走到目的地。
多边形寻路实现 - 阿赵 - 有爪的小羔羊阿赵
 
这是在scene视图的效果。其实这些组成多边形的点和多边形本身,只是我自己为了方便看,而特意用动态网格生成出来的。对计算是没有任何的意义的。
多边形寻路实现 - 阿赵 - 有爪的小羔羊阿赵

简单的说一下思路:
1、把可行走的区域分成若干个区域,每个区域都是凸多边形;
2、通过A*算法,先找出人物和目标点之间应该走的大区域(也就是图中的红色区域了);
3、得到大区域之后,使用向量叉乘,计算出人物应该行走的具体路线。

这个方法对比起Unity3d自带的NavMesh寻路,有以下优点:
1、自由度高,由于全部是自己写的,包括算法。所以可以根据使用情况自由扩展。
2、可以使用在任意的平台

对比原始的A*寻路,这种寻路会更快。因为一开始只是定一个大区域,网格的信息相对于A*整张地图的二维表,会少很多。
对比起路径点寻路,这种寻路出来的效果会更好看一点,因为人物很容易就能找到直线路径,而不像路径点一样,很容易让角色走Z型的路径。

缺点是,寻路的算法不是难点,难的地方是要动态的生成这些网格信息,如果要做成比较像样的地图编辑工具,会比较有难度。
折中的方法,就是像我现在这样,自己通过点来定寻路多边形了,不过这样做需要一定的熟悉,不然在制作过程中会很麻烦。

在之前写的网格寻路功能的基础上,加上了队伍群组寻路的功能。有些朋友可能会搞混,这个不是Unity3D自带的那个寻路功能,是自己重新写的。这个方法原则上能用在所有的平台,因为只是一些纯数学的计算。这里用Unity3D来表现,只是因为unity是可视化的操作,比较直观。
在默认的情况下,队伍的成员可以分散在地图的各个位置,当点击地图,让他们有一个目标点之后,各个成员就开始集合了。
队伍群组寻路 - 阿赵 - 有爪的小羔羊阿赵
 
在默认的情况下,队伍成员是分别各自做寻路运算,然后用群聚的算法让他们互相保持距离而又组成集体的往目标点移动。不过这种情况下,队伍不一定是整体移动,某些成员可能会找到更近的路,然后到达目的地。
队伍群组寻路 - 阿赵 - 有爪的小羔羊阿赵
 
这么多个队员,体积有大有小,但可以互相保持安全的距离。
队伍群组寻路 - 阿赵 - 有爪的小羔羊阿赵
 
刚才说的是默认的情况,也有不是默认的情况的,把这个isKeepGroup选项打钩,那么寻路就会以队伍为整体只寻路一次,然后整个队伍保持在一起的前进
队伍群组寻路 - 阿赵 - 有爪的小羔羊阿赵
 
这就是只寻路一次的效果了,只有一条路径线。这样的情况会减少寻路运算的次数,不过只适合整个队伍肯定是呆在一起的情况,如果有队员分散在其他地方,将会影响准确性。
队伍群组寻路 - 阿赵 - 有爪的小羔羊阿赵
 
这时候不会再有队员去找捷径了,都是保持在一起。
队伍群组寻路 - 阿赵 - 有爪的小羔羊阿赵

最后说一下原理:
原来的方法是:获取了路径点就直接在路径点之间移动角色。
现在改为了,让角色通过向量来移动。
影响角色移动的向量的因素有:
1、下一个路径点的位置。
2、旁边是否有其他的角色,如果有,给他一个排斥的方向向量,并根据两者之间的距离减少而增大排斥的向量大小。
3、还要有一个地形障碍的排斥力和队伍整体移动的平均向量的影响,这样队伍就可以动态绕过一些小型的障碍,并保持和队伍一致的前进。
4、得到角色应该走的向量方向后,先判断一下向量的大小,假如向量太小,就没必要去移动他了,让他原地先等一下,不然如果不判断向量大小都移动,那么人物很有可能在两个不停变动的小力之间不停的抖动。
5、判断角色应该移动之后,先把向量归一标准化,然后就可以乘以移动的速度,作为角色最后实际的移动速度了。
6、最后判断是否到达目的地。由于现在这么多的成员需要到达目的地,那么肯定有些队员是到不了目的地就被其他队员挡住了。那么就需要自己去设定到达的规则,可以是在队伍大部分成员都进入某个范围之内,就算到达,或者是看到前面的多个队员已经到达了,而自己也不能再移动了,那么就算是到达了。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是一个简单的Unity A*寻路例子: 1. 创建一个空的游戏对象,将其命名为“A*”,在其上添加一个空的脚本组件“AStarPathfinding”。 2. 在脚本添加以下代码: ``` public class AStarPathfinding : MonoBehaviour { public Transform startNode; public Transform endNode; public GameObject nodePrefab; public LayerMask unwalkableMask; public float nodeRadius; public float gridSize; public Vector2 gridWorldSize; Node[,] grid; void Start() { CreateGrid(); FindPath(startNode.position, endNode.position); } void CreateGrid() { int gridSizeX = Mathf.RoundToInt(gridWorldSize.x / gridSize); int gridSizeY = Mathf.RoundToInt(gridWorldSize.y / gridSize); grid = new Node[gridSizeX, gridSizeY]; Vector3 worldBottomLeft = transform.position - Vector3.right * gridWorldSize.x / 2 - Vector3.forward * gridWorldSize.y / 2; for (int x = 0; x < gridSizeX; x++) { for (int y = 0; y < gridSizeY; y++) { Vector3 worldPoint = worldBottomLeft + Vector3.right * (x * gridSize + nodeRadius) + Vector3.forward * (y * gridSize + nodeRadius); bool walkable = !(Physics.CheckSphere(worldPoint, nodeRadius, unwalkableMask)); grid[x, y] = new Node(walkable, worldPoint, x, y); } } } void FindPath(Vector3 startPos, Vector3 targetPos) { Node startNode = NodeFromWorldPoint(startPos); Node targetNode = NodeFromWorldPoint(targetPos); List<Node> openSet = new List<Node>(); HashSet<Node> closedSet = new HashSet<Node>(); openSet.Add(startNode); while (openSet.Count > 0) { Node currentNode = openSet[0]; for (int i = 1; i < openSet.Count; i++) { if (openSet[i].fCost < currentNode.fCost || (openSet[i].fCost == currentNode.fCost && openSet[i].hCost < currentNode.hCost)) { currentNode = openSet[i]; } } openSet.Remove(currentNode); closedSet.Add(currentNode); if (currentNode == targetNode) { RetracePath(startNode, targetNode); return; } foreach (Node neighbor in GetNeighbors(currentNode)) { if (!neighbor.walkable || closedSet.Contains(neighbor)) { continue; } int newMovementCostToNeighbor = currentNode.gCost + GetDistance(currentNode, neighbor); if (newMovementCostToNeighbor < neighbor.gCost || !openSet.Contains(neighbor)) { neighbor.gCost = newMovementCostToNeighbor; neighbor.hCost = GetDistance(neighbor, targetNode); neighbor.parent = currentNode; if (!openSet.Contains(neighbor)) { openSet.Add(neighbor); } } } } } List<Node> GetNeighbors(Node node) { List<Node> neighbors = new List<Node>(); for (int x = -1; x <= 1; x++) { for (int y = -1; y <= 1; y++) { if (x == 0 && y == 0) { continue; } int checkX = node.gridX + x; int checkY = node.gridY + y; if (checkX >= 0 && checkX < grid.GetLength(0) && checkY >= 0 && checkY < grid.GetLength(1)) { neighbors.Add(grid[checkX, checkY]); } } } return neighbors; } Node NodeFromWorldPoint(Vector3 worldPosition) { float percentX = (worldPosition.x + gridWorldSize.x / 2) / gridWorldSize.x; float percentY = (worldPosition.z + gridWorldSize.y / 2) / gridWorldSize.y; percentX = Mathf.Clamp01(percentX); percentY = Mathf.Clamp01(percentY); int x = Mathf.RoundToInt((grid.GetLength(0) - 1) * percentX); int y = Mathf.RoundToInt((grid.GetLength(1) - 1) * percentY); return grid[x, y]; } void RetracePath(Node startNode, Node endNode) { List<Node> path = new List<Node>(); Node currentNode = endNode; while (currentNode != startNode) { path.Add(currentNode); currentNode = currentNode.parent; } path.Reverse(); foreach (Node node in path) { Instantiate(nodePrefab, node.worldPosition, Quaternion.identity); } } int GetDistance(Node nodeA, Node nodeB) { int dstX = Mathf.Abs(nodeA.gridX - nodeB.gridX); int dstY = Mathf.Abs(nodeA.gridY - nodeB.gridY); if (dstX > dstY) { return 14 * dstY + 10 * (dstX - dstY); } else { return 14 * dstX + 10 * (dstY - dstX); } } public class Node { public bool walkable; public Vector3 worldPosition; public int gridX; public int gridY; public int gCost; public int hCost; public Node parent; public Node(bool _walkable, Vector3 _worldPos, int _gridX, int _gridY) { walkable = _walkable; worldPosition = _worldPos; gridX = _gridX; gridY = _gridY; } public int fCost { get { return gCost + hCost; } } } } ``` 3. 在场景添加两个球体,将其一个球体的Transform组件的位置设置为(-5, 0, 0),另一个球体的Transform组件的位置设置为(5, 0, 0),并将它们分别命名为“Start”和“End”。 4. 在场景添加一个平面作为地图,将其缩放为(10, 1, 10),并将其位置设置为(0, -0.5, 0)。 5. 在场景添加一个球体作为障碍物,将其缩放为(2, 2, 2),并将其位置设置为(0, 1, -2)。 6. 在“A*”游戏对象的脚本,将“startNode”变量设置为“Start”游戏对象的Transform组件,将“endNode”变量设置为“End”游戏对象的Transform组件,将“nodePrefab”变量设置为一个球体预制体,将“unwalkableMask”变量设置为“Obstacle”层,将“nodeRadius”变量设置为0.5,将“gridSize”变量设置为1,将“gridWorldSize”变量设置为(10, 10)。 7. 运行游戏,可以看到在“Start”和“End”之间生成了一条路径,路径上的节点用球体表示。可以尝试改变地图和障碍物的位置和大小,并在脚本调整相关变量,观察寻路结果的变化。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值