遊戲AI之感知(1)

視覺感知


視覺感知是一種常見的感知。
在許多即時戰略遊戲或者類DOTA遊戲裏,一個單位的視覺感知每每是圓形範圍的。算法

扇形視野

固然在其餘大部分俯視角遊戲裏,一個智能體的視覺感知應該是相似現實人眼觀看的扇形範圍。數組

對於橫板遊戲,能夠把視野「豎」起來,檢測方式無多少差異。
對於空間更加複雜的3D遊戲,可能須要視錐體(立體)檢測。性能

一個更快的技巧是照樣作成扇形檢測,只是再額外增長高度差檢測(即看做2.5D處理)。優化

可是視野實際還需考慮阻擋問題,這裏提供2種解決視野遮擋的思路:this

1. 在前方扇形範圍每隔一段弧度發出一條射線進行檢測,若檢測到某個射線第一個碰到的物體是目標物體,則感知到該目標。spa

2. 在所在區域的全部潛在目標進行遍歷,每次遍歷 先判斷是否在扇形範圍內,
再作一條智能體到目標的射線,若射線碰到的第一個物體是該目標,則感知到該目標。
code

第一個思路比較容易實現,第二個則算法效率比較高。orm

第二個思路的進一步優化是,預先「規劃」好區域,構建潛在可視集(PVS),儘量過濾沒必要要的目標,縮小所在區域的潛在目標數量
(例如屋外看不到房內的人,也就能夠過濾掉房內的人),那麼檢測速度就很是快。htm

示例(C++):

//視野感知
class ViewPerception {
public:
    //進行一次視野感知探測
    void check(Vector2 position) {
        //先清理上次的結果
        perceptionResult.clear();

        //逐個潛在目標檢測
        for (Object* target : potentialTargets) {
            //運用簡單的數學運算判斷點是否在扇形範圍:

            //先進行距離判斷是否在半徑內。
            Vector2 offset = target.getPosition() - position;
            float distanceSq = offset.lengthSquare();
            if (distanceSq > radiusSq)continue;

            //look向量和射線單位向量的數量積絕對值 若大於 數量積限制,
            //則證實該射線離look向量的角度 超出數量積限制的對應角度。
            float dotproduct = fabs(offset.normalize().dot(look));
            if (dotproduct > dotproductlimit)continue;

            //最後使用射線檢測第一個碰到的物體是否是目標物體
            //若射線第一個碰到的物體是目標物體,則可視爲 看見了該物體
            if (raycast(position, target->position).result.object == target) {
                perceptionResult.emplace_back(target);
            }
        }
    }
private:
    Vector2 look;               //朝前的單位向量
    float radiusSq;             //扇形半徑的平方
    float dotproductlimit;      //數量積限制
    std::vector<Target*> perceptionResult;  //感知到的目標(結果)
    //....
};

TIP:判斷點在圓形範圍應比較距離的平方和半徑的平方,每次判斷就能夠減小一次開方的運算

橢圓視野

上述扇形視野有幾個缺陷:

  • 智能體應該能看到貼近側方的物體(甚至能感知到貼近背後的位置)
  • 智能體對於正前方向應該能看的更遠

基於這些缺陷,咱們加了一個圓形和狹長的扇形視野範圍(紫色點爲智能體):

可是這樣計算量就提高了很多,一個代替方法是使用橢圓型視野(紫色點爲智能體):

橢圓(任意轉向)的長軸長爲2a,短軸長爲2b,兩個焦點離圓心的距離是c和-c(並且\(c^2=a^2-b^2\))。
橢圓上任意一點到兩個焦點的距離之和必等於2a,利用這個性質可推理出:
若某個點與橢圓上兩個焦點距離之和小於2a,則必在橢圓內。

所以咱們只要預設好常量值:

  • a:取決於視野的長度
  • b:取決於視野的寬度
  • c:由\(c^2=a^2-b^2\)計算出
  • d1,d2:在智能體位置正前方d1距離的點爲焦點c1,正前方d2距離的點爲焦點c2。
bool ViewPerception::checkPointInEllipse(Vector2 targetPosition){
  Vector2 c1 = this->position + this->look * d1;
  Vector2 c2 = this->position + this->look * d2;

  if(distance(c1,targetPosition)+distance(c2,targetPosition) <= 2*a)return true;
  
  return false;
}

橢圓型的視野不只能解決上述缺陷,在現實中也更貼近人類視覺的模型,計算量也只略高於一個扇形視野計算。

基於分片的高性能視域搜索系統

