Silverlight游戏开发中时常会碰上一些看似简单,可做时却发现挺棘手的问题。于是我打算通过一个小系列将平时较常用以及朋友们提问较多的问题进行归纳,同时分享我自己的解决方案,旨为大家提供更多实用且可行的参考,避免弯路。
一)知己知彼,轻松获取客户端相关参考信息
客户的机器配置各不相同,这我们确实无法控制。然而程序是活的,我们可以像做脚本及插件那样去适应不同的配置,从而使得每位客户都能获得都最佳体验效果,这也是未来游戏设计所需考虑到的重要环节之一。
首先以获取客户端IP地址为例,我们可以通过在在页面中的Silverlight对象中添加以下参数:
<object data="data:application/x-silverlight-2," type="application/x-silverlight-2" width="800" height="580">
……
<param name="initParams" value="<%= String.Format("{0}={1}","ClientIP", Request.UserHostAddress) %>" />
……
</object>
然后在后台即可通过Application.Current.Host.InitParams["ClientIP"]读取客户端IP地址。
同时还可通过HtmlPage.BrowserInformation获取客户端浏览器的相关信息,比如通过HtmlPage.BrowserInformation.ProductName、HtmlPage.BrowserInformation.ProductVersion分别获取客户浏览器的类型及版本。
至于如还想获取客户端CPU及显卡等硬件信息则需借助比如Javascript提升浏览器权限后再读取。
或许Silverlight在OOB模式及提升权限后也能做到,本人暂时还未能找到相关的API,望知道的朋友不吝赐教。
以下是该示例的Demo:
二)取其精华,实现Javascript的setInterval和setTimeout
只要是用过Javascript的朋友都会对setInterval和setTimeout流连忘返,没错,实在是太好用了。游戏中的中毒、冰冻、石化等等这些Buff和DeBuff以及技能的冷却都需要对过程进行计时。当然已有朋友将setInterval和setTimeout编写成了C#版本。不过这些代码还不能整体移植到Silverlight中直接处理UI逻辑,于是我将其进行了如下改造:
/// <summary>
/// 模拟Javascript中的setTimeout在指定时间过后执行指定的方法
/// </summary>
/// <param name="action">方法</param>
/// <param name="interval">间隔(毫秒)</param>
public void setTimeout(Action action, double interval) {
EventHandler handler = null;
DispatcherTimer dispatcherTimer = new DispatcherTimer() { Interval = TimeSpan.FromMilliseconds(interval) };
dispatcherTimer.Tick += handler = delegate {
dispatcherTimer.Tick -= handler;
dispatcherTimer.Stop();
action();
};
dispatcherTimer.Start();
}
DispatcherTimer timer = new DispatcherTimer();
EventHandler handler = null;
/// <summary>
/// 模拟Javascript中的setInterval在指定时间内重复执行指定的方法
/// </summary>
/// <param name="action">方法</param>
/// <param name="interval">间隔(毫秒)</param>
public void setInterval(Action action, double interval) {
clearInterval();
timer.Interval = TimeSpan.FromMilliseconds(interval);
timer.Tick += handler = delegate { action(); };
timer.Start();
}
/// <summary>
/// 模拟Javascript中的clearInterval停止setInterval
/// </summary>
public void clearInterval() {
timer.Tick -= handler;
timer.Stop();
}
使用的话和Js中一摸一样:
setTimeout(方法, interval); setInterval(方法, interval);
其中的Action可改成带返回值的Func,适用范围非常广;另外,我还为事件做了释放操作,更有利于封装处理,比如执行多少次停止,停止时触发Complete事件等均可轻松拓展;同时还增加了对Javascript中clearInterval函数的模仿,可自由控制setInterval的启动/停止。
以下是该示例的Demo,每次间隔都会触发数字+1:
三)借力用力,通过SilverX将Flash动画转成Silverlight控件
不可否认,十数年积累下的Flash设计师比Silverlight的多很多且不乏优秀之才;网络上随处可见漂亮的Flash动画,在学习之初或者作为项目中的简单辅助,我们完全可以通过SilverX这款软件将没有复杂AS代码的Flash动画(比如Loading、Waiting、login等简单又漂亮的小动画)的swf文件直接转换成Silverlight的xaml控件,老好用了。目前该软件仍在不断升级中,对于更复杂的Flash支持也将越来越好,作者深有体会,更期待完美版本的出现~。当前依旧存在的主要缺点有:转换后的文件容量会变大,颜色有些丢失,性能相对差些,并且好象不购买的话转出的只有xap而无源码,当然我们是可以解压出其中的dll直接使用的。
以下是我随便找到的一些swf小动画转换成xap后的Demo:
四)绿色体验,华而实用的九宫格控件
九宫格在Flash中已家喻户晓,它的好处不言而喻,资源的复用,动态局部搭配,轻松换肤等等。然而对于刚起步的Silverlight来说九宫格着实比较陌生。杨过兄早期就已公布过一些有趣的布局控件,包括九宫格。而游戏中的面板都是通过九宫格实现的,以下是我所编写的九宫格控件StyleBox,继承自可拖动的FloatableWindow:
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
namespace Demo19_3 {
/// <summary>
/// 可浮动窗体
/// </summary>
public abstract class FloatableWindow : Canvas {
/// <summary>
/// 3D整型坐标点
/// </summary>
public struct Point3D {
public int X,
Y,
Z;
public Point3D(int x, int y, int z) {
X = x;
Y = y;
Z = z;
}
}
/// <summary>
/// 获取或设置中心
/// </summary>
public virtual Point Center { get; set; }
/// <summary>
/// 获取或设置X、Y坐标
/// </summary>
public virtual Point Coordinate {
get { return new Point(Canvas.GetLeft(this) + Center.X, Canvas.GetTop(this) + Center.Y); }
set { Canvas.SetLeft(this, value.X - Center.X); Canvas.SetTop(this, value.Y - Center.Y); }
}
/// <summary>
/// 获取或设置Z层次深度
/// </summary>
public int Z {
get { return Canvas.GetZIndex(this); }
set { Canvas.SetZIndex(this, value); }
}
ContentControl HeadContent = new ContentControl(),
BodyContent = new ContentControl(),
FootContent = new ContentControl(),
CloserContent = new ContentControl();
/// <summary>
/// 设置头部对象
/// </summary>
public object Head {
set { HeadContent.Content = value; }
}
/// <summary>
/// 设置身体对象
/// </summary>
public object Body {
set { BodyContent.Content = value; }
}
/// <summary>
/// 设置脚部对象
/// </summary>
public object Foot {
set { FootContent.Content = value; }
}
/// <summary>
/// 设置关闭按钮对象
/// </summary>
public object Closer {
set { CloserContent.Content = value; }
}
public double HeadHeight {
get { return HeadContent.Height; }
set { HeadContent.Height = value; }
}
public double BodyHeight {
get { return BodyContent.Height; }
set { BodyContent.Height = value; }
}
public double FootHeight {
get { return FootContent.Height; }
set { FootContent.Height = value; }
}
/// <summary>
/// 设置头部空间位置
/// </summary>
public Point3D HeadPosition {
set {
Canvas.SetLeft(HeadContent, value.X);
Canvas.SetTop(HeadContent, value.Y);
Canvas.SetZIndex(HeadContent, value.Z);
}
}
/// <summary>
/// 设置身体空间位置
/// </summary>
public Point3D BodyPosition {
set {
Canvas.SetLeft(BodyContent, value.X);
Canvas.SetTop(BodyContent, value.Y);
Canvas.SetZIndex(BodyContent, value.Z);
}
}
/// <summary>
/// 设置脚部空间位置
/// </summary>
public Point3D FootPosition {
set {
Canvas.SetLeft(FootContent, value.X);
Canvas.SetTop(FootContent, value.Y);
Canvas.SetZIndex(FootContent, value.Z);
}
}
/// <summary>
/// 设置关闭按钮空间位置
/// </summary>
public Point3D CloserPosition {
set {
Canvas.SetLeft(CloserContent, value.X);
Canvas.SetTop(CloserContent, value.Y);
Canvas.SetZIndex(CloserContent, value.Z);
}
}
/// <summary>
/// 动画飞向
/// </summary>
/// <param name="fromX">起点X</param>
/// <param name="toX">目标X</param>
/// <param name="fromY">起点Y</param>
/// <param name="toY">目标Y</param>
/// <param name="speed">速度倍数</param>
public void AnimationFlyTo(double fromX, double toX, double fromY, double toY, double speed) {
Storyboard storyboard = new Storyboard();
storyboard.Completed += (s, e) => {
Storyboard sb = s as Storyboard;
sb = null;
};
int duration = Convert.ToInt32(Math.Sqrt(Math.Pow((toX - fromX), 2) + Math.Pow((toY - fromY), 2)) * speed);
DoubleAnimation xAnimation = new DoubleAnimation() {
From = fromX,
To = toX,
Duration = new Duration(TimeSpan.FromMilliseconds(duration))
};
Storyboard.SetTarget(xAnimation, this);
Storyboard.SetTargetProperty(xAnimation, new PropertyPath("(Canvas.Left)"));
storyboard.Children.Add(xAnimation);
DoubleAnimation yAnimation = new DoubleAnimation() {
From = fromY,
To = toY,
Duration = new Duration(TimeSpan.FromMilliseconds(duration))
};
Storyboard.SetTarget(yAnimation, this);
Storyboard.SetTargetProperty(yAnimation, new PropertyPath("(Canvas.Top)"));
storyboard.Children.Add(yAnimation);
storyboard.Begin();
}
/// <summary>
/// 关闭事件
/// </summary>
public event MouseButtonEventHandler Closed;
static int z; //层次
public FloatableWindow() {
this.Children.Add(HeadContent);
this.Children.Add(BodyContent);
this.Children.Add(FootContent);
this.Children.Add(CloserContent);
//设置可拖动方法
bool isMouseCaptured = false; //是否捕获了鼠标
Point clickPoint = new Point(); //点击点
HeadContent.MouseLeftButtonDown += (s, e) => {
z++;
Canvas.SetZIndex(this, z);
HeadContent.CaptureMouse();
isMouseCaptured = true;
clickPoint = e.GetPosition(s as UIElement);
e.Handled = true;
};
HeadContent.MouseLeftButtonUp += (s, e) => {
HeadContent.ReleaseMouseCapture();
isMouseCaptured = false;
e.Handled = true;
};
HeadContent.MouseMove += (s, e) => {
if (isMouseCaptured) {
TransformGroup transformGroup = this.RenderTransform as TransformGroup;
if (transformGroup == null) {
transformGroup = new TransformGroup();
}
if (transformGroup != null) {
transformGroup.Children.Add(new TranslateTransform() {
X = e.GetPosition(this).X - clickPoint.X,
Y = e.GetPosition(this).Y - clickPoint.Y
});
this.RenderTransform = transformGroup;
}
}
};
CloserContent.MouseLeftButtonDown += (s, e) => {
if (Closed != null) {
Closed(this, e);
};
e.Handled = true;
};
}
}
}
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Imaging;
namespace Demo19_3 {
/// <summary>
/// 九宫格控件
/// </summary>
public sealed class StyleBox : FloatableWindow {
internal TextBlock HeadText = new TextBlock() { HorizontalAlignment = HorizontalAlignment.Center, VerticalAlignment = VerticalAlignment.Center };
Image northWestImage = new Image();
Grid northCenterGrid = new Grid();
Image northEastImage = new Image() { Projection = new PlaneProjection() { RotationY = 180 } };
Image centerWestImage = new Image() { Stretch = Stretch.Fill };
ContentControl center = new ContentControl();
Image centerEastImage = new Image() { Stretch = Stretch.Fill, Projection = new PlaneProjection() { RotationY = 180 } };
Image southWestImage = new Image();
Image southCenterImage = new Image() { Stretch = Stretch.Fill };
Image southEastImage = new Image() { Projection = new PlaneProjection() { RotationY = 180 } };
Grid head = new Grid();
Grid body = new Grid();
Grid foot = new Grid();
public object CenterContent {
set { center.Content = value; }
}
public BitmapImage NorthEdgeImageSource {
set { northWestImage.Source = northEastImage.Source = value; }
}
public BitmapImage NorthImageSource {
set { northCenterGrid.Background = new ImageBrush() { ImageSource = value }; }
}
public double NorthCenterWidth {
set { northCenterGrid.Width = value; }
}
public BitmapImage CenterEdgeImageSource {
set { centerWestImage.Source = centerEastImage.Source = value; }
}
public double CenterWidth {
get { return center.Width; }
set { center.Width = value; }
}
public double CenterEdgeWidth {
set { centerWestImage.Width = centerEastImage.Width = value; }
}
public BitmapImage SouthEdgeImageSource {
set { southWestImage.Source = southEastImage.Source = value; }
}
public BitmapImage SouthImageSource {
set { southCenterImage.Source = value; }
}
public double SouthCenterWidth {
set { southCenterImage.Width = value; }
}
public StyleBox() {
RowDefinition row = new RowDefinition();
head.RowDefinitions.Add(row);
ColumnDefinition[] col = new ColumnDefinition[3];
for (int i = 0; i < 3; i++) {
col[i] = new ColumnDefinition();
head.ColumnDefinitions.Add(col[i]);
}
head.Children.Add(northWestImage); Grid.SetColumn(northWestImage, 0);
northCenterGrid.Children.Add(HeadText);
head.Children.Add(northCenterGrid); Grid.SetColumn(northCenterGrid, 1);
head.Children.Add(northEastImage); Grid.SetColumn(northEastImage, 2);
this.Head = head;
row = new RowDefinition();
body.RowDefinitions.Add(row);
for (int i = 0; i < 3; i++) {
col[i] = new ColumnDefinition();
body.ColumnDefinitions.Add(col[i]);
}
body.Children.Add(centerWestImage); Grid.SetColumn(centerWestImage, 0);
body.Children.Add(center); Grid.SetColumn(center, 1);
body.Children.Add(centerEastImage); Grid.SetColumn(centerEastImage, 2);
this.Body = body;
row = new RowDefinition();
foot.RowDefinitions.Add(row);
for (int i = 0; i < 3; i++) {
col[i] = new ColumnDefinition();
foot.ColumnDefinitions.Add(col[i]);
}
foot.Children.Add(southWestImage); Grid.SetColumn(southWestImage, 0);
foot.Children.Add(southCenterImage); Grid.SetColumn(southCenterImage, 1);
foot.Children.Add(southEastImage); Grid.SetColumn(southEastImage, 2);
this.Foot = foot;
this.Loaded += (s, e) => {
BodyPosition = new Point3D() { Y = (int)HeadHeight };
FootPosition = new Point3D() { Y = (int)HeadHeight + (int)BodyHeight };
};
}
}
}
同样的素材仅2.2KB可满足同一款游戏中几乎所有不同尺寸,不同位置及不同布局的面板需求,实实在在的完美绿色体验:
以下是该示例Demo,可随意拖动的九宫格面板:
本节源码请到目录中下载,更多实用小技巧,敬请关注下回分解。