遊戲底層邏輯,運動&&尋路(二)

目標驅動產生控制力

我們知道一個合理的物理世界的運動不是自發的,也不能是突發的。我們不能突然的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(重點是機器學習在遊戲中的應用)的長篇博客,歡迎大家指正交流╰( ̄▽ ̄)╯

發佈了34 篇原創文章 · 獲贊 4 · 訪問量 2萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章