一 效果图
先上效果图吧,这是为了吸引到你们的ヽ(。◕‿◕。)ノ゚
战争迷雾效果演示图
战争迷雾调试界面演示图
由于是gif录制,为了压缩图片,帧率有点低,实际运行时,参数调整好是不会像这样一卡一顿的。
二 战争迷雾概述
战争迷雾一般用于Startcraft等RTS类型游戏,还有就是War3等Moba类型游戏,主要包括三个概念:未探索区域、已探索区域、当前视野。
1)未探索区域:一般展示为黑色区域,像星际争霸这样的游戏,开局时未探索区域一般是暗黑的,只有地图上的原始晶体矿产能够被看到,敌人建筑、角色等都不暴露。
2)已探索区域:一般显示为灰色区域,已探索表示某块区域曾经被你的视野覆盖过,星际争霸中已探索的区域会保留你当时视野离开时该区域的建筑状态,所以可以看到敌人的建筑。
3)当前视野:一般全亮,视野范围内除了隐身单位等特殊设定,所有的建筑、角色、特效等都是可见的,视野一般锁定在可移动角色或者特定魔法上面,会随着角色的移动而移动,随着魔法的消失而消失。
三 实现原理
战争迷雾的实现方式大体上可以分为两个步骤:贴图生成、屏幕渲染。
3.1 贴图生成
贴图的生成有两种方式:
1)拼接法:
使用类似地图拼接的原理去实现,贴图如下:
战争迷雾拼接贴图
这种方式个人认为很不靠谱,局限性很大,而且迷雾总是会运动的,在平滑处理这点上会比较粗糙,不太自然。这里不再赘述它的实现原理。
2)绘制法:绘制法和使用的地图模型有很大关系,一般使用的有两种模型:一个是正方形地图,另外一个是六边形地图。六边形地图示例如下:
战争迷雾六边形地图贴图
原理简单直白,使用正方形/者六边形划分地图空间,以正方形/六边形为单位标记被探索过和当前视野区域。这里探索过的区域是棱角分明的,可以使用高斯模糊进行模糊处理。一般来说,正方形/六边形边长要选择合适,太长会导致模糊处理效果不理想,太短会导致地图单元格太多,全图刷新消耗增大。另外说一句,战争迷雾的地图和战斗系统的逻辑地图其实是可以分离的,所以两者并没有必然联系,你可以单独为你的战争迷雾系统选择地图模型。我也建议你不管是不是同一套地图,实现时都实现解耦。
3.2 屏幕渲染
得到如上贴图以后,就可以渲染到屏幕了,渲染方式一般来说有3种:
1)屏幕后处理:在原本屏幕显示图像上叠加混合战争迷雾贴图。
2)摄像机投影:使用投影仪进行投影,将战争迷雾投影到世界空间。
3)模型贴图:使用一张覆盖整个世界空间的平面模型来绘制战争迷雾贴图。
不管你选择使用哪一种方式,在这一步当中都需要在Shader里进行像素级别的平滑过渡。从上一个时刻的贴图状态过渡到当前时刻的贴图状态。
四 代码实现
原理大致上应该是清楚了,因为这个系统的设计原理实际上也不算是复杂,下面就一些重要步骤给出代码实现。这里实践的时候采用的是正方形地图,模型贴图方式。正方形地图模型不管是模糊处理还是Shader绘制都要比六边形地图简单。正方形贴图Buffer使用Color32的二维数组表示,根据位置信息,每个正方形网格会对应一个Color32数据,包含颜色值和透明度,能够很好的进行边缘平滑效果。
1 // Color buffers -- prepared on the worker thread. 2 protected Color32[] mBuffer0; 3 protected Color32[] mBuffer1; 4 protected Color32[] mBuffer2;
这里使用了3个Buffer,是因为图像处理是很耗时的,所以为它单独开辟了线程去处理,为了线程同步问题,才增设了Buffer,关于线程这点稍后再说。
4.1 刷新贴图Buffer
贴图Buffer需要根据游戏逻辑中各个带有视野的单位去实时刷新,在正方形地图模型中,是根据单位当前位置和视野半径做圆,将圆内圈住的小正方形标记为探索。
1 void RevealUsingRadius (IFOWRevealer r, float worldToTex) 2 { 3 // Position relative to the fog of war 4 Vector3 pos = (r.GetPosition() - mOrigin) * worldToTex; 5 float radius = r.GetRadius() * worldToTex - radiusOffset; 6 7 // Coordinates we'll be dealing with 8 int xmin = Mathf.RoundToInt(pos.x - radius); 9 int ymin = Mathf.RoundToInt(pos.z - radius); 10 int xmax = Mathf.RoundToInt(pos.x + radius); 11 int ymax = Mathf.RoundToInt(pos.z + radius); 12 13 int cx = Mathf.RoundToInt(pos.x); 14 int cy = Mathf.RoundToInt(pos.z); 15 16 cx = Mathf.Clamp(cx, 0, textureSize - 1); 17 cy = Mathf.Clamp(cy, 0, textureSize - 1); 18 19 int radiusSqr = Mathf.RoundToInt(radius * radius); 20 21 for (int y = ymin; y < ymax; ++y) 22 { 23 if (y > -1 && y < textureSize) 24 { 25 int yw = y * textureSize; 26