前面章节中讲解的包括对象头像面板、Mini雷达地图等窗体都是位置固定的,在处理起来方式多样且简单;而RPGSLG、休闲养成等类型的游戏中往往会大量使用到悬浮且可自由拖动的窗体,比如包裹面板、武器装备面板、个人属性面板、技能面板、系统设置面板等等,这就要求我们必须为游戏量身定做一个通用且易用的ChildWindow控件。那么本节我将为大家讲解如何制作一个包含可拖动头部、关闭按钮及内容主体3大部件高自由度的自定义ChildWindow控件,并用它来实现Mini寻路地图。

首先,我们需要确定该ChildWindow的功能需求,从布局上说包含上述3大部分,且这3部分都是可以用任意对象来实现的,这样才能实现高度拓展;从功能上说,ChildWindow可在游戏窗口中任意拖动,且多个ChildWindow实例之间同样存在一个可协调的层次关系,这方面内容大家可以参考我关于ChildWindow另一篇文章;从整体结构上说,ChildWindow即可以与游戏中与对象轻松交互,同时也不能影响到游戏中其它对象的活动。

那么接下来进入实质性设计阶段,此时我们有3条路可以选择:

1)通过底层的继承来实现。例如新建一个BaseWindow.cs类文件,然后让之继承ControlContentControlUserControl等均可,并通过代码添加并布局好各部分控件,同时实现各部分应有的功能事件,最后让this.Content=总的布局控件或其它方式来完成基类的定义。那么其他的控件就可以通过继承此BaseWindow,在拥有所有BaseWindow功能的基础上再拓展出各自控件自身的特定功能。总的来说这是传统的方案,但是在WPF/Silverlight中易用性不强,因为有着xaml这一层东西在;且目前大多数的开发者都不一定具备底层控件开发的技术与经验,实现起来难度较大。

2)通过Silverlight模板控件来实现。国外很多Silverlight开发者都比较喜欢使用此方式进行控件开发。具体步骤:在项目上右键->添加->新建项->Silverlight模板化控件:

新建好后,大家可以看到的项目结构中出现了一个Themes文件夹,且里面包含着一个名为Generic.xaml的模板文件,主体代码如下:

    <Style TargetType="local:QXChildWindow">

        <Setter Property="Template">

            <Setter.Value>

                <ControlTemplate TargetType="local:QXChildWindow">

                    ……模板内容……

                </ControlTemplate>

            </Setter.Value>

        </Setter>

    </Style>

以后如果还有新的模板添加进项目,那么所有模板的xaml部分均会以如上格式追加到此文件中,只是它们绑定的TargetType不同(cs文件不同)罢了:

当我们打开QXChildWindow.cs文件可以看到,此QXChildWindow模板控件继承自Control,且通过this.DefaultStyleKey = typeof(QXChildWindow);这句话来实现与Generic.xaml中的模板界面对接。既然是非partial文件,因此其拓展起来还是很方便的。我也曾尝试使用此方法来实现ChildWindow,功能是有了,但过程很是别扭,例如模板中对中文的支持不好;重载OnApplyTemplate()方法中的base.OnApplyTemplate()在控件加载中并不灵活等等,个人感觉从拓展度及开发难度上说效果并非最理想。

3)通过用户控件来实现。Silverlight中的用户控件大家应该再熟悉不过了,此时肯定会有朋友质疑它是否真的如此万能?连ChildWindow也可以实现吗?其实用户控件本身继承自UserControl,既然是面向对象的,又为何不能呢?下面是我写的QXChildWindow界面部分代码:

<UserControl x:Class="QXGameEngine.Control.QXChildWindow"

    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" MouseLeftButtonDown="UserControl_MouseLeftButtonDown">

    <Canvas>

        <Canvas x:Name="Head" MouseLeftButtonUp="Head_MouseLeftButtonUp" MouseMove="Head_MouseMove">

            <ContentPresenter x:Name="HeadPresenter" />

            <Rectangle x:Name="CloseButton">

                <ToolTipService.ToolTip>

                    <TextBlock x:Name="Tip" Text="关闭" />

                </ToolTipService.ToolTip>

            </Rectangle>

        </Canvas>

        <Canvas x:Name="Body">

            <ContentPresenter x:Name="BodyPresenter" />

        </Canvas>

    </Canvas>

</UserControl>

整个xaml就是如此简单,但是却蕴涵着高度的灵活性,此话怎讲?不妨先看下面这张结构描述图:

