遊戲算法整理(貼圖完整版)

算法一:A*尋路初探node

譯者序:好久之前就知道了A*算法,可是從未認真讀過相關的文章,也沒有看過代碼,只是腦子裏有個模糊的概念。此次決定從頭開始,研究一下這個被人推崇備至的簡單方法,做爲學習人工智能的開始。ios

這篇文章很是知名,國內應該有很多人翻譯過它,我沒有查找,以爲翻譯自己也是對自身英文水平的鍛鍊。通過努力,終於完成了文檔,也明白的A*算法的原理。毫無疑問,做者用形象的描述,簡潔詼諧的語言由淺入深的講述了這一神奇的算法,相信每一個讀過的人都會對此有所認識。程序員

原文連接:http://www.gamedev.net/reference/articles/article2003.asp算法

如下是翻譯的正文。(因爲本人使用ultraedit編輯,因此沒有對原文中的各類連接加以處理(除了圖表),也是爲了不未經許可連接的嫌疑,有興趣的讀者能夠參考原文。編程

會者不難,A*(念做A星)算法對初學者來講的確有些難度。數組

這篇文章並不試圖對這個話題做權威的陳述。取而代之的是,它只是描述算法的原理,使你能夠在進一步的閱讀中理解其餘相關的資料。安全

最後,這篇文章沒有程序細節。你盡能夠用任意的計算機程序語言實現它。如你所願,我在文章的末尾包含了一個指向例子程序的連接。 壓縮包包括C++和Blitz Basic兩個語言的版本,若是你只是想看看它的運行效果,裏面還包含了可執行文件。咱們正在提升本身。讓咱們從頭開始。。。app

 

序:搜索區域框架

假設有人想從A點移動到一牆之隔的B點,如圖,綠色的是起點A,紅色是終點B,藍色方塊是中間的牆。less

 [圖1]

    你首先注意到,搜索區域被咱們劃分紅了方形網格。像這樣,簡化搜索區域,是尋路的第一步。這一方法把搜索區域簡化成了一個二維數組。數組的每個元素是網格的一個方塊,方塊被標記爲可經過的和不可經過的。路徑被描述爲從A到B咱們通過的方塊的集合。一旦路徑被找到,咱們的人就從一個方格的中心走向另外一個,直到到達目的地。

這些中點被稱爲「節點」。當你閱讀其餘的尋路資料時,你將常常會看到人們討論節點。爲何不把他們描述爲方格呢?由於有可能你的路徑被分割成其餘不是方格的結構。他們徹底能夠是矩形,六角形,或者其餘任意形狀。節點可以被放置在形狀的任意位置-能夠在中心,或者沿着邊界,或 其餘什麼地方。咱們使用這種系統,不管如何,由於它是最簡單的。

 

開始搜索

正如咱們處理上圖網格的方法,一旦搜索區域被轉化爲容易處理的節點,下一步就是去引導一次找到最短路徑的搜索。在A*尋路算法中,咱們經過從點A開始,檢查相鄰方格的方式,向外擴展直到找到目標。

 

咱們作以下操做開始搜索:

   1,從點A開始,而且把它做爲待處理點存入一個「開啓列表」。開啓列表就像一張購物清單。儘管如今列表裏只有一個元素,但之後就會多起來。你的路徑可能會經過它包含的方格,也可能不會。基本上,這是一個待檢查方格的列表。

   2,尋找起點周圍全部可到達或者可經過的方格,跳過有牆,水,或其餘沒法經過地形的方格。也把他們加入開啓列表。爲全部這些方格保存點A做爲「父方格」。當咱們想描述路徑的時候,父方格的資料是十分重要的。後面會解釋它的具體用途。

   3,從開啓列表中刪除點A,把它加入到一個「關閉列表」,列表中保存全部不須要再次檢查的方格。

在這一點,你應該造成如圖的結構。在圖中,暗綠色方格是你起始方格的中心。它被用淺藍色描邊,以表示它被加入到關閉列表中了。全部的相鄰格如今都在開啓列表中,它們被用淺綠色描邊。每一個方格都有一個灰色指針反指他們的父方格,也就是開始的方格。

 [圖2]

接着,咱們選擇開啓列表中的臨近方格,大體重複前面的過程,以下。可是,哪一個方格是咱們要選擇的呢?是那個F值最低的。

 

路徑評分:選擇路徑中通過哪一個方格的關鍵是下面這個等式:

F = G + H

這裏:

    * G = 從起點A,沿着產生的路徑,移動到網格上指定方格的移動耗費。

    * H = 從網格上那個方格移動到終點B的預估移動耗費。這常常被稱爲啓發式的,可能會讓你有點迷惑。這樣叫的緣由是由於它只是個猜想。咱們沒辦法事先知道路徑的長 度,由於路上可能存在各類障礙(牆,水,等等)。雖然本文只提供了一種計算H的方法,可是你能夠在網上找到不少其餘的方法。

咱們的路徑是經過反覆遍歷開啓列表而且選擇具備最低F值的方格來生成的。文章將對這個過程作更詳細的描述。首先,咱們更深刻的看看如何計算這個方程。

 

正如上面所說,G表示沿路徑從起點到當前點的移動耗費。在這個例子裏,咱們令水平或者垂直移動的耗費爲10,對角線方向耗費爲14。咱們取這些值是由於沿對角線的距離是沿水平或垂直移動耗費的的根號2(別怕),或者約1.414倍。爲了簡化,咱們用10和14近似。比例基本正確,同時咱們避免了求根運算和小數。這不是隻由於咱們怕麻煩或者不喜歡數學。使用這樣的整數對計算機來講也更快捷。你不就就會發現,若是你不使用這些簡化方法,尋路會變得很慢。

 

既然咱們在計算沿特定路徑通往某個方格的G值,求值的方法就是取它父節點的G值,而後依照它相對父節點是對角線方向或者直角方向(非對角線),分別增長14和10。例子中這個方法的需求會變得更多,由於咱們從起點方格之外獲取了不止一個方格。

 

H 值能夠用不一樣的方法估算。咱們這裏使用的方法被稱爲曼哈頓方法它計算從當前格到目的格之間水平和垂直的方格的數量總和,忽略對角線方向,而後把結果乘以 10。這被成爲曼哈頓方法是由於它看起來像計算城市中從一個地方到另一個地方的街區數,在那裏你不能沿對角線方向穿過街區。很重要的一點,咱們忽略了一切障礙物。這是對剩餘距離的一個估算,而非實際值,這也是這一方法被稱爲啓發式的緣由。想知道更多?你能夠在這裏找到方程和額外的註解。

F的值是G和H的和。第一步搜索的結果能夠在下面的圖表中看到。F,G和H的評分被寫在每一個方格里。正如在緊挨起始格右側的方格所表示的,F被打印在左上角,G在左下角,H則在右下角。

 [圖3]

如今咱們來看看這些方格。寫字母的方格里,G = 10。這是由於它只在水平方向偏離起始格一個格距。緊鄰起始格的上方,下方和左邊的方格的G值都等於10。對角線方向的G值是14。

H 值經過求解到紅色目標格的曼哈頓距離獲得,其中只在水平和垂直方向移動,而且忽略中間的牆。用這種方法,起點右側緊鄰的方格離紅色方格有3格距離,H值就 是30。這塊方格上方的方格有4格距離(記住,只能在水平和垂直方向移動),H值是40。你大體應該知道如何計算其餘方格的H值了~。

每一個格子的F值,仍是簡單的由G和H相加獲得

 

繼續搜索

 

爲了繼續搜索,咱們簡單的從開啓列表中選擇F值最低的方格。而後,對選中的方格作以下處理:

 

   4,把它從開啓列表中刪除,而後添加到關閉列表中。

   5,檢查全部相鄰格子。跳過那些已經在關閉列表中的或者不可經過的(有牆,水的地形,或者其餘沒法經過的地形),把他們添加進開啓列表,若是他們還不在裏面的話。把選中的方格做爲新的方格的父節點。

   6,若是某個相鄰格已經在開啓列表裏了,檢查如今的這條路徑是否更好。換句話說,檢查若是咱們用新的路徑到達它的話,G值是否會更低一些。若是不是,那就什麼都不作。

    另外一方面,若是新的G值更低,那就把相鄰方格的父節點改成目前選中的方格(在上面的圖表中,把箭頭的方向改成指向這個方格)。最後,從新計算F和G的值。若是這看起來不夠清晰,你能夠看下面的圖示。

好了,讓咱們看看它是怎麼運做的。咱們最初的9格方格中,在起點被切換到關閉列表中後,還剩8格留在開啓列表中。這裏面,F值最低的那個是起始格右側緊鄰的格子,它的F值是40。所以咱們選擇這一格做爲下一個要處理的方格。在緊隨的圖中,它被用藍色突出顯示。

 [圖4]

首先,咱們把它從開啓列表中取出,放入關閉列表(這就是他被藍色突出顯示的緣由)。而後咱們檢查相鄰的格子。哦,右側的格子是牆,因此咱們略過。左側的格子是起始格。它在關閉列表裏,因此咱們也跳過它。

其 他4格已經在開啓列表裏了,因而咱們檢查G值來斷定,若是經過這一格到達那裏,路徑是否更好。咱們來看選中格子下面的方格。它的G值是14。若是咱們從當 前格移動到那裏,G值就會等於20(到達當前格的G值是10,移動到上面的格子將使得G值增長10)。由於G值20大於14,因此這不是更好的路徑。若是 你看圖,就能理解。與其經過先水平移動一格,再垂直移動一格,還不如直接沿對角線方向移動一格來得簡單。

當咱們對已經存在於開啓列表中的4個臨近格重複這一過程的時候,咱們發現沒有一條路徑能夠經過使用當前格子獲得改善,因此咱們不作任何改變。既然咱們已經檢查過了全部鄰近格,那麼就能夠移動到下一格了。

因而咱們檢索開啓列表,如今裏面只有7格了,咱們仍然選擇其中F值最低的。有趣的是,此次,有兩個格子的數值都是54。咱們如何選擇?這並不麻煩。從速度上 考慮,選擇最後添加進列表的格子會更快捷。這種致使了尋路過程當中,在靠近目標的時候,優先使用新找到的格子的偏好。但這可有可無。(對相同數值的不一樣對 待,致使不一樣版本的A*算法找到等長的不一樣路徑。)

 

那咱們就選擇起始格右下方的格子,如圖。

 

 [圖5]

 

此次,當咱們檢查相鄰格的時候,發現右側是牆,因而略過。上面一格也被略過。咱們也略過了牆下面的格子。爲何呢?由於你不能在不穿越牆角的狀況下直接到達 那個格子。你的確須要先往下走而後到達那一格,循序漸進的走過那個拐角。(註解:穿越拐角的規則是可選的。它取決於你的節點是如何放置的。)

這樣一來,就剩下了其餘5格。當前格下面的另外兩個格子目前不在開啓列表中,因而咱們添加他們,而且把當前格指定爲他們的父節點。其他3格,兩個已經在開啓 列表中(起始格,和當前格上方的格子,在表格中藍色高亮顯示),因而咱們略過它們。最後一格,在當前格的左側,將被檢查經過這條路徑,G值是否更低。沒必要 擔憂,咱們已經準備好檢查開啓列表中的下一格了。

 

咱們重複這個過程,知道目標格被添加進開啓列表,就如在下面的圖中所看到的。

 

 [圖6]

 

注 意,起始格下方格子的父節點已經和前面不一樣的。以前它的G值是28,而且指向右上方的格子。如今它的G值是20,指向它上方的格子。這在尋路過程當中的某處 發生,當應用新路徑時,G值通過檢查變得低了-因而父節點被從新指定,G和F值被從新計算。儘管這一變化在這個例子中並不重要,在不少場合,這種變化會導 致尋路結果的巨大變化。

 

那麼,咱們怎麼肯定這條路徑呢?很簡單,從紅色的目標格開始,按箭頭的方向朝父節點移動。這最終會引導你回到起始格,這就是你的路徑!看起來應該像圖中那樣。從起始格A移動到目標格B只是簡單的從每一個格子(節點)的中點沿路徑移動到下一個,直到你到達目標點。就這麼簡單。

 

 [圖7]

 

 

 

 

A*方法總結

好,如今你已經看完了整個說明,讓咱們把每一步的操做寫在一塊兒:

 

   1,把起始格添加到開啓列表。

   2,重複以下的工做:

      a) 尋找開啓列表中F值最低的格子。咱們稱它爲當前格。

      b) 把它切換到關閉列表。

      c) 對相鄰的8格中的每個?

          * 若是它不可經過或者已經在關閉列表中,略過它。反之以下。

          * 若是它不在開啓列表中,把它添加進去。把當前格做爲這一格的父節點。記錄這一格的F,G,和H值。

          * 若是它已經在開啓列表中,用G值爲參考檢查新的路徑是否更好。更低的G值意味着更好的路徑。若是是這樣,就把這一格的父節點改爲當前格,而且從新計算這一格的G和F值。若是你保持你的開啓列表按F值排序,改變以後你可能須要從新對開啓列表排序。

      d) 中止,當你

          * 把目標格添加進了開啓列表,這時候路徑被找到,或者

          * 沒有找到目標格,開啓列表已經空了。這時候,路徑不存在。

   3.保存路徑。從目標格開始,沿着每一格的父節點移動直到回到起始格。這就是你的路徑。

 

