上一篇:C#开发WPF/Silverlight动画及游戏系列教程(Game Course):(十五) 精灵控件横空出世!②

精灵控件让游戏开发更美好!有了它,离完善牵引式地图移动模式可谓一步之遥。只剩下最后一个环节了,大家加油吧。

上一节,我在界面线程中通过时时设置Canvas.SetLeft(Spirit, Spirit.X + Canvas.GetLeft(Map) - SpiritCenterX * GridSize);和Canvas.SetTop(Spirit, Spirit.Y + Canvas.GetTop(Map) - SpiritCenterY * GridSize);来实现主角跟随着地图移动。从该公式我们可以分析出影响主角在窗口中显示位置的两个因素:第一个为地图图片(Image Map)相对于窗口的位置(Canvas.GetLeft(Map),Canvas.GetTop(Map)),它是在鼠标牵引地图移动的时候时时改变的,与主角在地图上的走动无关;第二个则为主角自身的X,Y坐标属性(Spirit.X,Spirit.Y),当主角在地图上走动时,它是时时更改的。由此可以得到一个结论:要实现主角在此模式地图上的移动,只需要在它走路的时候时时更新它的坐标Spirit.X和Spirit.Y即可,这样界面线程中会同步更新主角在窗口中的位置而达到完美的游戏动画衔接。

找到了切入点,那么实现起来就简单多了。

这里,我们首先需要对前面章节中的A*寻路方法进行一些改进。在前面的章节中,由于地图是固定死不动的,且尺寸相当于窗口大小,这样我们简单的将地图和窗口示为一体。因此,在A*寻路过程(AStarMove())中同时实现了主角相对于地图的移动,即基于对象关联属性为PropertyPath("Canvas.Left"), PropertyPath("Canvas.Top")的Storyboard动画。但是在牵引式地图移动模式中就不能这样做了,根据前面分析的原理,则必须改为基于对象关联属性为PropertyPath("X"),PropertyPath("Y")的Storyboard动画。此时的动画或许将之理解为从寻路得到的路径序列点中连续取出坐标的计时器更加贴切,因为它只负责改变Spirit的X,Y属性而不负责在界面中更新Spirit的位置实现动画。但是这已经足够了,因为它已经满足了原理中更新精灵坐标Spirit.X和Spirit.Y的目的(剩下的任务交由界面线程去做就好了,代码与上一节中的一样,我们不需要理会)。那好,接下来就看我如何对A*寻路再次进行改造(可别怕,目前的A*寻路Storyboard动画方法已经是很成熟的了,只需要对它的几个关节进行修改即可以达到不同的使用目的,其实在第九节第十节中已经对其进行过修改了)。

接下来就是对A*寻路移动方法进行改造了:
  1.         private void AStarMoveTo(Point p) {

  2.             //进行坐标系缩小

  3.             int start_x = (int)(Spirit.X) / GridSize;

  4.             int start_y = (int)(Spirit.Y) / GridSize;

  5.             Start = new System.Drawing.Point(start_x, start_y); //设置起点坐标

  6.             int end_x = (int)p.X / GridSize;

  7.             int end_y = (int)p.Y / GridSize;

  8.             End = new System.Drawing.Point(end_x, end_y); //设置终点坐标



  9.             if (path == null) {

  10.                 //MessageBox.Show("路径不存在!");

  11.             } else {

  12. ......

  13.                 //创建X轴方向逐帧动画

  14.                 DoubleAnimationUsingKeyFrames keyFramesAnimationX = new DoubleAnimationUsingKeyFrames();

  15.                 //总共花费时间 = path.Count * cost

  16.                 keyFramesAnimationX.Duration = new Duration(TimeSpan.FromMilliseconds(path.Count * cost));

  17.                 Storyboard.SetTarget(keyFramesAnimationX, Spirit);

  18.                 Storyboard.SetTargetProperty(keyFramesAnimationX, new PropertyPath("X"));

  19.                 //创建Y轴方向逐帧动画

  20.                 DoubleAnimationUsingKeyFrames keyFramesAnimationY = new DoubleAnimationUsingKeyFrames();

  21.                 keyFramesAnimationY.Duration = new Duration(TimeSpan.FromMilliseconds(path.Count * cost));

  22.                 Storyboard.SetTarget(keyFramesAnimationY, Spirit);

  23.                 Storyboard.SetTargetProperty(keyFramesAnimationY, new PropertyPath("Y"));

  24.                 for (int i = 0; i < framePosition.Count(); i++) {

  25.                     //加入X轴方向的匀速关键帧

  26.                     LinearDoubleKeyFrame keyFrame = new LinearDoubleKeyFrame();

  27.                     //平滑衔接动画(将寻路坐标系中的坐标放大回地图坐标系中的坐标)

  28.                     keyFrame.Value = i == 0 ? Spirit.X : framePosition.X * GridSize;

  29.                     keyFrame.KeyTime = KeyTime.FromTimeSpan(TimeSpan.FromMilliseconds(cost * i));

  30.                     keyFramesAnimationX.KeyFrames.Add(keyFrame);

  31.                     //加入X轴方向的匀速关键帧

  32.                     keyFrame = new LinearDoubleKeyFrame();

  33.                     keyFrame.Value = i == 0 ? Spirit.Y : framePosition.Y * GridSize;

  34.                     keyFrame.KeyTime = KeyTime.FromTimeSpan(TimeSpan.FromMilliseconds(cost * i));

  35.                     keyFramesAnimationY.KeyFrames.Add(keyFrame);

  36.                 }

  37. ......

  38.             }

  39.         }
复制代码
TOP