2011年3月1日 星期二

讓 2D Server 變成偽 3D Server

Server 會使用數格子的原因其來有自, 不過數格子的方式已不太能滿足現今 3D MMORPG 的需求, 不但功能被限制住了, 很多可以做的很精彩的 Gameplay 也被綁死, 在數據精細度上也不夠, 因此如果要讓 Gameplay 豐富一點的話, Server 就必須做一些改變.

如果 Server 2D 數格子的架構, 並且也沒有浮點數的情況下, 該如何讓 2D Server 變成3D Server ? 下面將詳細的解說做法, 雖然沒有實作, 不過應該是可行的, 而且是在 Server 不需大修改, 只要小小的增加新功能的情況下即可完成, 這方法也適用在所有的 2D Server.

所謂的數格子就是如同很久以前的 2D RPG 遊戲一樣, 走一步就是一格, 打怪的距離是用格子計算, 施法的距離也是用格子計算, 這些在 2D MMORPG 中都算正常, 但是在 3D MMORPG 中可就不一樣了.



如上圖, 這是一張由上往下看的俯視圖, 紅色代表敵人, 藍色代表玩家, 兩個都是近戰系的攻擊方式, 假設攻擊距離是 2, 敵人可以打到玩家, 玩家也可以打到敵人, 看起來是很正常.



如上圖, 當視角切換到側面的時候觀看, 一個在山頂, 一個在山谷, 使用目測就可以很清楚的判斷兩者的距離絕對是超過攻擊距離, 但是為何彼此還能夠互打呢? 這就是 2D Server 直接拿來做成 3D MMORPG 所產生的結果.


這個 Bug 可以避免, 比如說把敵人種遠一點, 或是中間有阻檔牆等等都可以輕易解決, 但是這樣的結果卻大大了限制住 3D MMORPG 應該可以有的 Gameplay.

現在有一個可以彌補這個缺陷的方式, 就是我們讓 2D Server 變成3D Server, 會稱為偽 3D是因為它並不是真正的 3D, 而是我們透過一些方法來讓它真的很像 3D;



想像一下 2D Server, 角色是存在於一格一格的格子之中, 就如同一個棋盤一般





現在把這格子給 3D , 角色存在的位置就代表一個小立方體.





再想像一下, 如果角色是存在於一個 n x n 格的立方體陣中呢? 或是你可以想像成一個 nxn 格的魔術方塊中.

這就是為什麼要稱為偽 3D Server的原因, 因為角色的 Z 軸也是使用數格子的方式來計算.

為了樣讓 Server Z 軸資料, 所以場景編輯器在輸出場景資料的時候也必須輸出一個高度圖 (Height Map), 這個高度圖是一個 2維陣列,

HeightMap[n][n] = { {5, 2, 5, 2, 5....} …. }
陣列裡每個值就是代表每一格格子的高度.

除了地表的高度之外, 阻檔物的高度也必須計算在內, 因為除了距離之外, 我們還可以讓 Gameplay 更豐富一點 (後面會提到), 有一點必須注意, 因為高度是使用格子的方式在計算, 所以在某些情況下高度要使用四捨五入的方式來計算.



如上圖, 當高度在格子的一半以下時則以不足的條件捨去, 反之若大於一半則無條件增加一格, 會這樣做的原因是因為格子很大, 難免會有高度落於格子的之間, 會使用四捨五入的方式是因為可以讓玩家在視覺上的誤差減到最小, 如此就可以避免感覺距離夠卻打不到, 或是距離明明有差卻還被打到的感覺.

Server 有了高度圖後就要開始處理計算的問題, 在任何的距離運算都必須把高度納入運算之中, 有兩種方法, 一種是每次運算時依角色的 x,y 去取得當時的高度, 另一種則是角色的資料本身就有代表高度的 z .

在此建議使用後者, 為角色新增一個 z , 因為前者的方法每次計算距離時都要去取得高度, 而後者則只有在角色移動的時候才需要去取得高度更新至 z , 之後計算距離時只要把 z 值直接拿來使用即可, 當計算一多時, 兩者之間效能上就會有很大的差異.

假設有一個 Vertor3 class, 裡面有 x,y,z , 細部就不說明了, 只列出兩個重要的函式.

