即时战略类型游戏因其精确的微操,宏大的场面以及丰富的策略元素广受玩家的爱戴,《沙丘魔堡II》开创了真正意义上的即时战略游戏形态,之后Westwood创生了《命令与征服》系列加之暴雪的《魔兽争霸》及《星际争霸》系列瞬间将RTS的发展推向高潮。就是在这样的氛围下,微软的《帝国时代》系列孕育而生,踏着人类文明进步的足迹,陪伴我度过了高中那个人生转折的年代。
本节,我将为大家讲解的是利用场景编辑器搭建的帝国时代2游戏Demo。
即时战略游戏最大的特点就在于它的操作上,通过鼠标搭配键盘进行不同组合的单位管理。在帝国时代游戏中,鼠标左键负责选取单位,而右键则是指挥它们进行移动及攻击;其时,我们完全可以把所有的操作都集中到左键上,让玩家拥有更好的操作手感。然而,与此同时功能的多样化必将加大相关代码的复杂度,左键既要负责选中单个单位,又要在按住不放的情况下选中范围内单位,同时还要在无有效选择情况下指挥已选中的单位进行目标点移动;因此,我们必须充分利用并衔接好mouseLeftButtonDown、mouseLeftButtonUp以及mouseMove事件才能达到目的。这里我用到一个小技巧,就是在鼠标按下后如果拖动出的范围小于10*10像素则认定为无效的范围选取,从而执行其他操作;这也是一个关键技术点,当然,或许你有更加优秀的方案能完美的处理好这些判断的并发。
在范围选取单位对象时,即时战略游戏中的通常做法是从鼠标点击点开始向拖动的目标绘制一个矩形,在Silverlight中我们可以这样做,在左键按下后如判断为选取状态时记录下起始坐标selectedStart,然后在鼠标移动事件中进行如下处理即可:
/// <summary>
/// 鼠标移动绘制单位选取范围
/// </summary>
private void mouseMove(object sender, MouseEventArgs e) {
if (isMouseCaptured) {
selectedEnd = e.GetPosition(mainScene.Container);
//拖拉出的方块必须x,y大于10像素才进入选择范围状态,否则为指挥移动状态
if (Math.Abs(selectedEnd.X - selectedStart.X) >= 10 && Math.Abs(selectedEnd.Y - selectedStart.Y) >= 10) {
isSelecting = true;
bool scaleX = false, scaleY = false;
if (selectedEnd.X < selectedStart.X) { scaleX = true; }
if (selectedEnd.Y < selectedStart.Y) { scaleY = true; }
mainScene.Selector.RenderTransform = new ScaleTransform() { ScaleX = scaleX ? -1 : 1, ScaleY = scaleY ? -1 : 1 };
mainScene.Selector.Width = Math.Abs(selectedEnd.X - selectedStart.X);
mainScene.Selector.Height = Math.Abs(selectedEnd.Y - selectedStart.Y);
} else {
isSelecting = false;
}
}
}
代码虽很简单,却能实现随意方向的矩形绘制,效果是很完美的:
在写第一部教程时一直都会有朋友特别关心游戏中如何实现贴砖这样一个问题,其实真的很简单。帝国时代的设计师为每种类型的地形都各准备了100块不同的砖,比如:
然后通过自行开发的编辑器直接或随机选取即可填充成一张地图。贴砖的目地之一是为了节省资源,另一方面也是为了某些算法而用。那么言归正传,在场景编辑器搭建的游戏Demo中,我们该如何实现贴砖地图呢?这里我们要用到WriteableBitmap对象对所有的砖块进行合成,方法体如下(本节Demo中,我使用了5种地形,每种地形31块砖):
/// <summary>
/// 加载地图贴砖
/// </summary>
/// <param name="tile">砖类型代号(-1则为随机混拼)</param>
public void LoadMapTile(int tile) {
WriteableBitmap writeableBitmap = new WriteableBitmap((int)MapWidth, (int)MapHeight);
Canvas canvas = new Canvas();
Random random = new Random();
int countX = 0, countY = 0;
for (int y = 0; y < MatrixSize; y++) {
countY++;
if (countY % 2 == 0) { continue; }
for (int x = 0; x < MatrixSize; x++) {
countX++;
if (countX % 2 == 0) { continue; }
Image img = new Image() { Source = GetProjectImage(string.Format("Images/MapTile/{0}/{1}.png", tile == -1 ? random.Next(5) : tile, random.Next(31))) };
Point p = GetWindowCoordinate(Gradient, new Point(x, y), GridSize);
canvas.Children.Add(img);
Canvas.SetLeft(img, p.X);
Canvas.SetTop(img, p.Y);
}
}
writeableBitmap.Render(canvas, new TranslateTransform() { X = OffsetX - 55, Y = OffsetY + 10 }); //其中的55和10为大概进行的偏移处理,实际中如需精确定位或用障碍物围边缘
writeableBitmap.Invalidate();
map.Source = writeableBitmap;
}
由于素材是从帝国时代2中获取的,每块砖尺寸为97*49;因此我将场景的倾斜度设置为62.85以与之匹配并特意将单元格尺寸设定为26.4从而使得每4个单元格所占据的菱形刚好吻合一块砖图片,于是便可通过统计countX、countY每间隔一次绘制一块砖的方法实现随机的砖填满整个场景坐标系背景:
如上图,黑色部分为无法移动到的位置,根据前面的公式,这些位置也不会有贴砖,这样的场景更加符合标准的2.5D场景要求,也很形象。
这里要说明一下,虽然我们可以直接用一个Canvas包含掉所有的地图砖块的方式来呈现背景,但这样会大幅度加重游戏的性能负担;比起一次性用WriteableBitmap进行合成,最终Silverlight需要做的仅仅是对一个Image进行Clip而不是时时的滚动一堆的Image,性能方面优越性是巨明显的,唯一缺点就是合成的这个过程会有些卡。在本节Demo中,我专门设计了两个按钮:重新构建和随机混拼,大家不妨通过它们来体验下游戏中贴砖的解决方案及WriteableBitmap进行图象合成的强大功能:
即时战略游戏是游戏中的一门艺术,它强调的是团队合作、冷静的思维以及机敏的即时反映;下一节,我将赋予所有的单位以作战能力,搭配建立在艺术之上的阵型系统,一个气势磅礴的基于Silverlight的帝国时代Demo即将呈现在大家面前,敬请关注。
在线演示地址:http://silverfuture.cn