您的位置:首页 > 其它

游戏底层逻辑,运动&&寻路(三)

2016-04-16 10:42 323 查看
上篇文章我们解释了几种基本的控制力,今天我们会讨论几种较为复杂的行为,涉及了碰撞,以及辅助图形进行运动控制。

7、Wander徘徊(巡逻)

徘徊(四处巡逻)是一种很常见的行为,但是要得到smoothly平滑的转向行为,并不是特别容易,这里有一种借用辅助圆实现的平滑移动。



如图,我们在物体前面构造了一个辅助圆,我们控制目标点,从而用seek方法控制运动行为,如果我们让目标点在圆周上运动,就可以产生一个力,他有如下属性:

一定向前(分力向物体的当前运动方向)

单帧转角范围为切线与x轴的夹角

大小为圆与x轴交点的范围

这样我们就可以控制辅助圆的相对位置来控制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、方向变化有一定趋势,在一段时间内总是朝着一个方向转弯

知道这两点就能处理这个问题:控制力必然由两个力合成,其中一个力必然朝向物体运动方向(条件一)

另一个力控制转弯方向,我们只需要记录上一个力的方向(此处为记录目标点的位置),在此基础之上变化即可(条件二)

还有一个控制转弯方向的方法就是变化越大让力越小,此处利用了圆形,当然我们可以是一条直线(斜率为负值)

由此可知,此处的辅助图形可以有很多,目标点的控制也可以有很多方案,当然,经过大量的数据测试,调整参数也是必不可少的

8、obstacleAvoidance避开障碍

接下来的算法可能会遇到obstacle地形实体这个概念,在我们的实现中,该类继承了BaseEntity这个类,暂时用到的只有BoudingBox碰撞盒这个属性。,以后有机会再详细介绍几种基本的游戏类实现。

算法之前,我们先想象一下人类避开障碍的情形(心理独白^_^|||):

“天气真好,晚上干点什么呢?”

“前面是电线杆,小心点别撞上了。”

呵呵,有点无厘头,但是其中蕴含了一个很重要的信息,“前面是电线杆”,人类用视觉来感知障碍的存在,我们不会给游戏实体添加视觉元素,但是我们这里借由辅助矩形框来实现视觉这一概念。



如图,三角形物体为运动实体,两条垂直线为本地坐标系的坐标轴,长方形为辅助矩形,灰色圆形为障碍的碰撞盒,其外面的圆为预估圆(提前检测碰撞)。

预估圆存在的意义好比人类不会贴着障碍物移动一样,这里也是这样

我们遍历所有的obstacle,找出其中满足如下特征的:

与辅助矩形相交(有重叠面积)

圆心在物体正前方

预估圆与物体x轴线相交

计算交点(斥力点)

该方法我们通过几步来实现:

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(重点是机器学习在游戏中的应用)的长篇博客,欢迎大家指正交流╰( ̄▽ ̄)╯
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  游戏