題外話

 

離題一下,見諒,值得一提的是,當你在網上或者相關論壇看到關於A*的不一樣的探討,你有時會看到一些被看成A*算法的代碼而實際上他們不是。要使用A*,你 必須包含上面討論的全部元素--特定的開啓和關閉列表,用F,G和H做路徑評價。有不少其餘的尋路算法,但他們並非A*,A*被認爲是他們當中最好的。 Bryan Stout在這篇文章後面的參考文檔中論述了一部分,包括他們的一些優勢和缺點。有時候特定的場合其餘算法會更好,但你必須很明確你在做什麼。好了,夠多 的了。回到文章。

 

實現的註解

 

如今你已經明白了基本原理,寫你的程序的時候還得考慮一些額外的東西。下面這些材料中的一些引用了我用C++和Blitz Basic寫的程序,但對其餘語言寫的代碼一樣有效。

 

1, 維護開啓列表:這是A*尋路算法最重要的組成部分。每次你訪問開啓列表,你都須要尋找F值最低的方格。有幾種不一樣的方法實現這一點。你能夠把路徑元素隨意 保存,當須要尋找F值最低的元素的時候,遍歷開啓列表。這很簡單,可是太慢了,尤爲是對長路徑來講。這能夠經過維護一格排好序的列表來改善,每次尋找F值 最低的方格只須要選取列表的首元素。當我本身實現的時候,這種方法是個人首選。

 

在小地圖。這種方法工做的很好,但它並非最快的解決方 案。更苛求速度的A*程序員使用叫作「binary heap」的方法,這也是我在代碼中使用的方法。憑個人經驗,這種方法在大多數場合會快2~3倍,而且在長路經上速度呈幾何級數提高(10倍以上速度)。 若是你想了解更多關於binary heap的內容,查閱個人文章,Using Binary Heaps in A* Pathfinding。

 

2, 其餘單位:若是你剛好看了個人例子代碼,你會發現它徹底忽略了其餘單位。個人尋路者事實上能夠相互穿越。取決於具體的遊戲,這也許能夠,也許不行。若是你 打算考慮其餘單位,但願他們能互相繞過,我建議在尋路算法中忽略其餘單位,寫一些新的代碼做碰撞檢測。當碰撞發生,你能夠生成一條新路徑或者使用一些標準 的移動規則(好比老是向右,等等)直到路上沒有了障礙,而後再生成新路徑。爲何在最初的路徑計算中不考慮其餘單位呢?那是由於其餘單位會移動,當你到達 他們原來的位置的時候,他們可能已經離開了。這有可能會致使奇怪的結果,一個單位忽然轉向,躲避一個已經不在那裏的單位,而且會撞到計算完路徑後,衝進它 的路徑中的單位。

 

然而,在尋路算法中忽略其餘對象,意味着你必須編寫單獨的碰撞檢測代碼。這因遊戲而異,因此我把這個決定權留給你。參考文獻列表中,Bryan Stout的文章值得研究,裏面有一些可能的解決方案(像魯棒追蹤,等等)。

 

3, 一些速度方面的提示:當你開發你本身的A*程序,或者改寫個人,你會發現尋路佔據了大量的CPU時間,尤爲是在大地圖上有大量對象在尋路的時候。若是你閱 讀過網上的其餘材料,你會明白,即便是開發了星際爭霸或帝國時代的專家,這也迫不得已。若是你以爲尋路太過緩慢,這裏有一些建議也許有效:

 

    * 使用更小的地圖或者更少的尋路者。

    * 不要同時給多個對象尋路。取而代之的是把他們加入一個隊列,把尋路過程分散在幾個遊戲週期中。若是你的遊戲以40週期每秒的速度運行,沒人能察覺。可是他們會發覺遊戲速度忽然變慢,當大量尋路者計算本身路徑的時候。

    * 儘可能使用更大的地圖網格。這下降了尋路中搜索的總網格數。若是你有志氣,你能夠設計兩個或者更多尋路系統以便使用在不一樣場合,取決於路徑的長度。這也正是 專業人士的作法,用大的區域計算長的路徑,而後在接近目標的時候切換到使用小格子/區域的精細尋路。若是你對這個觀點感興趣,查閱個人文章Two- Tiered A* Pathfinding(雙層A*算法)。

    * 使用路徑點系統計算長路徑,或者預先計算好路徑並加入到遊戲中。

    * 預處理你的地圖,代表地圖中哪些區域是不可到達的。我把這些區域稱做「孤島」。事實上,他們能夠是島嶼或其餘被牆壁包圍等沒法到達的任意區域。A*的下限是,當你告訴它要尋找通往那些區域的路徑時,它會搜索整個地圖,直到全部可到達的方格/節點都被經過開啓列表和關閉列表的計算。這會浪費大量的CPU時 間。能夠經過預先肯定這些區域(好比經過flood-fill或相似的方法)來避免這種狀況的發生,用某些種類的數組記錄這些信息,在開始尋路前檢查它。 在我Blitz版本的代碼中,我創建了一個地圖預處理器來做這個工做。它也標明瞭尋路算法能夠忽略的死端,這進一步提升了尋路速度。

 

