因为最近进行求职的缘故,需要一个地方来show自己的所学所用。并非所有的求职网站都有相应的作品展示区域,因此选择了在csdn完成作品的展示。在这里只展示部分在我编程过程中完成的难点问题。有些东西也只是执行思路不涉及具体代码。以供交流,希望能给同样刚刚入门u3d的同学一些帮助。
相关内容:
1.随机地图生成
这个组件是基于mesh写出的地图地形。unity其实自带了有地图生成的功能,其中完善的组件也很丰富。但是在一开始我电脑坏掉的那段时间,使用一台破电脑跑自带地图卡的飞起之后,就没再使用过这个东西。当然,这也是个人喜好,我个人更偏向于随机生成的地图,由代码控制生成地形,只要地图能自由畅通,不断更换的地图会有更好的新鲜感。(其实一半以上原因是因为我懒得做美工而且极其缺乏美术细胞。)
基于mesh生成的地形,其实现原理,无非是数组的具现化。我们都知道,unity中,mesh是以数组的形式存储顶点坐标,法线及其uv信息。再以数组的形式,每三个为一组不划分的进行读入。因此,3d地图的实现可理解为一个3维数组。
在我的实现过程中,首先是可通行区域的2维数组划分。(因为除了日常可通行的区域外,游戏场景肯定还包括暗渊,高点等不能正常通过的区域穿插其中。)因此,这个二维数组的建立便是决定后续可通行的区域,往后,无论是高度为几的区域都是在此基础上进行建立的。
以下为一个数组示例,其体现了一座桥,及桥地下是可通行的路面。
可以看到,第一张矩阵是从纵向中间穿过的道路。第三张矩阵是横向中间穿过的道路。而第二张矩阵就要稍微抽象点。但其实并不难理解,人们不可能从高度1一下跨到高度3的位置,因此需要一个一个高度2的阶梯或是斜坡完成高度的过度。在这,我统一使用的是一个三角形的斜坡(因为阶梯并不是不会,而是太麻烦,况且本来也不是成品,我一向如此偷懒~)
效果是有点丑陋的…没办法,这个斜坡是自动生成的,在我的程序中,将地图分割成了多个不同高度的区域,于是我将可通行的区域划分为多个,在相邻区域间随机取一个点,对其周遭四格进行斜坡的生成。这个过程听起来是简单的,但执行起来有一点麻烦,因为有些区域并不清楚其厚度(也就是区域的宽)如果取值过大的话会造成一个斜坡横跨了两个区域(这是bug)。而为了规避这种情况,只能取一个点,也就形成了这样的布局,其坏处就是,不仅丑,而且坡度过大。(大概在40-500度的坡度,因为加入了细节运算,坡上的点位会有上下偏移。)
而后续关于这块的改进我也想好了。无非是进行区域生成的时候更加智能一些,以保证,取1-n个点位的时候,不会出现横跨两个区域的现象。当然,这个问题需要后续来解决。暂时我打算先完成其他游戏主体部分。
关于为什么使用mesh,其实我的初衷是游戏的随机性。(受到太吾绘卷的毒害我发现随机比固定剧情有意思多了。这也是我偷懒的借口……)但后续发现还是有很多其他好处的。
(1)性能优化
在我的地图中,以上这个地形是以一个mesh进行绘制的。没有错,虽然地图挺大的(80803,原谅我没见过世面的样子),但是其实它就是一个东西,而且在斜面生成处我费了些功夫,除了必要的面,基本没渲染其他的面。因此它很节约静态内存。它看起来是一个非常完整的路面,但是就只有一个架子,敲碎上面的砖块,下面就是空的。
(2)渲染优化
在学习shader的过程中,曾经看过几篇帖子是关于合并mesh的,为的就是讲shader的渲染次数减少,而这样生成的地图可以少到甚至只有一个,因此渲染次数并不多。
(3)游戏性
我针对这个随机生成地图想过很多游戏性的方案。例如在后续的地图上随机生成风眼(已经有做了,但我还缺个龙卷风),生成水下山洞,生成熔浆海等。这些随机生成的事物都存在其生成的不确定性,位置的不确定性,大小的不确定性,但其是否存在,其实都取决于地图布局,而他们的出现,可能会给地图带来其他的加成效果。例如,移速加快战斗时自带浮力,玩家可漂浮进行战斗。获得更加稀有的物品和一些奇遇。以及进入一个世外桃源的机会等等。
这些事物的生成都需要依赖于随机地图的区域划分,又为随机地图的存在增加一些筹码。
地图生成的部分大概就只讲这些。毕竟我也正在起步,其实还没有完善更多的内容以供参考。但如果想试试的朋友,请务必注意mesh的渲染顺序,这个点坑过我这个新手很多次。
2.细节优化
其实上面说的地图可以非常大,只需要增加其基础渲染的size即可。但是这就会出现细节过差的问题。针对这个点我说一下我关于游戏地图,细节点的制作。
在上面我们说到,mesh的渲染是以三角形为基础进行的。因此,我完成细节优化的方式就是往里面加入点。往三角形中加入一个点,以确定一个新的三个三角形,其效果类似这样。
每加入一次,便相当于该三角形被细节化一次。当然,这是不够的,我们的基础size是3,哪怕加入一个细节点,产生的三角形size至少还是1。因此再加入一次便是这个样子。
没有错,从这个三角形可以看出来我写这个程序的算法,是使用一个数组存储三角形顶点,然后每3个遍历一次,因此一个三角形一次细节化是加入一个点,两次是4个点,这是不停的对三角形进行细化,每次细化都是对原有的三角形一次精细。
当然,这还是不够,两次细化之后,size也仍然有0.75左右。这也是我的地图还是很丑的原因。在这里需要提到一个坑。mesh的顶点是有限制的,65k之后,每存储进去一个,会挤出去一个,而程序只会报一个警报。(天杀的警报,程序员只看error不看waring造成我当时卡在这个程序好几个小时。)
这个坑也是阻止我细节优化的关键位置。再多的话,地图就会错乱。因此,这也是我前面说到甚至只需要一个mesh就能实现地图的保留说法的原因。因为不可能只用一个mesh。想要继续进行细节优化,智能进行mesh的划分。其实这点还算容易解决。该地图实现的内容在二次优化时卡顿,则加入两个mesh最少可再进行一次,以此类推,在四次优化时,细节点位的size就会小到0.175,五次会小到0.0875。已经足够使用。而五次优化所需要不过是20-30个mesh。其实对于内存应该还是不会有特别大影响,尤其是细节,其实更多是针对一些特殊需求的地方。例如墙壁,坡道,对于平地来说,盖上草皮其实毫无差别。因此可着重细节化需求的位置。可节省大量的运算内存和静态内存。
在这除了提一下这些,还要说到另外一个坑。关于运算内存的坑。
其实一开始我所想的细节化是这样的
也就是每个线取一个中点进行细节,再对点位进行浮动。
但这个想法也让我的程序卡顿了整整两天。每次运算都需要巨量的时间,着实恐怖。
关于这点,后续我也大概知道了原因。这种细节化的思路,需要考虑相邻三角形的细节点位问题。因为相邻三角形必然会有一个细节点与之相重合,如果不管他,则会出现错位问题,如果管他,就需要再进入原来的vert数组里进行一次遍历。其效率可想而知,相当于每个for循环里都嵌套了无数for循环用来查询。因此这个方法被我pass了。也提醒一下后来的朋友,这个方法看似比以上更加合理,但需要考虑到如上问题。否则你就会像我一样run的时候,卡在那半天动不了,直想砸电脑。
今天先分享到这,后续可能写一些关于shader的学习历程,还有最近使用到的一个很实用的光雾。以及之前使用java无引擎编辑游戏时,需要到的一些困难。