視覺感知是一種常見的感知。
在許多即時戰略遊戲或者類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; //感知到的目標(結果) //.... };
上述扇形視野有幾個缺陷:
基於這些缺陷,咱們加了一個圓形和狹長的扇形視野範圍(紫色點爲智能體):
可是這樣計算量就提高了很多,一個代替方法是使用橢圓型視野(紫色點爲智能體):
橢圓(任意轉向)的長軸長爲2a,短軸長爲2b,兩個焦點離圓心的距離是c和-c(並且\(c^2=a^2-b^2\))。
橢圓上任意一點到兩個焦點的距離之和必等於2a,利用這個性質可推理出:
若某個點與橢圓上兩個焦點距離之和小於2a,則必在橢圓內。
所以咱們只要預設好常量值:
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. 首先每一個分片記錄一個數值(通常是用二維數組記錄),用於記錄該分片是否可視。
在實現時爲知足更復雜的需求能夠記錄額外的數據:
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