目標驅動產生控制力
我們知道一個合理的物理世界的運動不是自發的,也不能是突發的。我們不能突然的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聲明的同一元素,單純使用向量的時候感覺很坑。
1、Seek–靠近
實體將去追趕一個目標(以最快速度),很簡單的邏輯,加速度=預期速度-當前速度。
具體代碼:
Vec2 SteeringBehaviors::seek(const Vec2 target)
{
Vec2 desiredVelocity = (target - _ownerVehicle->position()).getNormalized()*_ownerVehicle->maxSpeed();
return (desiredVelocity - _ownerVehicle->velocity());
}
2、Flee–遠離
和seek相反,直接代碼
Vec2 SteeringBehaviors::flee(const Vec2 target)
{
Vec2 desiredVelocity = (_ownerVehicle->position() - target).getNormalized()*_ownerVehicle->maxSpeed();
return (desiredVelocity - _ownerVehicle->velocity());
}
3、Arrive–靠近+緩動
此運動函數的效果是很靠近目標時,速度變慢(很遠時和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;
}
4、Pursuit–追逐
追逐行爲要求目標爲動態物體,然後我們計算目標的預期位置,從而去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);
}
5、Evade逃避
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);
}
6、Interpose插入中間
該運動描述的是物體跑向兩個目標中間,同時也是預測位置。比如保鏢跑到其老闆和搶劫犯中間。
和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(重點是機器學習在遊戲中的應用)的長篇博客,歡迎大家指正交流╰( ̄▽ ̄)╯