inline int Vector3::Length()
{
 return x * x + y * y + z * z;
}

inline int Vector3::Distance(const Vector3 &kPos)
{
 return (*this - kPos).Length();
}

求得 A B 的距離 nDistance, 這個 nDistance 代表的是 A B 共有幾格的距離,
int nDistance = A.Distance(B);

為了運算速度考量, Length 中並沒有開平方根, 因此攻擊距離 nAttackRange 必須相乘.
if ( nDistance <= nAttackRange * nAttackRange)
{
 // 攻擊.
}

有了 z 軸後就不會有低形高低落差時可以互打的情況, 也為 Gameplay 增色不少


既然有了高度圖, 如果只拿來當作距離運算的話豈不太浪費了, 高度圖能做的還不止如此.





如上圖, 2D Server 角色走一步後就可從山谷瞬間移至山頂, 所以在中山頂與山谷之間必須要有阻檔牆, 或是完全交由 Client , 高度圖在此還有一個功能, 就是可以當做高低落差來使用.

當現在的格子高度與下一步的高度差太多時, 上坡時就可以限制無法移動, 但是下坡時依然可以移動, 而且也省去了做阻檔牆的工時, 除此之外, 這高度也不限於只能在地形上使用, 地上若有障礙物 (如石塊, 桌子..etc) 一樣適用, 甚至還可以做到和 3D Server 一樣的效果, 就是角色可以跳至障礙物上, 而且在障礙物上依然可以進行戰鬥.

這個除了可以拿來給玩家角色使用外, 還可以當作敵人在做路徑搜尋時的碰撞使用, 就不會產生敵人從山谷瞬間移至山頂的情況, 此時只要判斷 z 的差距即可.

此外高度圖還另有妙用, 可以做出媲美 3D Server 的視覺阻檔效果.


如上圖, 在這些地形下其實是不應該被攻擊的, 或者說攻擊不到彼此, 因為地形或地物阻礙了攻擊, 雖然說這效果一般都是以浮點向量運算來達成, 不過數格子的偽 3D一樣可以做到.

先求得 A B 的距離, 這個 nDistance 代表的是 A B 共有幾格的距離,
int nDistance = Sqrt(A.Distance(B)); // 這裡的 Distance 必須要開根號.

有了距離就可以求得 A B 的斜率 S, 但是若 Server 沒有浮點數的話, 斜率就會變得不精準, 在此就要使用定點浮點數的技巧 (不要使用乘法, 速度較慢).

X0000000 00000000 00000000 0fffffff

Server 的數值型態為Int 32 bit 有號數, 扣除最左邊一個正負號 X還有 31 bits, 為了求稍微精準一點, 我們小數 f 取兩位, 也就是 0.99 = 7 bits, 最後剩餘的 0 共有 24 bits = -16777214 ~ 16777215 這個數值區間對於 2D Server 來說是夠用了, 因此

Vector3 A1 = A << 7; // 除了空出右邊 7 bits 之外, 也順便將左邊一位正負號去除, 若有需要請自行加回來.
Vector3 B1 = B << 7;
Vector3 S = (B1 – A1) / (nDistance << 7); // nDistance 要先求出然後才能轉換成定點浮點數.

為什麼 nDistance 要先求出然後才能轉換成定點浮點數? 因為效能問題, nDistance 所代表的是 A B 點有幾格, Server是數格子的, Server 的資料也是以格子計算, 所以一次只要數一格就好, 不需要細到數半格或是 0.2 格之類的.

int nAttack = 1; // 0 = 不攻擊, 1 = 攻擊.
Vector3 kPos = A1;
Vector3 kNewPos;

for (i = 0; i < nDistance; i ++)
{
 kPos += S; // 加上斜率.
 kNewPos = kPos >> 7; // 定點浮點數轉回長整數.

 nHeight = GetHeightMap(kNewPos .x, kNewPos.y); 
 // 角色攻擊高度應該要高於角色原點(腳底)的高度甚至可以依身高而有所不同.
 if (角色所在高度 + 角色攻擊高度 < nHeight) // 小於 nHeight 表示該點是在障礙物之中.
 {
  nAttack = 0;
  break;
 }
}

if (1 == nAttack)
{
 // 攻擊.
}