以头部Head为例,它是Canvas类型,因此我们可以将它的Background设置为任意Brush对象,如渐变颜色、图片、乃至视频;并且它内部还包含着一个名为HeadPresenter ContentPresenter,这又是何方神圣?我们可以在后台代码中通过HeadPresenter=object来实现对其完全的重新定义,这意味着HeadPresenter可以是任何东西,比如矩形啦、图片啦、画布啦、甚至新的用户控件,甚至再来个QXChildWindow实例,嘿嘿(当然,这么做是没意义的)。且由于HeadPresenterCanvas画布中,因此对它的布局也是完全自由的。Body部分则是同样的道理,不累述了。接下来定义好对这些对象进行赋值的CLR属性,并通过路由的MouseLeftButtonDownMouseLeftButtonUpMouseMove事件分别实现ChildWindow应该有的功能,最终就完成了这个QXChildWindow控件的制作

如果您到此还不能完全感受到它的强大与灵活?那么接下来我将使用该控件实例出一个mini寻路地图,让大家近距离接触这个牛气烘烘的新家伙。

创建过程是很简单的,只需为游戏窗体注册一个键盘敲下事件,通过设置激发的按钮或按钮组合来显示窗口:

       //键盘按下事件

        private void UserControl_KeyDown(object sender, KeyEventArgs e) {

            //显示寻路地图(组合按住Ctrl+Tab)

            if (e.Key == Key.Tab) {

                if ((Keyboard.Modifiers & ModifierKeys.Control) == ModifierKeys.Control) {

                      ……

                }

            }

         }

这里我用的是Ctrl+Tab键,目前Silverlight3.0版本中键盘处理还不算完善,4.0中将实现所有的键位。接下来我们通过赋予一个QXChildWindow实例相应的参数,如头部宽、高、背景图片,身体宽、高、背景图片等等,并将之添加进Root中。到此,一个Mini地图寻路面板就完成了:

//初始化寻路地图

wayfindingWindow = new QXChildWindow() {

   Left = 105,

   Top = 40,

   HeadLeft = 0,

   HeadTop = 0,

   HeadWidth = 604,

   HeadHeight = 37,

   HeadBackground = new ImageBrush() { ImageSource = Super.GetImage("/Image/Plate/WayfindingMapHead.png") },

   BodyLeft = 0,

   BodyTop = 37,

   BodyWidth = 604,

   BodyHeight = 479,

   BodyBackground = new ImageBrush() { ImageSource = Super.GetImage("/Image/Plate/WayfindingMapBody.png") },

   CloseButtonLeft = 582,

   CloseButtonTop = 0,

   CloseButtonWidth = 22,

   CloseButtonHeight = 22,

   CloseButtonFill = new ImageBrush() { ImageSource = Super.GetImage("/Image/Icon/11.png") },

};

   Root.Children.Add(wayfindingWindow);

简约且简单~这才是我们开发的目的。而其面板中的其他一些内容,如地图名称(Grid),地图画板(Canvas)等则可以很轻松的分别通过前文提到的HeadPresenterBodyPresenter来实现,具体代码就不列出来了,大家可以到源码中查看。

在该Mini寻路地图中,主角位置的刷新是在间隔为500毫秒的后台线程中进行,与Mini雷达地图一样的道理,只有在自身为显示的前提下才会更新,这样不用时将节约出大量性能。

另外关于此Mini寻路地图中的一些小细节,其中的主角位置图标为一张默认朝北的三角图片,当主角朝向改变时,我通过Angle = Leader.Direction * 45来旋转它从而使之与主角实际的朝向吻合;另外,我在主角位置图标与寻路地图中点击的点之间添加了一条钓鱼连接线,模仿《剑侠世界》的啦,嘿嘿。该线可以方便玩家把握自身与目标之间的距离并监视目标点。

写了很多了~不知道大家是否能全部掌握?最后我们运行一下,在游戏中按下Ctrl+Tab来看看劳动成果吧:

当前由于Silverlight自身渲染方式的原因,在MouseMove中实现拖动对象性能并非最好,同时更让人感到奇怪的是它与MouseDownMouseUp同为路由事件,可是却无法实现e.Handled,导致游戏中的命中测试会穿越面上的ChildWindow层,希望4.0或未来的版本能将之做得更好~加油!

源码请到目录中下载,在线演示地址:http://silverfuture.cn

WPF/Silverlight
作者:深蓝色右手
出处:http://alamiye010.cnblogs.com
教程目录及源码下载:点击进入(欢迎加入WPF/Silverlight小组 WPF/Silverlight博客团队)
本文版权归作者和博客园共有,欢迎转载。但未经作者同意必须保留此段声明,且在文章页面显著位置给出原文连接,否则保留追究法律责任的权利。
posted on 2009-11-30 15:29  深蓝色右手  阅读(6762)  评论(15编辑  收藏  举报