C#开发WPF/Silverlight动画及游戏系列教程(Game Tutorial):(十)斜度α地图的构造及算法...
??? 在當(dāng)前的網(wǎng)絡(luò)游戲中,地圖基本都是采取一定斜度的拼裝地圖,這其中存在兩種斜度地圖的構(gòu)造方式:
??? 第一種我稱(chēng)之為偽斜度地圖:該類(lèi)型地圖表現(xiàn)層圖片為斜度的,但地圖基底障礙物等的構(gòu)造則實(shí)為正方形,如下圖:
??? 其實(shí)最典型的例子就是上一節(jié)所演示的內(nèi)容了,地圖是斜的,但是我們卻用垂直的障礙物對(duì)其進(jìn)行基底布局,這就是典型的偽斜度地圖了。
??? 這樣的地圖優(yōu)點(diǎn)在于可以使用簡(jiǎn)單直接的地圖構(gòu)造算法(上一節(jié)中有詳細(xì)的講解),同樣也可以擁有漂亮的畫(huà)面。但是,當(dāng)大家將之運(yùn)用到實(shí)際游戲運(yùn)行中將會(huì)發(fā)現(xiàn)人物在饒過(guò)不規(guī)則障礙物時(shí)會(huì)很別扭。當(dāng)然,如果您能制作出優(yōu)秀的地圖編輯器并且擁有與之默契匹配的地圖的話,這些或許不會(huì)成為大問(wèn)題。
??? 第二種即為真實(shí)的:斜度α地圖。下面我將就該類(lèi)型地圖的構(gòu)造基本原理及其在WPF/Silverlight中的基本實(shí)現(xiàn)及算法進(jìn)行講解。
??? 首先解釋一下關(guān)于α角度。通常來(lái)講,對(duì)局式或戰(zhàn)棋類(lèi)回合制網(wǎng)絡(luò)游戲鐘愛(ài)于60度、45度角的地圖構(gòu)造;而2D-MMORPG網(wǎng)絡(luò)游戲則無(wú)一定規(guī)律,可以是任意角度(根據(jù)地圖開(kāi)發(fā)策劃設(shè)定進(jìn)行統(tǒng)一的約束與規(guī)范)。下面我們先來(lái)看一張圖:
??? 該圖以夢(mèng)幻古龍對(duì)局戰(zhàn)斗時(shí)的場(chǎng)景為例進(jìn)行了非常詳細(xì)的分析標(biāo)注。首先我們要講解實(shí)際對(duì)應(yīng)我們WPF窗口的坐標(biāo)系W坐標(biāo)系。圖中的W(x),W(y)即對(duì)應(yīng)我們窗口坐標(biāo)系的X軸(Canvas.LeftProperty)和Y軸(Canvas.TopProperty) (當(dāng)然這其中有相對(duì)偏移量,我們后面會(huì)講到)。這兩軸是垂直的,也是我們最最常見(jiàn)的直角坐標(biāo)系了,這很好理解。而該游戲的界面坐標(biāo)系G坐標(biāo)系,我在圖中用藍(lán)色的線進(jìn)行了標(biāo)識(shí),其中G(x)正方向與G(y)負(fù)方向的夾角就是α了(在該游戲中為60度)。上圖我為了方便演示及說(shuō)明,假設(shè)它的兩個(gè)坐標(biāo)系均相交于一個(gè)點(diǎn),這個(gè)點(diǎn)我將之定義為坐標(biāo)原點(diǎn)(0,0)。大家回憶一下前兩節(jié)講解的關(guān)于障礙物數(shù)組Matrix[,]。該數(shù)組參數(shù)是無(wú)法有負(fù)值的,如Matrix[-1,5]、Matrix[6,-7]等,這些都是語(yǔ)法中非法的。所以假設(shè)按照坐標(biāo)與障礙物等值對(duì)應(yīng)原理(后面章節(jié)還會(huì)講到非等值對(duì)應(yīng)—參數(shù)集體偏移量),如Matrix[5,5]對(duì)應(yīng)G坐標(biāo)系(5,5)、Matrix[8,9]對(duì)應(yīng)G坐標(biāo)系(8,9),那么構(gòu)建的地圖布局將如上圖:紅色和藍(lán)色的菱形均代表G坐標(biāo)系下的坐標(biāo)點(diǎn)(按照GridSize放大過(guò)的),菱形上方也有標(biāo)識(shí)它們?cè)?/span>G坐標(biāo)系下的坐標(biāo)。很清晰的可以看見(jiàn),只要x或y值中有負(fù)值的,均為紅色,此區(qū)域?yàn)榻巧珶o(wú)法移動(dòng)到的區(qū)域(在上圖中我用淺綠色區(qū)域進(jìn)行標(biāo)識(shí))。而在其他正值區(qū)域中,菱形則均為藍(lán)色的。
??? 如上圖,下部份那大片藍(lán)色的區(qū)域(G系正值區(qū)域)就是我們最終的游戲真實(shí)場(chǎng)景所在了,在斜度的游戲世界里,所有人物角色的移動(dòng)范圍均在其中。上一節(jié)中有講過(guò),WPF窗口的左上角為原點(diǎn)(0,0)。但是上圖的W坐標(biāo)系的原點(diǎn)(0,0)卻在中上部(已經(jīng)標(biāo)識(shí)出來(lái),該點(diǎn)與左上角的x距離為a,y距離為b,圖中有標(biāo)注)。如果我們需要在WPF窗口中構(gòu)造出與上圖一模一樣的場(chǎng)景效果,就涉及到關(guān)于坐標(biāo)偏移量的計(jì)算了。就拿這個(gè)例子來(lái)說(shuō),該游戲此場(chǎng)景中的W(0,0)其實(shí)就是WPF的(Canvas.Left(a),Canvas.Top(b));同理,點(diǎn)W(40,60)則為(Canvas.Left(a+40),Canvas.Top(b+60)),以此類(lèi)推。這樣就很簡(jiǎn)單了不是嗎?只要將所有的人物角色對(duì)象它們自身的坐標(biāo)按以上方式進(jìn)行換算,那么就可以在WPF中實(shí)現(xiàn)以上的地圖坐標(biāo)系構(gòu)造了。這與上一節(jié)中講解到的關(guān)于將主角的坐標(biāo)定位到它的腳底如出一轍。所以在大多數(shù)的游戲中都會(huì)存在一個(gè)關(guān)鍵點(diǎn),比如MMORPG最典型了,主角始終處于屏幕的正中間(除非他位于地圖的8個(gè)邊緣,后面的章節(jié)會(huì)講到相關(guān)內(nèi)容),顯而易見(jiàn)它的腳底坐標(biāo)就是游戲的關(guān)鍵點(diǎn),其他所有的物體都以之為參照物進(jìn)行相對(duì)于它的位移。關(guān)于地圖和物體的移動(dòng)問(wèn)題需要大量的篇幅,相關(guān)內(nèi)容我將放在后面的章節(jié)中再進(jìn)行講解。那么下面的內(nèi)容就暫時(shí)以WPF窗口左上角為W系的(0,0)坐標(biāo)原點(diǎn),進(jìn)行簡(jiǎn)單演示在此基礎(chǔ)上構(gòu)建的斜度α的地圖。
??? 有了以上的基礎(chǔ)知識(shí)作鋪墊,后面的內(nèi)容可謂小兒科了。
??? 首要任務(wù):構(gòu)造W坐標(biāo)系與G坐標(biāo)系的換算公式。假設(shè)W坐標(biāo)系下某點(diǎn)坐標(biāo)為(W(x),W(y)),該點(diǎn)在G坐標(biāo)系中的坐標(biāo)為(G(x),G(y)),那么它們之間的換算公式即為:
??? W(x)=(G(x)-G(y))*sinα
??? W(y)=(G(x)+G(y))*cosα
??? G(x)=(W(y)*sinα+W(x)*cosα)/(2*sinα*cosα)
??? G(y)=(W(y)*sinα-W(x)*cosα)/(2*sinα*cosα)
??? 這乃本節(jié)之精華所在,好比上帝的右手,阿拉丁的神燈無(wú)所不能、天下無(wú)敵!汗一個(gè)。。。好了,有了該法寶,那么我們開(kāi)始練練手吧,看看一個(gè)斜度60的地圖是如何構(gòu)造的。
??? 首先我將該公式用代碼來(lái)表示寫(xiě)成兩個(gè)方法,方法名很明確,它們的作用是分別獲取某點(diǎn)在G坐標(biāo)系和W坐標(biāo)系中的坐標(biāo):
??????? //將窗口坐標(biāo)系中的坐標(biāo)換算成游戲坐標(biāo)系中的坐標(biāo)(縮小操作)
??????? private Point getGamePosition(double x, double y) {
??????????? return new Point(
??????????????? (int)((y + (x / 1.732)) / GridSize),
??????????????? (int)((y - (x / 1.732)) / GridSize)
??????????? );
??????? }
??????? //將游戲坐標(biāo)系中的坐標(biāo)換算成窗口坐標(biāo)系中的坐標(biāo)(放大操作)
??????? private Point getWindowPosition(double x, double y) {
??????????? return new Point(
??????????????? (x - y) * 0.886 * GridSize,
??????????????? (x + y) * 0.5 * GridSize
??????????? );
??????? }
??? 這里我進(jìn)行了簡(jiǎn)單的正弦與余弦的取值,即sin60=0.886,cos60=0.5。一張地圖中是不可能存在兩個(gè)α值的,所以本例在定義好α=60度后,我直接取它的正弦與余弦值這將有效的提高運(yùn)算效率。
??? 接下來(lái)就是構(gòu)建障礙物了,只有通過(guò)它我們才能非常直觀的看到這個(gè)斜度α地圖的構(gòu)造:
??????????? //構(gòu)建障礙物
??????????? for (int x = 10; x < 20; x++) {
??????????????? for (int y = 1; y < 10; y++) {
??????????????????? Matrix[x, y] = 0;
??????????????????? rect = new Rectangle();
??????????????????? //構(gòu)建菱形
??????????????????? TransformGroup transformGroup = new TransformGroup();
??????????????????? SkewTransform skewTransform = new SkewTransform(-10, -25);
??????????????????? RotateTransform rotateTransform = new RotateTransform(54);
??????????????????? transformGroup.Children.Add(skewTransform);
??????????????????? transformGroup.Children.Add(rotateTransform);
??????????????????? rect.RenderTransform = transformGroup;
????????? ??????????rect.Fill = new SolidColorBrush(Colors.GreenYellow);
??????????????????? rect.Opacity = 0.3;
??????????????????? rect.Stroke = new SolidColorBrush(Colors.Gray);
??????????????????? rect.Width = GridSize;
??????????????????? rect.Height = GridSize+2;
??????????????????? Carrier.Children.Add(rect);
??????????????????? Point p = getWindowPosition(x, y);
??????????????????? Canvas.SetLeft(rect, p.X);
??????????????????? Canvas.SetTop(rect, p.Y);
??????????????? }
??????????? }
??? 這里我用菱形方塊真實(shí)的模擬障礙物視覺(jué)效果。接下來(lái)就是在上一節(jié)代碼的基礎(chǔ)上將窗口鼠標(biāo)左鍵事件中相關(guān)的坐標(biāo)值通過(guò)上面寫(xiě)的兩個(gè)方法getGamePosition(double x, double y)和getWindowPosition(double x, double y)進(jìn)行替換,實(shí)際上改動(dòng)的地方不過(guò)4處,我用黃色背景色進(jìn)行了標(biāo)識(shí)(…….號(hào)表示該段代碼與上一節(jié)不變),具體如下:
??????? private void Carrier_MouseLeftButtonDown(object sender, MouseButtonEventArgs e) {
??????????? Point p = e.GetPosition(Carrier);
??????????? //進(jìn)行坐標(biāo)系縮小
??????????? Point start = getGamePosition(Canvas.GetLeft(Spirit) + SpiritCenterX,
?Canvas.GetTop(Spirit) + SpiritCenterY);
??????????? Start = new System.Drawing.Point((int)start.X, (int)start.Y); //設(shè)置起點(diǎn)坐標(biāo)
??????????? Point end = getGamePosition(p.X, p.Y);
??????????? End = new System.Drawing.Point((int)end.X, (int)end.Y); //設(shè)置終點(diǎn)坐標(biāo)
????????? ??…….
??????????? if (path == null) {
??????????????? MessageBox.Show("路徑不存在!");
??????????? } else {
??????????????? Point[] framePosition = new Point[path.Count]; //定義關(guān)鍵幀坐標(biāo)集
??????????????? for (int i = path.Count - 1; i >= 0; i--) {
??????????????????? //從起點(diǎn)開(kāi)始以GridSize為單位,順序填充關(guān)鍵幀坐標(biāo)集,并進(jìn)行坐標(biāo)系放大
??????????????????? framePosition[path.Count - 1 - i] = getWindowPosition(path[i].X, path[i].Y);
??????????????? }
????????? ??…….
??????????????? //用白色點(diǎn)記錄移動(dòng)軌跡
??????????????? for (int i = path.Count - 1; i >= 0; i--) {
??????????????????? rect = new Rectangle();
??????????????????? rect.Fill = new SolidColorBrush(Colors.Snow);
??????????????????? rect.Width = 4;
??????????????????? rect.Height = 4;
??????????????????? Carrier.Children.Add(rect);
??????????????????? Point target = getWindowPosition(path[i].X, path[i].Y);
??????????????????? Canvas.SetLeft(rect, target.X);
??????????????????? Canvas.SetTop(rect, target.Y);
??????????????? }
??????????? }
??????? }
??? 如果大家能將上一節(jié)中講解的內(nèi)容都吸收的話,那么可以將修改的部分與上一節(jié)的代碼進(jìn)行對(duì)比,再結(jié)合本節(jié)前部分內(nèi)容的講解就會(huì)慢慢的理解了(請(qǐng)大家發(fā)散自己的思維吧)。
??? 到這我們就完成了該斜度60的地圖構(gòu)造。按Ctrl+F5看看我們的成果吧:
??? 嘿嘿,A*尋路將我們的路徑描繪得非常明顯,顯然主角是沿著這樣一條斜度60的路線饒過(guò)這個(gè)片菱形障礙物區(qū)域的。而因?yàn)榇死覍?/span>W(0,0)點(diǎn)和G(0,0)都定位在窗口的左上角,所以根據(jù)本節(jié)前部分關(guān)于G坐標(biāo)系的講解,上圖中紅色的區(qū)域即為含有負(fù)值的區(qū)域,所以不被尋路方法所識(shí)別。您可以嘗試對(duì)該區(qū)域進(jìn)行點(diǎn)擊,它將告訴您路徑不存在,從而也證明了我們這個(gè)坐標(biāo)系的構(gòu)建是成功的。
??? 最后為了讓朋友們能更好的理解比較,我將本節(jié)例子中的障礙物代碼拷貝替換掉上一節(jié)的障礙物代碼,并將菱形換回成正方形,代碼如下:
??????????? //構(gòu)建障礙物
??????????? for (int x = 10; x < 20; x++) {
??????????????? for (int y = 0; y < 10; y++) {
??????????????????? Matrix[x, y] = 0;
??????????????????? rect = new Rectangle();
??????????????????? rect.Fill = new SolidColorBrush(Colors.GreenYellow);
??????????????????? rect.Opacity = 0.3;
??????????????????? rect.Stroke = new SolidColorBrush(Colors.Gray);
??????????????????? rect.Width = GridSize;
??????????????????? rect.Height = GridSize;
??????????????????? Carrier.Children.Add(rect);
??????????????????? Point p = getWindowPosition(x, y);
??????????????????? Canvas.SetLeft(rect, p.X);
??????????????????? Canvas.SetTop(rect, p.Y);
??????????????? }
??????????? }
??? 然后大家可以嘗試運(yùn)行一下新的Window9.xaml,運(yùn)行效果圖如下:
??? 同樣的障礙物代碼在第九節(jié)的直角地圖坐標(biāo)系中是垂直方型顯示的,而在本節(jié)中則為菱形方式顯示。同樣證明了本節(jié)斜度α地圖的成功構(gòu)造!
??? Good idea!難道不是嗎?嘿嘿,比較復(fù)雜也是非常重要的一節(jié)。如果你能掌握它,想想A*尋路在不同模式地圖中可以完全忽略基本單元格的樣式(無(wú)論是正方形的,或是菱形的,甚至六邊形的)可謂無(wú)所不能,想想斜α地圖在實(shí)際游戲開(kāi)發(fā)中的運(yùn)用幾乎無(wú)處不在,這難道不是莫大的成就嗎?
??? 至此,關(guān)于地圖表層的基礎(chǔ)知識(shí)基本都講解完了,地圖構(gòu)造原理涉及的知識(shí)方方面面,有人就打這樣的比方:一個(gè)好的地圖編輯器決定著一款游戲的成功與否,這毫不為過(guò)。所以我們離真正完成它還有很長(zhǎng)的路要走。下一節(jié)我將介紹如何實(shí)現(xiàn)地圖的遮罩效果,敬請(qǐng)關(guān)注。
作者:深藍(lán)色右手出處:http://alamiye010.cnblogs.com/
教程目錄及源碼下載:點(diǎn)擊進(jìn)入(歡迎加入WPF/Silverlight小組 WPF/Silverlight博客團(tuán)隊(duì))
本文版權(quán)歸作者和博客園共有,歡迎轉(zhuǎn)載。但未經(jīng)作者同意必須保留此段聲明,且在文章頁(yè)面顯著位置給出原文連接,否則保留追究法律責(zé)任的權(quán)利。
轉(zhuǎn)載于:https://www.cnblogs.com/alamiye010/archive/2009/06/17/1505344.html
總結(jié)
以上是生活随笔為你收集整理的C#开发WPF/Silverlight动画及游戏系列教程(Game Tutorial):(十)斜度α地图的构造及算法...的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: ASP常用内置函数
- 下一篇: [转]在Winform(C#)中使用Fl