Roguelike遊戲的視野演算法

遊資網發表於2019-12-13
Roguelike遊戲的視野演算法


【最近對這方面有興趣,康了些資料順便翻譯了,出處:

Roguelike Vision Algorithmswww.adammil.net

裡面還有很多資料,這沒貼出來,主要是implementations和對比】

簡介

實現Roguelike的一項任務是弄清楚如何計算玩家或怪物的可見區域。現有的演算法很多,但是都存在缺陷,因此我著手開發一種新的演算法,該演算法對我來說是快速,準確和美觀的。儘管我沒有建立理想的演算法,但我仍然認為我的演算法是對其他演算法的改進。

可見區域(視野/ field of view / FOV),判斷單位是否可以看到地形的某個部分。這裡假設地牢是很常見的基於tiles的型別。玩家是否可以“看到tile”(視線/ line of sight / LOS)有時與玩家是否可以“看到tile中的怪物”有所不同,也不同於他是否可以使用遠端武器或咒語”瞄準該怪物“(瞄準線/ line of targeting / LOT)。

理想的演算法屬性

接下來首先描述視野演算法一些常見而有用的特徵,然後說明為什麼大多數現有演算法缺少這些特徵中的一個或多個。

  • 對稱性(Symmetry): 如果站在tile A上可以看到tile B,那麼站在tile B上就應該能夠看到tile A。而且視野不對稱通常會導致戰術性和公平性變差(當LOS與LOT相同)。當然如果遊戲是刻意造成不對稱的話是例外。
  • 牆壁擴充套件(Expansive Walls):站在沒有凹陷的大房間時,你可以看到房間裡所有的牆;而站在長走廊時,您可以看到走廊兩側所有的牆。儘管它很少影響遊戲玩法,但如果演算法不具有此屬性,看起來就會很醜,並且會使探索變得乏味。
  • 擴大柱子陰影: 當視線被柱子遮擋時,柱子應以扇形投射陰影。這通常可以提供更具戰術性的玩法:更容易隱藏,伏擊和逃脫。但是許多roguelike根本沒有柱子,這個屬性就跟他們沒啥關係了。
  • 沒有盲角: 在拐角處可以在看到至少兩個tile。因此,如果沿拐角對角移動,你就不會發現自己旁邊突然出現之前看不見的怪物。這也意味著可以在進入大廳之前看到兩側至少兩個tile。大多數情況下這都是可取的,如果玩家走過每個角落都必須小心翼翼就有些乏味了。一些演算法允許拐角處可以看到無限遠,也算保護玩家免受遠端武器的傷害。
  • 沒有偽影(Artifacts): 儘管對於視野演算法而言什麼是“正確”有討論空間,但是演算法至少應該做到本份。意思是演算法應該定義如同現實世界的幾何圖形並準確地模擬光傳播。偽影的意思是有些演算法根本不符合現實世界的幾何體,是使用近似而非精確的數學來實現的,並且存在bug。
  • 效率: 演算法不應該花費很長時間,並且最好避免重複測試同一個tile。


現有演算法

不算詳盡,涵蓋了最常用的演算法。

光線投射(Ray casting)

優點: 簡單。相當快。擴大柱子陰影。良好的光影平衡。沒有盲角。

缺點: 不對稱。沒有牆壁擴充套件。間隙很多(可見性不連續)。

