本節書摘來異步社群《lua遊戲ai開發指南》一書中的第2章,第2.1節,作者: 【美】david young(楊) 譯者: 王磊 責編: 陳冀康,更多章節内容可以通路雲栖社群“異步社群”公衆号檢視。
現在已經可以建立智能體了,我們回過頭來看看智能體都有哪些屬性,以及它們的意義是什麼。
每當需要傳回智能體的朝向時,最簡單的方法是使用前向向量,它通常代表了智能體的運動方向。朝向的左向量和上向量也可以通路到。每當你需要改變智能體的方向時,隻需簡單地設定它的前向向量。
1.前向軸
為了擷取和設定智能體的前向向量,我們可以使用内建的getforward和setforward輔助函數。
2.左向軸
可以使用getleft輔助函數擷取左向量。
3.向上的軸
類似的,可以使用geup輔助函數擷取上向量。
智能體的位置是它的膠囊表象的質心在實體模拟中的位置。如果你需要确定智能體的原點,隻需簡單地傳回它的位置,然後用y分量減去它的高度的一半。
位置
可以使用getposition和setposition輔助函數擷取和設定智能體的位置。
智能體和它們的膠囊表現的大小是由高度和半徑來代表的。修改這兩個值的時候,實體模拟也會進行調整,并根據修改來為智能體建立一個新的表現形式。
1.高度
可以通過getheight和setheight函數來存取智能體的高度。
2.半徑
可以通過getradius和setradius函數來擷取和修改智能體的半徑。
雖然智能體都會參與實體模拟,但并不是智能體所有的實體參數都會在實體模拟層使用。比如,智能體的品質和在實體模拟中使用的品質相同,但智能體的maxforce和maxspeed函數隻會被它自身使用。這兩個參數分别代表智能體能對自身施加的最大的力和在不受任何外部影響時能達到的最大速率。
為什麼在處理智能體的實體模拟時做這樣的區分是有意義的呢?一個直覺的例子就是對重力的處理。當智能體加速到它的最大速率時,我們仍然希望重力能讓智能體在下落時向下加速。這個加速度能讓智能體達到比它的最大速率屬性更大的速率。
1.品質
可以使用智能體的getmass和setmass輔助函數來擷取和修改它的品質。
2.最大作用力
智能體的最大作用力可以使用getmaxforce和setmaxforce函數來擷取和設定。
3.最大速率
為了設定和擷取智能體的最大速率屬性,可以使用getmaxspeed和setmaxspeed函數。
4.速率
可以通過getspeed和setspeed函數來設定和擷取智能體的速率。
5.速度
類似地,可以通過getvelocity和setvelocity函數來擷取和設定智能體的速度。
智能體本身儲存有一個基本的知識集,使得外部的lua腳本(比如沙箱腳本)可以讓它作為代理來儲存一定的資料。例如,有時我們會建立一個智能體并讓它移動到一個目标位置,我們可能想讓沙箱來設定這個位置,而不是讓智能體自己來确定它的目标。
1.目标
智能體的目标是一個向量位置。通常,智能體使用目标來作為它們想到達的位置或者另一個智能體的已知位置。
2.目标半徑
目标半徑數量值是用來判定智能體是否足夠靠近它的目标,而不必精确地處于目标的位置上。這個值可以幫助解決智能體在靠近它的目标時,由于微小的數目偏差而不斷地在目标位置打轉的問題。
3.路徑
智能體的路徑是一系列的向量點。在路徑追蹤過程中,智能體内部會利用這些點來确定要移動到的位置。讓智能體記住它們的路徑是一個小小的優化,能避免在路徑追蹤計算過程中把路徑資料傳來傳去。當把路徑設定給智能體時,還可以傳遞一個布爾值參數,用來指定該路徑是否循環。
沙箱中所有的智能體都會通過實體系統自動進行實體模拟,是以有必要了解一下基本的牛頓實體學。
當智能體互相碰撞時,它的品質就會起作用了。智能體的品質決定了當它被施加一個力時,它的加速度會是多少。
2.速率
速率定義了智能體移動的快慢而不考慮其移動的方向。在沙箱中速率值的機關都是米/秒,代表速度向量的模。
3.速度
速度代表了智能體的移動速率和方向。它的機關是米/秒,用一個向量來表示的。
4.加速度
沙箱中的加速度機關是米/秒2,它代表了智能體的速度變化的快慢。
5.力
力是推動智能體四處移動的主要因素,它的機關是牛頓。
一旦受力,物體就會而加速或減速,加減速的快慢則取決于它的品質。
了解沙箱中使用的這些概念非常重要,它可以讓你對于速度、距離和品質等概念有一個直覺的認識。
我們的沙箱有了基本的智能體屬性和成熟的實體系統的支援,可以讓智能體在力的作用下真實地移動了。這種運作系統通常被稱為基于駕駛的運動系統。craig reynold的論文《staring behaviors for autonomous characters》很好地描述了移動角色的駕駛系統。轉向力能支援多種類型的移動,還能支援在一個角色上施加多個力。
由于沙箱使用opensteer庫來做轉向計算,在lua中請求一個轉向力就很容易了。既然opensteer處理了轉向計算,對力的應用則留在lua腳本中處理。
1.探索
探索是核心的轉向力之一,它計算一個讓智能體向目标移動的力。opensteer會合并探索和抵達轉向力,讓智能體在接近目标位置時緩慢減速。
2.對智能體應用轉向力
建立一個探索型智能體需要兩個主要元件。一個是在更新智能體的前進方向時對其施加力的計算,另一個就是把智能體的水準方向的速度限制在它的最大速率屬性範圍内。
每當我們對智能體施加力時,我們都會使用最大的力。這會讓智能體在最短的時間内達到最大速度。如果不施加最大的力,則一些較小的力将來不及對智能體産生影響。這一點非常重要,因為系統不會比較轉向力的大小。
首先,我們将實作agent_applyforce函數來處理對實體系統施加轉向力,并在智能體的速度方向有變化時更新智能體的前進方向。
在施加任何轉向力之前,我們會移除所有y軸方向的力。如果你的智能體能夠飛行的話,這可能不太合适。不過在沙箱中模拟的主要是類人形的智能體,而人除了跳躍是不能在y軸方向運動的。接下來,我們會把轉向力歸一化為一個機關向量,這樣就可以把這個轉向力縮放到被允許的最大的力。
雖然并不需要在所有的轉向計算中使用最大的力,但它能産生理想的結果。你可以自由地選擇如何對智能體施加力計算來體驗不同的轉向行為。
一旦計算好了力,我們隻需通過applyforce函數來施加這個力。現在我們使用力來計算智能體加速度的變化。有了加速度,我們可以把加速度乘上deltatimeinseconds參數來得到速度的變化量。速度變化量再加到智能體的目前速度上,得到向量的就是它的前進方向。
這隻是對智能體施加力計算的許多種方法中的一種,它對我們的智能體在運動方向和速率變化方面的表現做了很多的假設。稍後我們會對它進行進一步的優化,比如平滑方向的變化等。
要記住,所有的計算機關都是米、秒和千克。
3.限制智能體的水準速率
接下來,我們想要限制智能體的速率。如果考慮施加到智能體上的力,你會想到,加速度的存在會讓智能體的速度很快超過它們允許的最大速率。
在限制速度時,我們隻想限制智能體的橫向速度而忽略由重力産生的縱向速度。為了達到這種效果,我們先取出智能體的速度,把y軸方向的速度設為0,然後把速度的大小限制到最大速率範圍内。
在把速度設定回智能體前,我們會把原來y軸方向的速度再設定回去。
為了精确計算智能體的最大速率,我們限制了智能體的所有水準方向的速度,這還會造成一個重要的副作用。如果智能體已經達到它的最大速率,則所有能對它産生影響的外部力(比如推動智能體的實體對象)都不會對它的實際速度産生影響。
4.建立一個探索智能體
有了力的應用和最大速率的限制,我們可以建立移動的智能體了。探索智能體會計算一個推動力來讓目标移動到目标位置,當達到目标位置的目标半徑之内時,它将出發移動到一個新的目标。
首先,計算一個推動移動到目标位置的探索轉向力并使用agent_applyphysics steeringforce函數來施加到智能體上。接下來,調用agent_clamphorizontalspeed函數來限制任何可能超限的速率。
在每一幀中,還會繪制一些額外的調試資訊,以顯示智能體的移動方向以及目标的半徑大小。如果智能體移動到目标半徑以内,則會随機計算一個新的目标位置,然後智能體就開始向這個新的目标移動,如圖2-5所示。
重命名lua檔案。

