撰写了文章 更新于 2020-12-31 10:41:56
【自学笔记】制作动森岛建规划器(二)
前言
前面已经成功实现了在鼠标点击的位置生成模型的功能,接下来需要更进一步,让生成出的模型能够自动适应位置改变形状,并且对整个大功能进行一些细节上的完善。
自动3Dtile
悬崖与河流的创建中,有一个效果是必须要做的,只要仔细观察上图的河岸形状就能发现,根据河流的形状变化,将会决定每一格的河流哪一侧有河岸、河岸向什么方向衔接。
这个需求在2D游戏中相当常见,搜索「Rule Tile」就能找到相应的2D插件,能够让每一块Tile根据周围格子的关系自动决定自己的形状。
但是这样的功能在3D中我没有找到标准的做法或者插件,于是我只能自己琢磨实现的方式。
在实现效果之前,我首先需要正确的素材,有了素材之后我才能更好的把功能正确实现出来。于是我先快速的制作了一批方块Tile模型出来。
总共47种方块,包含了绝大部分的Tile连接类型,只有纯对角的链接没有做,因为没有这种形状连接的需求。
这47种形状的方块分别属于哪种连接状态,我这里用一张图来表示一下。
其中,「×」表示的是这个方向没有其他Tile,「→」表示的是这个方向有其他Tile,留空则表示这个方向的Tile不影响状态。
有这张图标就比较容易理解了,通过判定方块周围八个格子是否存在其他方块,根据不同的结果来决定自身应该显示为哪种形状。
那么,素材有了,接下来需要让方块能够判定到周围方块的存在,并根据其存在状态改变自身。
这个判定可能很多不同的方法,不过我现在有啥就用啥了,直接继续上射线!
从方块的中心(因为锚点在底部,所以发射点上升了0.5)向指定方向发射长度为1的射线,并判判定射线是否击中指定Layer的物体。
因为要重复进行八次判定,所以我将这段程序打包成了一个Super Unit,作用应该类似于函数。
接着在这里每帧按序列检测一次八个方向,然后执行一次结果判定来决定应该显示成什么形状。
接下来的问题在于,我没有找到一个简单的判定方式来得到最终结果,在一段时间尝试无果之后,我最终决定采取最「笨」的方法,使用一个树形结构的「if判定树」,逐个方向按顺序进行判定,树形结构上不同的分支末端就是不同的结果。
理论上,八个方向逐级逐个判断,结果数量会呈指数增长,理论上会产生2^8=256种结果,这个结果的数量是极其庞大的,但看前面的图表就能发现,实际的结果只有47种,其余209种结果都是无用的。
总结其规律可以发现,上下左右四个正方向的判定,是优先于斜方向的,如果正方向上没有方块,那么该侧的两个斜方向的方块就不会影响判定。因此优先判定正方向,排除大量无用的斜方向判定,就可以剔除掉所有无用的结果了。
不过即便如此,从上面的截图也能看出,这个树状结构的难以阅读,基本上无法轻松读懂这个树状结构的逻辑关系。这个问题既是写成C#代码我想也还是一样的难以阅读。
我不确定有没有更优的实现方案,如果有知道更优方案的希望可以告诉我。
最终得到的效果如下:
方块Tile检测优化
上面所实现的效果存在一个效率问题,因为对每个方块对周围的检测都是逐帧的,当方块数量变多之后,帧率会大幅度降低。
这里的优化思路很简单。
首先,不是随时都需要检测周围的方块,只需要在方块数量发生变化的时候检测一次就可以。而数量发生变化的条件必然是生成或销毁了一个方块。所以只需要在生成或销毁的时候进行一次检测就可以。
其次,不是每个方块都需要进行检测。方块的变化是局部的,只有增加或减少了方块的位置为中心的3x3范围内才需要进行一次检测,也就是说每次进行检测的方块数量只有8~9个。
思路明确就好办很多了。
首先将「Update」更改为「Start」,只在方块放下的时候进行一次检测。
其次,首次检测时对周围8格存在的方块发送一次「二次检测」的事件指令。触发了二次检测的方块不对周围方块发送指令,所以首次检测和二次检测会有些微的不同。
这样就实现了检测效率的优化,不再会所有方块都随时进行八次射线检测。
方块生成位置的反馈
接下来需要添加一个方块生成位置的反馈,也就是在点击后会生成方块的位置预显示一个半透明的方块。
首先需要的是生成出一个方块,然后让这个方块时刻跟随鼠标移动。生成方块的方式与之前一样,而不同地方就是这个方块需要跟随着鼠标移动,所以现在需要把这个跟随效果实现出来。
另一方面,之前生成方块的功能是写在相机上的,这个做法虽然不会出错,但是和认知不太一致,一般不会认为「相机可以生成方块」。而这个用来标示生成位置的方块正好可以作为一个生成器,因此这里也顺便将方块生成的代码转移过来。
因为是用于生成新的方块的,所以这个对象就命名为「BoxSpawner」。
这里的「MousePoint」变量就是鼠标射线所打击的坐标,这里作为一个变量储存后方便调用。获取到这个坐标后,就逐帧刷新设置「BoxSpawner」的坐标位置,同时进行方块生成的判定。
最后得到的效果如下:
进入/退出建造
接下来需要让现在完成的所有功能有一个出入口。
具体方案就是做一个UI,点击建造按钮后进入建造状态同时隐藏按钮,在建造状态下点击鼠标右键可以退出建造状态。
因为现在的整个建造过程是由「BoxSpawner」完成的,所以进入和退出建造状态的方式就是生成或销毁一个「BoxSpawner」
首先创建一个按钮,并且在点击按钮的时候向上级负责总体控制的父级界面「主界面」发送事件指令。
而「主界面」通过状态机控制当前的状态,当接收到建造按钮发送的事件指令后就进入建造状态。
进入「BuildingCliff」状态的条件是接收到「InBuildCliff」这个事件指令。
返回到「Default」状态的条件是点击鼠标右键。
进入建造状态时,切换界面不同部分的显示状态,并且生成一个「BoxSpawner」,而退出建造状态时,则给「BoxSpawner」发送一个销毁指令中止其继续生成更多方块。
重叠检测
接下来还剩最后一个功能「重叠检测」,第一部分的大功能就算是完成了。
因为并没有判定当前生成方块的位置上是否有其他方块存在,所以可以在同一个位置生成无限个方块,实现「重叠检测」后,一方面可以避免冗余生成的模型占用额外资源,另一方面在将来也必然需要这样的功能让家具等物体不会被重叠放置。
这个功能理解起来很简单,但是我实际制作的时候发现,我无法顺利判定「是否有碰撞体与自身重叠」。
还是借助搜索引擎的帮助,在经过一段时间搜索之后我找到了解决方案。
说起来很简单,只要判定的双方其中一方挂有「Rigid Body」这个Component,就能顺利进行判定,而我之前尝试失败的主要原因就是因为缺少了这个Component。
具体的判定方式就是使用「OnTriggerEnter」和「OnTriggerExit」来判定是否有碰撞体进入或离开,同时设置一个Int变量来记录与自身重叠的碰撞体数量。如果有碰撞体进入则变量值+1,如果有碰撞体离开则变量值-1,只有变量值为0的时候才能生成方块。
另外,如果「BoxSpawner」处于和其他方块重叠的状态,自身的颜色也需要发生改变,以产生「无法创建」的视觉反馈。
最终效果如下:
目录
STMMfyd 1年前
这种一般都是用MarchingCube做的,比如War3的地图编辑器,最近的例子比如那个TownScaper,开发者的twitter里分析了关于构建原型的技术,可以参考一下
绯色de弦月 [作者] 1年前
STMMfyd 1年前
发布