4, 不一樣的地形損耗:在這個教程和我附帶的程序中,地形只有兩種-可經過的和不可經過的。可是你可能會須要一些可經過的地形,可是移動耗費更高-沼澤,小山, 地牢的樓梯,等等。這些都是可經過可是比平坦的開闊地移動耗費更高的地形。相似的,道路應該比天然地形移動耗費更低。

 

這個問題很容易解 決,只要在計算任何地形的G值的時候增長地形損耗就能夠了。簡單的給它增長一些額外的損耗就能夠了。因爲A*算法已經按照尋找最低耗費的路徑來設計,因此 很容易處理這種狀況。在我提供的這個簡單的例子裏,地形只有可經過和不可經過兩種,A*會找到最短,最直接的路徑。可是在地形耗費不一樣的場合,耗費最低的 路徑也許會包含很長的移動距離-就像沿着路繞過沼澤而不是直接穿過它。

 

一種需額外考慮的狀況是被專家稱之爲「influence mapping」的東西(暫譯爲影響映射圖)。就像上面描述的不一樣地形耗費同樣,你能夠建立一格額外的分數系統,並把它應用到尋路的AI中。假設你有一張 有大批尋路者的地圖,他們都要經過某個山區。每次電腦生成一條經過那個關口的路徑,它就會變得更擁擠。若是你願意,你能夠建立一個影響映射圖對有大量屠殺 事件的格子施以不利影響。這會讓計算機更傾向安全些的路徑,而且幫助它避免老是僅僅由於路徑短(但可能更危險)而持續把隊伍和尋路者送到某一特定路徑。

 

5,處理未知區域:你是否玩過這樣的PC遊戲,電腦老是知道哪條路是正確的,即便它尚未偵察過地圖?對於遊戲,尋路太好會顯得不真實。幸運的是,這是一格能夠輕易解決的問題。

 

答 案就是爲每一個不一樣的玩家和電腦(每一個玩家,而不是每一個單位--那樣的話會耗費大量的內存)建立一個獨立的「knownWalkability」數組,每一個 數組包含玩家已經探索過的區域,以及被看成可經過區域的其餘區域,直到被證明。用這種方法,單位會在路的死端徘徊而且致使錯誤的選擇直到他們在周圍找到 路。一旦地圖被探索了,尋路就像往常那樣進行。

 

6,平滑路徑:儘管A*提供了最短,最低代價的路徑,它沒法自動提供看起來平滑的路徑。看一下咱們的例子最終造成的路徑(在圖7)。最初的一步是起始格的右下方,若是這一步是直接往下的話,路徑不是會更平滑一些嗎?

 

有 幾種方法來解決這個問題。當計算路徑的時候能夠對改變方向的格子施加不利影響,對G值增長額外的數值。也能夠換種方法,你能夠在路徑計算完以後沿着它跑一遍,找那些用相鄰格替換會讓路徑看起來更平滑的地方。想知道完整的結果,查看Toward More Realistic Pathfinding,一篇(免費,可是須要註冊)Marco Pinter發表在Gamasutra.com的文章

 

7,非方形搜索區域:在咱們的例子裏,咱們使用簡單的2D方形圖。你能夠不使用這種方式。你能夠使用不規則形狀的區域。想一想冒險棋的遊戲,和遊戲中那些國家。你能夠設計一個像那樣的尋路關卡。爲此,你可能須要創建一個國家相鄰關係的表格,和從一個國家移動到另外一個的G值。你也須要估算H值的方法。其餘的事情就和例子中徹底 同樣了。當你須要向開啓列表中添加新元素的時候,不需使用相鄰的格子,取而代之的是從表格中尋找相鄰的國家。

 

相似的,你能夠爲一張肯定的 地形圖建立路徑點系統,路徑點通常是路上,或者地牢通道的轉折點。做爲遊戲設計者,你能夠預設這些路徑點。兩個路徑點被認爲是相鄰的若是他們之間的直線上 沒有障礙的話。在冒險棋的例子裏,你能夠保存這些相鄰信息在某個表格裏,當須要在開啓列表中添加元素的時候使用它。而後你就能夠記錄關聯的G值(可能使用 兩點間的直線距離),H值(能夠使用到目標點的直線距離),其餘都按原先的作就能夠了。

 

另外一個在非方形區域搜索RPG地圖的例子,查看個人文章Two-Tiered A* Pathfinding。

 

進一步的閱讀

 

好,如今你對一些進一步的觀點有了初步認識。這時,我建議你研究個人源代碼。包裏面包含兩個版本,一個是用C++寫的,另外一個用Blitz Basic。順便說一句,兩個版本都註釋詳盡,容易閱讀,這裏是連接。

 

    * 例子代碼:A* Pathfinder (2D) Version 1.71

 

若是你既不用C++也不用Blitz Basic,在C++版本里有兩個小的可執行文件。Blitz Basic能夠在從Blitz Basic網站免費下載的litz Basic 3D(不是Blitz Plus)演示版上運行。Ben O'Neill提供一個聯機演示能夠在這裏找到。

 

你也該看看如下的網頁。讀了這篇教程後,他們應該變得容易理解多了。

 

    *  Amit的 A* 頁面:這是由Amit Patel製做,被普遍引用的頁面,若是你沒有事先讀這篇文章,可能會有點難以理解。值得一看。尤爲要看Amit關於這個問題的本身的見解。

    * Smart Moves:智能尋路:Bryan Stout發表在Gamasutra.com的這篇文章須要註冊才能閱讀。註冊是免費的並且比起這篇文章和網站的其餘資源,是很是物有所值的。Bryan 用Delphi寫的程序幫助我學習A*,也是個人A*代碼的靈感之源。它還描述了A*的幾種變化。

    * 地形分析:這是一格高階,可是有趣的話題,Dave Pottinge撰寫,Ensemble Studios的專家。這傢伙參與了帝國時代和君王時代的開發。別期望看懂這裏全部的東西,可是這是篇有趣的文章也許會讓你產生本身的想法。它包含一些對 mip-mapping,influence mapping以及其餘一些高級AI/尋路觀點。對"flood filling"的討論使我有了我本身的「死端」和「孤島」的代碼的靈感,這些包含在我Blitz版本的代碼中。

 

其餘一些值得一看的網站:

 

    * aiGuru: Pathfinding

    * Game AI Resource: Pathfinding

    * GameDev.net: Pathfinding

 

算法二:碰撞

1.   碰撞檢測和響應

碰撞在遊戲中運用的是很是普遍的,運用理論實現的碰撞,再加上一些小技巧,可讓碰撞檢測作得很是精確,效率也很是高。從而增長遊戲的功能和可玩性。

 

2D碰撞檢測

 

2D的碰撞檢測已經很是穩定,能夠在許多著做和論文中查詢到。3D的碰撞尚未找到最好的方法,如今使用的大多數方法都是創建在2D基礎上的。

 

碰撞檢測:

碰撞的檢測不只僅是運用在遊戲中,事實上,一開始的時候是運用在模擬和機器人技術上的。這些工業上的碰撞檢測要求很是高,而碰撞之後的響應也是須要符合現實生活的,是須要符合人類常規認識的。遊戲中的碰撞有些許的不同,何況,更重要的,咱們製做的東西充其量是商業級別,還不須要接觸到紛繁複雜的數學公式。

圖1

 

最理想的碰撞,我想莫過於上圖,徹底按照多邊形的外形和運行路徑規劃一個範圍,在這個範圍當中尋找會產生阻擋的物體,無論是什麼物體,產生阻擋之後,咱們運、動的物體都必須在那個位置產生一個碰撞的事件。最美好的想法老是在實現上有一些困難,事實上咱們能夠這麼作,可是效率倒是很是很是低下的,遊戲中,甚至於工業中沒法忍受這種速度,因此咱們改用其它的方法來實現。

圖2

最簡單的方法如上圖,咱們尋找物體的中心點,而後用這個中心點來畫一個圓,若是是一個3D的物體,那麼咱們要畫的就是一個球體。在檢測物體碰撞的時候,咱們只要檢測兩個物體的半徑相加是否大於這兩個物體圓心的實際距離。

圖3

這個算法是最簡單的一種,如今還在用,可是不是用來作精確的碰撞檢測,而是用來提升效率的模糊碰撞檢測查詢,到了這個範圍之後,再進行更加精密的碰撞檢測。 一種比較精密的碰撞檢測查詢就是繼續這種畫圓的思路,而後把物體細分,對於物體的每一個部件繼續畫圓,而後再繼續進行碰撞檢測,直到系統規定的,能夠容忍的 偏差範圍之後才觸發碰撞事件,進行碰撞的一些操做。

 

有沒有更加簡單的方法呢?2D遊戲中有許多圖片都是方方正正的,因此咱們沒必要把碰撞的範圍畫成一個圓的,而是畫成一個方的。這個正方形,或者說是一個四邊形和座標軸是對齊的,因此運用數學上的一些方法,好比距離計算等仍是比較方便的。這個檢測方法就叫AABBs(Axis-aligned Bounding Boxes)碰撞檢測,遊戲中已經運用的很是普遍了,由於其速度快,效率高,計算起來很是方便,精確度也是能夠忍受的。

 