5.追逐
建立追逐智能體和建立探索智能體的方法很相似,隻是追逐智能體會預測另一個目标移動智能體的位置。首先建立一個新的pursuingagent.lua腳本,實作基本的agent_cleanup、 agent_handleevent、 agent_initialize和agent_update函數。
建立lua檔案如下:
追逐智能體需要有一個被追逐的敵人,我們将在initialize和update函數範圍之外建立一個持久型lua變量enemy。在智能體的初始化過程中,我們會查詢沙箱中目前的所有智能體,然後把第一個智能體設定為敵人。
相比于探索智能體,我們對追逐智能體的一個小改變是使用predictfutureposition函數來計算它的未來位置。我們把預測的時間秒數作為參數傳遞,以計算追逐智能體的目标位置。
我們甚至可以讓追逐智能體的移動速度比它們的敵人更慢,但當敵人改變方向時,它們仍然會試圖在新的預測位置追趕上來。
由于追逐智能體需要一個敵人,我們會在追逐智能體初始化後在沙箱中建立這個敵人。
運作沙箱,你會看到一個追逐智能體在追逐一個探索智能體,如圖2-6所示。
6.逃跑
建立一個逃避行為基本上和建立一個探索行為類似。唯一差別在于智能體是從它的目标逃開,而不是向目标移動。可以通過agent.forcetofleeposition函數來擷取一個逃避的力。
7.躲避
躲避是一種讓智能體躲開另一個智能體的轉向行為。這種行為和追逐行為正好相反。我們從敵人将要去的位置逃開而不是向那個位置移動。
8.漫遊
漫遊行為本質上就是在智能體的前進方向上施加一個切向的轉向力。漫遊會對智能體的移動添加偏離,它本身不會被用作一個轉向力。把一個時間片段的毫秒值作為參數傳入,可以得到一個恒定速率變化的漫遊力。
9.目标速率
為了調整智能體的速率到想要的目标速率,可以使用forcetotargetspeed函數來計算一個加速或者減速的轉向力。
10.路徑追蹤
一個路徑追蹤智能體需要實作兩種不同的轉向行為。一個稱為forcetostayon path,用于将智能體保持在路徑上,另一個叫forcetofollowpath,用于讓智能體沿着路徑移動。
11.建立一個路徑追蹤智能體
建立路徑追蹤智能體和在seekingagent.lua中實作的探索型智能體很相似。 首先,建立一個新的lua腳本pathingagent.lua。
建立如下的lua檔案:
這一次,我們将使用debugutilities函數來繪制一條路徑。這個函數位于src/demo_ramework/ script/debugutilities.lua檔案中。當初始化尋路智能體時,我們将把這條路徑賦給它。路徑預設是循環的,是以智能體會一直繞着一個大圓轉圈。
agent_update函數添加了一些新的代碼,展示了如何把兩個轉向力加到一起。forcetofollowpath和forecetostayonpath都以一個與stayonpath力相關的較小的權重加到一起。尋路智能體中還添加了一個forcetotargetspeed函數以確定它的速度不會小于一個最小速度。
在沙箱中建立尋路智能體和建立其他智能體類似,隻不過這一次我們将建立20個速度各異的智能體,它們會跟随同一條路徑。運作沙箱,你會看到這些智能體會互相碰撞而不能互相超越。對于一個行為良好的路徑追蹤來說,我們還缺少避免碰撞的功能。
現在運作沙箱,我們會看到20個獨立的智能體沿着同一條預先定義的路徑運動,如圖2-7所示。
規避轉向行為是避免移動的智能體和其他智能體或對象互相碰撞。當智能體互相靠近時,forcetoavoidagents函數會在潛在智能體的切向方向上計算一個避免碰撞力。我們會使用預測性的移動計算來判斷兩個智能體是否會在給定的時間内互相碰撞。
另一方面,障礙物避免會使用球體來近似模拟沙箱對象,使用智能體的預測移動來建立一個在潛在碰撞切向方向上的轉向力。
1.避免碰撞
我們可以使用forcetoavoidagents函數來計算避免碰撞所需要的力,函數的參數是與其他智能體可能相撞的最小時間。
2.規避障礙物
類似地,forcetoavoidobjects函數可以用來計算避免與其他移動的障礙物相撞的力。
修改seeingagent.lua腳本,添加forcetoavoidagents和forcetoavoidobjects函數的權重合計值,可以讓探索智能體規避可能的碰撞。運作沙箱,試着向在路徑上移動的智能體發射箱子,觀察它是如何避開箱子的。
現在運作沙箱,我們可以向探索智能體發射箱子,觀察它是如何繞開每個對象的,如圖2-8所示。
群組移動可以拆分成3種主要的轉向行為:對齊、聚攏和分離。對齊轉向力讓一個群組中的智能體都面朝同樣的方向。聚攏轉向力則保持群組中的智能體聚集在一起。分離與聚攏相反,它讓群組中的智能體彼此之間都能保持一個最小的距離。
使用這3種行為的組合(也被稱為群聚),可以構造一群智能體,它們一起移動,同時又不會互相穿透和重疊,如圖2-9所示。
1.對齊
使用forcetosperate函數可以計算一個轉向向量,用來把智能體與群組中的其他智能體對齊。
2.聚攏
使用forcetocombine函數則可以計算一個聚集的力,用來保持智能體和群組中的其他智能體待在一起。
3.分離
使用forcetosperate函數可以計算一個力來讓智能體與群組中的其他智能體分開。
local forcetoseparate =
在這個示例中,我們将建立另一種ai類型,稱為追随者代理。這裡,一群追随者會聚集在一起并向它們的上司者移動。另一方面,上司者就是探索類型的智能體,它會在沙箱中随機地四處移動,完全不顧後面的追随者。
為建立追随者,我們對這些智能體使用多個轉向力以讓它們聚攏、分離和對齊它們追随的上司者。
建立一個lua檔案如下:
追随者智能體和尋路智能體很相似;首先,它會在初始化過程中找到一個上司者,然後使用聚合和對齊轉向力來嘗試盡可能地接近并保持與上司者的距離,同時使用一個分離的力來保持與其他智能體以及上司者至少兩米的距離。
建立5個追随者并繪制圍繞它們的調試圓以顯示它們的分離區域, 現在就能很容易地看到每個應用到智能體移動上的力了,如圖2-10所示。
目前為止,我們已經把一些權重的轉向力加到一起,并且當某些條件滿足時來施加這些力。但這些對于智能體的運動究竟有什麼用呢?把各種不同的轉向力疊加到一起有兩種通用的方法:權重合計法或者基于優先級的方法。
1.權重合計
權重合計法總是把所有轉向力納入計算中,每個轉向力根據與其他力的比較而得到一個固定的系數。當轉向力數量比較少時,這種方法非常直覺。但當數量龐大時,要平衡衆多互相對抗的力就會非常困難了。
通常這種方法是讓智能體移動的首選方法,但當需要處理更複雜的情況時,最好采用基于優先級的方法。
2.基于優先級的力
基于優先級的方法隻會基于某種優先級或條件來使用某些轉向力。例如,你可以忽略所有小于某個閥值的力,或者采用循環的方法,每次隻讓一個轉向力在一段時間内起作用。這種循環的方法可以避免權重合計法可能造成的一個問題,即不同的轉向力互相抵消,導緻智能體無法移動。
權重合計法和基于優先級的方法都不完美。為了讓智能體能按設想的方式運動,這兩種方法都需要做大量細緻的調整工作。