游戏底层逻辑,运动&&寻路(三)
2016-04-16 10:42
323 查看
上篇文章我们解释了几种基本的控制力,今天我们会讨论几种较为复杂的行为,涉及了碰撞,以及辅助图形进行运动控制。
如图,我们在物体前面构造了一个辅助圆,我们控制目标点,从而用seek方法控制运动行为,如果我们让目标点在圆周上运动,就可以产生一个力,他有如下属性:
一定向前(分力向物体的当前运动方向)
单帧转角范围为切线与x轴的夹角
大小为圆与x轴交点的范围
这样我们就可以控制辅助圆的相对位置来控制wander行为,比如我们想要总的运动轨迹为一个不规则的圆,我们可以将圆放到第一象限(逆时针旋转)或者第四象限(顺时针旋转),圆的位置为max_double,则进行直线运动。
接下来我们只需要写一个关于目标点在圆周上随机移动的方法就行(这里可以随便写,比如用三角函数),这里有一种实现:
目标点一定范围内随机移动
将其投影到圆上(获取其单位向量)
我们首先为SteeringBehaviors类添加新的成员变量:
接下来是wander函数的实现:
1、一定向前
2、方向变化有一定趋势,在一段时间内总是朝着一个方向转弯
知道这两点就能处理这个问题:控制力必然由两个力合成,其中一个力必然朝向物体运动方向(条件一)
另一个力控制转弯方向,我们只需要记录上一个力的方向(此处为记录目标点的位置),在此基础之上变化即可(条件二)
还有一个控制转弯方向的方法就是变化越大让力越小,此处利用了圆形,当然我们可以是一条直线(斜率为负值)
由此可知,此处的辅助图形可以有很多,目标点的控制也可以有很多方案,当然,经过大量的数据测试,调整参数也是必不可少的
算法之前,我们先想象一下人类避开障碍的情形(心理独白^_^|||):
“天气真好,晚上干点什么呢?”
“前面是电线杆,小心点别撞上了。”
呵呵,有点无厘头,但是其中蕴含了一个很重要的信息,“前面是电线杆”,人类用视觉来感知障碍的存在,我们不会给游戏实体添加视觉元素,但是我们这里借由辅助矩形框来实现视觉这一概念。
如图,三角形物体为运动实体,两条垂直线为本地坐标系的坐标轴,长方形为辅助矩形,灰色圆形为障碍的碰撞盒,其外面的圆为预估圆(提前检测碰撞)。
预估圆存在的意义好比人类不会贴着障碍物移动一样,这里也是这样
我们遍历所有的obstacle,找出其中满足如下特征的:
与辅助矩形相交(有重叠面积)
圆心在物体正前方
预估圆与物体x轴线相交
1、找出检测以辅助矩形长度为半径的圆范围内的所有障碍物
2、将筛选出来的obs转到物体的本地坐标系
3、排除x为负值的obs
4、排除y值大于预估圆半径(排除与x轴不相交)
筛选出来相交obs之后,我们需要求与x轴最近的交点,他就是施加斥力最大的地方,其他交点的斥力被我们省略。
5、寻找最近交点
我们来看看前五步的代码:
新增成员变量:
有几点忘了加以声明:
1、辅助矩形的长度与物体的当前速度正相关
2、计算时用Sprite来辅助计算本地坐标系,因为在此游戏中认为实体的坐标系属性与其Sprite完全相同(更新实体时同时更新Sprite),之所以实体不继承Sprite类是因为这样会出现大量bug,不推荐使用继承
3、预估圆的半径=obs的半径+辅助盒宽度/2(我们近似处理为物体半径)
分为两个力:平行于x轴的制动力,沿obs法线过斥力点的斥力
此处近似处理,方便计算,将此斥力处理为品行于y轴的力
这两个力的大小满反比于obs与实体的x轴距离
直接看代码:
最后别忘了转换到世界坐标系
今天先说到这里,我们下次继续
准备写一个有关游戏底层算法,物理算法,以及AI(重点是机器学习在游戏中的应用)的长篇博客,欢迎大家指正交流╰( ̄▽ ̄)╯
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(重点是机器学习在游戏中的应用)的长篇博客,欢迎大家指正交流╰( ̄▽ ̄)╯
相关文章推荐
- 我是运营,我没有假期
- 每个 Linux 游戏玩家都绝不想要的恼人体验
- 在 Fedora 上使用 Steam play 和 Proton 来玩 Windows 游戏
- Steam 让我们在 Linux 上玩 Windows 的游戏更加容易
- 如何使用 Steam Play 在 Linux 上玩仅限 Windows 的游戏
- 新一代iPad适配应用之游戏篇
- VB实现的《QQ美女找茬游戏》作弊器实例
- C#实现洗牌游戏实例
- C#实现的算24点游戏算法实例分析
- C#实现简单的井字游戏实例
- C++编写简单的打靶游戏
- C++实现基于控制台界面的吃豆子游戏
- 纯javascript实现的小游戏《Flappy Pig》实例
- 基于javascript实现句子翻牌网页版小游戏
- JavaScript实现俄罗斯方块游戏过程分析及源码分享
- JS小游戏之仙剑翻牌源码详解
- JS小游戏之宇宙战机源码详解
- Android基本游戏循环实例分析
- JavaScript游戏之优化篇
- js实现俄罗斯方块小游戏分享