1-9 改变选中地图单元的颜色

  在上一章中,我们通过坐标转换,可以知道鼠标点击在了哪一个地图单元上。接下来,我们在这个基础上改变被鼠标点击的地图单元的颜色。

  在HexGrid.cs中声明两个变量,分别为默认颜色和点击后变化的颜色,代码如下:

HexGrid.cs
1
2
3
4
//cell的默认颜色
public Color defaultColor = Color.white;
//cell被点击后的颜色
public Color touchedColor = Color.magenta;

  回到Unity中,选中Hex Grid物体,在Inspector面板中就能看到下图的效果,当然,我们也可以自定义初始颜色和点击后的颜色,如下图:

  回到HexCell.cs中,在这里我们声明一个变量,用来存储每个地图单元自己的颜色。代码如下:

HexCell.cs
1
2
3
4
5
6
7
8
9
public class HexCell : MonoBehaviour
{
//在实例化每个cell的时候会调用该实例
//针对每个cell,重新计算它的坐标值
public HexCoordinates coordinates;

//存储cell自身的颜色
public Color color;
}

  为每个地图单元赋初始颜色值,可以在HexGrid.CreateCell方法中实现。代码如下:

HexGrid.cs
1
2
3
4
5
6
7
8
9
10
private void CreateCell(int x, int z, int i)
{

//在不改变cell排列的情况下,重新计算每个cell的坐标位置
cell.coordinates = HexCoordinates.FromOffsetCoordinates(x, z);

//为每个cell赋颜色初始值
cell.color = defaultColor;

}

  当然,我们还需要将颜色的信息赋值给地图单元的Mesh组件,这样才能将颜色显示出来。代码如下:

HexMesh.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
//存储cell每个顶点的颜色信息
private List<Color> colors;

private void Awake()
{

//初始化vertices、triangles链表 用于存储顶点和面片信息
vertices = new List<Vector3>();
triangles = new List<int>();

//初始化colors链表,用于存储顶点颜色信息
colors = new List<Color>();
}

public void Triangulate(HexCell[] cells)
{

triangles.Clear();
colors.Clear();


//将所有的顶点位置信息,顶点位置信息的索引存储到链表中
hexMesh.vertices = vertices.ToArray();
hexMesh.triangles = triangles.ToArray();

//将所有顶点的颜色信息存储在colors链表中
hexMesh.colors = colors.ToArray();

}

  接着,在进行每个三角面片构建的时候,我们将颜色的信息赋值给每一个顶点,这样在构建出地图单元的时候,就带有了我们定义的初始颜色了。代码如下:

HexMesh.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public void Triangulate(HexCell[] cells)
{

//根据中点位置计算出其余的顶点位置信息,并按照顺序构建三角面片
for (int i = 0; i < 6; i++)
{
//构建三角面片
AddTriangle(
center,
center + HexMetrics.corners[i],
center + HexMetrics.corners[i + 1]
);

//为三角面片的顶点赋颜色值
AddTriangleColor(cell.color);
}
}

/// <summary>
/// 为每个三角面片的3个顶点赋颜色值
/// </summary>
/// <param name="color">三角面片顶点的颜色信息</param>
private void AddTriangleColor(Color color)
{
colors.Add(color);
colors.Add(color);
colors.Add(color);
}

  我们回到HexGrid.TouchCell方法中。这个方法的流程可以理解为:先将Unity内普通的坐标,转换成六边形地图中的坐标,即从Unity的Vector3(8.7, 0, 13.9)坐标转换为六边形地图的(0, -1, 1)坐标。

  接着,我们还需要知道被点击的地图单元在数组HexGrid.cell[]中具体的位置,也就是被惦记的地图单元在数组HexGrid.cell[]中的下标。才可以为该地图单元赋值新的颜色。

  如果要改变其颜色,依照现有的方法,我们还需要重新构建整个六边形地图的Mesh,因为这个Mesh是整体存储在一个变量中的。但是我们真的有必要因为某个地图单元的颜色改变而重新构建整个正六边形地图的Mesh吗?答案很显然,重新构建整个Mesh是不合理的。

  但是现在还不是优化这个步骤的时候,因为随着项目的功能不断增加,代码会变得越来越复杂,Mesh的外观也会变得复杂。所以现在做的任何优化在之后的代码中都会变得没有意义。虽然重新构建整个正六边形地图的Mesh虽然很无脑,但是却十分有效,目前我们先这样为地图单元更改颜色。代码如下:

HexGrid.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
private void TouchCell(Vector3 position)
{


//调用转换坐标的方法,定位具体点击到哪个cell上了
HexCoordinates coordinates = HexCoordinates.FromPosition(position);

//计算出cell位于cells[]数组中的位置
int index = coordinates.X + coordinates.Z * width + coordinates.Z / 2;

//获取这个cell的实例
HexCell cell = cells[index];

//为这个cell赋值颜色
cell.color = touchedColor;

//重新构建整个map的mesh
hexMesh.Triangulate(cells);
}

  代码完成之后,我们点击某一个地图单元,但是并没有任何的改变,被点击的地图单元也没有改变颜色。这是因为Unity中默认的着色器没有使用顶点颜色。我们需要自己创建一个自定义着色器(Assets/Create/Shader/Standard Surface Shader),并修改其中的代码。其实我们只需要对Unity新建的着色器进行2处很小的改动:1 在输入部分添加颜色信息。2 输出时让反射率与颜色值相乘。而且我们不必关心透明通道,只需要集中在rgb通道即可。代码如下: s

HexCell.shader
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
Shader "Custom/HexCell"
{


SubShader
{


struct Input
{
float2 uv_MainTex;
//添加颜色输入
float4 color:COLOR;
};



void surf (Input IN, inout SurfaceOutputStandard o)
{

//o.Albedo = c.rgb;

//输出部分,反射率与输入的颜色值相乘
o.Albedo = c.rgb * IN.color;


}

  接下来,新建一个材质球,使用我们修改后的shader,并将这个材质球替换掉场景中Hex Mesh物体组件上的材质球。最后效果如下图:

  这里要注意,某些情况下,点击地图单元后,可能会产生一些奇怪的阴影效果!这是因为在某些Unity版本中Standard Surface Shader可能会有阴影问题。如果发生了阴影抖动或者带状阴影的问题,这说明Z轴发生了冲突。调整全局方向光的阴影偏斜角度应该可以解决这个问题。

  这样我们就完成了基本的交互功能了。当我们点击一个地图单元后,这个地图单元就会变成我们预先定义好的被点击的颜色。下一章中我们在这个基础上更进一步,制作一个简单的地图编辑器功能,可以为不同的地图单元赋值不同的颜色。