2.5 操控行为——避开障碍
obstacle avoidance行为操控交通工具避开路上的障碍。障碍物是任何一个近似圆周(在3D中,将是球体)的物体。我们保持长方形区域(检测盒,从交通工具延伸出的)不被碰撞,就可以躲避障碍了。检测盒的宽度等于交通工具的包围半径,它的长度正比于交通工具的当前速度(它移动得越快,检测盒就越长)。
深入讲解处理过程之前,先看一下图,图中有一个交通工具、多个阻挡物和计算中用到的检测盒。
寻求最近的相交点
检查与障碍物的相交点的过程相当复杂,所以下面逐步讲解。
A.交通工具只考虑那些在检测盒内的障碍物。最初,避开障碍算法迭代游戏世界中所有的障碍物,标记那些在检测盒内的障碍物以作进一步分析。
B.算法然后把所有已标记的障碍物转换到交通工具的局部空间。转换坐标后,那些x坐标为负值的物体将不被考虑,所以问题就变得简单多了。
C.接下来,该算法必须测试障碍物是否和检测盒重叠。使障碍物的包围半径扩大检测盒(交通工具的包围半径)宽度的一半。然后测试该障碍物的y值是否小于这个值(即障碍物的包围半径,加上检测盒宽度的一半)。如果不小于,那么该障碍将不会与检测盒相交,所以不予继续讨论。
下图中列出了前面3步。图中障碍物上的字母A、B、C和前面的描述相对应。
D.此时,只剩下那些会与检测盒相交的障碍物了。接下来,我们找出离交通工具最近的相交点。我们将再一次在局部空间中计算,C步骤扩大了障碍物的包围半径。我们用简单的线-圆周相交测试方法可以得到被扩大的圆和x轴的相交点。如图所示,有两个相交点(我们不用担心和交通工具相切的情况,交通工具将擦过障碍物)。注意如下情况,在交通工具前面可能有个障碍物,但和交通工具相交在交通工具的后部,如图中的障碍物A,和交通工具的后部有个交点。该算法将不处理这种情况,只考虑存x轴上的交点。
此算法测试所有剩下的障碍物,从中找出一个有最近的(正的)相交点的障碍物。在讲解如何计算操控力之前,先列出实现躲避障碍算法中从步骤A到步骤B的相关代码。
//---------------------- ObstacleAvoidance ------------------------------- // // 给定一个障碍物集合,这个方法返回一个操控力, // 这个力让智能体不与最近的障碍物发生碰撞。 //------------------------------------------------------------------------ public Vector2 ObstacleAvoidance(List<BaseGameEntity> obstacles) { // 检测盒长度正比于智能体的速度 float _dBoxLength = Global.MinDetectionBoxLength + (_vehicle.Speed / _vehicle.MaxSpeed) * Global.MinDetectionBoxLength; // 标记在检测盒半径之内的障碍物 _vehicle.World.TagObstaclesWithinViewRange(_vehicle, _dBoxLength); // 保存最近的相交障碍物(CIB) BaseGameEntity ClosestIntersectingObstacle = null; // 到最近相交障碍物的距离 float DistToCIB = float.MaxValue ; // 保存最近相交障碍物在局部空间中的坐标 Vector2 LocalPosOfClosestObstacle=Vector2 .Zero ; foreach(BaseGameEntity curOb in obstacles ) { // 如果该障碍物被标记在范围内 if (curOb.IsTagged) { // 计算这个障碍物在智能体的局部空间中的位置 Vector2 LocalPos = Vector2.Transform(curOb.Pos, Matrix.Invert(Matrix.CreateRotationZ((float)(Math.Atan2(_vehicle.Heading.Y, _vehicle.Heading.X))) * Matrix.CreateTranslation(new Vector3(_vehicle.Pos, 0)))); // 如果局部空间中的X为负,那么障碍物在智能体后方,可以忽略。 // 所以我们只考虑X不为负的情况。 if (LocalPos.X >= 0) { // 如果障碍物到x轴的距离小于它的半径+检测盒宽度的一半, // 则有可能相交。 float ExpandedRadius = curOb.BRadius + _vehicle.BRadius; if (Math.Abs(LocalPos.Y) < ExpandedRadius) { // 进行线/圆周相交测试。圆周中心坐标用(cX, cY)表示, // 相交点在局部空间x轴上的坐标值由公式x = cX +/-sqrt(r^2-cY^2)给出,此时y=0。 // 我们只需要x的最小正值,它就是最近的相交点。 float cX = LocalPos.X; float cY = LocalPos.Y; float SqrtPart = (float)Math.Sqrt(ExpandedRadius*ExpandedRadius - cY*cY); float ip = cX - SqrtPart; if (ip <= 0.0) { ip = cX + SqrtPart; } // 测试是不是最近点,如果是则保存这个障碍物和的局部坐标 //record of the obstacle and its local coordinates if (ip < DistToCIB) { DistToCIB = ip; ClosestIntersectingObstacle = curOb; LocalPosOfClosestObstacle = LocalPos; } } } } }
计算操控力
计算操控力是容易的,通常分成两部分:侧向操控力和制动操控力,如图所示。
我们有许多方法可以计算侧向操控力,此处建议如下这种:障碍物包围半径减去其在局部空间中的y值。这会产生一个侧向操控力使其远离障碍物,它会随着障碍物到x轴的距离而减少。该力正比于交通工具到障碍物的距离(因为交通工具离障碍物越近,反应越快)。
操控力的另一部分是制动力。该力沿着图中的负x轴方向,其大小正比于交通工具到障碍物的距离。最后,把操控力转换到世界空间,就是该方法的返回的结果。代码如下:
// 如果我们找到了一个相交障碍物,则计算一个远离它的力 Vector2 SteeringForce=Vector2.Zero ; if (ClosestIntersectingObstacle!=null) { // 智能体离物体越近,操控力就应越强 float multiplier = 1.5f + (_dBoxLength - LocalPosOfClosestObstacle.X) / _dBoxLength; // 计算侧向力 if( LocalPosOfClosestObstacle.Y>0) SteeringForce.Y = (-ClosestIntersectingObstacle.BRadius - LocalPosOfClosestObstacle.Y) * multiplier; else SteeringForce.Y = (ClosestIntersectingObstacle.BRadius- LocalPosOfClosestObstacle.Y) * multiplier; // 施加一个制动力,它正比于智能体到障碍物的距离 const float BrakingWeight = 0.6f; SteeringForce.X = (ClosestIntersectingObstacle.BRadius - LocalPosOfClosestObstacle.X) * BrakingWeight; } // 最后,将操控力向量从局部坐标转换到世界坐标 return Vector2.Transform(SteeringForce, Matrix.CreateRotationZ((float)(Math.Atan2(_vehicle.Heading.Y, _vehicle.Heading.X)))); }
辅助方法
在上述ObstacleAvoidance方法中还用到了些辅助方法,这些辅助方法都位于GameWorld类中,代码如下:
// 创建一些随机大小的障碍物 private void CreateObstacles() { for (int i=0; i <Global.NumObstacles; i++) { bool bOverlapped = true; // 不停尝试直至找到一个不与其他障碍物重叠的障碍物。 int NumTrys = 0; int NumAllowableTrys = 2000; while (bOverlapped) { NumTrys++; if (NumTrys > NumAllowableTrys) return; // 随机产生一个介于MinObstacleRadius和MaxObstacleRadius之间的半径 int radius = Global.random.Next((int)Global.MinObstacleRadius, (int)Global.MaxObstacleRadius); // 障碍物至少与边界距离10,障碍物之间至少距离20 int border = 10; int MinGapBetweenObstacles = 20; Obstacle ob = new Obstacle(Global.random.Next(radius + border, _xClient - radius - border), Global.random.Next(radius + border, _yClient - radius - 40 - border), radius); if (!Overlapped(ob, _obstacles, MinGapBetweenObstacles)) { // 如果不重叠则添加到集合中 _obstacles.Add(ob); bOverlapped = false; } } } } //------------------------- Overlapped ----------------------------------- // // 测试一个实体是否与另一个实体集合中的某个相交 //------------------------------------------------------------------------ private bool Overlapped(BaseGameEntity ob, List<BaseGameEntity> conOb, float MinDistBetweenObstacles) { foreach (BaseGameEntity it in conOb) { if (TwoCirclesOverlapped(ob.Pos, ob.BRadius + MinDistBetweenObstacles, it.Pos, it.BRadius)) { return true; } } return false; } //----------------------------- TwoCirclesOverlapped --------------------- // // 如果两个圆相交则返回true //------------------------------------------------------------------------ private bool TwoCirclesOverlapped(Vector2 c1, float r1, Vector2 c2, float r2) { float DistBetweenCenters = Vector2.Distance(c1, c2); if ((DistBetweenCenters < (r1 + r2)) || (DistBetweenCenters < Math.Abs(r1 - r2))) { return true; } return false; } // 给在range半径范围内的障碍物做标记 public void TagObstaclesWithinViewRange(BaseGameEntity vehicle, float range) { TagNeighbors(vehicle, _obstacles, range); } //----------------------- TagNeighbors ---------------------------------- // // 在实体集合ContainerOfEntities中,若某个实体位于以指定实体entity为圆心, // radius为半径的范围内,则对它打上标记 //------------------------------------------------------------------------ private void TagNeighbors(BaseGameEntity entity, List<BaseGameEntity> ContainerOfEntities, float radius) { foreach (BaseGameEntity curEntity in ContainerOfEntities) { // 首先取消标记 curEntity.UnTag(); // 两个实体间的距离 Vector2 to = curEntity.Pos - entity.Pos; // 还需考虑包围球半径 float range = radius + curEntity.BRadius; // 若在范围内,则打上标记 if ((curEntity != entity) && (to.LengthSquared() < range * range)) { curEntity.Tag(); } } }
文件下载(已下载 581 次)
发布时间:2013/8/23 下午9:25:45 阅读次数:4535