5-12 笔刷尺寸

  在上一章中,我们将修改地图单元的高度和颜色这两个功能,通过选中与取消选中的方式进行了分离。现在可以只对地图单元进行高度编辑,或者只进行颜色编辑。现在,随着地图尺寸的增加,我们只对单个的地图单元修改,就显得非常繁琐,效率也很低。在本章中,我们来创建一个“笔刷”功能,使用户可以调整笔刷尺寸,来批量对地图单元进行修改。
  与高度滑动条类似,我们再创建一个笔刷尺寸滑动条,如下图:

  在HexMapEditor.cs中,添加一个方法来读取笔刷尺寸滑动条的数值,代码如下:

HexMapEditor.cs
1
2
3
4
5
6
7
8
9
10
11
12
public class HexMapEditor : MonoBehaviour
{
//笔刷的尺寸
private int brushSize;


//通过组件获取当前笔刷的尺寸
public void SetBrushSize(float size)
{
brushSize = (int)size;
}
}

  代码完成后,回到Unity中,将笔刷尺寸滚动条的最小值设置为0,最大值设置为4,并绑定SetBrushSize方法,如下图:

  接下来,我们需要创建一个方法,这个方法用来调用笔刷半径内所有地图单元的EditCell方法。其中鼠标选中的地图单元为笔刷的中心。代码如下:

HexMapEditor.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
private void HandleInput()
{
//射线起点为鼠标位置,经过主摄像机
Ray _inputRay = mainCamera.ScreenPointToRay(Input.mousePosition);

//检测射线是否碰撞到了collider
RaycastHit _hit;
if (Physics.Raycast(_inputRay, out _hit))
{
//hexGrid.ColorCell(_hit.point, activeColor);

//修改单个cell
//EditCell(hexGrid.GetCell(_hit.point));

//带笔刷 修改多个cell
EditCells(hexGrid.GetCell(_hit.point));
}
}

private void EditCells(HexCell center)
{
}

  我们定义了笔刷尺寸的最大值和最小值,当笔刷半径为0时,就只修改鼠标当前选中的地图单元。当半径为1时,包含当前选中的地图单元和跟选中地图单元相邻的地图单元。当半径为2时,包含中心地图单元、与中心地图单元相邻的所有地图单元、以及与中心地图单元相邻地图单元外侧相邻的地图单元。如笔刷范围示意图所示,此图下方代码也要用到:

  要编辑这些地图单元的高度或者颜色,我们在知道笔刷半径的情况下,就需要循环遍历笔刷覆盖的所有地图单元。那么首先就需要获取笔刷中心地图单元的坐标。这里我们可以直接使用HexCoordinates结构体中转换好的坐标。代码如下:

HexMapEditor.cs
1
2
3
4
5
6
private void EditCells(HexCell center)
{
//获取笔刷中心的cell坐标
int centerX = center.coordinates.X;
int centerZ = center.coordinates.Z;
}

  获取到笔刷中心地图单元的坐标后,我们需要根据这个坐标和笔刷半径,来确定其余在笔刷范围内的地图单元。这里我们先从Z坐标开始,并且将之分为两个部分,先从最底部一行开始遍历,一直遍历到笔刷中心地图单元所在的行为止。代码如下:

HexMapEditor.cs
1
2
3
4
5
6
7
8
9
10
11
12
private void EditCells(HexCell center)
{
//获取笔刷中心的cell坐标
int centerX = center.coordinates.X;
int centerZ = center.coordinates.Z;

//这里先进行Z(横行)的遍历,而且只遍历一半
//即从最底部一行开始,一直到中心cell所在的那行
for (int r = 0, z = centerZ - brushSize; z <= centerZ; z++, r++)
{
}
}

  通过观察笔刷范围示意图我们可以发现,在笔刷覆盖范围内的下半部分中,最底部一行的第一个地图单元,永远和中心地图单元有相同的X坐标值。并且行数越接近中心行,每行第一个地图单元的X坐标值就越小。每一行的最后一个地图单元,其X坐标值都等于笔刷中心地图单元的X坐标值加上笔刷半径。根据这两个规律,我们就可以遍历得到每一行中在笔刷范围内的地图单元的X坐标了。代码如下:

