程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> C# >> 關於C# >> C#開發WPF/Silverlight動畫及游戲系列教程(Game Tutorial):(四十五)

C#開發WPF/Silverlight動畫及游戲系列教程(Game Tutorial):(四十五)

編輯:關於C#

C#開發WPF/Silverlight動畫及游戲系列教程(Game Tutorial):(四十五)制作精美的可任意拖放對象的物品欄及裝備欄

在通常的網絡游戲中,物品、裝備、技能、快捷按鈕等窗口中的圖標都是可以相互拖放的,不同的欄目有著不同的限制,例如技能圖標不能拖放到物品欄及裝備欄中,且不是所有的魔法技能都可以拖放(如被動技能等);而非裝備類的所有物品則無法拖放到角色的裝備欄中。那麼本節我將向大家講解如何在本教程示例游戲中添加物品欄及裝備欄,並實現它們之間雙向物品交換的兩種模式:拖放模式和雙擊模式。

首先制作物品欄。這裡我使用的是官方的工具toolkit:ListBoxDragDropTarget,只需設置它的DragDrop.AllowDrop="True",那麼它內部的ListBox類型容器中的所有Item將均可以被拖放。相關示例大家可以參考紫色永恆的這篇文章:最新Silverlight Toolkit中的Drag&Drop支持。

僅僅使用ListBox的默認配置還是與實際游戲中的物品欄容器顯示相距太遠,物品欄直觀上給我們的印象是一個N*M的網格形容器,裡面的對象是圖標,對內對外都應能做到任意拖放。為了滿足上述需求,我們還得在ListBox的模版改造上下些工夫。

默認情況下, ListBoxDragDropTarget內部拖放時並不能作用於virtualized容器(ListBox的默認容器),因此我們首先需要重寫ItemsPanel 的ItemsPanelTemplate來實現ListBox內部子對象之間能相互拖放:

<toolkit:ListBoxDragDropTarget x:Name="dragDropTarget" window:DragDrop.AllowDrop="True">
 <ListBox x:Name="listBox" ……>
  <ListBox.ItemsPanel>
   <ItemsPanelTemplate>
    <StackPanel />
   </ItemsPanelTemplate>
  </ListBox.ItemsPanel>
 </ListBox>
</toolkit:ListBoxDragDropTarget>

問題又來了,StackPanel僅能實現水平或豎直方向上的子控件排列,仔細想想,要是它能折行,不也是一個網格嗎?既要Panel類型,又要具備折行功能,當然非WrapPanel莫屬。下面只需將<StackPanel />換成<toolkit:WrapPanel Orientation="Horizontal" />即可。同時,我們還必須讓該ListBox背景透明且去掉它的滾動條:Background="Transparent" ScrollViewer.HorizontalScrollBarVisibility="Disabled" ScrollViewer.VerticalScrollBarVisibility="Disabled"才算完美。

物品欄界面制作完了,如何對它內部進行物品顯示呢?這裡需要用到ObservableCollection<QXIcon>對象作為ListBox的子控件數據源,為什麼選擇ObservableCollection<>而不是List<>等普通列表對象呢?因為ObservableCollection<>在內部子對象變化時會即時提交反饋給界面執行重繪更新,而List則不會,大家不妨自行嘗試一下,可以體會到ObservableCollection<>是相當優雅的。

接下來,在游戲中我為主角定義了一個屬性記錄它現有的所有物品代號,當初始化物品欄時,游戲將讀取這些代號,並從xml中的物品詳細資料中查出相應的數據,例如:

<Items>
<Item Code="0" Categoriy="1" IconCode="45" EquipCode="0" Name="武威之逐日衣" Description="★★★★☆閃避很高" Value="0,0,0,0,200,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0"></Item>
<Item Code="1" Categoriy="1" IconCode="46" EquipCode="0" Name="劍影風紗" Description="很漂亮哦,防御很高" Value="0,0,0,0,0,0,0,0,0,200,0,0,0,0,0,0,0,0,0,0,0"></Item>
<Item Code="2" Categoriy="0" IconCode="47" EquipCode="0" Name="滅神之刃" Description="小心,很容易秒殺" Value="0,1450,8044,10,10,0,0,0,0,0,0,0,0,0,0,20,0,0,0,0,0"></Item>
<Item Code="3" Categoriy="0" IconCode="48" EquipCode="1" Name="幻影狂刀" Description="速度極快,殺人無數" Value="0,300,400,10,10,0,-200,0,0,0,0,0,0,0,0,20,0,0,0,0,0"></Item>
<Item Code="4" Categoriy="2" IconCode="49" EquipCode="0" Name="腰帶1" Description="作者無敵懶" Value="0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0"></Item>
<Item Code="5" Categoriy="2" IconCode="50" EquipCode="1" Name="腰帶2" Description="作者無敵懶" Value="0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0"></Item>
……
</Items>

其中Value值中的數據一一對應影響主角的21個基本屬性,最後將這些賦予QXIcon,再將所有的QXIcon綁定到物品欄的ObservableCollection<QXIcon>中,到此就全部實現了物品欄對主角所持物品的顯示:

