5-5 修复地图编辑器
在上一章中,我们已经可以创建更大尺寸的地图了。但是因为删除了一些代码的原因,现在地图编辑器处于失效的状态中。在这一章中,我们将根据新的生成地图单元方式,修复地图编辑器。在上一章中我们删除了HexGrid
中的Refresh
,如果现在要改变某个地图单元的高度或者颜色,应该刷新其所在地图块的网格,而不是所有的地图单元网格。所以在HexGridChunk
中添加新的Refresh
方法。代码如下:
1 | /// <summary> |
那应该在什么时候调用这个方法?之前我们是在改变地图单元的高度或者颜色的时候对mesh进行重新构建,因为那时只有一个mesh,但现在我们有很多的地图块,每个地图块都包含了一个mesh组件。就不能每个地图块都刷新,而是仅当地图块被改变时再刷新效率会更高,否则编辑较大地图时会感觉很卡。
接下来问题就变成了如何知道哪个地图块需要刷新。一个比较简单的方法是确保每个地图单元都知道它是属于哪一个地图块,这样地图单元就能在其被改变时刷新它所在的地图块,所以给HexCell
一个地图块的引用。代码如下:
1 | public class HexCell : MonoBehaviour |
当添加地图单元实例时HexGridChunk
可以直接把自己赋值给它。代码如下:
1 | public void AddCell(int index, HexCell cell) |
将这些引用连接建立之后,在HexCell
里创建一个Refresh
方法,地图单元刷新时就同步刷新自己所在的地图块。代码如下:
1 | … |
我们之所以不需要把HexCell.Refresh
设置为public
方法,因为只有地图单元自己清楚它什么时候发生了变化。例如,在高度改变之后。
1 | public int Elevation |
其实只有当前地图单元的高度被设置成了一个不同值时才需要刷新,并不需要在赋了一个相同的高度值后重新计算,所以新的高度值相同时,可以在set
属性的一开始就跳出。代码如下:
1 | public int Elevation |
然而这会跳过第一次设置高度为0时的计算,因为0是地图网格的默认高度,为预防这一点,确保初始值是永远都不会用到的值。代码如下:
1 | public class HexCell : MonoBehaviour |
什么是int.MinValue?
这是int所能表示的最小值,在C#中int是一个32位的数字,它有2的32次方种可能的整数值,分成正值和负值和0,其中一位用来指出这个值是不是负的。
最小值是负的2的31次方=-2147483648,我们永远不会使用这个高度等级。
最大值是2的31次方减1=2147483647,比2的31次方少1是因为还有0存在。
MSDN连接
除了改变高度会刷新当前的地图块,改变颜色也会。为了检测颜色是否被改变,我们也要把颜色设置成一个属性。重命名成首字母大写的Color,接着改成属性并使用私有的color变量。颜色的默认值是标准黑色,这里就不用再添加赋初始值的代码了。
1 | //cell颜色 |
回到Unity中运行,我们发现会报空引用异常,这是因为在把地图单元赋值给它所在的地图块之前,就设置了其默认的颜色和高度。最好的办法是在这里先不刷新,因为我们会在初始化完成之后三角化它们。换句话说就是只有在地图块被赋值完成后才进行刷新。代码如下:
1 | private void Refresh() |
现在又可以使用地图编辑器了,但是我们发现一个问题,就是在两个地图块的交界处,如果地图单元的颜色不是白色, 那就会产生很明显的一个边界。如下图:
这个问题很好理解,因为一个地图单元发生变化后,所有与它相邻的地图单元也会发生改变,而这些相邻的地图单元有可能在不同的地图块中。最简单的解决方案是当地图单元与其相邻地图单元不在一个地图块时,也刷新一下相邻单元格的地图块。代码如下:
1 | private void Refresh() |
这样修改虽然视觉效果正确,但我们要刷新单个地图块多次,一旦我们在一次绘制横跨多个地图单元时,情况就更糟糕了。我们没必要在地图块刷新信息时直接三角化,我们可以通知这个地图块需要刷新,然后在编辑完成时一次性三角化。
因为HexGridChunk
没有用来做其它的事情,我们可以用脚本的enable状态作为需要刷新的信号,当开始刷新时,给脚本设置enable状态,就算多次设置也没关系,因为不会有变化。稍后脚本更新时,我们就在这里进行三角化,然后再次设置状态为disable。
因我们使用LateUpdate,这样就能确保三角化发生在当前帧编辑完成之后。代码如下:
1 | public void Refresh() |
Update与LateUpdate有什么区别?
每一帧中, 所有enabled状态的组件中的Update会在随机时候调用。在这结束之后,LateUpdate方法也是同样的逻辑。所以这是两个更新步骤,一个早一些一个晚一些。Unity官方文档
Unity内脚本的生命周期如下图:
因为脚本组件默认状态就是enabled,所以不再需要在Start里进行三角构建,现在就可以删掉这个方法了。
1 | //private void Start() |
至此,我们就修复了地图编辑器的所有功能了,当我们编辑一个在地图块边缘的地图单元时,相邻地图块也会随之刷新。在接下来的章节中,我们会对现有代码进行一些优化。