高度圖為 2D Server 完成了幾件事,

1. 解決了高低落差時的攻擊距離問題.
2. 解決了障礙物高低落差時的角色行走問題, 甚至可以跳躍, 也可以摔死.
3. 增加了角色攻擊的障礙物判定.

1, 2 都是解決問題, 3 則是增加了更豐富的變化,

2011年2月17日 星期四

MMORPG 中的敵我感知系統

自從電腦遊戲誕生以來, 有許多的遊戲開發者無不絞盡腦汁的想要寫出能夠接近人類思維的 AI 系統, 希望 AI 夠聰明, 反應夠快, 能夠應付各種狀況, 有著和人類一樣的五種感官(視覺, 聽決, 嗅覺, 味覺, 觸覺), 在這麼多的 AI系統中, 敵我感知系統應該算是最廣泛分佈在各種類型的遊戲之中, 但是由於 MMORPG 的 Server 端需要應付大量的 GamePlay 運算及資料處理, 所以在敵我感知系統這一塊算是最弱的項目之一.

從最早的 2D MMORPG開始說起, 那時候的敵我感知系統被簡化到只偵測敵人的接近範圍, 這算是最簡單, 最基本, 也是一直被大量的遊戲並延用之今的一種方法, 它有一個專屬的名詞 - 生命感知, 隨著時間的演進, 漸漸的, 生命感知有些許的進步, 能夠分辨不同的屬性, 陣營, 敵我強弱...等等.

MMORPG 最早的生命感知應該是 1997 年Ultima Online 的 2維(2D) 數格子的方式, 這種方式至今仍有一些遊戲在使用中, 後來為了增加遊戲的精密度, 除了地圖的碰撞之外, 數格子的生命感知已漸漸的被淘汰, 大都已改成向量運算, 從早期的長整數向量到現在大量使用的浮點數向量, 除了精密度提升之外, 也為 GamePlay 帶來了更多的可能性.

如果說Ultima Online 是圖形化 MMORPG 的鼻祖, 那麼1999年 EverQuest 可以算是 MMORPG 的革命者, 這款經典遊戲裡有許多概念依然被現今大量的 MMORPG 所沿用著, 同時也是第一款將敵我感知推升到 3維(3D) 的 MMORPG, 3D化的敵我感知系統更為遊戲帶來了極為豐富的變化.

EverQuest 的敵我感知系統中除了生命感知之外, 還有一個非常重要的感知系統, 那就是視覺感知, 簡單的說就是隱形 (和我們一般熟知的盜賊潛行不一樣), 也許它不是第一個有隱形功能的 MMORPG, 不過我認為它是第一個將隱形功能發揮到極至的 MMORPG, 甚至是最後一個, 因為到目前為止我仍然沒有玩到能夠超越它這個系統的 MMORPG.

敵我都會隱形, 但是更重要的是, 不論敵我也都有可視隱形的能力, 這些隱形或可視隱形的能力, 對於敵人來說可能是天性, 也就是這種敵人天生本來就有這種能力, 或者是因為法術的加持; 而對玩家角色來說, 這些能力可能來自於某些職業的法術, 或是一些特殊裝備的附屬能力.

除此之外, SOE 還為 EverQuest 玩家的法術系統設計了一個不穩定因素 (只會影響部份法術), 也就是玩家的隱形術有可能比預期的時間還早消失, 而這個不穩定因素來自於施展隱形術玩家的某項屬性(還會透過一些公式計算), 這個屬性關係到了施法者施展所有法術的穩定性.

可以想像有時候為了穿越一大片敵人或是穿越一個高等區域時, 身上的隱形術有可能消失或是被看穿的刺激感嗎? 或是在某個區域裡, 只有某種法師型的敵人擁有可視隱形的能力, 因此玩家在穿越時就會刻意的避開這些敵人. 或是因為不小心現形或被看穿了, 但是敵人等級低所以不太想理, 然後拖了一大串的火車到處跑的狀觀場面與刺激感 (拖太多也是會被打死的...), 也有因為自己拖火車碾死其它玩家的場面....(樂~), 當然也有自己被其它玩家火車給碾死的慘痛經驗... (怒!!!)

