【GAD翻译馆】理解转向行为:路径跟踪

发表于2017-08-17
评论0 3.2k浏览

翻译:赵菁菁(轩语轩缘) 审校:李笑达(DDBC4747


路径跟踪是游戏开发中经常出现问题,本教程涵盖了路径跟踪转向行为,这样游戏角色就可以沿着由点和线组成的预定道路行走了。

注意:虽然这个教程是用AS3Flash写的,但是你应该可以在几乎所有的游戏开发环境中使用相同的技术和概念。你必须对数学向量有基本的了解。


简介

路径跟踪行为可以通过多种方式实现。原始的雷诺兹实现方法用的是一条由线条组成的路径,角色严格遵循这些路径行动,几乎就像铁轨上的火车一样。

根据具体情况,我们可能不需要这么精确。一个角色可以沿着一系列路径跟踪线移动,但是这些线只是作为参考,而不是作为“轨道”。

本教程中的路径跟踪行为的实现方法简化了雷诺兹提出的原始方法,我们的仍然可以产生良好的结果,但它不依赖于繁重的数学计算,如向量预测。

定义一条路径

路径可以定义为一组由线连接的点(节点)。即使曲线也可以用来描述路径,但点和线更容易处理,产生的结果几乎相同。

如果需要使用曲线,可以将其简化为一组连接点:

曲线和直线

Path类可以用来描述路径。基本上,类有一个点向量和一些管理列表的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Path
{
    private var nodes :Vector.<vector3d>;
  
    public function Path() {
        this.nodes = new Vector.<vector3d>();
    }
  
    public function addNode(node :Vector3D) :void {
        nodes.push(node);
    }
  
    public function getNodes() :Vector.<vector3d> {
        return nodes;
    }
}

路径中的每个点都是Vector3D,代表空间中的一个位置,同样,角色的position属性也有效。

从节点到节点移动

为了导航路径,角色将从一个节点移动到另一个节点,直到路线结束。

路径中的每个点都可以看作是一个目标,因此可以使用查找行为:

查找一个又一个点

角色将寻找当前点,直到到达为止,然后路径中的下一个点变成当前的点,以此类推。正如之前在避免碰撞教程中描述的,每次游戏更新,每个行为的力量都要重新计算,所以从一个节点过渡到另一个节点是无缝的、顺利的。

角色类将需要两个附加属性来测量导航过程:当前节点(角色正在寻找的节点)和对正在跟踪的路径的引用。该类如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public class Boid
{
    public var path :Path;
    public var currentNode  :int;
    (...)
  
    private function pathFollowing() :Vector3D {
        var target :Vector3D = null;
  
        if (path != null) {
            var nodes :Vector.<vector3d> = path.getNodes();
  
            target = nodes[currentNode];
  
            if (distance(position, target) <= 10) {
                currentNode = 1;
  
                if (currentNode >= nodes.length) {
                    currentNode = nodes.length - 1;
                }
            }
        }
  
        return null;
    }
  
    private function distance(a :Object, b :Object) :Number {
        return Math.sqrt((a.x - b.x) * (a.x - b.x) (a.y - b.y) * (a.y - b.y));
    }
    (...)
}

pathFollowing()方法负责生成路径跟踪力。目前,它没有产生任何力量,但它会选择适当的目标。

path != null测试检查角色是否正沿任何路径移动。如果是这样的话,currentNode 属性是用于在点列表中查找当前的目标(角色必须查找的目标)。

如果当前目标与角色位置之间的距离小于10,则意味着该字符已到达当前节点。如果发生这种情况,currentNode递增1,意味着将寻找路径中的下一个点。重复这个过程,直到路径中没有剩余点了。

计算和添加力

用于将角色推向路径中每个节点的力是搜索力。pathFollowing()方法已经选择了合适的节点,所以现在需要返回将角色推向那个节点的力:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
private function pathFollowing() :Vector3D {
  var target :Vector3D = null;
  
  if (path != null) {
    var nodes :Vector.<vector3d> = path.getNodes();
  
    target = nodes[currentNode];
  
    if (distance(position, target) <= 10) {
        currentNode = 1;
  
        if (currentNode >= nodes.length) {
            currentNode = nodes.length - 1;
        }
    }
  }
  
  return target != null ? seek(target) : new Vector3D();
}

在计算路径跟踪力之后,必须像往常一样将其添加到角色的速度向量中:

1
2
3
4
5
6
7
8
steering = nothing(); // the null vector, meaning "zero force magnitude"
steering = steering pathFollowing();
  
steering = truncate (steering, max_force)
steering = steering / mass
  
velocity = truncate (velocity steering, max_speed)
position = position velocity

遵循转向力的路径与追踪行为极为相似,角色不断调整方向以捕捉目标。区别在于角色如何寻找一个不可移动的目标,一旦角色离目标太近,就会被另一个目标所忽略。

结果如下:


行动中的路径跟踪。点击可以显示力量。

平滑移动

当前的实现方法需要所有角色“触碰”当前路径中的当前点,以便选择下一个目标。因此,一个角色可能要执行不理想的运动模式,例如转圈围绕一个点移动,直到到达这个点为止。

本质上,每一个动作都趋向于遵循省力原则。例如,一个人不会一直走在走廊的中间;如果有转弯,他会在转弯的时候走得离墙很近,以便缩短距离。

可以通过在路径中添加半径来重新创建该模式。半径应用于点,它可以被视为路线“宽度”。半径将控制一个角色从一个点开始可以沿着道路移动多远:

半径对于路径跟踪的影响

如果角色和点之间的距离小于或等于半径,则认为达到该点。因此,所有角色都将使用线和点作为向导进行移动:


带有半径的路径跟踪。点击“力”按钮显示力。单击“ ”和“-”按钮动态调整半径大小。

半径越大,路线越宽,在转弯时,角色与点的距离越大。可以调整半径的值以产生不同的跟踪模式。

往返运动

有时角色在到达路径的结尾后继续移动是很有用的。例如,在巡逻模式中,角色在到达终点后,应该沿着相同的点返回路线起始位置。

这可以通过向角色类添加pathDir 属性实现;这是一个整数,可以控制角色沿着路径移动的方向。如果pathDir 1,这意味着角色走向路径结尾;-1是指朝起始位置移动。

pathFollowing()方法可以改为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
private function pathFollowing() :Vector3D {
    var target :Vector3D = null;
  
    if (path != null) {
        var nodes :Vector.<vector3d> = path.getNodes();
  
        target = nodes[currentNode];
  
        if (distance(position, target) <= path.radius) {
            currentNode = pathDir;
  
            if (currentNode >= nodes.length || currentNode < 0) {
                pathDir *= -1;
                currentNode = pathDir;
            }
        }
    }

与老版本不同,现在pathDir的值添加到属性currentNode (而不只是增加1)中。这允许角色根据当前方向选择路径中的下一个点。

之后,测试检查角色是否已经到达路线的结尾,如果是这样的话,pathDir乘以-1,这会取它本身的相反数,也会使角色的运动方向反转。

这是往返运动模式的结果:


 

具有半径和往返模式的路径跟踪。点击“力量”按钮显示力量。单击“ ”和“-”按钮动态调整半径大小。

结论

路径跟踪行为允许任何角色沿着预定义的路径移动。该路线是由点指导的,它可以被调整得更广泛或更狭窄,产生更自然的运动模式。

本教程中的实现方法是对雷诺兹提出的原始路径跟踪行为的简化,但这种方法仍然可以产生令人信服和吸引人的结果。


【版权声明】

原文作者未做权利声明,视为共享知识产权进入公共领域,自动获得授权;

如社区发表内容存在侵权行为,您可以点击这里查看侵权投诉指引