作到這一步,許多遊戲的需求都已經知足了。可是,老是有人但願近一步優化,並且方法也是很是陳舊的:繼續對物體的各個部分進行細分,對每一個部件作AABB的矩形,那這個優化之後的系統就叫作OBB系統。雖說這個優化之後的系統也不錯,可是,許多它能夠運用到的地方,別人卻不愛使用它,這是後面會繼續介紹的 地方。

 

John Carmack不知道看的哪本書,他早在DOOM中已經使用了BSP系統(二分空間分割),再加上一些小技巧,他的碰撞作得就很是好了,再加上他發明的 castray算法,DOOM已經不存在碰撞的問題,解決了這樣的關鍵技術,我想他再也不須要在什麼地方分心了,只要繼續研究渲染引擎就能夠了。 (Windows遊戲編程大師技巧P392~P393介紹)(凸多邊形,多邊形退化,左手定律)SAT系統很是複雜,是SHT(separating hyperplane theorem,分離超平面理論)的一種特殊狀況。這個理論闡述的就是兩個不相關的曲面,是否可以被一個超平面所分割開來,所謂分割開來的意思就是一個曲 面貼在平面的一邊,而另外一個曲面貼在平面的另外一邊。我理解的就是有點像相切的意思。SAT是SHT的特殊狀況,所指的就是兩個曲面都是一些多邊形,而那個 超平面也是一個多邊形,這個超平面的多邊形能夠在場景中的多邊形列表中找到,而超平面可能就是某個多邊形的表面,很巧的就是,這個表面的法線和兩個曲面的 切面是相對應的。接下來的證實,我想是很是複雜的事情,但願從此可以找到源代碼直接運用上去。而咱們如今講究的快速開發,我想AABB就足以知足了。

 

3D碰撞檢測

 

3D 的檢測就沒有什麼很標準的理論了,都創建在2D的基礎上,咱們能夠沿用AABB或者OBB,或者先用球體作粗略的檢測,而後用AABB和OBB做精細的檢測。BSP技術不流行,可是效率不錯。微軟提供了D3DIntersect函數讓你們使用,方便了許多,可是和一般同樣,當物體多了之後就很差用了,明顯 的就是速度慢許多。

 

碰撞反應:

碰撞之後咱們須要作一些反應,好比說產生反衝力讓咱們反彈出去,或者停下來,或者讓阻擋咱們的物體飛出去,或者穿牆,碰撞最討厭的就是穿越,原本就不合邏輯,查閱了那麼多資料之後,歷來沒有看到過須要穿越的碰撞,有摩擦力是另一回事。首先看看彈性碰撞。彈性碰撞就是咱們初中物理中說的動量守恆。物體在碰撞先後的動量守恆,沒有任何能量損失。這樣的碰撞運用於打磚塊的遊戲中。引入質量的話,有的物體會是有必定的質量,這些物體一般來講是須要在碰撞之後進行另一個方向的運動的,另一些物體是設定爲質量無限大的,這些物體一般是碰撞牆壁。

 

當物體碰到質量很是大的物體,默認爲碰到了一個彈性物體,其速度會改變,可是能量不會受到損失。通常在代碼上的作法就是在速度向量上加上一個負號。

 

絕對的彈性碰撞是不多有的,大多數狀況下咱們運用的仍是非彈性碰撞。咱們如今玩的大多數遊戲都用的是很接近現實的非彈性碰撞,例如Pain-Killer中的那把吸力槍,它彈出去的子彈吸附到NPC身上時的碰撞響應就是非彈性碰撞;那把殘忍的分屍刀把牆打碎的初始算法就是一個非彈性碰撞,其後使用的剛體力學就是先創建在這個算法上的。那麼,是的,若是須要非彈性碰撞,咱們須要介入摩擦力這個因素,而咱們也沒法簡單使用動量守恆這個公式。

咱們能夠採起比較簡單的方法,假設摩擦係數μ很是大,那麼只要物體接觸,而且擁有一個加速度,就能夠產生一個無窮大的摩擦力,形成物體中止的狀態。

基於別人的引擎寫出一個讓本身滿意的碰撞是不容易的,那麼若是本身創建一個碰撞系統的話,如下內容是沒法缺乏的:

——一個可以容忍的碰撞系統;

——一個從概念上能夠接受的物理系統;

——質量;

——速度;

——摩擦係數;

——地心引力。

算法三:尋路算法新思惟

目前經常使用尋路算法是A*方式原理是經過不斷搜索逼近目的地的路點來得到

 

若是經過圖像模擬搜索點,能夠發現:非啓發式的尋路算法其實是一種窮舉法,經過固定順序依次搜索人物周圍的路點,直到找到目的地,搜索點在圖像上的表現爲一個不斷擴大的矩形。以下:

 

很快人們發現如此窮舉致使搜索速度過慢,並且不是很符合邏輯,試想:若是要從(0,0)點到達(100,0)點,若是每次向東搜索時可以走通,那麼幹嘛還要 搜索其餘方向呢?因此,出現了啓發式的A*尋路算法通常經過已經走過的路程 + 到達目的地的直線距離 代價值做爲搜索時的啓發條件,每一個點創建一個代價值,每次搜索時就從代價低的最早搜索,以下:

 

綜上所述,以上的搜索是一種矩陣式的不斷逼近終點的搜索作法。優勢是比較直觀,缺點在於距離越遠、搜索時間越長。

如今,我提出一種新的AI尋路方式——矢量尋路算法

經過觀察,咱們能夠發現,全部的最優路線,若是是一條折線,那麼、其每個拐彎點必定發生在障礙物的突出邊角,而不會在尚未碰到障礙物就拐彎的狀況:以下圖所示:

 

 

咱們能夠發現,全部的紅色拐彎點都是在障礙物(能夠認爲是一個凸多邊形)的頂點處,因此,咱們搜索路徑時,其實只須要搜索這些凸多邊形頂點不就能夠了嗎?若是將各個頂點鏈接成一條通路就找到了最優路線,而不須要每一個點都檢索一次,這樣就大大減小了搜索次數,不會由於距離的增大而增大搜索時間

 

這種思路我還沒有將其演變爲算法,姑且提出一個僞程序給各位參考:

1.創建各個凸多邊形頂點的通路表TAB,表示頂點A到頂點B是否可達,將可達的頂點分組保存下來。如: ( (0,0) (100,0) ),這一步驟在程序剛開始時完成,不要放在搜索過程當中空耗時間。

2.開始搜索A點到B點的路線

3.檢測A點能夠直達凸多邊形頂點中的哪一些,挑選出最合適的頂點X1。

4.檢測與X1相連(可以接通)的有哪些頂點,挑出最合適的頂點X2。

5.X2是不是終點B?是的話結束,不然轉步驟4(X2代入X1)

 

如此下來,搜索只發生在凸多邊形的頂點,節省了大量的搜索時間,並且找到的路線無需再修剪鋸齒,保證了路線的最優性

這種方法搜索理論上能夠減小大量搜索點、缺點是須要實現用一段程序得出TAB表,從本質上來講是一種空間換時間的方法,並且搜索時A*可以用的啓發條件,在矢量搜索時依然能夠使用。

 

 

算法四:戰略遊戲中的戰爭模型算法的初步探討

  《三國志》系列遊戲相信你們都有所瞭解,而其中的(宏觀)戰鬥時關於雙方兵力,士氣,兵種剋制,攻擊力,增援以及隨戰爭進行兵力減小等數值的算法是十分值得研究的。或許是因爲簡單的緣故,我在網上幾乎沒有找到相關算法的文章。 下面給出這個戰爭的數學模型算法能夠保證遊戲中戰爭的遊戲性與真實性兼顧,但願能夠給有須要這方面開發的人一些啓迪。

假設用x(t)和y(t)表示甲乙交戰雙方在t時刻的兵力,若是是開始時可視爲雙方士兵人數。

  假設每一方的戰鬥減員率取決於雙方兵力和戰鬥力,用f(x,y)和g(x,y)表示,每一方的增援率是給定函數用u(t)和v(t)表示。

   若是雙方用正規部隊做戰(可假設是相同兵種),先分析甲方的戰鬥減員率f(x,y)。可知甲方士兵公開活動,處於乙方沒一個士兵的監視和殺傷範圍以內, 一但甲方的某個士兵被殺傷,乙方的火力當即集中在其他士兵身上,因此甲方的戰鬥減員率只與乙方的兵力有關可射爲f與y成正比,即f=ay,a表示乙方平均 每一個士兵對甲方士兵的殺傷率(單位時間的殺傷數),成爲乙方的戰鬥有效係數。相似g= -bx

這個戰爭模型模型方程1爲

x’(t)= -a*y(t)+u(t) x’(t)是x(t)對於t 的導數

y’(t)= -b*x(t)+v(t) y’(t)是y(t)對於t的導數

利用給定的初始兵力,戰爭持續時間,和增援兵力能夠求出雙方兵力在戰爭中的變化函數。

(本文中解法略)

若是考慮因爲士氣和疾病等引發的非戰鬥減員率(通常與本放兵力成正比,設甲乙雙方分別爲h,w)