EQ 的視覺感知帶來了什麼? 它為 GamePlay帶來了很多的變化, 也為玩家帶來了很多的驚喜, 這真的很難用三言兩語來說盡, 相信有深入玩過的人應該都會很懷念那種驚喜的感覺.... XD

2002 年 Final Fantasy XI Online 為 MMORPG 的敵我感知帶來了新的體驗, 除了生命感知之外, 還有消形(視覺), 消聲(聽覺), 消味(嗅覺), 在遊戲中我們稱之為三消.

某些敵人還細分到某項能力特別的強, 如視覺看穿隱形, 或是靠聽覺聽腳步聲, 味覺嗅出玩家的蹤跡, 不過其骨子裡還是 EQ 的那一套, 只是換了不同的名詞而已, 這三種實質上的功能可以說是一模一樣, 而且在 FF 11當中, 並沒有把這套系統給發揚光大, 它有為 GamePlay帶來些許的變化, 但是它的影響遠遠不及 EQ 中的隱形術.

2010年末, RIFT: Planes of Telara開始 CB, RIFT 又為玩家帶來了新的敵我感知系統, 除了一般的生命感知之外, RIFT 也加入了視覺感知系統, 不過這個視覺感知和 EverQuest 是不一樣的, RIFT 因為有和 WOW 一樣的 PVP 系統, 所以為了公平起見, RIFT 應該也是沒有隱形術的, RIFT這套視覺感知系統不算是首創, 因為在單機遊戲中出現過很多例子, 比如說 魔鬼戰將(Commandos), 潛龍諜影(Metal Gear Solid)...等等.

如上圖, 扇形的綠色區域為敵方的視覺感知範圍, 若玩家不在感知範圍內的話則不會被發現, 不過 RIFT 的視覺感知的角度會比上圖來得大一點, 而且和單機遊戲不一樣的是扇形不會轉來轉去掃描, 而是敵人的前方在哪, 扇形就轉到哪, 所以遇到會巡邏走動的就要隔外小心敵人忽然轉向.

這視覺感知系統只有在人型或類人型的敵人才有, 而且根據研究好像只會出現在有職業屬性的敵人上, 一般的怪物還是以生命感知為主, 不過人型怪光有視覺感知是不夠的, 所以這類型的敵人是同時擁有兩套感知系統的.

如下圖, 藍色圓點代表敵人, 綠色區域為視覺感知範圍, 紅色區域為生命感知範圍.

會發現的原因是有一次為了閃避敵人, 卻沒有注意其它的敵人, 就這樣沿著如下圖的路線從敵人身旁走過, 而這隻敵人之前為了做任務有打過, 所以知道他的敵我感知範圍很大, 但是這次走這麼近居然沒有被發現, 後來整個 CB 6 就花了一些時間開始研究 RIFT 的敵我感知系統.

RIFT 的視覺感知還不止如此, 他的半徑範圍會隨著職業而有所不同, 近戰類的職業半徑較小, 遠程攻擊職業的半徑就很大, 這時才解開在 CB 4 時就一直很納悶的問題 (我是從 CB 4開始參與的), 問題就是 "為何 RIFT 的遠程攻擊的敵人敵我感知範圍會這麼的大, 這樣的設計很容易 Add, 難道開發小組不知道嗎?”. 相信以後其它的 End-User 應該也會發覺到才是.

即然有了這套獨特的敵我感知系統, RIFT 小組也沒浪費它的特性, 下面就舉個例子, 它在某些地方的敵人配置非常的巧妙.

在一般的 MMORPG 中, 遇到如下圖的敵人配置, 如果要從 A 走到 B 的話, 可以有幾種選擇, 清掉上排的怪, 或下排, 或全清, (一般的 MMORPG 因為只有生命感知, 所以通常範圍都有相當程度的大小).

但是在 RIFT 中你可以有另外一個選擇, 你可以把他當作匿蹤遊戲玩 XD (誤), 同一個地方為了解任務難免會路過或者就是任務區挑怪打而來很多次, 不過要小心, 通常也會有巡邏的敵人走來走去.