嘿嘿,怎麼樣?這些物品與背景網格匹配得很好吧~而且物品欄裡的所有物品都可以相互間拖放變換位置,很有意思呢。

完成了物品欄,下面我們用同樣的方法來實現角色的裝備欄。此時問題又來了,裝備欄裡的所有裝備格的排放並非都有規律,一些在這,一些在那:

而不論ListBox套用何種模板,也只能實現內部子對象按相應的規律擺放,要實現裝備任意位置的擺放,我們不妨以一個裝備對應一個ListBoxDragDropTarget 和ListBox來實現。這樣就可以很簡單的布局出主角的9件裝備欄了:

此時我們嘗試一下打開物品欄,任意拖放一個物品到裝備欄中,OK,成功了。但是反過來,從裝備欄中將已裝備的東西拖到物品欄中卻報錯了,而且這個錯誤無法調試,後來經過反復嘗試,發現問題原來出在WrapPanel上,如果不使用WrapPanel,就算在兩個StackPanel之間拖放都不會存在任何問題;而從一個ListBox中將對象拖到另一個WrapPanel中時,卻會出現JS無法調試的錯誤;同樣的,我還曾嘗試使用一個PagedCollectionView來對WrapPanel進行分頁,確實做到了,但是當將對象拖放到WrapPanel的第二頁時,同樣會報JS無法調試錯誤,基本肯定問題出來WrapPanel控件上,希望MS在未來的版本中能將這個實用的控件兼容性與功能完善好。沒辦法,都做到這個地步了,硬著頭皮也得寫完呀,看來只能自己去實現相應的事件功能模塊了。還是從ListBoxDragDropTarget的所有事件的理解著手。ListBoxDragDropTarget在拖放方面的事件真不少:DragEnter、DragLeave、DragOver、Drop、GiveFeedback、ItemDragCompleted、ItemDragStarting、ItemDroppedOnSource、ItemDroppedOnTarget、QueryContinueDrag,其中又可以化分為拖(Drag)和放(Drop)兩類,從字面意思上大家可以很容易理解,正常狀態下,從一個ListBox(這裡記做listBox1)往另外一個ListBox(記做listBox2)拖放控件,當鼠標在listBox1中的一個子控件上按住左鍵不放時,首先觸發的是listBox1的ItemDragStarting事件,然後會卡那麼一下(沒去看具體源碼,或許在執行遍歷,就算在4核的電腦上也同樣會卡一下,可見MS趕工的水平)完成抓取並觸發listBox1的ItemDragCompleted事件,如果是在自身listBox1中放下,則觸發listBox1的ItemDroppedOnSource事件,而如果在listBox2中放開,則先觸發listBox2的Drop事件,再最後觸發listBox1的ItemDroppedOnTarget事件。感覺上去還是比較混亂的,這個拖放做得真是。。。當然,在整個過程中還會觸發另外剩下的那幾個事件,字面上都很好理解,這裡就不細說了。

充分理解DragDrop事件的順序與原理後,我們制作物品欄與裝備欄之間的拖放就輕松多了,注冊相應的事件並對每個物品的類型進行判斷是否可以放置等即可(例如藥水是裝備不了的,而帽子是無法拖到鞋子上的):

物品欄與裝備欄之間除了通過拖放交互,在雙擊某個裝備時,應該實現同樣的裝/卸功能。因此,這裡我為QXIcon添加新的鼠標左鍵雙擊事件:public event MouseButtonEventHandler MouseLeftButtonDoubleClick;並通過如下代碼實現雙擊功能:

doubleClickTimer = new DispatcherTimer() { Interval = TimeSpan.FromMilliseconds(250) };
doubleClickTimer.Tick += (s, e) => { doubleClickTimer.Stop(); };
this.MouseLeftButtonDown += (s, e) => {
 if (doubleClickTimer.IsEnabled) {
  doubleClickTimer.Stop();
  if (MouseLeftButtonDoubleClick != null) {
   MouseLeftButtonDoubleClick(s, e); }
 } else {
   doubleClickTimer.Start();
 }
};

嘿嘿,到此就大功告成了。當然,在換裝時還需要處理主角屬性變化等邏輯,且實際武器衣服的更換同樣會發生在主角身上(類似紙娃娃系統),這些內容在不同的游戲中處理方式不同,具體就不列舉了。下面是最後的效果圖,非常酷吧:

DragDropTarget拖放控件在目前來說限制太多,且支持的類型也很少,還存在BUG。但是它的出現著實讓人迷戀,在它還未完善前,大家如果打算使用相應功能,自行定義實現其實也是不錯的選擇,原理和ChildWindow有異曲同工之處,至少你可以很好的把握住從Drag到Drop整個流程到底誰被抓取,抓取源是什麼,放到了哪個目標源,替換的是哪個對象等等;而這些在目前我所用到的DragDropTarget控件中都很難去實現,或者說極不方便;畢竟這個開源工具集還在不斷的更新與完善中,Silverlight版本更新過快也對其有很大的影響。總來的說,一切美好東西的形成總是需要時間去磨練,期待更好,是我們每位開發者理想的追求。

本節源碼請到目錄中下載,在線演示地址:http://silverfuture.cn

出處:http://alamiye010.cnblogs.com/

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved