Understanding Steering Behavior: Collision Avoidace(行为控制之:碰撞躲避)

原文链接:http://gamedevelopment.tutsplus.com/tutorials/understanding-steering-behaviors-collision-avoidance--gamedev-7777

介绍

进行碰撞躲避的方法实际上非常的简单,就是在我们检测到“将要”和一个碰撞物相撞的时候,我们产生一个躲避的力来避免进行碰撞。记住,我们仅仅使用一个最近的障碍物来计算躲避的力,就算有很多的障碍物,这样的算法同样能够很好的运行。

所以可以看出,这个算法就需要解决两个问题:如何确定最近的物体,以及如何进行躲避力的产生。

读者需要注意,我们这里阐述的碰撞躲避算法并不是使用一种寻路算法来避免碰撞到障碍物。这种算法对于比较复杂的环境可能不是很适用,但是对于相对比较简单的环境来说,这种算法效果更加的出色。

朝向向量

进行碰撞躲避的首要问题就是预先处理碰撞。我们唯一需要躲避的障碍物就是距离我们的人物最近的障碍物。我们首先产生一个称为ahead的向量,这个向量实际上和我们的速度向量方向一致,所以我称之为朝向向量,但是他们的长度并不是相同的:

我们使用如下的方法来计算这个ahead向量的位置:

ahead = position + normlize(velocity) * MAX_SEE_AHEAD ;

这个ahead向量的长度定义了它在距离障碍物多远的时候就能够进行碰撞躲避。MAX_SEE_HEAD越大,那么角色反应的时间就越早,就越有可能躲避成功。但是同样的,如果太大了,那么障碍物在距离角色很远的时候就进行躲避了,所以我们需要定义合适的值:

碰撞检测

为了能够进行碰撞检测,我们这里将所有的障碍物都假设为圆形的。使用圆形的障碍物,效果非常的好,也容易实现。

为了进行碰撞检测,我们需要对使用直线-圆相交的碰撞检测,但是在这里我会使用一个更加简单有效的方法。

我们使用ahead向量再次的创建一个向量ahead2,这个向量和ahead向量方向一致,只是长度只有它的一半:

我们使用下面的方法来计算这个ahead2向量:

ahead = position + normlaize(velocity) * MAX_SEE_AHEAD ;

ahead2 = position + normlaize(velocity) * MAX_SEE_AHEAD * 0.5 ;

进行碰撞检测,我们只需要判断这两个点是否在障碍物所构成的圆形中就可以。

如果他们之间的距离小于或者等于圆形的半径,那么我们就认为这个碰撞发生了:

当d < r的时候,我们就认为预先碰撞发生了。

如果这两个点都不在原型中,那么就认为没有发生碰撞,下面是进行碰撞的代码:

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));
}

private function lineIntersectsCircle(ahead :Vector3D, ahead2 :Vector3D, obstacle :Circle) :Boolean {
    // the property "center" of the obstacle is a Vector3D.
    return distance(obstacle.center, ahead) <= obstacle.radius || distance(obstacle.center, ahead2) <= obstacle.radius;
}

如果有多个物体挡住了路,那么我们只对最近的一个障碍物进行处理:

计算躲避力

躲避力必须要将角色推开,这样才能够使角色躲避掉障碍物。我们可以用如下的代码来计算躲避力:

avoidance_force = ahead - obstacle_center

avoidance_force = normalize(avoidance_force) * MAX_AVOID_FORCE

当我们有了躲避力之后,我们就用MAX_AVOID_FORCE来对躲避力进行缩放,这样就能够得到某个方向上特定大小的躲避力了。

躲避障碍物

下面是整个collisionAvoidance的实现,这个函数用来产生一个躲避力:

private function collisionAvoidance() :Vector3D {
    ahead = ...; // calculate the ahead vector
    ahead2 = ...; // calculate the ahead2 vector

    var mostThreatening :Obstacle = findMostThreateningObstacle();
    var avoidance :Vector3D = new Vector3D(0, 0, 0);

    if (mostThreatening != null) {
        avoidance.x = ahead.x - mostThreatening.center.x;
        avoidance.y = ahead.y - mostThreatening.center.y;

        avoidance.normalize();
        avoidance.scaleBy(MAX_AVOID_FORCE);
    } else {
        avoidance.scaleBy(0); // nullify the avoidance force
    }

    return avoidance;
}

private function findMostThreateningObstacle() :Obstacle {
    var mostThreatening :Obstacle = null;

    for (var i:int = 0; i < Game.instance.obstacles.length; i++) {
        var obstacle :Obstacle = Game.instance.obstacles[i];
        var collision :Boolean = lineIntersecsCircle(ahead, ahead2, obstacle);

        // "position" is the character's current position
        if (collision && (mostThreatening == null || distance(position, obstacle) < distance(position, mostThreatening))) {
            mostThreatening = obstacle;
        }
    }
    return mostThreatening;
}

躲避力必须对物体的速度产生影响,如果是基于力的系统,我们就简单的将力积蓄就可以。

steering = nothing(); // the null vector, meaning "zero force magnitude"
steering = steering + seek(); // assuming the character is seeking something
steering = steering + collisionAvoidance();