除此之外, RIFT 的敵我感知系統是會被障礙物所遮蔽的, 它的障礙物遮蔽比 WOW 更勝一籌, WOW的障礙物遮蔽以某方面來說可以說是假的, 即使是繞到樹後方(不論大中小樹), 只要敵人是遠程攻擊型的一樣會停留在樹的另一邊, 法術及弓箭照樣穿樹而過, 當然我們都知道是為了節省 Server 運算效能考量.

不過 RIFT的障礙物遮蔽可就不同了, 它做的非常的漂亮, 對於這個障礙物遮蔽當然也花了些時間研究了一下, 一度還以為是 Server 有計算多邊形或是有好的障礙物篩選演算法, 甚至是利用雲端運算 (User 的電腦是雲), 結果應該都不是(我猜的), 而且很容易就可以做到.

也許再過個幾年, 又會有更新潁的敵我感知系統出現在 MMORPG 吧.

2011年2月15日 星期二

Early-Z

介紹 Early-Z 之前先來回顧一下 Z-Buffer 的歷史. 並且會以 DirectX 為例.

最早還沒有 3D 加速卡之前, 為了解決深度排序的問題, 因此發展出了各式各樣的演算法, 但是對於太複雜的物件時, 深度排序依然無法完美的解決問題, 因此後來就發展出來了 Z-Buffer, 最早的 Z-Buffer 是屬於軟體 Z-Buffer, 後來才成為 3D 加速卡的標準規格.

Z-Buffer 解決了複雜的深度排序卻也帶來了另一個問題, 就是 3D 加速卡才有的問題-像素填充率, 簡單的說明像素填充率就是顯示卡每秒能畫在螢幕上的像素的數量是有限制的, 而每顆 不同等級GPU 所能承載的像素填充率也不相同.

但是隨著遊戲畫面越來越精細, 物件越來越多, 這個問題就越來越嚴重, 畫面上同一個像素的位置被重覆畫了太多次, 我們稱之為 Overdraw, 因此後來才會有建議在 Render 時儘量由前畫到後, 利用 Z-Buffer 來過濾 Overdraw 的問題, 這方法就和軟體 3D 為了解決深度排序的問題由後畫到前不同了.

只是演算法沒有完美的, 硬體也沒有完美的, 在硬體的限制下, DrawPrimitive SetRenderState 呼叫越多次, 速度就會越慢, 所以在複雜的畫面之下該用什麼方式 Render 一直是被討論的話題. 由前畫到後, 可以解決 Overdraw 的問題, SetRenderState 卻有可能成了效能的瓶頸; 使用 Texture 優先 Render, 可以解決頻繁切換 SetRenderState 的問題, 但是又有 Overdraw 的問題, 哪個方法好是見人見智的問題, 每套不同的 3D Graphics Engine 的處理方式也不一樣.

幾年前 nVidia 發表了一項技術 – Early-Z, Early-Z 的做法就是先將畫面的物件(所有或部份物件)的 Z 值先寫入到 Z-Buffer 中, 如此一來就可以解決一些問題, 但是結果並非如此完美. 為了讓物件 Z 值先寫入 Z-Buffer 中, 物件必須渲染兩次, 第一次先將 Z 值寫入, 第二次才真的將物件繪製出來. 這個方法帶來了極大的代價, 而且並不是每種狀況都適用.

1. 渲染過程變得更複雜化, 因為第一次只寫入 Z 值不繪出物件, 遊戲中幾百甚至幾千個物件的 RenderState 都必須設過, 即使是在最優化的情況下, 一定數量的 RenderState 設定還是無法避免, 更不用說你還要使用什麼樣的 Render 順序, 以及將物件給提取出來...等等, 這些都是效能的消耗.

RenderState 就像一個個的開關, 在 DirectX 9 以前, 一次只能開關一個, 所以要設幾個就必須呼叫幾次, 到了 DirectX 10之後, 新的硬體架構允許一次設定多個 RenderState, 也就是一次就可以開關好幾個 RenderState.

那使用 Shader 呢? Engine 裡某些基本事情還是必須做的, 更不用提 SetShader Code還要再做一次的問題了.

2. 由於物件要繪製兩次, 因此呼叫 DrawPrimitive 的次數也會變成兩倍, 所有的 DirectX 教學都再三的警告我們要減少呼叫 DrawPrimitive 的次數, 因為 DrawPrimitive 是很非常耗效能的.

