本节的内容很酷哦,作为圣诞礼物送给大家~嘿嘿
在通常的网络游戏中,物品、装备、技能、快捷按钮等窗口中的图标都是可以相互拖放的,不同的栏目有着不同的限制,例如技能图标不能拖放到物品栏及装备栏中,且不是所有的魔法技能都可以拖放(如被动技能等);而非装备类的所有物品则无法拖放到角色的装备栏中。那么本节我将向大家讲解如何在本教程示例游戏中添加物品栏及装备栏,并实现它们之间双向物品交换的两种模式:拖放模式和双击模式。
首先制作物品栏。这里我使用的是官方的工具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