深蓝Silverlight-MMORPG游戏引擎单机部分即将开源;这两节里,我将为大家讲解本教程示例游戏从WPF向Silverlight移植的一些关键性优化与性能提升技巧,以及新增加的内容等等。接下来的时间里,我将接着前面的教程,通过Silverlight平台继续为大家演义,目标只有一个:誓将Silverlight游戏引擎完美到底!
一、主要改进:
1)Silverlight3.0上的右键实现:
//注册右键事件
HtmlPage.Document.AttachEvent("oncontextmenu", Game_MouseRightButtonDown);
//鼠标右键事件
private void Game_MouseRightButtonDown(object sender, HtmlEventArgs e) {
e.PreventDefault(); //取消右键弹出菜单
……逻辑部分
}
通过上述方法还必须配合<param name="Windowless" value="true" />或System.Windows.Interop.Settings.Windowless = true才能实现右键功能。另外需要特别说明的是,此方法并非官方所提供的解决方案,而是第三方间接的实现方式。因此,在使用前,您必须解为Silverlight解禁右键将付出的代价:①Windowless = true将降低程序整体性能;②无法使用输入法;③无法被所有的浏览器所兼容,例如在Google Chrome中,虽然可以激发出右键功能,但是取消不了弹出右键菜单。综上,在Silverlight3.0中,您还是得谨慎再谨慎的考虑是否使用右键。
2)撤消精灵及其他所有控件中的x,y,z坐标定位用关联属性,取而代之的是一个名为Coordinate的关联属性,其完整定义如下:
/// <summary>
/// 获取或设置控件坐标(关联属性)
/// </summary>
public Point Coordinate {
get { return (Point)GetValue(CoordinateProperty); }
set { SetValue(CoordinateProperty, value); }
}
public static readonly DependencyProperty CoordinateProperty = DependencyProperty.Register(
"Coordinate",
typeof(Point),
typeof(QXSprite),
new PropertyMetadata(ChangeCoordinateProperty)
);
private static void ChangeCoordinateProperty(DependencyObject d, DependencyPropertyChangedEventArgs e) {
QXSprite obj = (QXSprite)d;
if (obj.Visibility == Visibility.Visible) {
Point p = (Point)e.NewValue;
obj.SetValue(Canvas.LeftProperty, p.X - obj.CenterX);
obj.SetValue(Canvas.TopProperty, p.Y - obj.CenterY);
obj.SetValue(Canvas.ZIndexProperty, Convert.ToInt32(p.Y));
}
}
Coordinate的类型为Point,因此,我将原先精灵移动用的DoubleAnimation动画类型替换成了PointAnimation;这样,不论是在代码结构还是性能上均得到很大的优化。更改控件坐标时,只需修改它的Coordinate = new Point(x,y)即可,系统会判断该关联属性的值是否发生改变而激发ChangeCoordinateProperty方法,从而更新该控件最终在画面中的LeftProperty、TopProperty和ZIndexProperty。没错,关联属性就是这么强大。
3)A*移动的优化。我已留下接口,根据不同的参数设置,可以启动不同效率、不同路径长短、不同精确度的A*寻路,这里我给大家推荐两种现成的方案,第一种—程序默认A*寻路方案,此方案找到的路径最精确,但性能消耗最高;另一种方案可以实现最高效的寻路,但得到的路径并非最短:
PathFinderFast pathFinderFast = new PathFinderFast(varyObstruction) {
HeavyDiagonals = false,
HeuristicEstimate = 100,
};
我在Silverlight引擎中封装的A*寻路DLL,是根据教程第七节的老外A*改编而成。因此,您完全可以将之作为一个调试器,调试不同的搭配方案,然后将参数赋予pathFinderFast里:
例如上图,我通过模拟测试,发现最终找到路径所消耗的时间为0.0071秒,假如我已对此设置所产生的路径长度与性能感到满意,那么接下来要做的就是将此方案的配置记录下来: Diagonals = true ; Heavy Diagonals = true ; Henuristic = 5 ; Formula = Max(DX,DY) ; Use Tie Breaker = false ; Search Limit = 40000 ; 寻路对象使用的是FastPathFinder。
OK,最后来在Silverlight引擎中,我就可以这样来启动A*寻路:
PathFinderFast pathFinderFast = new PathFinderFast(varyObstruction) {
Diagonals = true,
HeavyDiagonals = true,
HeuristicEstimate = 5,
Formula = HeuristicFormula.MaxDXDY,
TieBreaker = false,
SearchLimit = 40000,
};
嘿嘿,其实使用A*是可以如此简单的,不是吗?
二、主要优化:
1) 地图切片实现了最优化加载方法。即不需要额外做多余判断,也无需每次对切片容器进行Clear。只需按从0到8的顺序对这9个切片重新赋值Source即可,性能真的很优哦:
private void ChangeMapSection() {
……
countSection = 0;
for (int x = startSectionX; x <= endSectionX; x++) {
for (int y = startSectionY; y <= endSectionY; y++) {
mapSection[countSection].Source =
Super.GetImage(string.Format("/Image/Map/{0}/Surface/{1}_{2}.jpg", mapCode, x, y));
Canvas.SetLeft(mapSection[countSection], x * mapSectionWidth);
Canvas.SetTop(mapSection[countSection], y * mapSectionHeight);
countSection++;
}
……
}
2)改进了 “托盘式”主位地图移动模式。首先我想向一些朋友道歉,一时找不到是哪篇文章后面评论中有提到对一个Canvas进行移动而不是遍历所有精灵,这样可以提升逻辑方面的性能;我当时有测试过,为什么一直坚持不行,因为我没转过弯,主角和其他所有对象是完全可以放在一个Canvas里的,这也意味着它们的ZIndex顺序照样可以很好的处理,同时实现“托盘式”地图移动模式。最终在QQ群里“内Cool超人”的感化下,我才得以觉醒。这样,虽然画面性并无提升,但是,配合上Coordinate坐标关联属性的回调方法使用,可以去掉循环遍历地图上所有对象位置,在逻辑上大大的提升了性能。
3)隐藏远离画面窗口的精灵对象。这是基于Web游戏所必须做的处理,它将大大减少不必要元素的呈现及逻辑运算:
……
//隐藏及显示区域范围内精灵
if ((Math.Abs(sprite.Coordinate.X - Leader.Coordinate.X) > this.ActualWidth / 2) || (Math.Abs(sprite.Coordinate.Y - Leader.Coordinate.Y) > this.ActualHeight / 2)) {
sprite.Visibility = Visibility.Collapsed;
sprite.Timer.Stop();
}else {
if (!sprite.Timer.IsEnabled) {
sprite.Visibility = Visibility.Visible;
sprite.Timer.Start();
}
……
}
……
在间隔0.5秒的辅助计时器事件中进行类似如上判断,当某个精灵超出了主角可视范围,即在我们屏幕窗口所能看到的区域以外,则将之隐藏掉,并停止它的切帧动作,否则反之。这对提升游戏整体性能起着决定性关键作用。如果是网络版,我们则可以拓展出2级范围,其中1级范围即为上述范围;而2级范围则为:当某个已被隐藏的精灵远离主角到了更遥远的地方,则我们将之移除掉,从而减少逻辑且实现不必要资源的及时释放与回收。
4)改进了时时障碍物系统。整个游戏有两个障碍物数组(可以记录0-255,0代表障碍物,除0外的所有其他字节均代表无障碍。这里我使用1标识无任何对象可通行区域,10-19用来标识传送点。如果以后需要加入新的地形效果拓展,那么同样可以使用类似设定:例如20用来标识可通行水域,21标识可通行沙漠等等;这样,现当主角在这些区域中移动时,会发出相应的脚步声,使游戏效果更为逼真)。动态障碍物系统实现代码如下,首先定义一个固定数组和一个动态数组:
byte[,] fixedObstruction, varyObstruction;
fixedObstruction是地图加载后永远不变的地图信息描述载体,它记录了地图中肯定无法通过的地形及传送点的位置等等。varyObstruction是时时的动态地图信息,会根据所有精灵时时的位置来填充障碍物。
在每次A*移动时,我们通过先去掉精灵脚底的障碍物区域(HoldWidth和HoldHeight),然后启动A*寻路,找到路径后再补回精灵的脚底障碍物区域:
……
SetSpriteObstruction(sprite, 1);
AStarMove(sprite, GetSpriteEdge(enemy));
sprite.UseAStarMove = true;
SetSpriteObstruction(sprite, 0);
……
其中SetSpriteObstruction方法为:
/// <summary>
/// 设置精灵占位障碍物对应值
/// </summary>
private void SetSpriteObstruction(QXSprite sprite, byte sign) {
int x = (int)(sprite.Coordinate.X / gridSizeX);
int y = (int)(sprite.Coordinate.Y / gridSizeY);
for (int m = x - sprite.HoldWidth; m <= x + sprite.HoldWidth; m++) {
for (int n = y - sprite.HoldHeight; n <= y + sprite.HoldHeight; n++) {
if (fixedObstruction[m, n] != 0) {
varyObstruction[m, n] = sign;
}
}
}
}