HexMapEditor.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
private void EditCells(HexCell center)
{
//获取笔刷中心的cell坐标
int centerX = center.coordinates.X;
int centerZ = center.coordinates.Z;

//这里先进行Z(横行)的遍历,而且只遍历一半
//即从最底部一行开始,一直到中心cell所在的那行
for (int r = 0, z = centerZ - brushSize; z <= centerZ; z++, r++)
{
//遍历每一行中在笔刷范围内cell的X坐标
for (int x = centerX - r; x <= centerX + brushSize; x++)
{
//通过计算后得出的坐标,获取到对应cell的实例,并对其进行高度或颜色的改变
EditCell(hexGrid.GetCell(new HexCoordinates(x, z)));
}
}
}

  这里要注意,我们目前并没有hexGrid.GetCell(HexCoordinates coordinates)这个重载,所以需要在HexGrid.cs中创建这个重载方法。目的是通过地图中的一组坐标值,来获取到对应坐标值的地图单元实例。代码如下:

HexGrid.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class HexGrid : MonoBehaviour
{


/// <summary>
/// 通过hexmap中的坐标,来获取cell的实例
/// </summary>
/// <param name="coordinates">hexmap中cell的坐标</param>
/// <returns>对应hexmap坐标值的cell实例</returns>
public HexCell GetCell(HexCoordinates coordinates)
{
int z = coordinates.Z;
int x = coordinates.X + z / 2;
return cells[x + z * cellCountX];
}
}

  至此,我们就可以修改一半笔刷范围内的地图单元了,如下图:

  接下来,我们只需要参考之前的逻辑,将循环进行对称,也就是从最顶端一行开始遍历,一直到除了中间行之外的上半部分所有的地图单元。代码如下:

HexMapEditor.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
private void EditCells(HexCell center)
{


//这里是通过循环补足上半部分地图单元的代码
//出了避免重复排除了中间行外,其余的逻辑是完全对称的
for (int r = 0, z = centerZ + brushSize; z > centerZ; z--, r++)
{
for (int x = centerX - brushSize; x <= centerX + r; x++)
{
EditCell(hexGrid.GetCell(new HexCoordinates(x, z)));
}
}
}

  现在笔刷范围虽然完整了,但是当我们对地图进行修改的时候,如果笔刷范围延伸到地图边界之外时,就会报一个数组越界的错误。为了避免这个错误,我们在HexGrid.GetCell(HexCoordinates coordinates)方法中检测边界,并且当获取到不存在的地图单元时返回null。代码如下:

HexGrid.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public HexCell GetCell(HexCoordinates coordinates)
{
//为了避免产生数组越界,这里要先检查X和Z坐标是否在范围内
int z = coordinates.Z;
if (z < 0 || z >= cellCountZ)
{
return null;
}

int x = coordinates.X + z / 2;
if (x < 0 || x >= cellCountX)
{
return null;
}

return cells[x + z * cellCountX];
}

  并且为了防止空引用异常,在HexMapEditor.EditCell(HexCell cell)方法中要检查被编辑的地图单元是否有实例存在。代码如下:

HexMapEditor.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
private void EditCell(HexCell cell)
{
//cell.Color = activeColor;

//避免空引用异常,首先检查被编辑的cell是否存在
if (cell)
{
//当applyColor为true,也就是索引值大于等于0,才会修改cell的颜色
if (applyColor)
{
cell.Color = activeColor;
}

//cell.Elevation = activeElevation;
//hexGrid.Refresh();

//当toggle勾选时,才会修改cell的高度
if (applyElevation)
{
cell.Elevation = activeElevation;
}
}
}

  至此,我们就完成了笔刷的基本功能,我们可以自定义笔刷大小,来批量修改地图单元的颜色或者高度,这样效率就会高很多,对用户也非常友好。在下一章中,我们将完成目前地图编辑器的最后一部分:显示和隐藏坐标值UI。

Github代码