steering = truncate (steering, max_force)
steering = steering / mass

velocity = truncate (velocity + steering, max_speed)
position = position + velocity

由于每一次更新,我们都重新的计算躲避力,所以只要还在躲避的范围,就会一直受到这个躲避力的作用,从而躲避开来。

如果ahead向量没有与障碍物发生碰撞,那么就不需要进行躲避了。

Oops,博主自己的话语

上面的东西,只是简单对原文进行了翻译。为了很好的理解这个概念,我自己做了一个Demo。读者需要注意,上面的方法是基于力的,如果你的项目并不是基于力(和博主自己的Demo一样),而只是简单的使用速度的话,同样也可以实现。下面我用cocos2d-x来实现一个Demo。

首先,我创建四个障碍物,如下所示:

//Create 4 obstacles
for(int i = 0 ; i < 4 ; i ++)
{
  float x = 100 ;
  float y = 100 ;
  if(i >= 2)
	x = 380 ;
  if(i == 1 || i == 3)
        y = 220 ;
  m_Obstacals[i] = CCSprite::create("Obstacle.png");
  m_Obstacals[i]->retain();
  m_Obstacals[i]->setPosition(ccp( x, y ));
  this->addChild(m_Obstacals[i]);
}

然后创建一个角色:

//Create the character
 m_Character = CCSprite::create("Character.png");
 m_Character->retain();
 m_Character->setPosition(ccp(0,0));
 this->addChild(m_Character);
 m_velocity = ccp(2,4);

这个很简单,只是在窗口中贴上几张图而已。

下面是场景的Update方法,主要的任务都是在这里进行的:

void HelloWorld::update(float dt)
{
	CCPoint pos = m_Character->getPosition();
	pos = ccpAdd(pos, m_velocity);
	m_Character->setPosition(pos);

	if(pos.x < 0 || pos.x > 480)
		m_velocity.x =- m_velocity.x ;
	if(pos.y < 0 || pos.y > 320)
		m_velocity.y = - m_velocity.y ;

	//Collision avoidance
	//------------------------------------------------
	//Step 1: Find the most closest obstacal
	CCPoint c_pos = m_Character->getPosition();
	float length = FLT_MAX ;
	int  index = -1 ;
	for(int i = 0 ; i < 4 ; i ++)
	{
		CCPoint temp_pos = m_Obstacals[i]->getPosition();
		temp_pos = ccpSub(temp_pos, c_pos);
		float temp_length = ccpLength(temp_pos);
		if(temp_length < length)
		{
			length = temp_length ;
			index = i ;
		}
	}// end for

	//Step2: Check for collision
	CCPoint ob_pos = m_Obstacals[index]->getPosition();
	float ob_radious = m_Obstacals[index]->getContentSize().width/2 ;

	float dynamic_velocity = MAX_SEE_VALUE ;
	CCPoint ahead = ccpAdd(c_pos ,cocos2d::ccpMult(ccpNormalize(m_velocity), dynamic_velocity));
	CCPoint ahead2 = ccpAdd(c_pos ,cocos2d::ccpMult(ccpNormalize(m_velocity), dynamic_velocity * 0.5));

	if(ccpDistance(ahead, ob_pos) > ob_radious &&
		ccpDistance(ahead2, ob_pos) > ob_radious &&
		ccpDistance(c_pos, ob_pos) > ob_radious)
		return ;

	//Step3 : Do avoidance
	CCPoint nor_velocity = ccpNormalize(m_velocity);
	CCPoint toCenter = ccpSub(ob_pos, c_pos);
	toCenter = ccpNormalize(toCenter);

	CCPoint avoidance_velocity = ccp(nor_velocity.y, -nor_velocity.x);
	if(ccpDot(toCenter, avoidance_velocity) > 0)
	{
		avoidance_velocity = ccp(-nor_velocity.y, nor_velocity.x);
	}// end if

	m_velocity = ccpAdd(m_velocity, ccpMult(avoidance_velocity, MAX_AVOIDANCE_SPEED));
	if(ccpLength(m_velocity) > MAX_SPEED)
	{
		m_velocity = ccpMult(ccpNormalize(m_velocity), MAX_SPEED);
	}
}

这个函数很简单,首先我们使用速度来对角色的位置进行更新。

然后进行边界检查,判断是否出了屏幕区域,如果是,就将速度取反。

下面的就是进行碰撞躲避的算法了。

第一步,找到最近的障碍物,这个很简单,只要遍历所有的障碍物列表,然后计算最近的障碍物即可。

第二步,判断是否进行了碰撞。这个就和原文中使用的算法一致,加上判断角色是否已经在障碍物里面的条件。

第三部,进行躲避,由于不是基于力的系统,所以我就简单的计算一个躲避速度,然后把这个速度加到物体原有的速度上即可。但是注意,我这里计算躲避速度的方法与原文不一样。原文是使用ahead - center的方式来计算一个躲避力。这种方法,在物体不是直接朝着障碍物移动的时候,有效。但是,当角色直接的相障碍物移动的时候,你会发现这样计算出来的力的方向与物体移动的方向刚好相反,也就是说,会将物体向后推走,这样的方法,在屏幕显示上感觉会很怪,不是很平滑。所以我使用垂直于角色速度方向的方向作为躲避力的方向,这样不管是不是直接朝着障碍物中心移动,我们都能够平滑的躲避。

