所有的游戏设计辅助工具都是为了提高游戏开发效率而出现的,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.PixelWidthbitmapImage.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的网络游戏,它动态加载的素材该如何配置?

最后,还想和大家说说心里话:开源,不是哗众取宠,更不是炫耀,而是一种精神。这种精神会永远推动着你和你身边的朋友们走得更远更犀利!Silverlight2D图形方面所展示的效果华丽而极至,随着计算机硬件的发展,如果公元2012年地球还未曾毁灭,我们一同携手去畅想Web-3D那精彩的虚幻世界~未尝不是一大幸事。

说到天花乱坠事实胜过雄辩。该出手了,是时候展示此场景编辑器的强大了,后面的内容更精彩!一同见证吧!

在线演示地址:http://silverfuture.cn

WPF/Silverlight
作者:深蓝色右手
出处:http://alamiye010.cnblogs.com/
教程目录及源码下载:点击进入(欢迎加入WPF/Silverlight小组 WPF/Silverlight博客团队)
本文版权归作者和博客园共有,欢迎转载。但未经作者同意必须保留此段声明,且在文章页面显著位置给出原文连接,否则保留追究法律责任的权利。
posted on 2010-03-09 17:34  深蓝色右手  阅读(11841)  评论(34编辑  收藏