在不少策略遊戲裏,視域(Line-of-Sight,簡稱LOS)是很重要的概念。
在典型RTS遊戲裏,視域分爲可見區域,不可見區域,已探索(但不可見)區域,說白了就是戰爭迷霧機制。

爲了實現視域系統,咱們先把遊戲世界分爲一個個整齊的分片(能夠是正方形網格,六邊形...)。
當咱們檢測某個分片是否可見時,直觀的作法是直接判斷該分片位置是否位於玩家視野幾何形狀。潛在的問題是,分片越多須要檢測的次數呈幾何級數增加。

而更高性能的作法是:

1. 首先每一個分片記錄一個數值(通常是用二維數組記錄),用於記錄該分片是否可視。

在實現時爲知足更復雜的需求能夠記錄額外的數據:

  • 多單位視野共享,應該用一個計數,當其中一個單位再也不看見該分片時,能夠減小計數,而不是直接修改成不可視。
  • 多方視野,應該用一個(可能多個)Byte值,其中每一個位表示某方視野是否可見。這能夠用在觀戰系統,隨時屏蔽某一方或者只關注某一個玩家的視野。
  • 多種視域類型,例如可見區域,不可見區域,已探索區域...則得記錄枚舉值。

2. 每幀將玩家的舊視野(上一幀的視野)對應的全部分片數值修改成不可視,而後根據新視野(當前幀的視野)對應的全部分片數值修改成可視。

在修改的時候,咱們能夠用一個LOS模板來幫助咱們快速找到視野分片,並修改之。
這個LOS模板實際上就是列表數組,每一行記錄該行全部視野分片的位置:

for(int i = 0 ; i < LOStemplate.size() ; ++i){
  for(int j = 0 ; j < LOStemplate[i].size(); ++j){
    tiles[positionX + i - offset][positionY + LOStemplate[i][j]] = true;
  }
}

得益於LOS模板,咱們不只能夠引入圓形LOS,還能夠引入相似手電筒視野的LOS:

因爲遊戲裏玩家可能轉向,對於一些非圓形LOS,咱們能夠準備多個LOS模板(例如對應90°,60°,30°..方向的LOS模板):

由於LOS模板徹底能夠經過預計算先算出來,因此使用它的CPU開銷只與它的視野分片數相關而不是與地圖分片數相關,這個性能開銷已經很不錯了。

3. 當須要檢測分片是否可見時,直接訪問記錄來獲取。

一個技巧是,不要主動搜索,而是利用分片記錄來主動通知:
例如當一個單位須要搜索視野內的一個敵人時,不是在該單位的LOS模板範圍內主動遍歷搜索敵人所在的分片,
而是敵人本身根據當前位置的分片數據(多方視野記錄),主動通知可看到該分片的單位。

簡單來講,思想是基於事件驅動而非輪詢,效率也提高的至關不錯。

聽覺感知


聽覺感知通常比較簡單粗暴:一個圓形/球形範圍檢測,
並且通常還無需考慮阻擋問題(現實中的聲音傳播可近似看做無阻擋)。

另外的,聽覺感知通常須要獲得的信息:

  • 聲音來源(例如發出聲音的生物)
  • 聲音大小和距離

經過簡單的線性計算,由聲音大小和距離能夠計算出實際接受聲音的大小。
將這個信息做爲額外數據交由決策使用。
(例如一個警衛,聽到太大的聲音就進入敵對狀態,小的聲音則進入警惕狀態)

示例(C++):

//聽覺感知
class ListenPerception {
public:
    //進行一次聽力感知探測
    void check(Vector2 position) {

        perceptionResult.clear();

        //逐個潛在聲源檢測
        for (Voice& voice : potentialVoices) {
            //判斷目標點是否在圓形範圍,即距離是否在半徑內。
            Vector3 offset = voice.getPosition() - position;
            float distanceSq = offset.lengthSquare();
            if (distanceSq > radiusSq)continue;

            //實際聲音大小會隨着距離增大而衰減
            float volume = voice.getVolume() / distanceSq;

            perceptionResult.emplace_back(voice.getTarget(),volume);
        }
    }
private:
    float radiusSq;         //範圍半徑
    std::vector<std::pair<Target*, float>> perceptionResult;    //感知到的目標+實際聲音大小(結果)
};

其它感知


這個其實應該叫雜項感知,由於通常來講,視覺感知和聽力感知已經足夠一個基本的智能體所需感知了。

但極少狀況還可能一些智能體須要知道各類雜項信息(例如隊長給警衛發送了一條無線電消息,要求警衛趕往隊長所在位置支援)。


遊戲AI 系列文章:https://www.cnblogs.com/KillerAery/category/1229106.html

相關文章
相關標籤/搜索