這演算法會將光線從玩家投射到地圖邊緣上的每個點(或視野半徑邊緣的每個點)。光線用簡單的畫線演算法投射(如Bresenham'的演算法[0]),一碰到牆就會停止。光線投射是最簡單而且速度很快的演算法,但是有許多問題。

  • 不對稱。 Bresenham的演算法是不對稱的,但是即使你用對稱的畫線演算法,結果還是不對稱,因為在替換位置時線條的端點不會簡單地反轉。
  • 在可見點和陰影上也都存在間隙,並且很古怪。後期處理可以修復間隙,可以消除難看的偽影,但會減慢速度,並且無法解決其他問題。
  • 會多次重複測試tile,效率有些低下,但是由於簡單性,最終還是非常快,尤其是在較小的視線範圍。


光線投射以最快的演算法聞名,但是這主要是由於較複雜的演算法普遍實現的較差。實現良好的陰影投射演算法永遠比光線投射更勝一籌。但如果撇開間隙和不對稱,僅考慮光線和陰影的形狀,我認為光線投射比陰影投射和菱形牆等更復雜的演算法產生更好的結果。而且,隨著視線半徑的減小,大多數問題都變得沒那麼嚴重了;如果圓形視線半徑為4或更小,它其實可以很好地工作,不需要進行後期處理(儘管還是有些一些偽影)。

Roguelike遊戲的視野演算法

程式碼:http://www.adammil.net/blog/v125_Roguelike_Vision_Algorithms.html#raycode

陰影投射(點對tile或點對點)( Shadow casting)

優點:快。擴大柱子陰影。牆壁擴充套件。連續的可見性。

缺點:對角線視野比平常窄得多。盲角。光束通過門擴充套件得太多。不對稱。消除偽影的方法複雜(nontrivial)。

陰影投射是從玩家向外投射扇形光線的技術。當一個扇區碰到牆時,該扇區可能會減小角度或分成兩個扇區,然後分別進行處理。實現方式各不相同,但是好的實現只會訪問每個tile一次或接近一次,並且每個tile僅進行少量且大致恆定的工作。如果實現得當,陰影投射會成為最快的演算法之一;但在實現不佳的情況下,它在走廊拐角和具有許多小障礙物的開放區域中的速度可能較慢。 “陰影投射”有點用詞不當,因為實際投射的是光,但我還是會用這詞,因為“Light casting”跟“Ray casting”有些像。在我見過的所有case,陰影投射都使用正方形的tile,但是其他形狀也是可能的。

在通常的實現中,如果從玩家的tile中心到目標tile的任何部分之間都存在一條暢通的線條,則tile為可見。這是一個示例,說明陰影投射如何針對單個八分圓扇形進行工作。 (並非所有實現都可以在八分圓中工作)

Roguelike遊戲的視野演算法
↑一個45度的扇形投影到了45度八分圓處,綠線為頂部,藍線為底部。顯示的分數是直線的斜率,數值永遠是0到1之間,帶有圓圈的象限被視為可見。然後,演算法從發射點向外工作,對於每一列,它從扇區內的tile從上倒下掃。如果找到從透明到不透明的分界,則調整扇區至不包含該不透明。

Roguelike遊戲的視野演算法
↑已經掃描了前三列,並且在第四列中發現了分界(不透明>>透明),因此向下調整了頂部。

Roguelike遊戲的視野演算法
↑找到分界(透明>>不透明),因此向上調整了底部。

Roguelike遊戲的視野演算法
↑在第五列中找到了兩種分界,因此將扇區一分為二,每個扇區獨立繼續。當演算法達到最大視距或所有扇區變空(底部斜率>頂部斜率),演算法將停止。

Roguelike遊戲的視野演算法
陰影投射的一些功能和問題(使用普通的正方形tile)。

上面就是不對稱性會減弱戰術性的一個原因。如果可以瞄準看到的東西(LOS==LOT),那麼走廊中間會比拐角具有優勢,儘管拐角看上去更隱蔽。拐角的單位可以在不見攻擊者的情況下被射殺。像這樣的不對等就是對稱性是一種理想特性的原因。但是,如果上述不對稱性被逆轉,則實際上它可能是優於對稱演算法:可以使拐角真的更加隱蔽,使遊戲更具戰術性(更佳的可能是LOS對稱,而LOT不對稱)。有一種稱為“反向陰影投射”的演算法可以逆轉不對稱性,但通常看起來更差並且執行更慢。

不過,對陰影投射程式碼進行小的修改就足以使其對稱。這是通過更改演算法來實現的,因此只有在從玩家的tile中心到目標tile中心(而不是目標tile的任何部分)有一條暢通無阻的線條時,它才判定tile可見。如下所示,這可以解決一些問題但會導致其他問題。

Roguelike遊戲的視野演算法

程式碼:http://www.adammil.net/blog/v125_Roguelike_Vision_Algorithms.html#shadowcode

【key point is the strategy of how to determine a tile is visible: just need the sector cover the tile? or need to cover the center point? or cover a certain percentage of area? 發射點固定了在格子中央,但是被觀察的一方則是視線稍為蹭到格子都算看見,因此不對稱】

菱形牆(Diamond walls)(點對tile或點對點)

優點:相當快。擴大支柱陰影。牆壁擴充套件。沒有盲角。可見性基本連續。

缺點:光束通過門會擴充套件太多。不對稱;小更改即可解決,但相對會丟失牆壁擴充套件並導致不連續

就像陰影投射一樣,如果從玩家tile的中心到目標tile的任何部分都存在一條暢通無阻的線條,則tile為可見,但是它會將牆視為菱形。這比標準陰影投射有更大的拐角可視距離空間,更好地窺視各個角落。但它本身會帶來一些問題,主要是有點過於寬容,使太多的tile可見,儘管如此,這似乎也是對標準陰影投射的一種改進。 (我稍為修改陰影投射程式碼來實現)至於效率,它比普通陰影投射要慢一些,因為它需要為每個tile做更多的工作,但是仍然相當快。

將牆視為菱形實際上在roguelikes中很有用。原因是大多數roguelikes允許單位在對角相鄰的牆之間移動,如果牆是正方形的,它們的角就會碰觸,沒有空間。如下圖所示。使遊戲的物理學更加一致【指可移動範圍與可視範圍有一致性? 】。菱形牆還可以使拐角處的視野更好,通常是好事。

Roguelike遊戲的視野演算法

Roguelike遊戲的視野演算法

Roguelike遊戲的視野演算法

不幸的是,菱形牆存在理論上的問題。與菱形成切線視之為不相交;零寬度的光束仍然能照亮tile。這樣可以在拐角處提供更好的視野,但在以下情況下使單位可以透視牆壁。應該要在特殊情況下禁止透視牆壁,但會造成遊戲物理在某些情況下不一致。

Roguelike遊戲的視野演算法

Roguelike遊戲的視野演算法
菱牆的一些功能和問題

Roguelike遊戲的視野演算法
簡單更改足以將菱牆演算法轉換為對稱演算法,但有與標準陰影投射相同的優缺點

程式碼:http://www.adammil.net/blog/v125_Roguelike_Vision_Algorithms.html#diamondcode

半寬牆(Half-width walls)

優缺點: 與菱形牆相同,但更寬容,但速度稍慢。

另一個類似於菱形牆的想法,分別是它使用的牆是通常寬度的一半。它也解決了在對角tile之間的視野問題,但在我看來過於寬容,感覺比菱形牆更差。由於並非每個牆都是相同形狀,因此實施速度也稍慢。 (形狀取決於是否有相鄰的牆要連線。)我姑且實現了這演算法,但實在不值得花這氣力。

寬容的FOV(Permissive field of view)(tile to tile)

優點: 對稱。沒有盲角。牆壁擴充套件。連續可見性。

缺點: 慢。沒有擴大的支柱陰影。拐角處的可見性可能過多。

如果從玩家tile的任何部分到目標tile的任何部分之間都存在一條暢通無阻的線條,則視tile為可見。此方法的大多數實現都是估算,例如僅對對角進行相互測試【並不是任何部分,而是隻有目標和自己的四個邊角點】,在某些情況下會失敗。精準的實現可以適應所有情況,但是慢。我提供了一個精確的實現(改編自Jonathon Duerig[1])。該演算法的主要特徵是對稱,並且在拐角可以看很遠,但對我而言有些太寬容了,所以我沒有很努力優化這演算法。可以說如果所有生物的視線半徑都較短,那這演算法感覺上和實際上都會更好。

有一個這演算法的版本,可以在執行時更改寬容度(permissivity)。我玩了一下,把通常寬容度減半看上去很不錯,但就不再對稱了,而且我想有個更快的演算法anyway。

Roguelike遊戲的視野演算法

程式碼: http://www.adammil.net/blog/v125_Roguelike_Vision_Algorithms.html#permissivecode

Digital FOV(菱到菱)

優點和缺點: 與寬容FOV演算法相同。

Digital FOV將每個tile都視為菱形,並且如果從玩家菱形的任何部分到目標菱形的任何部分都存在一條暢通的線條,則認為該tile可見。結果,它甚至比允寬容FOV稍微寬容一些(因為牆壁的障礙性更小) 。在其他方面所有相同的特徵和缺點一樣,但更慢。這個想法是基於的相當笨拙的digital straight line segments概念,我沒有費心實現這個。 One interesting feature of the algorithm is that the knowledge of the digital line segment from a line-of-sight calculation allows easy tracing of a projectile path through space without hitting any walls, even down somewhat twisted tunnels (such as the Kuo corridor above) 。但是複雜性似乎超過了收益。

我的演算法

以上沒有一個令我滿意。首先除了ray casting之外,要麼太寬容,要麼太過侷限,有時兩者都有。而且ray casting有太多偽影,用不來。下面我打算修復的問題和我想要的功能。

  • 沒大多數其他演算法那麼寬鬆
  • 比其他演算法對稱
  • 能夠做到對稱(至少跟不能穿牆的怪物相對)而不會損失牆壁擴充套件
  • 良好的拐角視野,但不能太好
  • 良好的狹窄的空間視野,而不會損失牆壁擴充套件
  • 比其他演算法更少陰影間隙
  • 與菱形牆相當的效率
  • 沒有死角
  • 沒有偽影
  • 一致的物理


為此,我會將地牢的幾何結構像下圖一樣表示。 (注意:圖2中的兩個角應該是斜角的。我畫錯了。不過,這圖說明了內部正方形的用途。)

Roguelike遊戲的視野演算法

Roguelike遊戲的視野演算法

1.        如果一個牆tile的兩個相鄰都不是牆,則將該角切成斜角。

2.        如果光束與牆的形狀相交,則牆tile可見;

3.        如果光束與中心正方形(size最大為tile的1/2)相交,則空白tile可見。

4.        與圖形成切線不算相交,並且零寬度的扇形無法照亮。

帶有斜角的實心牆允許在拐角處窺視並可以看到對角空間,同時避免了鑽石牆的理論問題。除非光線在中心附近通過,否則不要看見空白tile,這應該會沒那麼寬容,減少陰影間隙,減少不對稱性並搞定光線通過窄空間的行為。

圖2說明了光線穿過狹窄空間的問題。不管沿著隧道走多遠,扇形都會在離開走廊時開啟,而其他演算法會立即照亮出口上下方的tile。支柱也是同理,就像走廊的牆壁一樣,扇形在穿過支柱之間後開啟。不照亮光線照射的比較少的tile就可以避免這種情況,還能讓柱子有效的阻擋光線。因為能防止小束光線照亮tile,應該還能減少陰影間隙。

可以通過調整斜角的角度和內部正方形的大小來調整寬容度。可以使用正方形以外的其他形狀,但是起碼要可以放在tile中的最大菱形區域(就是不應該比柱子大)。用正方形的話,最大值為tile長寬的1/2,測試表明這樣的結果最好。使用3/8的寬度,可以在長走廊內和拐角的單位之間對稱的互望,很不錯,但在其他情況感覺有些侷限(尤其是在有許多柱子的環境下),所以我會用1 /2。實際的實現使用了修改的陰影投射演算法,只要我能避免在每個tile上做太多工作就能有不錯的效能。

Roguelike遊戲的視野演算法
不對稱版本的例子【1/2大小】

我的演算法有兩個對稱版本:一個是完全對稱的版本,另一個是基本對稱的版本(就是單位之間是對稱但穿牆單位另算)。

Roguelike遊戲的視野演算法

基本對稱的版本。對稱時,它不會遇到其他演算法的相同問題。最重要的是不會失去牆壁擴充套件。這是兩個對稱版本中較好的,除非你需要與穿牆單位也對稱

Roguelike遊戲的視野演算法

完全對稱版本。它與所有穿牆單位都有對稱性,但沒了了牆壁擴充套件;雖然擴充套件程度比大多數演算法更好(寬容FOV除外)。與對稱的菱形牆非常相似但不相同

程式碼:http://www.adammil.net/blog/v125_Roguelike_Vision_Algorithms.html#mycode
[0] Bresenham's algorithm: https://en.wikipedia.org/wiki/Bresenham%27s_line_algorithm
[1]Jonathon Duerig: https://user.xmission.com/~tyrecius/

作者:淮山
專欄地址:https://zhuanlan.zhihu.com/p/94699665


相關文章