可獲得改進戰爭模型方程2:

x’(t)= -a*y(t)-h*x(t)+u(t)

y’(t)= -b*x(t)-w*y(t)+v(t)

利用初始條件一樣能夠獲得雙方兵力在戰爭中的變化函數和戰爭結果。

此外還有不一樣兵種做戰(兵種剋制)的數學模型:

模 型1中的戰鬥有效係數a能夠進一步分解爲a=ry*py*(sry/sx),其中ry是乙方的攻擊率(每一個士兵單位的攻擊次數),py是每次攻擊的命中 率。(sry/sx)是乙方攻擊的有效面積sry與甲方活動範圍sx之比。相似甲方的戰鬥有效係數b=rx*px*(srx/sy),rx和px是甲方的 攻擊率和命中率,(srx/sy)是甲方攻擊的有效面積與乙方活動範圍sy之比。因爲增長了兵種剋制的攻擊範圍,因此戰鬥減員率不光與對方兵力有關,並且 隨着己放兵力增長而增長。由於在必定區域內,士兵越多被殺傷的就越多。

方程

x’(t)= -ry*py*(sry/sx)*x(t)*y(t)-h*x(t)+u(t)

y’(t)= -rx*px*(srx/sy)*x(t)*y(t)-w*y(t)+u(t)

算法五:飛行射擊遊戲中的碰撞檢測

  在遊戲中物體的碰撞是常常發生的,怎樣檢測物體的碰撞是一個很關鍵的技術問題。在RPG遊 戲中,通常都將場景分爲許多矩形的單元,碰撞的問題被大大的簡化了,只要判斷精靈所在的單元是否是有其它的東西就能夠了。而在飛行射擊遊戲(包括象荒野大 鏢客這樣的射擊遊戲)中,碰撞倒是最關鍵的技術,若是不能很好的解決,會影響玩遊戲者的興趣。由於飛行射擊遊戲說白了就是碰撞的遊戲——躲避敵人的子彈或 飛機,同時用本身的子彈去碰撞敵人。

  碰撞,這很簡單嘛,只要兩個物體的中心點距離小於它們的半徑之和就能夠了。確實,並且我也看到很 多人是這樣作的,可是,這隻適合圓形的物體——圓形的半徑到處相等。若是咱們要碰撞的物體是兩艘威力巨大的太空飛船,它是三角形或矩形或其餘的什麼形狀,就會出現讓人尷尬的情景:兩艘飛船眼看就要擦肩而過,卻出人意料的發生了爆炸;或者敵人的子彈穿透了你的飛船的右弦,你卻安然無恙,這不是咱們但願發生的。因而,咱們須要一種精確的檢測方法。

  那麼,怎樣才能達到咱們的要求呢?其實咱們的前輩們已經總結了許多這方面的經驗,如上所述的半徑檢測法三維中的標準平臺方程法邊界框法等等。大多數遊戲程序員都喜歡用邊界框法,這也是我採用的方法。邊界框是在編程中加進去的不可見的邊界。邊界框法,顧名思義,就是用邊界框來檢測物體是否發生了碰撞,若是兩個物體的邊界框相互干擾,則發生了碰撞。用什麼樣的邊界框要視不一樣狀況而定,用最近似的幾何形狀。固然,你能夠用物體的準確幾何形狀做邊界框,但出於效率的考慮,我不同意這樣作,由於遊戲中的物體通常都很複雜,用複雜的邊界框將增長大量的計算,尤爲是浮點計算,而這正是咱們想盡可能避免的。但邊界框也不能與準確幾何形狀有太大的出入,不然就象用半徑法同樣出現奇怪的現象。

   在飛行射擊遊戲中,咱們的飛機大多都是三角形的,咱們能夠用三角形做近似的邊界框。如今咱們假設飛機是一個正三角形(或等要三角形,我想若是誰把飛機設計 成左右不對稱的怪物,那他的審美觀必定有問題),個人飛機是正着的、向上飛的三角形,敵人的飛機是倒着的、向下飛的三角形,且飛機不會旋轉(大部分遊戲中 都是這樣的)。咱們能夠這樣定義飛機:

中心點O(Xo,Yo),三個頂點P0(X0,Y0)、P1(X1,Y1)、P2(X2,Y2)。

中心點爲正三角形的中心點,即中心點到三個頂點的距離相等。接下來的問題是怎樣肯定兩個三角形互相干擾了呢?嗯,如今咱們接觸到問題的實質了。若是你學過平面解析幾何,我相信你能夠想出許多方法解決這個問題。判斷一個三角形的各個頂點是否在另外一個三角形裏面,看起來是個不錯的方法,你能夠這樣作,但我卻發現一個小問題:一個三角形的頂點沒有在另外一個三角形的裏面,卻可能發生了碰撞,由於另外一個三角形的頂點在這個三角形的裏面,因此要判斷兩次,這很麻煩。有沒有一次判斷就能夠的方法?

咱們把三角形放到極座標平面中,中心點爲原點,水平線即X軸爲零度角。咱們發現三角造成了這個樣子:在每一個角度咱們均可以找到一個距離,用以描述三角形的邊。既然咱們找到了邊到中心點的距離,那就能夠用這個距離來檢測碰撞。如圖一,兩個三角形中心點座標分別爲(Xo,Yo)和(Xo1, Yo1),由這兩個點的座標求出兩點的距離及兩點連線和X軸的夾角θ,再由θ求出中心點連線與三角形邊的交點到中心點的距離,用這個距離與兩中心點距離比 較,從而判斷兩三角形是否碰撞。由於三角形左右對稱,因此θ取-90~90度區間就能夠了。哈,如今問題有趣多了,-90~90度區間正是正切函數的定義 域,求出θ以後再找對應的邊到中心點的距離就容易多了,利用幾何知識,如圖二,將三角形的邊分爲三部分,即圖2中紅綠藍三部分,根據θ在那一部分而分別對 待。用正弦定理求出邊到中心點的距離,即圖2中淺綠色線段的長度。可是,若是飛機每次移動都這樣判斷一次,效率仍然很低。咱們能夠結合半徑法來解決,先用 半徑法判斷是否可能發生碰撞,若是可能發生碰撞,再用上面的方法精確判斷是否是真的發生了碰撞,這樣基本就能夠了。若是飛機旋轉了怎麼辦呢,例如,如圖三 所示飛機旋轉了一個角度α,仔細觀察圖三會發現,用(θ-α)就能夠求出邊到中心點的距離,這時你要注意邊界狀況,即(θ-α)可能大於90度或小於- 90度。囉羅嗦嗦說了這麼多,不知道你們明白了沒有。我編寫了一個簡單的例程,用於說明個人意圖。在例子中假設全部飛機的大小都同樣,而且沒有旋轉。

 

/////////////////////////////////////////////////////////////////////

//example.cpp

//碰撞檢測演示

//做者 李韜

/////////////////////////////////////////////////////////////////////

//限於篇幅,這裏只給出了碰撞檢測的函數

//define/////////////////////////////////////////////////////////////

#define NUM_VERTICES 3

#define ang_30 -0.5236

#define ang60  1.0472

#define ang120 2.0944

//deftype////////////////////////////////////////////////////////////

 

struct object

{

    float xo, yo;

    float radio;

    float x_vel, y_vel;

    float vertices[NUM_VERTICES][2];

}

 

//faction/////////////////////////////////////////////////////////////

//根據角度求距離

float AngToDis(struct object obj, float angle)

{

    float dis, R;

    R = obj.radius;

    if (angle <= ang_30)

        dis = R / (2 * sin(-angle));

    else if (angle >= 0)

        dis = R / (2 * sin(angle + ang60));

    else dis = R / (2 * sin(ang120 - angle));

    return dis;

}

 

//碰撞檢測

int CheckHit(struct object obj1, struct object obj2)

{

    float deltaX, deltaY, angle, distance, bumpdis;

    deltaX = abs(obj1.xo - obj2.xo);

    deltaY = obj1.yo - obj2.yo;

    distance = sqrt(deltaX * deltaX + deltaY * deltaY);

    if (distance <= obj.radio)

    {

         angle = atan2(deltaY, deltaX);

         bumpdis1 = AngToDis(obj1, angle);

         return (distance <= 2 * bumpdis);

    }

    ruturn 0;

}

//End//////////////////////////////////////////////////////////////

 

  上面程序只是用於演示,並不適合放在遊戲中,但你應該明白它的意思,以便寫出適合你本身的碰撞檢測。遊戲中的狀況是多種多樣的,沒有哪一種方法能適應全部狀況,你必定能根據本身的狀況找到最適合本身的方法。

高級碰撞檢測技術

 

高級碰撞檢測技術 第一部分

Advanced Collision Detection Techniques

 

這文章原載於Gamasutra,共有三部分。我想將它翻譯,請你們指教。

 

http://www.gamasutra.com/features/20000330/bobic_01.htm

http://www.gamasutra.com/features/20000330/bobic_02.htm

http://www.gamasutra.com/features/20000330/bobic_03.htm

 

 

/ 1 ………………………………………………………………………………………………….

