理解引导行为:路径跟踪
路径跟踪是游戏开发中一个经常出现的问题。此教程涵盖引导行为的路径跟踪,这里的路径是提前根据点和线预设好的。
注意:虽然此教程是用AS3和Flash实现的,你也可以在其他语言开发环境下使用这些类似的技巧和概念。当然前提是需要有基本的数学向量的知识。
介绍
路径跟踪可以用多种方式实现。Reynolds原型就是使用线来实现的,这里需要严格的按照线来实现。
根据不同情况,准确度要求不同。角色可以根据线路运动,但是只是做一个参照而不是严格按照轨道来运行。
此教程的路径跟踪是Reynolds原型的简化实现。运行效果不错,但是严重依靠数学的向量投影计算。
设定路径
路径可以是线连接的点的集合。即使曲线也可以用来描述路径,点和线处理处理得到相同的结果要简单的多。
1 public class Path
2 {
3 private var nodes :Vector.<Vector3D>;
4
5 public function Path() {
6 this.nodes = new Vector.<Vector3D>();
7 }
8
9 public function addNode(node :Vector3D) :void {
10 nodes.push(node);
11 }
12
13 public function getNodes() :Vector.<Vector3D> {
14 return nodes;
15 }
16 }
如果需要使用曲线,则可以将其简化为一些连接的点:
曲线和直线
Path类将用来描述路径。一般来说此类包含一个点向量和一些处理列表的方法:
每个路径中的点都是一个三维空间中点的表示方式,角色的位置也是如此。
逐个节点移动
为了根据路径导航,角色从路线起始逐个节点移动直到到达路线末点。
路径中的每个点都可以看做为一个对象,因此行为查看如下所示:
挨个点查看
角色会不断查看当前点,直到到达为止,然后路径中下一个点作为当前点。正如前面的碰撞避免教程中所述,每个行为的力都是在游戏更新时重新计算,因此从一个节点到另一个节点是圆滑的。
Character类需要两个额外的属性来引导导航过程:当前节点和路径跟踪的引用。
类如下所示:
1 public class Boid
2 {
3 public var path :Path;
4 public var currentNode :int;
5 (...)
6
7 private function pathFollowing() :Vector3D {
8 var target :Vector3D = null;
9
10 if (path != null) {
11 var nodes :Vector.<Vector3D> = path.getNodes();
12
13 target = nodes[currentNode];
14
15 if (distance(position, target) <= 10) {
16 currentNode += 1;
17
18 if (currentNode >= nodes.length) {
19 currentNode = nodes.length - 1;
20 }
21 }
22 }
23
24 return null;
25 }
26
27 private function distance(a :Object, b :Object) :Number {
28 return Math.sqrt((a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y));
29 }
30 (...)
31 }
pathFollowing()函数用来产生路径跟踪方向,当前是没有的,但是它还是会选择合适的目标。Path!=null测试角色是否跟踪任何路径。如果是的话,就用当前节点作为当前目标。
如果当前点和角色位置之间的目标数少于10,意味着角色已经到达当前节点,如果上述成立则将currentNode增加一,表示角色将在路径中寻找下一个点。整个过程循环运行,知道路径里没有点为止。
计算和添加方向
促使角色在路径中不断寻找节点的是寻找力。pathFollwing()函数以及选择了合适的节点,现在需要返回促使角色前进的的节点:
路径追踪后会计算方向,然后加到角色速度向量里:
1 private function pathFollowing() :Vector3D {
2 var target :Vector3D = null;
3
4 if (path != null) {
5 var nodes :Vector.<Vector3D> = path.getNodes();
6
7 target = nodes[currentNode];
8
9 if (distance(position, target) <= 10) {
10 currentNode += 1;
11
12 if (currentNode >= nodes.length) {
13 currentNode = nodes.length - 1;
14 }
15 }
16 }
17
18 return target != null ? seek(target) : new Vector3D();
19 }
路径引导方向跟追寻行为类似,角色不断根据目标调整方向。不同之处在于角色是在寻找一个不动的目标,这样就可以在角色逐渐靠近的时候忽略另一个。
结果如下:
1 steering = nothing(); // the null vector, meaning "zero force magnitude"
2 steering = steering + pathFollowing();
3
4 steering = truncate (steering, max_force)
5 steering = steering / mass
6
7 velocity = truncate (velocity + steering, max_speed)
8 position = position + velocity
实际中的路径跟踪。点击来查看方向。
柔化轨迹
当前的实现需要所有角色接触路径中的点来选择下一个目标。因此角色可能执非预期的动作模式,例如围绕一个点圆周运动直到到达。
实际中,每个运动都遵循最优原则。例如一个人不可能总是在走廊中间行走。如果是一个拐弯,那么人会不断靠近墙,然后拐弯来缩短距离。
上述的模式可以通过在路径中添加半径来不断重复。将半径应用到点,就可以看到路径的宽。这样就可以控制角色不断沿着路径走。
半径对路径跟踪的影响
如果角色和点之间的距离小于等于半径,那表示已经到达点。这样就可以根据线和点来做引导。
利用半径进行路径跟踪。点击Force按钮来查看方向,点击+跟-按钮来动态调整半径尺寸。
半径越大,路径越宽,角色拐弯的距离也就越大。半径的值可以用来产生不同的追踪模式。
前进和后退
有时候需要让角色不断移动,直到到达路径末尾。例如在巡逻模式中,角色需要在到达末尾后返回起点,追踪相同的点。
我们可以在character类里添加pathDir属性。此属性是一个整型值,用来控制角色沿着路径的方向。如果pathDir是1,则表示向路径末尾运动,-1表示向起始点运动。
pathFollowing()函数做如下改动:
1 private function pathFollowing() :Vector3D {
2 var target :Vector3D = null;
3
4 if (path != null) {
5 var nodes :Vector.<Vector3D> = path.getNodes();
6
7 target = nodes[currentNode];
8
9 if (distance(position, target) <= path.radius) {
10 currentNode += pathDir;
11
12 if (currentNode >= nodes.length || currentNode < 0) {
13 pathDir *= -1;
14 currentNode += pathDir;
15 }
16 }
17 }
18
19 return target != null ? seek(target) : new Vector3D();
20 }
跟之前版本不同,此处的pathDir添加到currentNode属性里。这样就允许角色选择基于当前位置的路径的下一个点了。
然后通过一个测试来检测角色是否到达路径末尾。如果到达,就将pathDir乘以-1,也就是取相反值,来让角色反方向行走。
结果是一个不断来回运动的模式:
上述是利用半径的来回路径追踪模式。点击Force按钮来显示方向,点击+或者-来动态调整半径尺寸。
总结
路径追踪行为运行任何角色按照预先设定好的路径运动。路径是根据点来指引,可以调宽窄,产生不同运动模式来更贴近现实。
此教程中涵盖的实现时给予Reynolds提出的路径追踪行为原型的,但是还是获得良好的效果。
原文链接:理解引导行为:路径跟踪