首先我们需要写出两种移动方法(具体代码就不列出来了,在本教程的目录中有下载):第一种我定义为NormalMove移动方法,它就是我第一节中讲到的点与点之间的直线移动,在此方法中我稍微改动了一些,使坐标定位到主角的脚底,并且将移动目标终点记录到Point Target中。第二我将之定义为AStarMove移动方法,它就是我前面几节讲到的A*寻路方式。设置好后,我们就可以根据从副本地图中获取的点的颜色判断来调用相应的移动模式了。
那么在主角移动的时候,它的X,Y坐标属性是时时更新的,因此我们需要一个线程去捕获它,并且在此线程中时时判断主角是否采在了黑色点上(障碍物)。那么这里我采用了
第二节中所讲到的CompositionTarget界面线程,注册了该线程dispatcherTimer1_Tick事件后,接下来就进入关键代码了:
- private void Carrier_MouseLeftButtonDown(object sender, MouseButtonEventArgs e) {
- Point p = e.GetPosition(Carrier);
- //假如点到的地方不是障碍物
- if (pickColor(Deeper, (int)p.X, (int)p.Y) != Colors.Black) {
- target = p;
- NormalMove(p); //直线移动
- //AStarMove(p); //A*寻路移动
- }
- }
- BitmapSource Deeper = new BitmapImage((new Uri(@"Map\Deeper.jpg", UriKind.Relative))); //设置地图副本
- int X, Y; //主角当前的窗口真实坐标(非缩放)
- Point target; //主角移动的最终目的
- private void dispatcherTimer1_Tick(object sender, EventArgs e) {
- X = Convert.ToInt32(Canvas.GetLeft(Spirit) + SpiritCenterX * GridSize);
- Y = Convert.ToInt32(Canvas.GetTop(Spirit) + SpiritCenterY * GridSize);
- //message.Text = "坐标:" + X + " " + Y;
- message.Text = pickColor(Deeper, X, Y).ToString();
- //假如碰到障碍物则采用A*寻路
- if (pickColor(Deeper, X, Y) == Colors.Black) {
- AStarMove(target);
- } else if (pickColor(Deeper, X, Y) == Colors.Yellow) {
- //假如是传送点则条到坐标(200,20)
- storyboard.Stop();
- Canvas.SetLeft(Spirit, 200 - SpiritCenterX * GridSize);
- Canvas.SetTop(Spirit, 20 - SpiritCenterY * GridSize);
- }
- //用白色点记录移动轨迹
- rect = new Rectangle();
- rect.Fill = new SolidColorBrush(Colors.Snow);
- rect.Width = 5;
- rect.Height = 5;
- Carrier.Children.Add(rect);
- Canvas.SetLeft(rect, X);
- Canvas.SetTop(rect, Y);
- }
复制代码上面代码已经进行了很详细的描述:首先我在鼠标左键事件中判断点击的地方是否是障碍物(点击的点在副本地图中是否是黑色的),是的话主角就不移动,否则就启动简单的直线移动。接下来我们需要设置几个变量它们分别存储副本地图的图片源、主角的X,Y精确坐标以及主角最终移动目的点。设置好后最后就是在界面线程中时时获取主角的X,Y坐标,并且判断主角当前位置是否是障碍物了。如果是则将当前的移动由普通直线移动转换成A*寻路移动;同时我们还可以判断如果是黄色的话则传送到点(200,20),当然大家还可以设置其他很多颜色来启动相应的事件,是不是很神奇?嘿嘿。
下面两图分别是采用A*寻路的主角移动和改进型的A*寻路(直线移动+A*寻路)的主角移动:
附件:
2.jpg 附件:
3.jpg 从上图可以明显看到,单纯的使用A*进行主角移动及饶过障碍物是不自然的,路线很机械且并不真实。而通过直线移动+副本地图+A*实现的改进型A*寻路所实现的主角移动则可谓几乎接近完美,与现实吻合。
这里要顺带提一下的是:本节只为演示需要所以我使用的副本地图只有一窗口大小,因此当主角移动到窗口外时pickColor()方法的参数X,Y超出了副本地图的大小导致抛出异常并使程序关闭这是肯定的而并非BUG,这样也同样证明了pickColor()方法的正确性。
小结:关于如何对A*进行改进其实也并非一定需要配合该副本地图,我们同样的可以通过时时比较主角的(SpiritWindowX,SpiritWindowY)坐标对应的障碍物Matrix[SpiritWindowX, SpiritWindowY]是否==1来判断是否启动寻路。但是本节的目的是要告诉大家副本地图的万用功能,别的方法能做的,它同样能做,而且它扩展性更强,描述对象更简单且直观,这才叫解放思想、面向对象!其实类似此副本地图的使用在魔兽世界等著名的游戏中都有用到,只是我这个算是简单版的。虽然它构造简单但是在辅助地图引擎方面却显示出强大的威力。目前我感到唯一的缺憾就是还没有研究出如何使用它单独的进行有效精确的寻径(即完全抛弃复杂的A*寻径算法),曾想过在副本地图中画些寻路点(即障碍物旁边可以起到诱导饶过障碍物的点),当主角碰到障碍物时,寻找副本地图中离自己最近的寻路点作为临时目标进行移动,到了此临时目标后再向最终目标移动,这样就可以巧妙饶过障碍物了。当然这在不太复杂的地图中是完全可行的,而且仿佛比A*更强悍且简单,但是一旦遇到复杂的地图了呢?我们该如何优化它?还需要对此感兴趣的朋友们开动一下脑筋,或许真能想出完美的解决方案呢。
不管怎样,些许的缺陷是永远无法掩埋它的伟大的。连美工都可以轻松的参与到游戏地图引擎的主要设计中,难道不是游戏设计界中神圣的革新吗?快速开发是我们的理想与追求,只需要一张副本图片就可以较好的替代以往制作一个游戏地图引擎必须的四大步骤:切割、拼图、编辑、转换。是不是很邪恶?简化了过程却实现同样的效果,让人觉得措手不及!这就是21世纪!YEAR一个。
下一节我将就主角与地图及其他对象进行相对移动进行详细讲解,敬请关注。
下一篇:
C#开发WPF/Silverlight动画及游戏系列教程(Game Course):(十三)牵引式地图移动模式①文/
深蓝色右手