标签:
上篇文章我们解释了几种基本的控制力,今天我们会讨论几种较为复杂的行为,涉及了碰撞,以及辅助图形进行运动控制。
徘徊(四处巡逻)是一种很常见的行为,但是要得到smoothly平滑的转向行为,并不是特别容易,这里有一种借用辅助圆实现的平滑移动。
如图,我们在物体前面构造了一个辅助圆,我们控制目标点,从而用seek方法控制运动行为,如果我们让目标点在圆周上运动,就可以产生一个力,他有如下属性:
这样我们就可以控制辅助圆的相对位置来控制wander行为,比如我们想要总的运动轨迹为一个不规则的圆,我们可以将圆放到第一象限(逆时针旋转)或者第四象限(顺时针旋转),圆的位置为max_double,则进行直线运动。
接下来我们只需要写一个关于目标点在圆周上随机移动的方法就行(这里可以随便写,比如用三角函数),这里有一种实现:
我们首先为SteeringBehaviors类添加新的成员变量:
//wander attributes
Vec2 _wanderTarget;//目标点
double _wanderRadius;//辅助圆半径
double _wanderDistance;//辅助圆离物体的距离
double _wanderJitter;//给目标点的随机位置一个限制值,这是为了减少速度变化过快产生的抖动
接下来是wander函数的实现:
Vec2 SteeringBehaviors::wander()
{
//random move
_wanderTarget = Vec2(Random::Rand(-1, 1)*_wanderJitter,
Random::Rand(-1, 1)*_wanderJitter);
//project it onto the circle
_wanderTarget = _wanderTarget.getNormalized()*_wanderRadius;
//add jitter
Vec2 targetOnLocal = _wanderTarget + Vec2(_wanderJitter, 0);
//we use sprite instead of entity itself
if (_ownerVehicle->getSprite()->getPosition() != _ownerVehicle->position())
{
_ownerVehicle->getSprite()->setPosition(_ownerVehicle->position());
}
Vec2 targetOnWorld = _ownerVehicle->getSprite()->convertToWorldSpaceAR(targetOnLocal);
return targetOnWorld - _ownerVehicle->position();
}
wander方法讲述了一种倒推算法的途径,我们可以想象物体运动的情况,想要得到一个平滑无抖动的运动效果,就是要限制运动方向和大小的变化,此处有几个特征:
1、一定向前
2、方向变化有一定趋势,在一段时间内总是朝着一个方向转弯
知道这两点就能处理这个问题:控制力必然由两个力合成,其中一个力必然朝向物体运动方向(条件一)
另一个力控制转弯方向,我们只需要记录上一个力的方向(此处为记录目标点的位置),在此基础之上变化即可(条件二)
还有一个控制转弯方向的方法就是变化越大让力越小,此处利用了圆形,当然我们可以是一条直线(斜率为负值)
由此可知,此处的辅助图形可以有很多,目标点的控制也可以有很多方案,当然,经过大量的数据测试,调整参数也是必不可少的
接下来的算法可能会遇到obstacle地形实体这个概念,在我们的实现中,该类继承了BaseEntity这个类,暂时用到的只有BoudingBox碰撞盒这个属性。,以后有机会再详细介绍几种基本的游戏类实现。
算法之前,我们先想象一下人类避开障碍的情形(心理独白^_^|||):
“天气真好,晚上干点什么呢?”
“前面是电线杆,小心点别撞上了。”
呵呵,有点无厘头,但是其中蕴含了一个很重要的信息,“前面是电线杆”,人类用视觉来感知障碍的存在,我们不会给游戏实体添加视觉元素,但是我们这里借由辅助矩形框来实现视觉这一概念。
如图,三角形物体为运动实体,两条垂直线为本地坐标系的坐标轴,长方形为辅助矩形,灰色圆形为障碍的碰撞盒,其外面的圆为预估圆(提前检测碰撞)。
预估圆存在的意义好比人类不会贴着障碍物移动一样,这里也是这样
我们遍历所有的obstacle,找出其中满足如下特征的:
该方法我们通过几步来实现:
1、找出检测以辅助矩形长度为半径的圆范围内的所有障碍物
std::vector<BaseEntity*> EntityManenger::getNeighbors(BaseEntity* centreEntity, std::vector<BaseEntity*> entityList, double radius)
{
std::vector<BaseEntity*> vec;
for_each(entityList.cbegin(), entityList.cend(), [&radius,centreEntity,&vec](BaseEntity* neighborEntity)
{
Vec2 dis = neighborEntity->position() - centreEntity->position();
radius += 0.5*max(neighborEntity->getSprite()->getBoundingBox().size.height,
neighborEntity->getSprite()->getBoundingBox().size.width);
if (centreEntity != neighborEntity&&dis.getLengthSq() < radius*radius)
{
vec.push_back(neighborEntity);
}
}
);
return vec;
}
2、将筛选出来的obs转到物体的本地坐标系
3、排除x为负值的obs
4、排除y值大于预估圆半径(排除与x轴不相交)
筛选出来相交obs之后,我们需要求与x轴最近的交点,他就是施加斥力最大的地方,其他交点的斥力被我们省略。
5、寻找最近交点
我们来看看前五步的代码:
新增成员变量:
double _dBoxLenth;
Vec2 SteeringBehaviors::obstacleAvoidance(const std::vector<BaseEntity*>& obstacles)
{
_dBoxLenth = minDetectionBoxLength*(1 + _ownerVehicle->speed() / _ownerVehicle->maxSpeed());
//nearest intersection dis alone axis x
double NIOL_x = max_double;
//nearest intersection position on local
Vec2 NIOL_po = Vec2::ZERO;
//nearest obstacle
BaseEntity* NObstacle = NULL;
//entities within view range
std::vector<BaseEntity*> neighborObstacles = EMGR->getNeighbors(_ownerVehicle, EMGR->getVecByType(1), _dBoxLenth);
//find the point of the force
for_each(neighborObstacles.begin(), neighborObstacles.end(), [this,&NIOL_x,&NIOL_po,&NObstacle](BaseEntity* obstacle)
{
Vec2 poOnLocal = _ownerVehicle->getSprite()->convertToNodeSpaceAR(obstacle->position());
//tips: better to use "do while(0)" struct
if (poOnLocal.x >= 0)
{
double expandedRadius = obstacle->getBoundingRadius() + _ownerVehicle->getBoundingRadius();
if (fabs(poOnLocal.y) < expandedRadius)
{
//find out the intersections
double intersectionX = poOnLocal.x - (expandedRadius*expandedRadius - (poOnLocal.y)*(poOnLocal.y));
//just determined by the positive axis X
if (intersectionX <= 0)
{
intersectionX = poOnLocal.x + (expandedRadius*expandedRadius - (poOnLocal.y)*(poOnLocal.y));
}
if (intersectionX < NIOL_x)
{
NIOL_x = intersectionX;
NIOL_po = poOnLocal;
NObstacle = obstacle;
}
}
}
}
);
//to be continued
有几点忘了加以声明:
1、辅助矩形的长度与物体的当前速度正相关
2、计算时用Sprite来辅助计算本地坐标系,因为在此游戏中认为实体的坐标系属性与其Sprite完全相同(更新实体时同时更新Sprite),之所以实体不继承Sprite类是因为这样会出现大量bug,不推荐使用继承
3、预估圆的半径=obs的半径+辅助盒宽度/2(我们近似处理为物体半径)
有了斥力点,我们就很容易计算控制力了,经典处理如下:
分为两个力:平行于x轴的制动力,沿obs法线过斥力点的斥力
此处近似处理,方便计算,将此斥力处理为品行于y轴的力
这两个力的大小满反比于obs与实体的x轴距离
直接看代码:
//go on
//is obstacle exist
if (NObstacle)
{
//no matter on the x or y, the closer, the stronger the for shouble be
//the effect on x
double multiplier = 1.0 + (_dBoxLenth - NIOL_po.x) / _dBoxLenth;
//the effect on y
steeringForce.y = (NObstacle->getBoundingRadius() - NIOL_po.y)*multiplier;
//apply a braking force
steeringForce.x = (NObstacle->getBoundingRadius() - NIOL_po.x)*brakingWeight;
}
//convert the force from local to world space
return _ownerVehicle->getSprite()->convertToWorldSpaceAR(steeringForce);
}
最后别忘了转换到世界坐标系
今天先说到这里,我们下次继续
准备写一个有关游戏底层算法,物理算法,以及AI(重点是机器学习在游戏中的应用)的长篇博客,欢迎大家指正交流╰( ̄▽ ̄)╯
标签:
原文地址:http://blog.csdn.net/qq_22984991/article/details/51165455