我们知道一个合理的物理世界的运动不是自发的,也不能是突发的。我们不能突然的moveTo。我们首先知道,由牛顿定理可知,物体的变速需要力的作用,而在游戏世界里,力不是由其他物体施加的,而是由目标驱动的,这一原则将是接下来运动的基本哲学。
我们先抛开实体的详细设计,在控制力实现过程中逐步添加实体属性。
这里有一个简单的可运动物体的设计(省略了ctor&set&get):
MovingEntity.h
class MovingEntity :public BaseEntity
{
protected:
Vec2 _velocity;//速度
Vec2 _heading;//朝向
Vec2 _side;//侧向,与朝向垂直
double _mass;//质量
double _maxSpeed;
double _maxForce;
double _maxTurnRate;//旋转速率(弧度每秒)
}
抛开可运动物体不谈,我们进入经典的SteeringForce设计。
虽然经典,但是可提升空间并不小,计算机哲学永远都有更先进的思维,绝不要被其所限制
Tips:此处我们模糊了很多物理概念,比如力和加速度,不要在意,后面会有说明。
Tips:吐槽一下,cocos的平面向量Vec2和点Point是用typedef声明的同一元素,单纯使用向量的时候感觉很坑。
实体将去追赶一个目标(以最快速度),很简单的逻辑,加速度=预期速度-当前速度。
具体代码:
Vec2 SteeringBehaviors::seek(const Vec2 target)
{
Vec2 desiredVelocity = (target - _ownerVehicle->position()).getNormalized()*_ownerVehicle->maxSpeed();
return (desiredVelocity - _ownerVehicle->velocity());
}
和seek相反,直接代码
Vec2 SteeringBehaviors::flee(const Vec2 target)
{
Vec2 desiredVelocity = (_ownerVehicle->position() - target).getNormalized()*_ownerVehicle->maxSpeed();
return (desiredVelocity - _ownerVehicle->velocity());
}
此运动函数的效果是很靠近目标时,速度变慢(很远时和seek效果一致),我们依然计算预期速度,预期速度和距离成正比即可。在这里我们用 [预期速度/约定时间] 来计算速度变化。
#define decelerationWeaker 0.3
enum Deceleration
{
slow=3,
normal=2,
fast=1
};
Vec2 SteeringBehaviors::arrive(const Vec2 target, Deceleration dece)
{
Vec2 toTarget = target - _ownerVehicle->position();
double distance = toTarget.getLength();
if (distance > 0.00001)
{
//减速公式
double speed = distance / (double)dece*decelerationWeaker;
speed = std::min(_ownerVehicle->maxSpeed(), speed);
Vec2 desiredVelocity = toTarget / distance*speed;
return (desiredVelocity - _ownerVehicle->velocity());
}
else
return Vec2::ZERO;
}
追逐行为要求目标为动态物体,然后我们计算目标的预期位置,从而去seek这个位置
有一种特殊情况,当两者相对前进时,我们就不用预测位置,直接向其移动即可。
预测的难点在于预测时间的估计,我们这里不难想象:
这个时间正比于两者的距离,越远追赶上需要的时间越久
反比于速度,此处为两者速度和,速度越快越快追上
Vec2 SteeringBehaviors::pursuit(const Vehicle* evader)
{
Vec2 toEvader = evader->position() - _ownerVehicle->position();
//dk how to descripe this angle,追踪者和逃亡者朝向的夹角
double relativeHeading = _ownerVehicle->heading().dot(evader->heading());
if (toEvader.dot(_ownerVehicle->heading()) > 0/*are they face towards each-other*/
&& (relativeHeading < -0.95))
{
return seek(evader->position());
}
//ahead time
double lookAheadTime = toEvader.getLength() / (_ownerVehicle->maxSpeed() + evader->speed());
return seek(evader->position() + evader->velocity()*lookAheadTime);
}
evade算法和pursuit算法一致,不过无需计算夹角。
Vec2 SteeringBehaviors::evade(const Vehicle* pursuer)
{
Vec2 toPursuer = pursuer->position() - _ownerVehicle->position();
double lookAheadTime = toPursuer.getLength() / (_ownerVehicle->maxSpeed() + pursuer->speed());
return flee(pursuer->position() + pursuer->velocity()*lookAheadTime);
}
该运动描述的是物体跑向两个目标中间,同时也是预测位置。比如保镖跑到其老板和抢劫犯中间。
和pursuit相同,时间预测是该算法的困难之处。这里我们用该公式近似得到时间预测:
ok,这下子代码简单了
Vec2 SteeringBehaviors::interpose(const Vehicle* agent1, const Vehicle* agent2)
{
Vec2 midPo = (agent1->position() + agent2->position()) / 2;
//regard the time vehicle to mid point with max speed as prediction time
double time2reachMidPo = Vec2(_ownerVehicle->position() - midPo).getLength() / _ownerVehicle->maxSpeed();
//predict the position these two agent will be
Vec2 prePosition1 = agent1->position() + agent1->velocity()*time2reachMidPo;
Vec2 prePosition2 = agent2->position() + agent2->velocity()*time2reachMidPo;
//use this var temporarily
midPo = (prePosition1 + prePosition2) / 2.0;
return arrive(midPo, fast);
}
我们可以想象,我们之前使用的时间预测算法都是为了照顾效率得出的折衷方案,完全可能有更好的方法来精准预测。
好了,今天先介绍基本的运动算法,接下来的算法涉及到障碍体,下回分晓。
准备写一个有关游戏底层算法,物理算法,以及AI(重点是机器学习在游戏中的应用)的长篇博客,欢迎大家指正交流╰( ̄▽ ̄)╯