士郎 用Unity盖房子(二)——《勇者斗恶龙:建造者2》地形生成和人物控制

15
回复
1191
查看
打印 上一主题 下一主题
[ 复制链接 ]
排名
1
昨日变化

8054

主题

8612

帖子

3万

积分

Rank: 16

UID
1231
好友
186
蛮牛币
12203
威望
30
注册时间
2013-7-29
在线时间
4123 小时
最后登录
2019-8-21

活力之星原创精华达人突出贡献奖财富之证游戏蛮牛QQ群会员蛮牛妹VIP

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有帐号?注册帐号

x
前言
接着上篇继续:(点击此处https://()
上篇用一个相对好理解方式简单的把基本功能实现了,但想要达到最终目的,仅在此基础上扩展还不够,这篇会把地图的逻辑修改部分,用更为高效的方式来实现。那么之前说好的两篇完结这个系列估计篇幅太长,暂定扩展到三篇。

当然,这不是说第一篇的内容就是无用功,大家之前对于实现方式的思考也不是白费的,其实这表现了真实开发过程中的一个常态,完善的代码逻辑都是要通过反复思考和工程迭代得来,极少有人能一步到位。



地图逻辑的重构
不知道大家有没有想过,为什么说在上一篇文章的基础上难以为继?
之前使用的方式是在每一个方块的中心点生成一个空物体,根据它的坐标为基础生成mesh数据。这种方式的好处在于很直观又容易保存每个方块的相邻关系,且在处理不同朝向的方块时可以方便的把世界坐标系下的方块顶点转换为本地坐标系的向量。

虽然说了这么多好处,但还有一个致命问题让我们不得不放弃这种方法,那就是效率问题。
现在地图规模还太小,不太感觉的出来。一旦规模稍微大一点,比如生成一个长宽高皆为100个小方块长度的区域,那么总的小方块个数就是100X100X100=100万个!这么小一点区域在实际游戏中的大小微不足道,但生成这么一小块区域就已经是在场景中实例化100万个物体的巨大效率问题,就别说生成完整的地形了。

不用舍不得老代码,直接新起一个工程重新开始。虽说之前的代码全部废弃了,但仍有一些思路是可以复用的。


其实之前的思路方向大体没问题,现在只是要稍做一些修改。我们不再把单独的一个方块看做一个实例,而是把多个方块的集合体看做一个整体块(chunk),然后用更小的数据体去存每个方块的具体信息。简而言之就是用一个能装很多个小方块的“大方块”来替代原来小方块的位置。


为方便计算,大方块也应该是正方形的.所以只用存每个边长小方块的个数,同时小方块的自己的边长不再限制为1,改设为一个常量。之前的方块坐标计算也做相应的修改。这里设置的每个方块的边长为2,每个chunk的边长为16个方块的长度。



现在重新来写mesh生成的逻辑。这次不同于之前的工程,我们把mesh生成单独拆开一个脚本.让其只用输入坐标和三角形就能生成任意形状,方便之后的扩展。大部分逻辑与之前相同,为缩减篇幅,这里就只贴出大致的结构,不贴出完整代码了。




在chunk中也给它一个自定义的坐标结构体用来定位,并修改为属性,同时在传入时同时修改chunk的实际位置和名字。



chunk的预制体,用Gizmos显示它的范围






接下来就是根据信息来生成mesh,这里要修改的地方就比较多了。
从上面的代码可以看到,用的是一个byte类型的数组来存储cube的数据,也就是每一个cube的数据为一个字节。可能有同学会好奇,一个字节的容量似乎太少,存不了什么东西。但我们可以用位运算的方式可以把需要的基本信息进行压缩,达到节省存储空间的目的。


简单来说就是,把一个字节内的八位分别用一位存储是否在场景中存在的信息,一位存是否是边缘透明的信息(这个会在之后用到),两位用来存朝向信息,四为用来存种类信息。这样我们就能保存四种朝向和16种方块类型,这对于我们目前的小工程来说,也足够用了。




其实这里没有方块也可以看做是方块的类型,可以合在一起存储,这样一来方块的种类就达到了31种

然后用一个结构体和几种枚举写出相互转换的方法,方便读取和存储数据。


当然这里的方法不是唯一的,自己做简单的数据映射也是一种很有效的信息压缩方法,大家可以使用自己习惯的方式。


接着就是根据chunk内存储的数据生成mesh,这里的逻辑与第一篇里的大致相同,只有两个地方需要修改。





首先,我们在CubeMetrics里存储的方块的顶点是相对于方块自身坐标系的,而现在方块的朝向是有四种可能变化的。在上一篇文章中,我们在修改方块朝向的同时也修改了transfrom自身的朝向,然后使用transform.InverseTransformVector()方法把顶点转换为自身坐标系,达到修改生成的mesh朝向问题。





但现在不存在这个实体了,这一步我们没法偷懒使用现成组件的方法了,因此只能手动去写一个针对我们这四个朝向的矩阵变换方法,并在生成mesh时对每一个顶点进行转换。




cube数据化带来的另外一个问题是,没法直接保存每个cube的相邻引用了。所幸每个cube的相对位置可以通过其在数据数组中的下标算出来。





然后提前把chunk的相邻引用保存下来,在越界的时候去检测相邻的chunk内是否存在方块。




于是就可以通过方块的面对方向算出这个面是否需要隐藏。



后面就不截出来了,逻辑是一样的



接着写一点测试代码,根据噪声图去生成地形数据。如果没有现成的噪声纹理,unity里的mathf库里是有封装好的API的,可以直接使用。




现在还没设置UV,但不妨碍通过mesh的形状验证逻辑是否正确。




现在我们把UV贴上。这次我终于认识到了自己不是搞美术材料的事实,老老实实的去挑了一份还看得过去的纹理图。



这里可以根据距离地形的深度,在每个地质层以随机概率生成一些矿石之类的东西,就好像真的地形一样。



看上去还像是那么回事地形动态生成

现在根据指定的坐标生成地形是没问题了,但在真正的游戏中,人是可以移动的。我们不能在一开始就把地图生成很大的范围,那样的话加载时间会拉到很长,内存占用也是极大。所以只能使用折中的办法,让地形随着人物位置生成,大部分开放世界的逻辑都是如此。



首先思考一下实现方式。让人物每移动一点距离就把周围地图刷新一次显然不太合适,这样相当于只要人物在移动时一直都在不停刷新地图,性能肯定不允许。





理想中的方法是人物在一个区域内移动时地图不会刷新,但一旦超过这个区域的位置时刷新区域的坐标会根据人物当前位置更新,换句话说就是根据移动距离有一个阈值来判断刷新。我们可以直接实时获取人物坐标,以此为基准计算,但吃不准到底刷新区域的大小和刷新频率设置成多少比较好。因此最好能专门写一个方法,能方便的调整测试,我们新建一个类去试试。



用Unity内置的Bounds(包围盒)类来表示刷新的区域,让其中一个包围盒的中心点实时的跟随人物位置的中心点移动。设置一个比例参数去调整跟随人物移动的包围盒与刷新区域包围盒之间的大小比例。





逻辑其实很简单,当较小包围盒(人物身上的)的最大点和最小点不在较大包围盒(刷新区域)里时,调用刷新方法。




写这个刷新方法之前,先处理一下根据移动坐标刷新的大包围盒中心的方法。由于我们的chunk在世界中的坐标是根据边长的固定比例计算的,所以最好把处理后的坐标点与此同步。





接下来就是根据包围盒的位置和大小去找到需要刷新哪些chunk。




在chunk的根节点上新建一个Grid脚本,存储和管理所有的chunk和其数据。按我们的逻辑,在处于刷新区域内时,刷新并显示当前所有处于其内的chunk,而在离开时把可能改动的数据存储在以坐标为key的字典中。在刷新时优先读取以存在的数据,没有时才根据自身坐标生成数据。否则我们辛辛苦苦盖的房子出去走了一圈回来就不见了可就不太对劲了。顺便使用对象池来管理chunk,进一步减少GC,这是很常见的优化方法,就不把这部分贴出来了。





最后,把需要刷新的chunk放在一个容器中,在协程中以每帧一个的速度刷新到场景中。这里使用的容器是栈(stack),主要是考虑在移动速度可能过快的情况下,优先刷新后改变的chunk,也就是面朝移动方向的先刷新。





最后验证一下效果:



可以看到速度过快时刷新速度会有些跟不上,但是加快刷新速度又会明显降低帧数。这里有很多办法去优化,比如在静止不动时刷新更大的区域给予更多缓冲区域之类的。但对我们的 demo来说,只要限制速度,也足够用了,这部分的优化留给大家自己发挥。当务之急是检测一下对地图修改后的数据能不能正确保存下来。
在地上挖一个大坑,看看出去一圈有没有变化。




可以看到这部分逻辑也没问题,那么现在对我们来说,只要内存允许,我们的地图就是“无限“的。可以针对这部分做的优化还有很多,比如增加进入游戏的加载时间让开始的地图变得大一些、优化刷新逻辑使其更流畅、地图数据转存到硬盘实现存档功能等等,不过这些都是后话了。




简单的人物控制
经过前面的逻辑实现,这部分已经没什么好写的了。功能与方法的接口在前面都已经搭好了,照着去调用就是了。
关于人物的移动和视角还有个很有意思的地方。大家都知道,大部分的类似方块建造的开放世界游戏大多使用的是第一人称视角,但DQ2却是第三人称的!虽然也能切回第一人称视角就是了。我能大概理解可能是基于RPG要素的考虑设计了这么一个操作模式,但是在游玩的时候觉得有些需要精确建造的位置确实有点不方便,很难把视角对准。但当我尝试去复现的时候,才发现DQ2的第三人称视角已经是做过许多设计和功能上优化了(以玩家角度很难感觉到开发者的苦啊),完全复现实在是工作量太大。所以抱歉的容我在这偷个懒,用第三人称移动和第一人称建造的混合模式去糊弄过去(这其实有点像DQ2后期的建造师手套功能,也算是某种程度上的还原吧)。


首先是人物,二头身的3D模型还加动画,这个要求分明就是在刁难胖虎,不过还好找了很久之后,终于找到一个水手服双马尾萝莉,还不要钱。(我没有在开车,你们也没证据)





现在人物有了,下一步就是人物动画,移动控制和相机控制,这部分任何游戏都可能会遇到,与今天的主题没多大直接关系,不在此赘述了,对这部分有兴趣的同学自己下载工程吧。


总之,在挂上控制脚本后,把之前的刷新地图脚本也挂载在人物身上,我们的小萝莉就可以在无限宽广的世界中任意遨游了。



这里还需要说一下一个我遇到的小问题,mesh刷新时这里是直接把原来的缓存数据的mesh清空,读值之后再给meshCollider赋值的。这样会带来的问题是当赋了一个没有顶点的空mesh之后,这个chunk的meshCollider就会失效,之前测试时我的小萝莉经常跑着跑着就掉下去了。


后经查阅,推测是在给meshCollider赋值时,如果引用不变,无法触发set属性达到刷新碰撞的目的,因此这个地方稍作修改就行了。





还没完,现在还需要让小萝莉能盖房子,不然都对不起这个标题。这个时候另外8个刚才没用到的方块纹理就派上用场了,先做个简单的UI,用toggle组件把图标与放置方块时的类型一一对应起来。







根据鼠标位置发射一条射线,把打倒chunk的坐标点换算到chunk的自身坐标里,再去修改方块的数据。





最后,在人物的update里实时去刷新这个坐标的值,仅在点击时调用setData方法。为了方便观察逻辑是否正确,为当前射线击中坐标添加一个预览方块,并添加一点粒子效果表示chunk上数据的修改。




就别吐槽这个鸡啄米的动画了... 免费素材能用的动画就那么几个。




另外提一句预览框的效果怎么做。png格式的图片是可以保存alpha通道信息的,但是直接把这种镂空纹理的图拖入材质球是没效果的,还要把标准着色器里的渲染模式改成Cutout。



结束
现在基本逻辑都大概还原了,总算是站在了最先想要实现功能的起点上,老实说在一开始的时候真没想到要绕这么大一个圈。这期的内容可能会有些多,但是以文章为载体我没办法说的很详细,所以感兴趣的同学还是下载工程研究。


知乎@沈琰


1.jpg (47.88 KB, 下载次数: 8)

1.jpg
7日久生情
2922/5000
排名
2230
昨日变化

1

主题

1896

帖子

2922

积分

Rank: 7Rank: 7Rank: 7Rank: 7

UID
119154
好友
0
蛮牛币
3256
威望
0
注册时间
2015-8-21
在线时间
395 小时
最后登录
2019-8-18
沙发
2019-7-11 08:41:56 只看该作者
谢谢楼主大大、。
7日久生情
2115/5000
排名
4092
昨日变化

0

主题

1391

帖子

2115

积分

Rank: 7Rank: 7Rank: 7Rank: 7

UID
254705
好友
1
蛮牛币
1915
威望
0
注册时间
2017-11-16
在线时间
362 小时
最后登录
2019-8-21
板凳
2019-7-11 08:42:53 只看该作者
6666666666666666666666666666
4四处流浪
320/500
排名
12479
昨日变化

3

主题

125

帖子

320

积分

Rank: 4

UID
310955
好友
0
蛮牛币
410
威望
0
注册时间
2019-1-7
在线时间
132 小时
最后登录
2019-8-21
地板
2019-7-11 09:20:47 只看该作者
哇,用的是我最喜欢的方块概念材质唉!(•̀⌄•́)
排名
64935
昨日变化

0

主题

28

帖子

49

积分

Rank: 1

UID
303137
好友
0
蛮牛币
12
威望
0
注册时间
2018-11-6
在线时间
19 小时
最后登录
2019-7-30
5#
2019-7-11 10:01:22 只看该作者
66666666666666666
排名
64935
昨日变化

0

主题

43

帖子

57

积分

Rank: 2Rank: 2

UID
215482
好友
0
蛮牛币
3
威望
0
注册时间
2017-3-30
在线时间
12 小时
最后登录
2019-8-16
6#
2019-7-11 10:09:21 只看该作者
顶顶顶顶顶顶顶顶顶顶顶顶顶顶顶顶顶顶顶

0

主题

3

帖子

9

积分

Rank: 1

UID
163271
好友
0
蛮牛币
4
威望
0
注册时间
2016-8-20
在线时间
6 小时
最后登录
2019-7-12
7#
2019-7-11 20:04:05 只看该作者
6666666666666666666666666
7日久生情
2115/5000
排名
4092
昨日变化

0

主题

1391

帖子

2115

积分

Rank: 7Rank: 7Rank: 7Rank: 7

UID
254705
好友
1
蛮牛币
1915
威望
0
注册时间
2017-11-16
在线时间
362 小时
最后登录
2019-8-21
8#
2019-7-15 15:47:22 只看该作者
6666666666666666666666666666666
7日久生情
2382/5000
排名
1387
昨日变化

0

主题

742

帖子

2382

积分

Rank: 7Rank: 7Rank: 7Rank: 7

UID
135463
好友
0
蛮牛币
73
威望
0
注册时间
2016-1-23
在线时间
748 小时
最后登录
2019-8-21
9#
2019-7-16 15:24:52 只看该作者
666666666666666666666666666hhhhhhhhhhhhh
5熟悉之中
719/1000
排名
6394
昨日变化

2

主题

46

帖子

719

积分

Rank: 5Rank: 5

UID
125626
好友
1
蛮牛币
86
威望
0
注册时间
2015-10-15
在线时间
459 小时
最后登录
2019-8-19
10#
2019-7-17 18:27:06 只看该作者
66666的一批
5熟悉之中
765/1000
排名
10706
昨日变化

0

主题

507

帖子

765

积分

Rank: 5Rank: 5

UID
301976
好友
1
蛮牛币
1140
威望
0
注册时间
2018-10-31
在线时间
160 小时
最后登录
2019-8-21
11#
2019-7-19 10:28:37 只看该作者
MeshPro...
7日久生情
2181/5000
排名
601
昨日变化

3

主题

140

帖子

2181

积分

Rank: 7Rank: 7Rank: 7Rank: 7

UID
44272
好友
3
蛮牛币
6152
威望
0
注册时间
2014-9-10
在线时间
658 小时
最后登录
2019-7-22
QQ
12#
2019-7-22 11:55:35 只看该作者
干货~~可以哈。~!
排名
28931
昨日变化

0

主题

40

帖子

96

积分

Rank: 2Rank: 2

UID
21931
好友
0
蛮牛币
135
威望
0
注册时间
2014-4-18
在线时间
44 小时
最后登录
2019-8-20
QQ
13#
2019-7-22 23:11:29 只看该作者
学习学习
4四处流浪
330/500

3

主题

63

帖子

330

积分

Rank: 4

UID
156657
好友
0
蛮牛币
1329
威望
0
注册时间
2016-12-22
在线时间
256 小时
最后登录
2019-8-19
14#
2019-7-24 09:26:33 只看该作者
有些图挂了啊
5熟悉之中
595/1000
排名
5175
昨日变化

0

主题

105

帖子

595

积分

Rank: 5Rank: 5

UID
192868
好友
0
蛮牛币
493
威望
0
注册时间
2016-12-16
在线时间
212 小时
最后登录
2019-7-31
15#
2019-7-26 18:12:28 只看该作者
学习mark
您需要登录后才可以回帖 登录 | 注册帐号

本版积分规则