与当年盛大通过代理《传奇》一举成名,九城代理《奇迹》一夜发迹完全不同,金山、网易凭借他们自主的研发团队,数年时间倾力打造了《剑侠》及《西游》等系列非常优秀的纯国产网游,不仅开创了此领域的先河,且作为中国网游行业的榜样,让我时常感到崇敬与骄傲。西山居、烈火、大话西游等工作室这些如雷贯耳的称号已家喻户晓,所有成功的背后都有着一段源自对中国古风古韵故事的生动阐述,事实也证明了神化传说终是游戏设计中永不褪色的主题。
让经典重现是无比让人神往的体验。本节,我将以制作《梦幻西游》Demo为例,继续向大家讲解并演示如何通过Silverlight-2D场景编辑器搭建出结构更复杂的游戏场景。
与前两节不同,由于《梦幻西游》与《三国策》同属回合制RPG游戏类型,因此我们首先要做的是直接按照第七节的方法,但是使用基于第八节《三国策》Demo源码之上而直接修改出来新的Demo,并取名为MhxyDemo。
精致的网络游戏中精灵应该具备多部件纸娃娃系统,梦幻西游中的角色也不例外。在此Demo中,我以原始素材为依据,同样将玩家角色精灵分成身体、武器、坐骑、影子等4个部分:
此时大家或许会有些疑惑,组合后的图似乎与这些分开的素材并不吻合?是的,上图中的武器、身体图片均为精灵在无坐骑状态时的素材;而当精灵由“步行”变换为“骑乘”状态后,武器、身体的素材又会更换成另一套,这里涉及到各部分素材该如何合理布局这样一个问题。寻找相应解决方案,我们得从游戏的整体设计出发。精灵是游戏中的主角,以精灵为本,在回合制RPG游戏中,精灵拥有3种不同的显示模式:步行模式、骑乘模式及战斗模式,在这3种模式下精灵的姿势及武器的位置等均不相同:
如上图,3张均为同一角色东朝向移动时的第二帧图片,显然是截然不同的。因此,为了逻辑代码上的便利,我们必须通过对这些素材进行类似如下布局方能轻松实现灵活的素材文件加载:
至于这些素材该怎样组合,方案也是多种多样的。最简单的方案就是通过直接叠加的方式,此方式不需耗费像素合成,工作量也小,然而缺点同样明显,素材尺寸一致将导致总体容量很大。传统游戏的做法是通过配置或表头文件为每张图片定义好它的偏移量,每次调用时进行相应偏移或定点截取即可:
唯一麻烦之处是需要前期大量的美工处理及布局调整。总体来说,多部件纸娃娃系统可以大幅提升游戏的趣味性,深度套劳玩家那颗对“角色Show”痴迷的心;当然,成功的背后一切都离不开良好的游戏结构设定与布局。
素材方面就讲到这。梦幻西游除了拥有优秀的角色设定外,在场景上同样也很卓越。在本节的Demo中,我以原形为基础搭建出了传说中的2.8D斜视角双背景层次结构:
底层为云彩,包含于Window窗口容器中,可固定不动或朝主角相反的方向1/10左右速度相对移动;表层则是正常的地图背景,主位式镜头跟随主角相对移动。通过此方式对场景进行分层处理可以实现华丽的2.8D效果,玩家在游戏中将亲历置身于天空之城般神奇的体验。而若在此基础上,将双背景换成双场景,那么《梦幻诸仙》的飞空系统不同样可以轻易实现?嘿嘿,又一次的印证了基于场景的游戏架构定能让游戏开发更加简单。
同时,在本节的Demo中我还添加了第一部教程第三十七节中的讲到的地图切片及按需加载技术,此方案能大幅提升游戏的流畅性且降低服务器端负荷。这里还是想再次感谢goods,在他提供的9切片地图加载算法基础上,我将之再次升级为动态下载模式,核心代码如下:
Point2D _sectionCenter;
/// <summary>
/// 获取或设置地图切片中心
/// </summary>
public Point2D sectionCenter {
get { return _sectionCenter; }
set {
if (!_sectionCenter.Equals(value)) {
ChangeMapSection(_sectionCenter = value);
}
}
}
/// <summary>
/// 更新呈现的地图切片
/// </summary>
/// <param name="leaderSection">引导场景地图变换的精灵主角所处的窗口切片坐标</param>
private void ChangeMapSection(Point2D leaderSection) {
int startSectionX, startSectionY, endSectionX, endSectionY;
//根据主角当前位置来加载周边地图切片
if (leaderSection.X == 0) {
startSectionX = 0; endSectionX = 2;
} else if (leaderSection.X == SectionXNum - 1) {
startSectionX = leaderSection.X - 2; endSectionX = leaderSection.X;
} else {
startSectionX = leaderSection.X - 1; endSectionX = leaderSection.X + 1;
}
if (leaderSection.Y == 0) {
startSectionY = 0; endSectionY = 2;
} else if (leaderSection.Y == SectionYNum - 1) {
startSectionY = leaderSection.Y - 2; endSectionY = leaderSection.Y;
} else {
startSectionY = leaderSection.Y - 1; endSectionY = leaderSection.Y + 1;
}
int index = 0;
//取回已经超出主角周围9块范围的切片(x,y初始-1,第一次加载9块)
tempSection[] tempSectionList = sectionList.Where(c => c.x < startSectionX || c.x > endSectionX || c.y < startSectionY || c.y > endSectionY).ToArray();
for (int x = startSectionX; x <= endSectionX; x++) {
for (int y = startSectionY; y <= endSectionY; y++) {
//9点位中假如已经有该点位的切片在则检查下一个
if (sectionList.Where(c => c.x == x && c.y == y).Count() > 0) {
continue;
} else {
//将超出范围的切片填充到新的位置上,并重置它们的x,y点位坐标值
tempSectionList[index].x = x;
tempSectionList[index].y = y;
//下载实际图片
Downloader downloader = new Downloader() {
Args = string.Format("{0},{1},{2}", index, x, y)
};
downloader.Loading += (s, e) => {
mapSection[tempSectionList[index].sectionNo].Source = GetProjectImage(string.Format("Images/MiniMap/{0}/Surface/{1}_{2}{3}", this.Code, x, y, FileType(MapFormat)));
};
downloader.Completed += (s, e) => {
Downloader loader = s as Downloader;
string[] args = loader.Args.Split(',');
int tempIndex = Convert.ToInt32(args[0]), tempX = Convert.ToInt32(args[1]), tempY = Convert.ToInt32(args[2]);
if (tempSectionList[tempIndex].x == tempX && tempSectionList[tempIndex].y == tempY) {
mapSection[tempSectionList[tempIndex].sectionNo].Source = GetWebImage(string.Format("Images/Map/{0}/Surface/{1}_{2}{3}", this.Code, tempX, tempY, FileType(MapFormat)));
}
};
downloader.GetImage(WebPath(string.Format("Images/Map/{0}/Surface/{1}_{2}{3}", this.Code, x, y, FileType(MapFormat))));
Canvas.SetLeft(mapSection[tempSectionList[index].sectionNo], x * SectionWidth);
Canvas.SetTop(mapSection[tempSectionList[index].sectionNo], y * SectionHeight);
index++;
}
}
}
}
另外,整张地图包含有3个景点,景点之间均是相互隔离的。在未修改任何上一节传送部分代码的基础上,我们只需配置xml文件中传送点传送到的坐标及ToScene等于当前场景代号,即可实现同场景不同位置间的传送(瞬移也是类似原理,封装好的传送代码为我们提供了相当大的便利):
<!--传送点-->
<Teleports>
<Teleport ID="0" Code="0" X="2820" Y="2040" Z="184" ToScene="0" ToX="105" ToY="49" ToDirection="2" Tip="传送到:比武场"/>
<Teleport ID="1" Code="0" X="2455" Y="1680" Z="164" ToScene="0" ToX="57" ToY="95" ToDirection="6" Tip="传送到:武门殿"/>
<Teleport ID="2" Code="0" X="4270" Y="1530" Z="156" ToScene="0" ToX="99" ToY="83" ToDirection="2" Tip="传送到:空中亭阁"/>
<Teleport ID="3" Code="0" X="835" Y="1510" Z="154" ToScene="0" ToX="88" ToY="79" ToDirection="0" Tip="传送到:空中亭阁"/>
</Teleports>
再复杂的游戏设计也离不开最普通的游戏架构基础,光有强大的场景仍然无法支撑游戏的完美表述;因此,下一节我会在此Demo的基础上进一步丰富游戏,赋予游戏更多的生命与灵气。赶快加入到Silverlight游戏开发的行列中来吧,世界将因你而变得更精彩!
在线演示地址:http://cangod.com
源码请到目录中下载
![WPF/Silverlight](http://cangod.com/MyImages/Design.jpg)
出处: http://alamiye010.cnblogs.com/
教程目录及源码下载: 点击进入( 欢迎加入WPF/Silverlight小组 WPF/Silverlight博客团队)
本文版权归作者和博客园共有,欢迎转载。但未经作者同意必须保留此段声明,且在文章页面显著位置给出原文连接,否则保留追究法律责任的权利。