自 從電腦遊戲降臨以來,程序員們不斷地設計各類方法去模擬現實的世界。例如Pong(著名的碰球遊戲),展現了一個動人的場面(一個球及兩根擺繩)。當玩家 將拽住擺繩移動到必定高度的,而後放開球,球就會離開玩家向對手衝去。以今天的標準,這樣的基礎操做也許就是原始碰撞檢測的起源。如今的電腦遊戲比之前的 Pong複雜多了,並且更可能是基於3D的。這也使3D碰撞檢測的困難要遠遠高於一個簡單的2D Pong。一些較早的飛行模擬遊戲說明瞭糟糕的碰撞檢測技術是怎樣破壞一個遊戲。如:當你的飛機撞到一座山峯的時候,你竟然還能夠安全的倖存下來,這在現 實中是不可能發生的。甚至最近剛出的一些遊戲也存在此類問題。許多玩家對他們喜好的英雄或是女英雄部分身體竟然能夠穿過牆而感到失望。甚至更壞的是玩家被 一顆沒有和他發生碰撞關係的火箭擊中。由於今天的玩家要求增長惟實論的要求愈來愈高,咱們遊戲開發者們將盡量在咱們的遊戲世界作一些改進以便接近真實的 世界。

  Since the advent of computer games, programmers have continually devised ways to simulate the world more precisely. Pong, for instance, featured a moving square (a ball) and two paddles. Players had to move the paddles to an appropriate position at an appropriate time, thus rebounding the ball toward the opponent and away from the player. The root of this basic operation is primitive(by today’s standards) collision detection. Today’s games are much more advanced than Pong, and most are based in 3D. Collision detection in 3D is many magnitudes more difficult to implement than a simple 2D Pong game. The experience of playing some of the early flight simulators illustrated how bad collision detection can ruin a game. Flying through a mountain peak and surviving isn’t very realistic. Even some recent games have exhibited collision problems. Many game players have been disappointed by the sight of their favorite heroes or heroines with parts of their bodies inside rigid walls. Even worse, many players have had the experience of being hit by a rocket or bullet that was 「not even close」 to them. Because today’s players demand increasing levels of realism, we developers will have to do some hard thinking in order to approximate the real world in our game worlds as closely as possible.

 

/ 2 …………………………………………………………………………………………………

這 篇碰撞檢測的論文會使用一些基礎的幾何學及數學知識。在文章的結束,我也會提供一些參考文獻給你。我假定你已經讀過Jeff Lander寫的圖形教程中的碰撞檢測部分(「Crashing into the New Year,」 ; 「When Two Hearts Collide,」; and 「Collision Response: Bouncy, Trouncy, Fun,」 )。我將給你一些圖片讓你能快速的聯繫起核心例程。咱們將要討論的碰撞檢測是基於portal-based 及BSP-based 兩種類型的引擎。由於每一個引擎都有本身組織結構,這使得虛擬世界物體的碰撞檢測技術也不盡相同。面向對象的碰撞檢測是使用得比較多的,但這取決於你的現實 可實性,就想將引擎分紅兩部分同樣。稍後,咱們會概述多邊形碰撞檢測,也會研究如何擴展咱們的彎曲物體。

This article will assume a basic understanding of the geometry and math involved in collision detection. At the end of the article, I’ll provide some references in case you feel a bit rusty in this area. I’ll also assume that you’ve read Jeff Lander’s Graphic Content columns on collision detection (「Crashing into the New Year,」 ; 「When Two Hearts Collide,」; and 「Collision Response: Bouncy, Trouncy, Fun,」 ). I’ll take a top-down approach to collision detection by first looking at the whole picture and then quickly inspecting the core routines. I’ll discuss collision detection for two types of graphics engines: portal-based and BSP-based engines. Because the geometry in each engine is organized very differently from the other, the techniques for world-object collision detection are very different. The object-object collision detection, for the most part, will be the same for both types of engines, depending upon your current implementation. After we cover polygonal collision detection, we’ll examine how to extend what we’ve learned to curved objects.

 

/ 3 …………………………………………………………………………………………………

重要的圖片

編寫一個最好的碰撞檢測例程。咱們開始設計而且編寫它的基本程序框架,與此同時咱們也正在開發着一款遊戲的圖形管線。要想在工程結束的時候才加入碰撞檢測是比較很差的。由於,快速的編寫一個碰撞檢測會使得遊戲開發週期延遲甚至會致使遊戲難產。在一個完美的遊戲引擎中,碰撞檢測應該是精確、有效、並且速度要快。這些意味着碰撞檢測必須經過場景幾何學的管理途徑。蠻力方法是不會工做的 — 由於今天,3D遊戲每幀運行時處理的數據量是使人難以置信的。你能想象一個多邊形物體的檢測時間。

在一個完美的比賽發動機,碰撞察覺應該是精確, 有效,而且很快的。這些要求意味着那碰撞察覺必須仔細到景色被繫住幾何學管理管道。禽獸力量方法嬴得’t 工做—今天’s 3D 比賽每框架處理的數據的數量能是介意猶豫。去是你能覈對對在景色的每另外的多角形的一個物體的每多角形的時間。

The Big Picture

To create an optimal collision detection routine, we have to start planning and creating its basic framework at the same time that we’re developing a game’s graphics pipeline. Adding collision detection near the end of a project is very difficult. Building a quick collision detection hack near the end of a development cycle will probably ruin the whole game because it’ll be impossible to make it efficient. In a perfect game engine, collision detection should be precise, efficient, and very fast. These requirements mean that collision detection has to be tied closely to the scene geometry management pipeline. Brute force methods won’t work — the amount of data that today’s 3D games handle per frame can be mind-boggling. Gone are the times when you could check each polygon of an object against every other polygon in the scene.

 

/ 4 …………………………………………………………………………………………………

讓咱們來看看一個遊戲的基本循環引擎。(Listing 1)

http://www.gamasutra.com/features/20000330/bobic_l1.htm

這 段代碼簡要的闡明瞭咱們碰撞檢測的想法。咱們假設碰撞沒發生而且更新物體的位置,若是咱們發現碰撞發生了,咱們移動物體回來而且不容許它經過邊界(或刪除 它或採起一些另外預防措施)。然而,由於咱們不知道物體的先前的位置是否仍然是可獲得的,這個假設是太過度簡單化的。你必須爲這種狀況設計一個解決方案 (不然,你將可能經歷碰撞而你將被粘住)。若是你是一個細心的玩家,你可能在遊戲中會注意到,當你走近一面牆而且試圖經過它時,你會看見牆開始動搖。你正 在經歷的,是感動運動返回來的效果。動搖是一個粗糙的時間坡度的結果(時間片)。

Let’s begin by taking a look at a basic game engine loop (Listing 1). A quick scan of this code reveals our strategy for collision detection. We assume that collision has not occurred and update the object’s position. If we find that a collision has occurred, we move the object back and do not allow it to pass the boundary (or destroy it or take some other preventative measure). However, this assumption is too simplistic because we don’t know if the object’s previous position is still available. You’ll have to devise a scheme for what to do in this case (otherwise, you’ll probably experience a crash or you’ll be stuck). If you’re an avid game player, you’ve probably noticed that in some games, the view starts to shake when you approach a wall and try to go through it. What you’re experiencing is the effect of moving the player back. Shaking is the result of a coarse time gradient (time slice).

 

/ 5 …………………………………………………………………………………………………

但 是咱們的方法是有缺陷的。咱們忘記在咱們的方程中加入時間。圖1顯示了時間的重要性,於是它不能省去。就算一個物體不在時間 t1 或 t2 抵觸,它能夠在時間t1 < t < t2穿過t邊界哪兒。這是很是正確的,咱們已經有大而連續的框架可操做。咱們會發現必須還要一個好方法來處理差別。

But our method is flawed. We forgot to include the time in our equation. Figure 1 shows that time is just too important to leave out. Even if an object doesn’t collide at time t1 or t2, it may cross the boundary at time t where t1 < t < t2. This is especially true when we have large jumps between successive frames (such as when the user hit an afterburner or something like that). We&#39;ll have to find a good way to deal with discrepancy as well.

 

/ 6 …………………………………………………………………………………………………

咱們應該將時間做爲第4維也加入到全部的計算中去。這些使得計算變得很複雜,然而,咱們只能捨棄它們。咱們也可從原來的物體在時間 t1 和 t2 之間的佔據,而後靠着牆測試結果(圖 2 )。

We could treat time as a fourth dimension and do all of our calculations in 4D. These calculations can get very complex, however, so we’ll stay away from them. We could also create a solid out of the space that the original object occupies between time t1 and t2 and then test the resulting solid against the wall (Figure 2).

 

/ 7 …………………………………………………………………………………………………

一條簡單的途徑就是在 2 不一樣的時間在一個物體的地點附近創造凸殼。這條途徑的效率很低而且毫無疑問它會下降你遊戲的執行速度。若是不創建凸殼,咱們能夠在物體附近創建一個範圍框。在咱們熟悉幾種技術後,咱們要再次回到這個問題上。

An easy approach is to create a convex hull around an object’s location at two different times. This approach is very inefficient and will definitely slow down your game. Instead of constructing a convex hull, we could construct a bounding box around the solid. We’ll come back to this problem once we get accustomed to several other techniques.

 

