C#开发WPF/Silverlight动画及游戏系列教程(Game Tutorial):(二十五)完美捕捉精灵之神器 -- HitTest...
??? 怪物們都出現(xiàn)了,如何選中自己心儀的怪是主角目前首要做的事。
??? 為了進(jìn)行鼠標(biāo)狀態(tài)區(qū)別,我首先對(duì)鼠標(biāo)變化規(guī)則進(jìn)行約束:當(dāng)鼠標(biāo)在屏幕上空曠地圖區(qū)域移動(dòng)時(shí),鼠標(biāo)光標(biāo)形態(tài)表現(xiàn)為默認(rèn)光標(biāo) (0號(hào)光標(biāo)圖片),當(dāng)鼠標(biāo)經(jīng)過精靈(懸停于其上方)時(shí)則變成發(fā)光光標(biāo)(1號(hào)光標(biāo)圖片),如果指向的精靈對(duì)象為敵對(duì)狀態(tài)時(shí)則鼠標(biāo)光標(biāo)變?yōu)楣艄鈽?biāo)(2號(hào)光標(biāo)圖片),當(dāng)使用魔法快捷鍵時(shí),鼠標(biāo)光標(biāo)變成凝法狀態(tài)(3號(hào)光標(biāo)圖片)。
??? 接下來要做的就是用代碼來實(shí)現(xiàn)這些規(guī)則。要實(shí)現(xiàn)鼠標(biāo)光標(biāo)的變換,我們首先得將這4個(gè)光標(biāo)加入到系統(tǒng)中,這里我新建一個(gè)名為Cursors的文件夾用于保存這4個(gè)光標(biāo),具體添加方法詳見第五節(jié)。然后在使用的時(shí)候如果該代號(hào)光標(biāo)不存在,則通過數(shù)據(jù)流將光標(biāo)添加進(jìn)系統(tǒng)內(nèi)存中:
??????? public static Cursor[] GameCursors = new Cursor[4];
??????? /// <summary>
??????? /// 返回指定標(biāo)號(hào)光標(biāo)
??????? /// </summary>
??????? /// <param name="sign">標(biāo)號(hào)</param>
??????? /// <returns>光標(biāo)</returns>
??????? public static Cursor getCursor(int sign) {
??????????? if (GameCursors[sign] == null) {
????????????????GameCursors[sign] = new Cursor(new FileStream(string.Format(@"Cursors\{0}.ani", sign), FileMode.Open, FileAccess.Read, FileShare.Read));
??????????? }
??????????? return GameCursors[sign];
??????? }
??? 一切就緒,現(xiàn)在正式開始實(shí)現(xiàn)游戲窗體的鼠標(biāo)移動(dòng)事件。既然是鼠標(biāo)在地圖上滑動(dòng)時(shí)產(chǎn)生的效果,因此我們首先添加游戲窗體鼠標(biāo)移動(dòng)事件:MouseMove="Window_MouseMove",然后在后臺(tái)代碼中的Window_MouseMove方法里寫入相應(yīng)內(nèi)容:
??????? private void Window_MouseMove(object sender, MouseEventArgs e) {
??????????? this.Cursor = e.Source is QXSpirit ? Super.getCursor(1) : this.Cursor = Super.getCursor(0);
??????? }
??? 假如鼠標(biāo)經(jīng)過的對(duì)象是QXSpirit類型,則鼠標(biāo)的光標(biāo)變?yōu)?/span>1號(hào),其他情況時(shí),鼠標(biāo)光標(biāo)變?yōu)?/span>0號(hào)。這種效果對(duì)于做習(xí)慣了.NET網(wǎng)站開發(fā)的朋友們來說再熟悉不過了,好比導(dǎo)航欄上的鼠標(biāo)懸停圖片切換CSS或JS效果。
??? 這么短短一句話即實(shí)現(xiàn)了最簡(jiǎn)易的精靈對(duì)象捕獲,我們先來測(cè)試一下程序:
??? 細(xì)心的朋友會(huì)發(fā)現(xiàn),雖然是勉強(qiáng)實(shí)現(xiàn)了但這其實(shí)并不準(zhǔn)確;因?yàn)楫?dāng)鼠標(biāo)并不在怪物實(shí)體上時(shí),鼠標(biāo)仍然會(huì)顯示為1號(hào)光標(biāo)(如下圖),是代碼出問題了嗎?
??? 其實(shí)問題并非出在代碼上,這是因?yàn)榫`的圖片源是背景透明的PNG或GIF格式圖片,就拿上圖中的“絕對(duì)無敵”來說吧,它的每幀圖片為200*200尺寸(如下圖),
??? 它的有效實(shí)體只是該圖片的中間區(qū)域,而它的旁邊有著比較大面積的透明無效區(qū)域,雖然在顯示上透明區(qū)域是不會(huì)顯示出來的,但是它整個(gè)作為200*200尺寸的Image類型控件而存在。因此當(dāng)鼠標(biāo)在游戲窗體上移動(dòng)時(shí),只要處于這200*200區(qū)域內(nèi)時(shí)均會(huì)顯示為1號(hào)光標(biāo)而并不會(huì)理睬它是否停留在精靈的有效實(shí)體部分。
??? 精靈的圖片源均為位圖類型,目前我暫時(shí)還未發(fā)現(xiàn)在WPF/Silverlight中如何實(shí)現(xiàn)將位圖轉(zhuǎn)換成矢量圖的高效直接方法。因此目前解決這個(gè)問題的方式只有兩種,第一種為通過對(duì)當(dāng)前拾取對(duì)象的圖片源進(jìn)行點(diǎn)對(duì)點(diǎn)的顏色拾取,然后判斷當(dāng)前鼠標(biāo)的位置相對(duì)于圖片源中的點(diǎn)是否為透明,如果不透明則拾取該精靈,具體方法如下:
??????? /// <summary>
??????? /// 獲取圖片源某點(diǎn)顏色
??????? /// </summary>
??????? public static Color getImagePointColor(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());
??????????? }
??????????? //藍(lán)pixels[0] 綠pixels[1]? 紅pixels[2] 透明度pixels[3]
??????????? return Color.FromArgb(pixels[3], pixels[2], pixels[1], pixels[0]);
??????? }
??? 此方法的優(yōu)點(diǎn)是精確,可以定位到精靈有效實(shí)體的任一像素角落;而缺點(diǎn)是只能在WPF中使用且性能不好,更麻煩的是必須將之放 Try{}Catch{}塊內(nèi)使用,否則極易出錯(cuò),因?yàn)榫`的圖片切換太快了。
??? 解決此問題的另一方式為通過定義精靈實(shí)體區(qū)域參數(shù)public double[] EfficaciousSection來實(shí)現(xiàn),此方法也是我推薦使用的方法,兼顧WPF/Silverlight。
??? EfficaciousSection由4個(gè)數(shù)組成,以上圖為例,它的EfficaciousSection = new double []{80,125,50,145},其中第一個(gè)數(shù)字表示紅色區(qū)域左邊線距離圖片左的距離,第二個(gè)數(shù)字表示紅色區(qū)域右邊距離圖片左邊距離,第三個(gè)數(shù)字表示紅色區(qū)域上邊距離圖片頂部的距離,第四個(gè)數(shù)字代表紅色區(qū)域底邊距離圖片頂部的距離,上面所說的紅色區(qū)域即為精靈的有效實(shí)體區(qū)域,在后面的鼠標(biāo)點(diǎn)擊或移動(dòng)判斷中,只有當(dāng)鼠標(biāo)進(jìn)入精靈的有效實(shí)體區(qū)域時(shí)我們才變換鼠標(biāo)光標(biāo)。
??? 精靈獲得了有效實(shí)體區(qū)域,是否代表可以完美準(zhǔn)確的捕捉精靈對(duì)象了呢?我們將窗體鼠標(biāo)移動(dòng)方法進(jìn)行如下改進(jìn):
??????????? if (e.Source is QXSpirit) {
??????????? ????QXSpirit Spirit = e.Source as QXSpirit;
??????????????? Point p = e.GetPosition(Spirit);
??????????????? if (p.X >= Spirit.EfficaciousSection[0] && p.X <= Spirit.EfficaciousSection[1]
??????????????????? && p.Y >= Spirit.EfficaciousSection[2] && p.Y <= Spirit.EfficaciousSection[3]) {
??????????????????? this.Cursor = Super.getCursor(1);
??????????????? } else {
??????????????????? this.Cursor = Super.getCursor(0);
??????????????? }
??????????? }
??? 然后再運(yùn)行一下游戲,結(jié)果更奇怪的事情出現(xiàn)了:
??? 如上圖,此時(shí)當(dāng)鼠標(biāo)停在主角身上時(shí)竟然沒有變換光標(biāo)圖片,是代碼出問題了嗎?當(dāng)然也不是。我們還是得從圖片上找原因。此時(shí)怪物的圖片遮擋住了主角,因此當(dāng)鼠標(biāo)懸停在主角身上時(shí),系統(tǒng)卻仍然判斷當(dāng)前捕獲的是“絕對(duì)無敵”,并且鼠標(biāo)也未進(jìn)入它的有效實(shí)體范圍,因此鼠標(biāo)光標(biāo)仍然是0號(hào)。
??? 怎么辦?搞了這么久到頭來仍然是一場(chǎng)空。有朋友提出了將圖片裁剪成剛好包裹住精靈有效實(shí)體區(qū)域不就好了。想法是好的,但是將造成每一幀圖片都為不同尺寸規(guī)格,在動(dòng)作中如何切換?每張圖片都得定義它距離容器Canvas左上角的距離,一個(gè)怪物幾百張圖片,每張都要定義,這將大大增加游戲的開發(fā)負(fù)擔(dān)。
??? 難道沒有完美的解決方案了嗎?WPF/Silverlight中最不起眼但卻有著極其重要作用的神器登場(chǎng)了!對(duì),就是它了:HitTest(命中測(cè)試)。
??? 稱之為命中測(cè)試,不如叫它穿透點(diǎn)擊來得更形象些。因?yàn)樗鼜?qiáng)大到只要游戲窗口中有的東西,它都能抓出來,想抓幾個(gè)抓幾個(gè),想抓到什么深度(Zindex)就抓到什么深度;更甚者,它可以肢解封裝的控件直接抓取其內(nèi)部任意對(duì)象控件;完成以上各種任務(wù)如若探囊取物搬輕盈且高效,僅僅是通過模擬鼠標(biāo)點(diǎn)擊幾乎忽略不計(jì)的敏捷捕獲。關(guān)于HitTest的更多相關(guān)知識(shí)及原理請(qǐng)大家自行網(wǎng)上查閱,這里不具體講解了。接下來我們看下圖:
??? 在游戲中如何使用HitTest進(jìn)行對(duì)象捕獲的原理在上圖中已經(jīng)描述得非常清楚了,接下來看我如何通過代碼進(jìn)行實(shí)現(xiàn):
首先我定義一個(gè)精靈容器用于將捕獲到的所有精靈進(jìn)行收容管理:
??????? List<QXSpirit> SpiritList = new List<QXSpirit>();
??? 接下來定義HitTest的過濾器HitFilter,用于篩選HitTest捕獲的對(duì)象,我們只需要捕獲QXSpirit類型對(duì)象即可,然后將之添加進(jìn)精靈容器:
??????? public HitTestFilterBehavior HitFilter(DependencyObject dObject) {
??????????? if (dObject is QXSpirit) {
??????????????? SpiritList.Add(dObject as QXSpirit);
??????????? }
??????????? return HitTestFilterBehavior.Continue;
??????? }
??? 每執(zhí)行一次過濾器后,我們必須重復(fù)以上過程繼續(xù)向更深層次進(jìn)行捕獲,因此在HitTest結(jié)果HitResult中執(zhí)行繼續(xù)操作以供向下個(gè)節(jié)點(diǎn)輪循:
??????? public HitTestResultBehavior HitResult(HitTestResult result) {
??????????? return HitTestResultBehavior.Continue;
??????? }
??? HitFilter和HitResult是HitTest中控制流程非常重要的參數(shù),定義完它兩后接下來我們?cè)诖绑w的鼠標(biāo)移動(dòng)事件中進(jìn)行如下HitTest命中測(cè)試:
??????? private void Window_MouseMove(object sender, MouseEventArgs e) {
??????????? SpiritList.Clear();
??????????? Point p = e.GetPosition(Carrier);
??????????? VisualTreeHelper.HitTest(
??????????? Carrier,
??????????? new HitTestFilterCallback(HitFilter),
??????????? new HitTestResultCallback(HitResult),
??????????? new PointHitTestParameters(p));
??????????? if (SpiritList.Count > 0) {
??????????????? for (int i = 0; i < SpiritList.Count; i++) {
???????????????? if (isEfficaciousSection(SpiritList[i].EfficaciousSection, e.GetPosition(SpiritList[i]))) {
??????????????????????? this.Cursor = Super.getCursor(1);
??????????????????????? label3.Content = SpiritList[i].Name; //調(diào)試用
??????????????????????? break;
???????????????? } else {
??????????????????????? this.Cursor = Super.getCursor(0);
???????????????? }
?????????????? }
?????????? ?}
??????? }
??? 每次鼠標(biāo)移動(dòng)的時(shí)候我們必須清空精靈容器,然后對(duì)鼠標(biāo)當(dāng)前的點(diǎn)在Carrier中的位置進(jìn)行點(diǎn)擊測(cè)試,通過前面的HitFilter和HitResult過濾后得到所有位于鼠標(biāo)位置的精靈放進(jìn)容器,然后遍歷精靈容器里的所有精靈,只有當(dāng)該點(diǎn)位于精靈Canvas里的位置處于精靈的有效實(shí)體區(qū)域時(shí),才算真正的捕獲到了精靈。一旦捕獲到了精靈則同時(shí)更改鼠標(biāo)光標(biāo)為1號(hào)光標(biāo)然后退出循環(huán);這里我為了測(cè)試是否精確的捕獲了精靈對(duì)象,設(shè)置了名叫label3的文本來顯示抓取到的精靈名字。
??? 到此就完成了整個(gè)HitTest精確捕獲精靈流程,下面我在地圖密集的區(qū)域內(nèi)添加30個(gè)擁有不同的名字的怪物精靈,然后嘗試移動(dòng)鼠標(biāo)去分別捕獲,通過label3中的名字顯示該方法實(shí)現(xiàn)起來是極其準(zhǔn)確的,比衛(wèi)星定位還要精確與高效^_^||:
??? 已經(jīng)能完美捕捉想要的精靈了,但是如何讓被捕獲的精靈進(jìn)行特效顯示呢?目前的網(wǎng)絡(luò)游戲中最常用的方式有兩種:1、對(duì)被捕獲的精靈進(jìn)行描邊;2、讓被捕獲的精靈半透明化。
??? 第一種方法的實(shí)現(xiàn)需要首先為精靈控件中的身體部分控件添加一個(gè)WPF專有的OuterGlowBitmapEffect效果:
??????? <Image x:Name="Body" Stretch="Fill">
??????????? <Image.BitmapEffect>
??????????????? <OuterGlowBitmapEffect GlowColor="Blue" GlowSize="5" Noise="0" Opacity="1" />
??????????? </Image.BitmapEffect>
??????? </Image>
??? 具體意思就是在精靈身體圖片不透明區(qū)域進(jìn)行外發(fā)光:藍(lán)色,5像素寬,無噪音,完整透明度。其運(yùn)行效果如下圖:
??? 看到這張圖的時(shí)候或許大家開始有些欣喜若狂了,但是我想告訴大家:此方法絕對(duì)的行不通,為什么?一方面此方法只能在WPF中使用,它的原理是時(shí)時(shí)動(dòng)態(tài)查找圖片不透明區(qū)域的邊緣,然后對(duì)邊緣路徑進(jìn)行發(fā)光濾鏡處理;而另一方面由于它是對(duì)圖片源不透明區(qū)域進(jìn)行時(shí)時(shí)的邊緣查找,將極大的占用游戲的界面線程資源,是極其不友好的表現(xiàn)方式。
??? 因此,為了同時(shí)適應(yīng)WPF/Silverlight,我使用第二種方法作為最終解決方案。這種方法實(shí)現(xiàn)起來簡(jiǎn)單多了,只需要在前面代碼的基礎(chǔ)上加進(jìn)行如下更改:
??????? private void Window_MouseMove(object sender, MouseEventArgs e) {
??????????? ……
??????????? if (SpiritList.Count > 0) {
??????????????? bool targetIsFound = false;
??????????????? for (int i = 0; i < SpiritList.Count; i++) {
????????????????if (!targetIsFound && isEfficaciousSection(SpiritList[i].EfficaciousSection, e.GetPosition(SpiritList[i]))) {
??????????????????????? this.Cursor = Super.getCursor(1);
??????????????????????? SpiritList[i].Opacity = 0.6;
??????????????????????? targetIsFound = true;
???????? ???????????????label3.Content = SpiritList[i].Name;
??????????????????? } else {
??????????????????????? if (!targetIsFound) { this.Cursor = Super.getCursor(0); }
??????????????????????? SpiritList[i].Opacity = 1;
??????????????????? }
??????????????? }
? ??????????}
??????? }
??? 在鼠標(biāo)移動(dòng)事件中僅僅增改6行代碼即可以輕松的實(shí)現(xiàn),運(yùn)行效果如下:
??? 到此為止即完美實(shí)現(xiàn)了對(duì)精靈的精確捕獲。忽忽,是不是感覺向完整的游戲框架目標(biāo)又邁出了一大步?
??? 在此,我還想對(duì)那些極端的朋友說一下:由于目前暫時(shí)采用多線程結(jié)構(gòu),在單核CPU電腦以及Win2003以前的操作系統(tǒng)上運(yùn)行時(shí),怪物密集的地方會(huì)有些卡。但是這根本代表不了游戲引擎的最終性能,教程還有非常非常多的內(nèi)容沒有講到,優(yōu)化的技術(shù)還在后面呢,太多了就不一一羅列了,大家應(yīng)該都明白本系列既然取名為教程,代表的就是一個(gè)由淺入深的過程,很多人連基礎(chǔ)原理都沒弄清楚,源碼對(duì)你有何意義?
??? 小結(jié):HitTest功能強(qiáng)大到幾乎無所不能,它是我們實(shí)現(xiàn)打怪與施放魔法的前提條件。下一節(jié)我將講解精靈面板界面,以及精靈3大基本屬性(生命、魔力、經(jīng)驗(yàn)值)表現(xiàn)形式的實(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)作者同意必須保留此段聲明,且在文章頁面顯著位置給出原文連接,否則保留追究法律責(zé)任的權(quán)利。
轉(zhuǎn)載于:https://www.cnblogs.com/alamiye010/archive/2009/07/11/1521418.html
總結(jié)
以上是生活随笔為你收集整理的C#开发WPF/Silverlight动画及游戏系列教程(Game Tutorial):(二十五)完美捕捉精灵之神器 -- HitTest...的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 不要让海浪中奔腾的豪情任岁月摧折,不要让
- 下一篇: c#启动单个程序(互斥机制)