C#開發WPF/Silverlight動畫及游戲系列教程(Game Course):(十二)神奇的副本地圖
前面幾節詳細的講解了游戲地圖的完整構造,比較有難度的是關於地圖內層如障礙物的實現。A*算法往往能讓眾多的初學者望而止步,斜度α地圖則更需要一定的幾何知識及抽象思維。很多朋友就問了:什麼年代了,都在說面向對象、提高開發效率,難道就沒有大眾化可以讓各層次能力的朋友們都能輕松制作地圖引擎的方法嗎?大家是否還記得上一節中遺留的一個小懸念,殺手澗就是它了:神奇的副本地圖。
大家先看上圖,左邊的是地圖表現層,它的尺寸為800*600。右邊的則是我通過Photoshop在原圖基礎上勾勒出來的該地圖的副本,同樣它的尺寸也為800*600。這裡特別要提的是該副本是由簡單純色調組成的,因此能夠壓縮到極小的容量,幾乎忽略不計,這是它能作為我們得力工具的前提,也是Silverlight制作基於網頁游戲的必要條件。好了,接下來我們詳細介紹一下此副本:大家對照原圖很容易會發現它上面的黑色其實代表的就是地圖中的障礙物,那大片的白色區域呢?其實就是我們可以任意通行的區域了。至於黃色,聰明的朋友應該也不難猜到,它代表的是地圖中的傳送點。當然,您還可以在此副本中增加例如紅色代表陷阱,綠色代表特殊NPC等等。是否覺得像畫畫一樣的?嘿嘿,這就是我主張的面向對象的游戲編程創新思想了。到此地圖副本制作完成了,那麼該如何利用它呢?
精華又出現啦,來看看優美的拾色方法:
//圖片拾色
private Color pickColor(BitmapSource bitmapsource, int x, int y) {
CroppedBitmap crop = new CroppedBitmap(bitmapsource as BitmapSource, new Int32Rect(x, y, 1, 1));
byte[] pixels = new byte[4];
try {
crop.CopyPixels(pixels, 4, 0);
crop = null;
} catch (Exception ee) {
MessageBox.Show(ee.ToString());
}
//藍pixels[0] 綠pixels[1] 紅pixels[2] 透明度pixels[3]
return Color.FromArgb(pixels[3], pixels[2], pixels[1], pixels[0]);
}
太強大了,有了它就好比呂布拿上方天畫戟-游刃有余!(該方法只能在WPF使用,至於如何在Silverlight中調用,Silverlight3.0將會給您一個完美的解決方案。^_^)
副本地圖的作用是非常凶猛的,在它上面我們可以自由繪畫出紅黃藍綠青橙紫等等N多顏色來描繪不同的地圖屬性,然後實現類似以下操作:
1、如果主角采到的點是黑色就相當於主角碰到了障礙物,這時主角的動作即為停止。
2、如果是傳送點,則根據坐標范圍(或其他條件等)判斷是傳送到哪張地圖;
3、如果是陷阱則將觸發什麼事件,如去血或被傳送,或是刷怪等等;
4、當然還可以有其他顏色,假如游戲中有飛行坐騎等元素存在(實現2D地圖中的三維空間),那麼同樣可以用一個例如藍色來代表空中障礙物區域,或用紫色來代表陸地和空中均屬的障礙物,這些都是相當靈活的。
5、白的則為可以通行,主角在上面可以正常移動。
更美妙的是,此方法可以與A*尋徑相結合,從而創造出更加優美的角色移動(例如帝國時代中采取的就是它獨特的改進型A*,所以根據游戲自身的特點您可以對A*進行優化,Gameres論壇有很多高手的文章,大家可以參考一下)。接下來大家來回憶一下第七節講到的A*尋徑算法,此方法找到的路徑中如果有經過障礙物倒還好,但是如果沒有障礙物的,那麼此路徑全程中將或多或少會有些折疊的地方(如下圖)。
人類總是希望將東西做得完美導致本節的重點出現了:如何通過神奇的副本地圖來優化A*尋路,讓它更加貼近真實呢?這裡我們需要先理解一個關鍵知識點:主角所處的地圖中所有的點與副本地圖中所有的點都是一一對應(映射)的關系。例如假設主角在地圖中的坐標為(356,248),那麼此坐標對應副本地圖坐標也同樣為(356,248),這樣我們就可以通過函數方法,將主角的坐標點作為參數在副本地圖中找該點的顏色,看看顏色分別是黑的,還是白的,或是黃的等等,從而映射回主角的地圖可知主角當前處於地圖中是障礙物,還是可同行區域,或是傳送點等等。了解了原理後,下面我就用代碼來實現它:
首先我們需要寫出兩種移動方法(具體代碼就不列出來了,在本教程的目錄中有下載):第一種我定義為NormalMove移動方法,它就是我第一節中講到的點與點之間的直線移動,在此方法中我稍微改動了一些,使坐標定位到主角的腳底,並且將移動目標終點記錄到Point Target中。第二我將之定義為AStarMove移動方法,它就是我前面幾節講到的A*尋路方式。設置好後,我們就可以根據從副本地圖中獲取的點的顏色判斷來調用相應的移動模式了。
那麼在主角移動的時候,它的X,Y坐標屬性是時時更新的,因此我們需要一個線程去捕獲它,並且在此線程中時時判斷主角是否采在了黑色點上(障礙物)。那麼這裡我采用了第二節中所講到的CompositionTarget界面線程,注冊了該線程dispatcherTimer1_Tick事件後,接下來就進入關鍵代碼了:
private void Carrier_MouseLeftButtonDown(object sender, MouseButtonEventArgs e) { Point p = e.GetPosition(Carrier); //假如點到的地方不是障礙物 if (pickColor(Deeper, (int)p.X, (int)p.Y) != Colors.Black) { target = p; NormalMove(p); //直線移動 //AStarMove(p); //A*尋路移動 } } BitmapSource Deeper = new BitmapImage((new Uri(@"Map\Deeper.jpg", UriKind.Relative))); //設置地圖副本 int X, Y; //主角當前的窗口真實坐標(非縮放) Point target; //主角移動的最終目的 private void dispatcherTimer1_Tick(object sender, EventArgs e) { X = Convert.ToInt32(Canvas.GetLeft(Spirit) + SpiritCenterX * GridSize); Y = Convert.ToInt32(Canvas.GetTop(Spirit) + SpiritCenterY * GridSize); //message.Text = "坐標:" + X + " " + Y; message.Text = pickColor(Deeper, X, Y).ToString(); //假如碰到障礙物則采用A*尋路 if (pickColor(Deeper, X, Y) == Colors.Black) { AStarMove(target); } else if (pickColor(Deeper, X, Y) == Colors.Yellow) { //假如是傳送點則條到坐標(200,20) storyboard.Stop(); Canvas.SetLeft(Spirit, 200 - SpiritCenterX * GridSize); Canvas.SetTop(Spirit, 20 - SpiritCenterY * GridSize); } //用白色點記錄移動軌跡 rect = new Rectangle(); rect.Fill = new SolidColorBrush(Colors.Snow); rect.Width = 5; rect.Height = 5; Carrier.Children.Add(rect); Canvas.SetLeft(rect, X); Canvas.SetTop(rect, Y); }
上面代碼已經進行了很詳細的描述:首先我在鼠標左鍵事件中判斷點擊的地方是否是障礙物(點擊的點在副本地圖中是否是黑色的),是的話主角就不移動,否則就啟動簡單的直線移動。接下來我們需要設置幾個變量它們分別存儲副本地圖的圖片源、主角的X,Y精確坐標以及主角最終移動目的點。設置好後最後就是在界面線程中時時獲取主角的X,Y坐標,並且判斷主角當前位置是否是障礙物了。如果是則將當前的移動由普通直線移動轉換成A*尋路移動;同時我們還可以判斷如果是黃色的話則傳送到點(200,20),當然大家還可以設置其他很多顏色來啟動相應的事件,是不是很神奇?嘿嘿。
下面兩圖分別是采用A*尋路的主角移動和改進型的A*尋路(直線移動+A*尋路)的主角移動:
從上圖可以明顯看到,單純的使用A*進行主角移動及饒過障礙物是不自然的,路線很機械且並不真實。而通過直線移動+副本地圖+A*實現的改進型A*尋路所實現的主角移動則可謂幾乎接近完美,與現實吻合。
這裡要順帶提一下的是:本節只為演示需要所以我使用的副本地圖只有一窗口大小,因此當主角移動到窗口外時pickColor()方法的參數X,Y超出了副本地圖的大小導致拋出異常並使程序關閉這是肯定的而並非BUG,這樣也同樣證明了pickColor()方法的正確性。
小結:關於如何對A*進行改進其實也並非一定需要配合該副本地圖,我們同樣的可以通過時時比較主角的(SpiritWindowX,SpiritWindowY)坐標對應的障礙物Matrix[SpiritWindowX, SpiritWindowY]是否==1來判斷是否啟動尋路。但是本節的目的是要告訴大家副本地圖的萬用功能,別的方法能做的,它同樣能做,而且它擴展性更強,描述對象更簡單且直觀,這才叫解放思想、面向對象!其實類似此副本地圖的使用在魔獸世界等著名的游戲中都有用到,只是我這個算是簡單版的。雖然它構造簡單但是在輔助地圖引擎方面卻顯示出強大的威力。目前我感到唯一的缺憾就是還沒有研究出如何使用它單獨的進行有效精確的尋徑(即完全拋棄復雜的A*尋徑算法),曾想過在副本地圖中畫些尋路點(即障礙物旁邊可以起到誘導饒過障礙物的點),當主角碰到障礙物時,尋找副本地圖中離自己最近的尋路點作為臨時目標進行移動,到了此臨時目標後再向最終目標移動,這樣就可以巧妙饒過障礙物了。當然這在不太復雜的地圖中是完全可行的,而且仿佛比A*更強悍且簡單,但是一旦遇到復雜的地圖了呢?我們該如何優化它?還需要對此感興趣的朋友們開動一下腦筋,或許真能想出完美的解決方案呢。
不管怎樣,些許的缺陷是永遠無法掩埋它的偉大的。連美工都可以輕松的參與到游戲地圖引擎的主要設計中,難道不是游戲設計界中神聖的革新嗎?快速開發是我們的理想與追求,只需要一張副本圖片就可以較好的替代以往制作一個游戲地圖引擎必須的四大步驟:切割、拼圖、編輯、轉換。是不是很邪惡?簡化了過程卻實現同樣的效果,讓人覺得措手不及!這就是21世紀!YEAR一個。
下一節我將就主角與地圖及其他對象進行相對移動進行詳細講解,敬請關注。