/ 8 …………………………………………………………………………………………………

另外的途徑,它是更容易的實現可是少些精確,是在正中央爲交叉的一半和測試細分給的時間間隔。

另外的途徑,其是更容易的實現可是少些精確,是細分在爲在midpoint 的交叉的一半和測試的給的時間間隔。這計算能遞歸地爲每一個結果的一半返回。這途徑將比先前的方法更快,可是它不能保證精確檢測全部碰撞的。

Another approach, which is easier to implement but less accurate, is to subdivide the given time interval in half and test for intersection at the midpoint. This calculation can be done recursively for each resulting half, too. This approach will be faster than the previous methods, but it’s not guaranteed to catch all of the collisions.

 

/ 9 …………………………………………………………………………………………………

另外的隱藏的問題是 collide_with_other_objects ()例程,它檢查一個對象是否在場景內與任何另外的對象交叉。若是咱們的場景有不少物體時,這例程會變得更重要。若是咱們須要在場景對全部的別的對象檢查,咱們將粗略地作

Another hidden problem is the collide_with_other_objects() routine, which checks whether an object intersects any other object in the scene. If we have a lot of objects in the scene, this routine can get very costly. If we have to check each object against all other objects in the scene, we’ll have to make roughly

 

圖三

(N choose 2 )的比較。所以,咱們將要完成的工做就是比較數字的關係N2 (or O(N2))。可是咱們能避免施行 O ( N2 )在若干方法之一的對明智的比較。例如,咱們能把咱們的世界劃分紅是靜止的物體( collidees )而且移動的物體( colliders )的初速度 v=0 。例如,在一個房間裏的一面僵硬的牆是一碰撞面和向牆被扔的一個網球球是一碰撞對象。咱們能創建一個二叉樹(爲每一個組的一個)給這些對象,而且而後檢查哪 個對象確實有碰撞的機會。咱們能甚至進一步限制咱們的環境以便一些碰撞對象不會與咱們沒有在 2 顆子彈之間計算碰撞的對方發生抵觸,例程。當咱們繼續前進,這個過程將變得更清楚,爲如今,讓咱們就說它是可能的。(爲了減小場景方面數量的另外的方法就 是創建一個八叉樹,這已經超出這篇文章的範圍,可是你能夠在文末參看我給你列出的參考文獻)如今讓看看基於portal-based引擎的碰撞檢測。

(N choose 2) comparisons. Thus, the number of comparisons that we’ll need to perform is of order N2 (or O(N2)). But we can avoid performing O(N2) pair-wise comparisons in one of several ways. For instance, we can divide our world into objects that are stationary (collidees) and objects that move (colliders) even with a v=0. For example, a rigid wall in a room is a collidee and a tennis ball thrown at the wall is a collider. We can build two spatial trees (one for each group) out of these objects, and then check which objects really have a chance of colliding. We can even restrict our environment further so that some colliders won’t collide with each other — we don’t have to compute collisions between two bullets, for example. This procedure will become more clear as we move on, for now, let’s just say that it’s possible. (Another method for reducing the number of pair-wise comparisons in a scene is to build an octree. This is beyond the scope of this article, but you can read more about octrees in Spatial Data Structures: Quadtree, Octrees and Other Hierarchical Methods, mentioned in the 「For Further Info」 section at the end of this article.) Now lets take a look at portal-based engines and see why they can be a pain in the neck when it comes to collision detection.

 

算法六:關於SLG中人物可到達範圍計算的想法

下面的沒有通過實踐,所以極可能是錯誤的,以爲有用的初學朋友讀一讀吧:)

但願高人指點一二 :)

 

簡介:

在標準的SLG遊戲中,當在一我的物處按下鼠標時,會以人物爲中心,向四周生成一個菱形的可移動區範圍,以下:

 

  0

 000

00s00

 000

  0

 

這個圖形在剛開始學習PASCAL時就應該寫過一個畫圖的程序(是否有人懷念?)。那個圖形和SLG的擴展路徑同樣。

 

1、如何生成路徑:

從人物所在的位置開始,向四周的四個方向擴展,以後的點再進行擴展。即從人物所在的位置從近到遠進行擴展(相似廣寬優先)。

 

2、擴展時會遇到的問題:

一、當擴展到一個點時,人物的移動力沒有了。

二、當擴展的時候遇到了一個障礙點。

三、當擴展的時候這個結點出了地圖。

四、擴展的時候遇到了一我的物正好站在這個點(與2同?)。

五、擴展的點已經被擴展過了。當擴展節點的時候,每一個節點都是向四周擴展,所以會產生重複的節點。

 

當 遇到這些問題的時候,咱們就不對這些節點處理了。在程序中使用ALLPATH[]數組記錄下每個等擴展的節點,不處理這些問題節點的意思就是不把它們加 入到ALLPATH[]數組中。咱們如何去擴展一個結點周圍的四個結點,使用這個結點的座標加上一個偏移量就能夠了,方向以下:

 

  3

  0 2

  1

 

偏移量定義以下:

int offx[4] = { -1, 0, 1, 0 };

int offy[4] = { 0, 1, 0, -1 };

 

擴展一個節點的相鄰的四個節點的座標爲:

for(int i=0; i<4; i )

{

    temp.x = temp1.x offx[i];

    temp.y = temp1.y offy[i];

}

 

3、關於地圖的結構:

一、地圖的二維座標,用於肯定每一個圖塊在地圖中的位置。

二、SLG中還要引入一個變量decrease表示人物通過這個圖塊後他的移動力的減小值。例如,一我的物如今的移動力爲CurMP=5,與之相領的圖塊的decrease=2;這時,若是人物移動到這裏,那它的移動力變成CurMP-decrease。

三、Flag域:寬度優先中好像都有這個變量,有了它,每個點保證只被擴展一次。防止一個點被擴展屢次。(一個點只被擴展一次真的能獲得正確的結果嗎?)

四、一個地圖上的圖塊是否能夠經過,咱們使用了一個Block表明。1---不能夠經過;0---能夠經過。

 

這樣,咱們能夠定義一個簡單的地圖結構數組了:

 

#define MAP_MAX_WIDTH 50

#define MAP_MAX_HEIGHT 50

typedef struct tagTILE{

    int x,y,decrease,flag,block;

}TILE,*LPTILE;

TILE pMap[MAP_MAX_WIDTH][MAP_MAX_HEIGHT];

 

以上是順序數組,是否使用動態的分配更好些?畢竟不能事先知道一個地圖的寬、高。

 

4、關於路徑:

SLG遊戲中的擴展路徑是一片區域(以人物爲中心向四周擴展,固然,當人物移動時路徑只有一個)。這些擴展的路徑必需要存儲起來,全部要有一個好的結構。我定義了一個結構,不是很好:

 

typedef struct tagNODE{

    int x,y;   //擴展路徑中的一個點在地圖中的座標。

    int curmp; //人物到了這個點之後的當前的移動力。

}NODE,*LPNODE;

 

上面的結構是定義擴展路徑中的一個點的結構。擴展路徑是點的集合,所以用以下的數組進行定義:

 

NODE AllPath[PATH_MAX_LENGTH];

 

其中的PATH_MAX_LENGTH表明擴展路徑的點的個數,咱們不知道這個擴展的路徑中包含多少個點,所以定義一個大一點的數字使這個數組不會產生溢出:

 

#define PATH_MAX_LENGTH 200

 

上 面的這個數組頗有用處,之後的擴展就靠它來實現,它應該帶有兩個變量nodecount 表明當前的數組中有多少個點。固然,數組中的點分紅兩大部分,一部分是已經擴展的結點,存放在數組的前面;另外一部分是等擴展的節點,放在數組的後面爲何 會出現已擴展節點和待擴展節點?以下例子:

 

當前的人物座標爲x,y;移動力爲mp。將它存放到AllPath數組中,這時的起始節點爲等 擴展的節點。這時咱們擴展它的四個方向,對於合法的節點(如沒有出地圖,也沒有障礙......),咱們將它們存放入AllPath數組中,這時的新加入 的節點(起始節點的子節點)就是等擴展結點,而起始節點就成了已擴展節點了。下一次再擴展節點的時候,咱們不能再擴展起始節點,由於它是已經擴展的節點 了。咱們只擴展那幾個新加入的節點(待擴展節點),以後的狀況以此類推。那麼咱們如何知道哪些是已經擴展的結點,哪些是等擴展的節點?咱們使用另外一個變量 cutflag,在這個變量所表明的下標之前的結點是已擴展節點,在它及它以後是待擴展結點。

 

5、下面是基本框架(只擴展一我的物的可達範圍):

 

int nodecount=0; //AllPath數組中的點的個數(包含待擴展節點和已經擴展的節點

int cutflag=0; //用於劃分已經擴展的節點和待擴展節點

NODE temp; //路徑中的一個點(臨時)

temp.x=pRole[cur]->x; //假設有一個關於人物的類,表明當前的人物

temp.y=pRole[cur]->y;

temp.curmp=pRole[cur]->MP; //人物的最大MP

AllPath[nodecount ]=temp; //起始點入AllPath,此時的起始點爲等擴展的節點

 

while(curflag<nodecount) //數組中還有待擴展的節點

