紧接着上一节,我们打开QXSpirit.xaml.cs文件。在游戏设计中,为了能够轻易控制及管理精灵的各项属性及功能等,我赋予每个精灵一个"生命钟",它在精灵的使用中起到关键作用:

       public QXSpirit() {

            InitializeComponent();

            InitThread(); //初始化精灵线程

        }

        DispatcherTimer Timer = new DispatcherTimer();

        private void InitThread() {

            Timer.Tick += new EventHandler(Timer_Tick);

            Timer.Start();

        }

        //精灵线程间隔事件

        int count = 0;

        private void Timer_Tick(object sender, EventArgs e) {

            Body.Source = new BitmapImage((new Uri(ImageAddress + count + ".png", UriKind.Relative)));

            count = count == 7 ? 0 : count + 1;

        }

    DispatcherTimer类似于Thread中的Join,它并未创建一个新的线程,但是为了更形象的描述它,后面的章节中我均称呼它为“生命线程”。那为何要在精灵控件中配置一个“生命线程”呢?打这样一个比方吧:我们可以把精灵比做一个人,它的生命就好比这个线程,每个人只有一条命,在精子与卵子结合之后(控件的初始化中创建),生命即开始鲜活(创建后即启动)简单说就是“悬一线”啦。汗一个。目前该线程与前面章节中的一样,暂时只做精灵动作图片切换用(Timer_Tick()),至于其他功能,我将在后面的章节中进行讲解。

    赋予了精灵生命以后,接着需要培养它的性格,让它拥有更多的能力、更多的属性。当然,大家首先迫切想要实现的就是前两节遗留下来关于精灵的XY属性。那么先来看代码:

        //精灵X坐标(关联属性)

        public double X {

            get { return (double)GetValue(XProperty); }

            set { SetValue(XProperty, value); }

        }

        public static readonly DependencyProperty XProperty = DependencyProperty.Register(

            "X", //属性名

            typeof(double), //属性类型

            typeof(QXSpirit), //属性主人类型

            new FrameworkPropertyMetadata(

                 (double)0, //初始值0

                 FrameworkPropertyMetadataOptions.None, //不特定界面修改

                 //不需要属性改变回调

                 null,//new PropertyChangedCallback(QXSpiritInvalidated),

                 //不使用强制回调

                 null

                 )

            );

        //精灵Y坐标(关联属性)

        public double Y {

            get { return (double)GetValue(YProperty); }

            set { SetValue(YProperty, value); }

        }

        public static readonly DependencyProperty YProperty = DependencyProperty.Register(

            "Y",

            typeof(double),

            typeof(QXSpirit),

            new FrameworkPropertyMetadata(

                 (double)0,

                 FrameworkPropertyMetadataOptions.None,

                 null,

                 null

                 )

            );

    以上代码实现了QXSpirit控件的XY关联属性。大家不要被看似复杂的代码所吓着,其实很简单的,让我一一道来。首先将以上代码分成两部分:X坐标为第一部分,Y坐标为第二部分。它们的结构是一模一样的,我们可以忽略Y坐标,只要理解了X关联属性的实现,将X换成Y即可。

    关于关联属性上一节中的AttachProperty(附加属性)其实也是通过关联属性来实现的)的相关知识,网上不要太多,它不是本教程的重点所以就不多做解释了。理解它的朋友都明白,上面代码是它的标准创建形式,public double X是它的属性访问器,public static readonly DependencyProperty XProperty 则是定义它。就如上面代码注释中写到的,分别定义它的属性名、类型、所处类名等等。这样,一个完整的X关联属性就完成了。有的朋友又困惑了,为什么要那么麻烦去创建关联属性?我直接这样写不就得了:

     public double X { get; set; }

    即传统又简单。但是,我想告诉大家的是,在WPF/Silverlight中,只有关联属性才能被更好的使用及识别,例如在属性的绑定,Storyboard目标属性的设定等等中,都必须使用到关联属性来实现,后面的章节中会讲到它的必要性。而像如上的属性访问器只能用于创建纯描述性属性,例如精灵图片地址目录等,就可以使用属性访问器:

     //精灵图片源目录地址

     public string ImageAddress { get; set; }

 

    至此,我们完成了一个初具雏形的精灵控件,接下来就是如何将之加入到游戏中了。首先要做的当然是添加精灵控件的引用:

    using WPFGameCourse.Controls;

    接下来就是创建精灵控件实例并将之添加进窗口的Carrier控件中:

     QXSpirit Spirit = new QXSpirit();

        private void InitSpirit() {

            Spirit.X = 300; //为精灵关联属性X赋值

            Spirit.Y = 400; //为精灵关联属性Y赋值

            Spirit.Timer.Interval = TimeSpan.FromMilliseconds(150); //精灵图片切换频率

            Spirit.ImageAddress = @"..\Player\"; //精灵图片源地址

            Carrier.Children.Add(Spirit);

        }

    从代码可以看出,我们已经可以自由的使用SpiritXY属性了,并且轻松的控制该精灵的图片切换频率(为什么我们需要去控制它的切换频率呢?因为在游戏中,角色施放魔法有施法速度;物理攻击时有攻击速度、甚至可能会被冻结(移动速度减)、麻痹(精灵不动)、加速移动攻击BUFF等等,这些不光需要更改角色的相关属性逻辑,更需要在游戏窗口表现时通过调整精灵图片切换速率来实现之,因此意义是相当相当重大的),是不是有些成就感了?

    至于我们在牵引地图移动的同时,如何实现角色及障碍物的跟随移动?有了XY属性以后,接下来的就再简单不过了,首先来看这张图:

    因为地图图片在随鼠标牵引的情况下,它的Canvas.getLeft(Map)和Canvas.getTop(Map)属性是时时更新的,在第十三节中有提到。那么在地图动的时候,当已知主角在地图图片中的坐标(Spirit.X,Spirit.Y)后,即可以按上图根据公式计算出它在游戏窗口中显示的时时位置为:Spirit.X+ Canvas.getLeft(Map) - SpiritCenterX * GridSize,Spirit.Y+ Canvas.getTop(Map) - SpiritCenterY * GridSize。有了它,在后面章节中包括A*寻路计算,直线移动等方面的计算不再需要考虑SpiritCenterX和SpiritCenterY,这将大大简化游戏设计。据此,我们在游戏窗口的Timer_Tick事件中这样写:

        int scrollspeed = 3; //定义地图滚动速度

        private void Timer_Tick(object sender, EventArgs e) {

            double mapleft = Canvas.GetLeft(Map);

            double maptop = Canvas.GetTop(Map);

            ……

            //主角跟随地图同时移动

            Canvas.SetLeft(Spirit, Spirit.X + mapleft – SpiritCenterX * GridSize);

            Canvas.SetTop(Spirit, Spirit.Y + maptop – SpiritCenterY * GridSize);

            //所有障碍物实体同样跟随移动(实际中并不需要下面代码,这里只为测试用)

            foreach (UIElement uie in Carrier.Children) {

                if (uie is Rectangle) {

                    Rectangle r = uie as Rectangle;

                    Point p = getPointFromTag(r.Tag); //此方法在上一节有介绍

                    Canvas.SetLeft(r, mapleft + p.X * GridSize);

                    Canvas.SetTop(r, maptop + p.Y * GridSize);

                }

            }

        }

    上面黄色代码部分即为通过公式来改变主角在游戏窗口中的显示。由于此时的障碍物实体为Rectangle,因此可以通过foreach来改变窗口中所有障碍物显示实体Rectangle对象的显示位置来描绘障碍物同样随着地图的移动而移动(实际中并不需要此段代码,只为了演示)。

    到此为止就完成了牵引式地图移动模式中的所有对象跟随地图移动。最后大家按下CTRL+F5并任意移动移动地图看看:

    嘿嘿,都能跟随移动了呢,但是仿佛还遗漏了什么??对了,还没实现此模式下主角通过鼠标点击进行走路呢,而且在走路的同时如果我们牵引地图移动,主角也能同样的显示在正确窗口位置上,这又涉及到多个坐标系中坐标的换算,就让这头疼的问题留给下节去处理吧,敬请关注。

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