3. 物件的頂點全部都要計算兩次 (Vertex Shader).

4. Alpha 物件無法參與 Early-Z.

那麼難道 Early-Z 真的沒有用嗎? 當然是有用, 但是要看情況, 就以 MMORPG 來說應該是不適用的. 第一個使用 Early-Z 技術的就是 Doom3, 但是它使用是有原因的, 因為它的 Pixel Shader 使用量非常的吃重, 因此 Early-Z 可以來幫助它減輕 PS 的負擔.

隨著技術的演進, 硬體也跟著演進,

這是 DirectX 9 管線的一角, 圖中可以看出, Depth Test 是在 PS 之後, 因此 Early-Z 的卻是有幫助的.


下圖則是 DirectX 10 管線的一角, 圖中可以看出, Depth Test 是在 PS 之前, 是的, Early-Z 已和 Z-Buffer 一樣變成由硬體加速的標準規格了.


但是, DirectX 10已移除了 AlphaTest texkill 來做, 因此如果有 Alpha 物件的話就會使得 Early-Z 失效.

感覺 DirectX 9 更應該使用軟體 Early-Z 不是嗎? 但是你願意付出上面那幾點中的 1 ~ 3點來換取小小的效能提升嗎? Nvidia 說可以提高 20% 以上的效能, 這是有限制的, 在 MMORPG 這麼複雜的畫面之下根本是不可能達到這個數字, 而且動態物件很多, Alpha 物件也不少, Early-Z 能做的其實非常的有限.

在公司新的 Terrain Engine 中做過實驗, 雖然Terrain Render 是純 Shader, 但是因為分割成一個個小塊, 因此 DrawPrimitive 和其它都無法避免多次的呼叫, 在效能上不但沒有提升反而變得更糟.

另外就是渲染過程變得更複雜化就覺得很不值得, 程式會變得更難維護, 更不用說當你的效能瓶頸不是在 像素填充率 或是 PS 上時, Early-Z 就更顯得沒有意義.

DirectX 10 之後 Early-Z 已由硬體支援加速, 因此軟體 Early-Z 也更沒有必要.

想要在 DirectX 9 上使用 Early-Z 最好先確定.
  1. 效能瓶頸在像素填充率上.
  2. 效能瓶頸在 PS 上.
當瓶頸在像素填充率上時, 先看看你的 Render 順序的演算法是否有弄好, 物件的裁剪是有否做好, View Frustum Culling 是否有做好.

當瓶頸在 PS 上時, 先看你的 PS 是否處理太多的事情, 某些運算是否能夠移到 VS 就先做好. 目前公司的要求的低標配備是不允許有大量的 PS 運算的.

然後, 最後再來考慮 Early-Z 的問題, 如果你效能瓶頸不再這兩項上, 使用 Early-Z 只會增加困擾而已.

什麼時候 DirectX 9 該用 Early-Z? 以目前來說是不需要.

1. 想要減少 RenderState 的呼叫必須使用 DirectX 10以上, 以目前公司要求的低標配備是不允許的.

2. 想要減少 DrawPrimitive 的呼叫必須使用就必須使用 Hardware Instance, 這功能要 DirectX 9.0c Shader 3.0 以上才有的功能, 以目前公司要求的低標配備也是不允許的.

那麼當公司的低標配備提高了的話是否就可以使用軟體 Early-Z? 那時候就不如直接上 DirectX 10 以上吧.

2010年5月27日 星期四

Ogre3D 與 wxWidgets

Ogre3D 與 wxWidgets 連結的基本元件已處理完畢, 接下來就是要正式開始製作工具的內容, 會先從地圖編輯器開始.

所謂工欲善其事必先利其器, 因此工具 UI 的 Layout 顯得格外的重要, 這部份可能要花一點時間來構思, 目前正在下載 UDK 當做參考, 雖然說是不可能寫成像它那樣, 不過拿來參考看看它的 Layout 及其它的功能應該還算不錯, 也順便想想是不是有哪些東西當初有漏掉了, 或是換另一個方法來做或許會更好.

2010年5月19日 星期三

wxWidgets 基本元件

 wxWidgets 基本元件還在進行中, 因為有太多的雜事, 進度比預想中還慢, 紀念一下第一個四分割視窗.