这里需要注意,垂直于速度方向上的向量有两个,他们互相相反,如何选取哪一个向量了?我们来看下面的图:

我们从上图中可以看出,有normal1和normal2两个向量,很明显,我们要旋转normal1,但是为什么了?因为它更容易使物体进行躲避。也就是说,我们只要能够判断“更容易是物体进行躲避”的条件就可以了。

这个条件就是将normal向量与toCenter向量进行点积操作,如果值小于0那么就是我们想要的那个向量,如果大于0就不是我们想要的。如果是0的话,那么随便哪一个都可以,因为这时物体是直接向障碍物中心移动的。

好了,就到这里了。下面是这个Demo的截图:

完整源代码请到这里下载:

Source.zip

Understanding Steering Behavior: Collision Avoidace(行为控制之:碰撞躲避)

时间: 2024-10-07 18:07:39

Understanding Steering Behavior: Collision Avoidace(行为控制之:碰撞躲避)的相关文章

Understanding Steering Behaviors: Seek (行为控制-寻找)

原文地址链接:http://gamedevelopment.tutsplus.com/tutorials/understanding-steering-behaviors-seek--gamedev-849 位置,速度和移动 在行为控制中的所有的算法实现都是通过数学上的向量计算来实现的.由于这个控制会改变人物的速度和位置,所以同样的我们也可以使用向量来表示这些属性. 虽然向量拥有一个方向,但是当用向量来表示一个位置的时候,我们往往可以忽略它的方向: 上面的图中P向量表示的是一个点(x,y),V向

Understanding Steering Behaviors:Flee and Arrival(行为控制-逃离和抵达)

原文链接:http://gamedevelopment.tutsplus.com/tutorials/understanding-steering-behaviors-flee-and-arrival--gamedev-1303 跑开 在前面介绍的寻找行为中,我们介绍了一种基于两个向量力来将人物推向目标的行为方式:预期速度和控制力 desired_velocity = normalize(target - position) * max_velocity ; steering = desired

unity3d中的trigger和collision消息以及刚体与碰撞体

一直困惑于unity3d中的触发和碰撞消息在什么条件下能够发生,平时用时也是一知半解.磨刀不误砍柴工,是时候发点时间一劳永逸的解决这个问题了XD. OnTriggerEnter, OnTriggerStay, OnTriggerExit 是为触发类消息,记为trigger OnCollisionEnter, OnCollisionStay, OnCollisionExit是为碰撞类消息, 记为collision None表示两类消息都没发生 如果对象有刚体(rigidbody)且其 IsKine

steering behaviors 转向行为 集群模拟 小结

继今年(虽然离2015年只有一天了但还是2014)暑假简单接触了一些flocking 集群的概念后一直没有完完整整的把这个算法里面的一些行为规则做一些归类和实现.最近一直还在深入的学习Houdini里面的VEX语言,这里简单讲一讲我的一些学习过程,并附上在Houdini里面实现的方法和源码. 集群模拟中每个个体的行为规则(前 1 - 8 是已经实现了的,后面的在游戏领域拥有应用,这里只提一下). seek 寻找 flee 逃跑 wander 随意行走 separation 分离 cohesion

Understanding the Bias-Variance Tradeoff

Understanding the Bias-Variance Tradeoff When we discuss prediction models, prediction errors can be decomposed into two main subcomponents we care about: error due to "bias" and error due to "variance". There is a tradeoff between a m

碰撞描述类 collision

碰撞描述类 collision Collision 信息被传递到Collider . OnCollisionEnter , Collider . OnCollisionStay和Collider.OnCollisionExit事件.参见: ContactPoint.     变量 ◆var collider : Collider    //  描述:碰撞到的Collider ( 只读 ).为了得到所有被碰撞到的碰撞器的细节,你需要迭代接触点( contacts属性). ◆var contacts

UIView---汇总

视图.绘图.贴图.手势.变形.布局.动画.动力.特效 UIBezierPath.UIGestureRecognizer.CGAffineTransform.frame.bounds.center.transform.UITouch.UIEvent.Layout.Autoresizing.Auto Layout.Animation.UIImage.NSTimer.UIView.Core Animation.CALayer.CAAnimation.CABasicAnimation.CAKeyfram

再译《A *路径搜索入门》之五

■实施上的注意事项 Notes on Implementation 现在您了解了基本的方法,当你编写自己的程序时,有一些额外的事情要考虑.下面给出我用C ++和Blitz Basic编写的程序,用其他语言也同样有效. Now that you understand the basic method, here are some additional things to think about when you are writing your own program. Some of the f

C# 内存模型

C# 内存模型 This is the first of a two-part series that will tell the long story of the C# memory model. The first part explains the guarantees the C# memory model makes and shows the code patterns that motivate the guarantees; the second part will detai