{

    int n=nodecount; //記錄下當前的數組節點的個數。

    for(int i=cutflag;i<nodecount;i ) //遍歷待擴展節點

    {

        for(int j=0;j<4;j ) //向待擴展節點的四周各走一步

        {

            //取得相鄰點的數據

            temp.x=AllPath[i].x offx[j];

            temp.y=AllPath[i].y offy[j];

            temp.curmp=AllPath[i].curmp-pMap[AllPath[i].x][AllPath[i].y].decrease;

//如下爲檢測是否爲問題點的過程,若是是問題點,不加入AllPath數組,繼續處理其它的點

            if(pMap[temp.x][temp.y].block)

                continue; //有障礙,處理下一個節點

            if(temp.curmp<0)

                continue; //沒有移動力了

            if(temp.x<0||temp.x>=MAP_MAX_WIDTH|| temp.y<0||temp.y>=MAP_MAX_HEIGHT)

                continue; //出了地圖的範圍

            if(pMap[temp.x][temp.y].flag)

                continue; //已經擴展了的結點

            //通過了上面幾層的檢測,沒有問題的節點過濾出來,能夠加入AllPath

            AllPath[nodecount]=temp;

        }

        pMap[AllPath[i].x][AllPath[i].y].flag=1; //將已經擴展的節點標記爲已擴展節點

    }

    cutflag=n; //將已擴展節點和待擴展節點的分界線下標值移動到新的分界線

}

for(int i=0;i<nodecount;i )

    pMap[AllPath[i].x][AllPath[i].y].bFlag=0; //標記爲已擴展節點的標記設回爲待擴展節點。

算法七:無限大地圖的實現

這已經不是什麼新鮮的東西了,不過如今實在想不到什麼好寫,並且版面上又異常冷清,我再不說幾句就想要倒閉了同樣。只好暫且拿這個東西來湊數吧。

無限大的地圖,聽上去很是吸引人。原本人生活的空間就是十分廣闊的,人在這麼廣闊的空間裏活動纔有一種自由的感受。遊戲中的虛擬世界因爲受到計算機存儲空間 的限制,要真實地反映這個無限的空間是不可能的。而對這個限制最大的,就是內存的容量了。因此在遊戲的空間裏,咱們通常只能在一個狹小的範圍裏活動,在一 般的RPG中,從一個場景走到另外一個場景,即便兩個地方是牢牢相連的,也要有一個場景的切換過程,通常的表現就是畫面的淡入淡出。

這樣的場景切換給人一種不連續的感受(我不知道可不能夠把這種稱做「蒙太奇」:o)),從城內走到城外還有情可緣,由於有道城牆嘛,可是兩個地方明明沒有界限, 卻恰恰在這一邊看不到另一邊,就有點不現實了。固然這並非毛病,一直以來的RPG都是遵循這個原則,咱們(至少是我)已經習慣了這種走路的方式。我在 這裏說的僅僅是另一種看起來更天然一點的走路方式,僅此而已。

固然要把整個城市的地圖一會兒裝進內存,如今的確是不現實的,每一次只能放一部分,那麼應該怎麼放纔是咱們要討論的問題。

咱們在之前提到Tile方法構造地圖時就談到過Tile的好處之一就是節省內存,這裏仍然能夠借鑑Tile的思想。咱們把整個大地圖分紅幾塊,把每一塊稱做一個區域,在同一時間裏,內存中只保存相鄰的四塊區域。這裏每一個區域的劃分都有必定的要求:

每一個區域大小應該相等這是必定的了,否則判斷當前屏幕在哪一個區 域中就成了一個很是使人撓頭的事;另外每一個區域的大小都要大於屏幕的大小,也只有這樣才能保證屏幕(就是圖中那塊半透明的藍色矩形)在地圖上盪來盪去的時 候,最多同時只能覆蓋四個區域(象左圖中所表示的),內存裏也只要保存四個區域就足夠了;還有一點要注意的,就是地圖上的建築物(也包括樹啦,大石頭啦什 麼的)必須在一個區域內,這樣也是爲了畫起來方便,固然牆壁——就是那種連續的圍牆能夠除外,由於牆壁原本就是一段一段拼起來的。

咱們在程序中能夠設定4個指針來分別指向這4個區域,當每次主角移動時,就判斷當前滾動的屏幕是否移出了這四個區域,若是移出了這四個區域,那麼就廢棄兩個 (或三個)已經在目前的四個相鄰區域中被滾出去的區域(說得很彆扭,各位見諒),讀入兩個(或三個)新滾進來的區域,並從新組織指針。這裏並不涉及內存區 域的拷貝。

 

這樣的區域劃分方法恰好適合咱們之前提到的Tile排列方法,只要每一個區域橫向Tile的個數是個偶數就好了,這樣相鄰的兩個 區域拼接起來恰好嚴絲合縫,並且每一個區域塊的結構徹底一致,沒有那些須要重複保存的Tile(這個我想我不須要再畫圖說明了,你們本身隨便畫個草圖就看得 出來了)。在文件中的保存方法就是按一個個區域分別保存,這樣在讀取區域數據時就能夠直接做爲一整塊讀入,也簡化了程序。另外還有個細節就是,咱們的整個 地圖可能不是一個規則的矩形,可能有些地方是沒法達到的,如右圖所示,背景是黑色的部分表明人物不能達到的地方。那麼在整個地圖中,這一部分區域(在圖中 藍色的3號區域)就能夠省略,表如今文件存儲上就是實際上不存儲這一部分區域,這樣能夠節省下很多存儲空間。對於這種地圖能夠用一個稀疏矩陣來存儲,你們 也能夠發揮本身的才智用其餘對於編程來講更方便的形式來存儲地圖。  

 

這就是對無限大地圖實現的一種方法,歡迎你們提出更好的方法。也但願整個版面可以活躍一點。

Ogre中的碰撞檢測

Ogre採用樹樁管理場景中的各類"元素"(攝像機、燈光、物體等),全部的東西都掛在"樹"上,不在"樹"上的東西不會被渲染。

Ogre::SceneManager就是"樹"的管理者,Ogre::SceneNode是從SceneManager中建立的(固然BSP和8*樹的管理也和這兩個類有關,這暫時不討論)。

AABB(軸對齊包圍盒)

這個東西是碰撞檢測的基礎(怎麼總想起JJYY呢),和它相似的還有OBB(有向包圍盒),因爲OBB建立複雜,因此Ogre採用了AABB。

最簡單的碰撞檢測:

經過Ogre::SceneNode::_getWorldAABB()能夠取得這個葉子節點的AABB(Ogre::AxisAlignedBox), Ogre::AxisAlignedBox封裝了對AABB的支持,該類的成員函數Ogre::AxisAlignedBox::intersects ()能夠判斷一個AABB和"球體、點、面以及其餘面"的相交狀況(碰撞狀況)。

    m_SphereNode樹的葉子,掛了一個"球"

    m_CubeNode樹的葉子,掛了一個"正方體"

    AxisAlignedBox spbox=m_SphereNode->_getWorldAABB();

AxisAlignedBox cbbox=m_CubeNode->_getWorldAABB();

if(spbox.intersects(cbbox))

{

     //相交

}

區域查詢:

簡單的講就是,查詢某一區域中有什麼東西,分爲AABB、球體、面查詢。

   //建立一個球體查詢,這裏的100是m_SphereNode掛着的那個球體的半徑

   SphereSceneQuery * pQuery=m_SceneMgr->createSphereQuery(Sphere(m_SphereNode->getPosition(),100));

   //執行這個查詢

   SceneQueryResult QResult=pQuery->execute();

   //遍歷查詢列表找出範圍內的物體

   for (std::list<MovableObject*>::iterator iter = QResult.movables.begin(); iter != QResult.movables.end();++iter)

   {

    MovableObject* pObject=static_cast<MovableObject*>(*iter);

    if(pObject)

    {

     if(pObject->getMovableType()=="Entity")

     {

      Entity* ent = static_cast<Entity*>(pObject);

      //這裏簡化了操做,因爲只有一個"球體"和一個"正方體",

      //因此只判斷了球體和正方體的相交

      if(ent->getName()=="cube")

      {

       //改變位置防止物體重疊

       vtl=-vtl;

       m_SphereNode->translate(vtl);

       break;

      }

     }

    }

   }

相交查詢

遍歷全部的對象,找到一對一對的相交物體(廢話呀,相交固然至少兩個物體)。

    //建立相交檢測

    IntersectionSceneQuery* pISQuery=m_SceneMgr->createIntersectionQuery();

    //執行查詢

    IntersectionSceneQueryResult QResult=pISQuery->execute();

    //遍歷查詢列表找出兩個相交的物體

    for (SceneQueryMovableIntersectionList::iterator iter = QResult.movables2movables.begin();

     iter != QResult.movables2movables.end();++iter)

    {

    

     SceneQueryMovableObjectPair pObject=static_cast<SceneQueryMovableObjectPair>(*iter);

     //if(pObject)

     {

      String strFirst=pObject.first->getName();

      String strSecond=pObject.second->getName();

      //下面加入你本身的兩個物體相交判斷代碼,或者簡單的用AABB的判斷方法,

     }

    }

相關文章
相關標籤/搜索