所有的游戏设计辅助工具都是为了提高游戏开发效率而出现的,Silverlight-2D游戏场景编辑器(QXSceneEditor)同样也不例外,虽然它不比《魔兽》、《星际》、《帝国》等大作的地图编辑器拥有强大到甚至可以通过“换肤”直接创造出一款新游戏;但是大家可以通过它,配合上前几节讲述的游戏架设方式几乎能搭建任意一款2D游戏图形框架。无论是RPG(角色扮演)、SLG(策略)、RTS(即时战略)还是ACT(动作)、STG(射击)、CAG(卡片游戏)等游戏类型,不论精灵是2方向(如:《地下城与勇士》、《三国群英传》) 4方向(如:《漂流幻境》)还是8方向(如:《破天一剑》)甚至16方向(如:《剑侠世界》中的魔法),不论场景是横向、纵向、主视角、牵引模式,倾斜的还是垂直的,不论游戏空间是2层的(如:《梦幻诸仙》),3层的(如:《奇迹》)还是N层的(如:《英雄无敌》、《铁血联盟》)等等,只要是2D的均可用作场景的布局或架设。
场景编辑器是如何做到如此的高度通用而神乎其神的?在第二节中,我已较详细的讲解了它的核心功能:动态坐标系系统。通过动态修改场景以及它内部的地图、坐标系等对象的某些参数即可实现场景的任意倾斜度、单元格尺寸、参照系、2D位置,3D旋转以及主角在位置显示及移动方面的多模式。至于其中的核心代码我一再强调其实不过就两个,是否与第一部第十节的公式吻合大家可自行比对,事实证明它们是正确的,因此也请特别关注它的朋友们放一万个心:
/// <summary>
/// 将窗口坐标系中的坐标换算成游戏坐标系中的坐标
/// </summary>
public Point GetGameCoordinate(double angle, Point p, uint gridSize) {
if (angle == 0) {
return new Point((int)(p.X / gridSize), (int)(p.Y / gridSize));
} else {
double radian = GetRadian(angle);
return new Point(
(int)((p.Y / (2 * Math.Cos(radian)) + p.X / (2 * Math.Sin(radian))) / gridSize),
(int)((p.Y / (2 * Math.Cos(radian)) - p.X / (2 * Math.Sin(radian))) / gridSize)
);
}
}
/// <summary>
/// 将游戏坐标系中的坐标换算成窗口坐标系中的坐标
/// </summary>
public Point GetWindowCoordinate(double angle, Point p, uint gridSize) {
if (angle == 0) {
return new Point(p.X * gridSize, p.Y * gridSize);
} else {
double radian = GetRadian(angle);
return new Point(
(p.X - p.Y) * Math.Sin(radian) * gridSize,
(p.X + p.Y) * Math.Cos(radian) * gridSize
);
}
}
再回到功能上,仅有以上这些还是不够的;作为一个场景编辑器,更重要的是作为后续章节中我将为大家展示更多Demo的基础框架,它还需要拥有更好更强大的功能。
作为场景的编辑器,光能实现坐标系的变化只能算完成了一半,如能加上任意地图的导入才能算得上动态搭建:
//加载地图
button.Click += (s, e) => {
OpenFileDialog openFileDialog = new OpenFileDialog();
openFileDialog.Multiselect = false;
openFileDialog.Filter = "图象文件(*.jpg *.png)|*.jpg;*.png";
try {
bool result = (bool)openFileDialog.ShowDialog();
if (!result) {
return;
} else {
FileInfo fileInfo = openFileDialog.File;
Stream stream = fileInfo.OpenRead();
BitmapImage bitmapImage = new BitmapImage();
bitmapImage.SetSource(stream);
if (bitmapImage.PixelWidth < this.Width || bitmapImage.PixelHeight < this.Height) {
MessageBox.Show(string.Format("地图载入失败!图片规格小于窗口尺寸:{0} * {1}", this.Width, this.Height));
} else {
scene.MapSource = bitmapImage;
mapDetailsOutPut.Text = string.Format("宽:{0}px 高:{1}px", scene.MapWidth = bitmapImage.PixelWidth, scene.MapHeight = bitmapImage.PixelHeight);
}
stream.Close();
}
} catch {
MessageBox.Show("地图载入失败!请检查该图片格式是否正确");
}
};
Silverlight中,我们可以通过OpenFileDialog实现本地资源的导入;Silverlight目前版本仅能支持少数的图片格式,我们可以通过Filter进行文件类型过滤;由于图片位于本地机器,因此一旦选取图片后(bitmapImage.SetSource(stream))我们即刻便会知道图片的尺寸(bitmapImage.PixelWidth及bitmapImage.PixelHeight),这是非常重要的信息,因为当前游戏窗口的尺寸是800*580,如果地图图片的宽、高小于该数值对于游戏中地图的移动模式是无任何意义的,于是我在逻辑上还做了额外的筛选判断。
通过不同地图背景配合上相应的坐标系系统,这才叫场景搭建嘛~:
同时,场景编辑器还应该具备地形的编辑能力;因此,我还在当前版本中定义了5种最常见的地形:障碍、平地、草地、沙漠和河流:
/// <summary>
/// 地形
/// </summary>
public enum Terrains {
/// <summary>
/// 障碍
/// </summary>
Obstacle = 0,
/// <summary>
/// 平地
/// </summary>
Flat = 1,
/// <summary>
/// 草地
/// </summary>
Grassland = 2,
/// <summary>
/// 沙漠
/// </summary>
Desert = 3,
/// <summary>
/// 河流
/// </summary>
River = 4,
/// <summary>
/// 无
/// </summary>
None = 100,
}
接下来我们只需将参照系设定为“方块”,并选择相应的绘制对象,即可在地图的坐标系中通过鼠标左键绘制相应的地形:
之前有很多朋友曾提出过这样的问题:能否让主角经过不同地形时发出不同的脚步声?其实答案很简单。在第三节中我有讲到游戏基类的时空结构,为了让对象能够发声,我们得在此基础上再增加一个声音属性,即每一个继承自GameBase的类都具备有且仅有的一个“声源”。例如“游戏窗口”拥有游戏的背景音乐;“场景”负责局域环境的背景音效;“精灵”斧头挥舞的风声、受伤时的叫喊声、走路时的脚步声以及“魔法”施放时的咒语和“魔法”自身的爆破声等等,哪怕是“面板”,当我们鼠标在“面板”的按钮上点击时同样会听到清脆的点击声,它就是我们伟大的GameBase新增的一个摸不着,看不见的优秀成员sound:
/// <summary>
/// 声音
/// </summary>
MediaElement sound = new MediaElement() {
IsHitTestVisible = false,
Visibility = Visibility.Collapsed,
AutoPlay = true,
};
当游戏对象需要发出声音时,只需告诉sound,我要发声了,并设置好声音的来源即可:
/// <summary>
/// 发出声音
/// </summary>
/// <param name="uri">路径</param>
/// <param name="loop">是否循环</param>
public void MakeSound(string uri, bool loop) {
sound.Source = new Uri(string.Format(@"../Media/{0}", uri), UriKind.Relative);
sound.Position = TimeSpan.Zero;
SoundStart(loop);
}
/// <summary>
/// 开始发音
/// </summary>
public void SoundStart(bool loop) {
if (loop) { sound.MediaEnded += sound_MediaEnded; }
sound.Play();
}
赶快啦,带上您的耳机来体验吧~嘿嘿。 ^ () ^
看着“精灵”们都活蹦乱跳的,我们此时是否应该考虑让场景也动感些?于是乎我想到了《WPF/Silverlight深度解决方案》中的HLSL自定义渲染特效之完美攻略。龌龊的事情要发生啦!一不小心,我将该Demo中所有的HLSL特效复制到了场景编辑器中,嘿嘿,见证奇迹的时刻:
以前我们做的仅仅是对精灵进行渲染,当时很多朋友还看不出个端疑来;但这回换成了整个场景,喜欢回合制RPG的朋友一眼必能看出,这样的情形太像“踩地雷”时的过渡特效了:主角在地图上行走时,一旦主动或随机遇到怪物即会立刻进行场景切换,《仙剑奇侠传》、《轩辕剑》等无数经典画面此时又占据了我的思绪,感慨呀……回过神来,这仅仅不过才是6种渲染动画,如果某天您把HLSL着色语言精通了,Silverlight任意特效玩弄于股掌之间将绝非难事,大家努力加油吧!
要收尾了,应朋友们的要求,我特意将此编辑器中的“动态物体”例如“精灵”等获取图片源的方式修改为动态下载,且在下载完成前用一张图例代替呈现。完整代码如下:
static Dictionary<string, bool> downLoadImages = new Dictionary<string, bool>();
/// <summary>
/// 设置图片控件图片源(还未下载完前用图例进行设置)
/// </summary>
/// <param name="image">源图片控件</param>
/// <param name="uri">源图片路径</param>
/// <param name="legend">图例路径</param>
public void SetImage(Image image, string uri, string legend) {
if (downLoadImages.ContainsKey(uri)) {
if (!downLoadImages[uri]) {
image.Source = new BitmapImage((new Uri(string.Format(@"../SceneEditorImages/{0}", legend), UriKind.Relative))) {
CreateOptions = BitmapCreateOptions.None
};
} else {
image.Source = new BitmapImage((new Uri(string.Format(@"../SceneEditorImages/{0}", uri), UriKind.Relative))) {
CreateOptions = BitmapCreateOptions.None
};
}
} else {
BitmapImage bitmapImage = new BitmapImage((new Uri(string.Format(@"../SceneEditorImages/{0}", uri), UriKind.Relative)));
bitmapImage.ImageOpened += (s, e) => {
image.Source = s as BitmapImage;
downLoadImages[uri] = true;
};
image.Source = bitmapImage;
image.Source = new BitmapImage((new Uri(string.Format(@"../SceneEditorImages/{0}", legend), UriKind.Relative))) {
CreateOptions = BitmapCreateOptions.None
};
downLoadImages.Add(uri, false);
}
}
您没看走眼,是的,这就是全部,很简单对吧。当然,您同样也可以通过Webclient对图片素材进行按需下载,替换的相应代码如下:
WebClient webClient = new WebClient();
webClient.OpenReadCompleted += (s, e) => {
BitmapImage bitmapImage = new BitmapImage();
bitmapImage.SetSource(e.Result);
image.Source = bitmapImage;
downLoadImages[uri] = true;
};
webClient.OpenReadAsync(new Uri(string.Format(@"../SceneEditorImages/{0}", uri), UriKind.Relative), uri);
WebClient下载资源更为稳定,特别是在客户端网速不正常的情况下效果尤为显著。以上两种下载图片的方式优点很多,例如每张图片只需下载一次后即可一直使用而无需自定义缓存;另外,它特别适合我第一部教程第50节中谈到的用一张图来描述“精灵”等对象序列帧图片的下载,因为在完成整张图下载前,玩家可以看到一张图例;而一旦该图下载完成后,那么无论该对象做何动作,进行何种动画,均不会有一点闪烁或破绽,仿佛加载本地图片般流畅与完美~。当然了,由于业余时间有限,又不想再重复以前用过的素材,因而导致此场景编辑器中由N*M张图片组成的“精灵”呈现得无比“闪耀”,这是相当糟糕的事情,更多的留给大家去思考这样一个问题:基于Silverlight的网络游戏,它动态加载的素材该如何配置?
最后,还想和大家说说心里话:开源,不是哗众取宠,更不是炫耀,而是一种精神。这种精神会永远推动着你和你身边的朋友们走得更远更犀利!Silverlight在2D图形方面所展示的效果华丽而极至,随着计算机硬件的发展,如果公元2012年地球还未曾毁灭,我们一同携手去畅想Web-3D那精彩的虚幻世界~未尝不是一大幸事。
说到天花乱坠…事实胜过雄辩。该出手了,是时候展示此场景编辑器的强大了,后面的内容更精彩!一同见证吧!
在线演示地址:http://silverfuture.cn
出处:http://alamiye010.cnblogs.com/
教程目录及源码下载:点击进入(欢迎加入WPF/Silverlight小组 WPF/Silverlight博客团队)
本文版权归作者和博客园共有,欢迎转载。但未经作者同意必须保留此段声明,且在文章页面显著位置给出原文连接,否则保留追究法律责任的权利。