This tutorial is the tenth part of a series about hexagon maps. This time, we'll add walls in between cells.

Duplicate one of the other toggle's UI elements and adjust them so they control the walled state. I put them in the UI panel with the other features.

Unlike rivers and roads, walls don't go from cell to cell. They're in between them. So we don't need to worry about dragging. When the wall toggle is active, just set the the current cell's walled state based on the toggle.

To adjust the walled state of cells, we have to add support for a toggle to HexMapEditor . So add another OptionalToggle field and a method to set it.

To support walled cells, let's add a Walled property to HexCell . It's a simple toggle. Because the walls are placed in between cells, we have to refresh both the edited cell and its neighbors.

Walls are terrain features, although large ones. Like the other features, we don't edit them directly. Instead, we edit the cells. We're not going to place individual wall segments, we'll wall off entire cells.

To support walls, we have to know where to place them. We'll put them in between cells, along the edges that connects them. As our already existing features are placed in the central area of cells, we don't need to worry about walls cutting through those features.

Creating Walls

Because walls follow the contours of cells, they don't have a fixed shape. So we cannot simply use a prefab for them, like we do for the other features. Instead, we have to construct a mesh, as we do for the terrain. This means that our chunk prefab needs another HexMesh child. Duplicate one of its other mesh children, and make sure the new Walls objects cast shadows. It doesn't need anything besides vertices and triangles, so all HexMesh options should be disabled.

Walls prefab child.

It makes sense that walls are an urban feature, so I used the red urban material for them.

Managing Walls Because walls are features, they are the responsibility of HexFeatureManager . So give the feature manager a reference to the Walls object, and have it invoke the Clear and Apply methods. public HexMesh walls; … public void Clear () { … walls.Clear(); } public void Apply () { walls.Apply(); } Walls connected to the feature manager. Shouldn't Walls be a child of Features? You could arrange the objects like that, but it's not necessary. Because the hierarchy view only shows the direct children of prefab root objects, I prefer to keep Walls a direct child of Hex Grid Chunk. Now we have to add a method to the manager so walls can be added to it. As walls exists along the edges between cells, it needs to know the relevant edge vertices and cells. HexGridChunk will invoke it via TriangulateConnection , so with the cell currently being triangulated, and one of its neighbors. From this point of view, the current cell is on the near side of the wall, and the other cells is on the far side. public void AddWall ( EdgeVertices near, HexCell nearCell, EdgeVertices far, HexCell farCell ) { } Invoke this new method in HexGridChunk.TriangulateConnection after all other connection work is done, right before we move on to the corner triangle. We'll leave it to the feature manager to decide whether a wall should actually be placed. void TriangulateConnection ( HexDirection direction, HexCell cell, EdgeVertices e1 ) { … if (cell.GetEdgeType(direction) == HexEdgeType.Slope) { … } else { … } features.AddWall(e1, cell, e2, neighbor); HexCell nextNeighbor = cell.GetNeighbor(direction.Next()); if (direction <= HexDirection.E && nextNeighbor != null) { … } }

Building a Wall Segment An entire wall will snake through multiple cell edges. Each edge contains just a single segment of the wall. From the point of view of the near cell, the segment begins at the left side of the edge, and ends at the right side. Let's add a separate method to HexFeatureManager that creates a single segment, based on the four vertices at the corners of an edge. void AddWallSegment ( Vector3 nearLeft, Vector3 farLeft, Vector3 nearRight, Vector3 farRight ) { } Near and far sides. AddWall can invoke this method with the first and last vertices of the edges. But walls should only be added when we have a connection between a walled cell and one that is not walled. It doesn't matter which cell is on the inside or the outside, only that their state is different. public void AddWall ( EdgeVertices near, HexCell nearCell, EdgeVertices far, HexCell farCell ) { if (nearCell.Walled != farCell.Walled) { AddWallSegment(near.v1, far.v1, near.v5, far.v5); } } The simplest possible wall segment is a single quad that stands in the middle of the edge. We find its bottom vertices by interpolating halfway from the near to the far vertices. void AddWallSegment ( Vector3 nearLeft, Vector3 farLeft, Vector3 nearRight, Vector3 farRight ) { Vector3 left = Vector3.Lerp(nearLeft, farLeft, 0.5f); Vector3 right = Vector3.Lerp(nearRight, farRight, 0.5f); } How high should our walls be? Let's define this in HexMetrics . I made them as high as a single elevation level. public const float wallHeight = 3f; HexFeatureManager.AddWallSegment can use this height to position the third and fourth vertices of our quad, and add it to the walls mesh. Vector3 left = Vector3.Lerp(nearLeft, farLeft, 0.5f); Vector3 right = Vector3.Lerp(nearRight, farRight, 0.5f); Vector3 v1, v2, v3, v4; v1 = v3 = left; v2 = v4 = right; v3.y = v4.y = left.y + HexMetrics.wallHeight; walls.AddQuad(v1, v2, v3, v4); You can now edit walls and they will show up as strips of quads. However, you won't see an unbroken wall. Each quad is only visible from one side. Its face is oriented towards the cell it was added from. Single-sided wall quads. We can quickly solve this by adding a second quad that's facing the other side. walls.AddQuad(v1, v2, v3, v4); walls.AddQuad(v2, v1, v4, v3); Two-sided walls. The entire walls are now visible, although there are still gaps at the cell corners, where three cells meet. We'll fill those later.

Thick Walls Although the walls are visible from both sides, they don't have any thickness. The walls are effectively as thin as paper, making them nearly invisible from certain view angles. So let's make them solid by adding thickness. Let's define how thick they are in HexMetrics . I picked 0.75 units as an arbitrary value that looked good to me. public const float wallThickness = 0.75f; To make the walls thick, we have to pull the two quads apart. They have to move in opposite directions. One side should move towards the near edge, the other towards the far edge. The offset vector to do this is simply far - near , but to keep the top of the wall flat, we should set its Y component to zero. Because we have to do this for both the left and right part of the wall segment, let's add a method to HexMetrics to compute this offset vector. public static Vector3 WallThicknessOffset (Vector3 near, Vector3 far) { Vector3 offset; offset.x = far.x - near.x; offset.y = 0f; offset.z = far.z - near.z; return offset; } To keep the wall at the center of the edge, the actual distance to move along this vector is equal to half the thickness per side. And to make sure we indeed move the desired distance, normalize the offset vector before scaling it. return offset .normalized * (wallThickness * 0.5f) ; Use this method in HexFeatureManager.AddWallSegment to adjust the position of the quads. As the offset vector goes from near to far, subtract it from the near quad, and add it to the far quad. Vector3 left = Vector3.Lerp(nearLeft, farLeft, 0.5f); Vector3 right = Vector3.Lerp(nearRight, farRight, 0.5f); Vector3 leftThicknessOffset = HexMetrics.WallThicknessOffset(nearLeft, farLeft); Vector3 rightThicknessOffset = HexMetrics.WallThicknessOffset(nearRight, farRight); Vector3 v1, v2, v3, v4; v1 = v3 = left - leftThicknessOffset ; v2 = v4 = right - rightThicknessOffset ; v3.y = v4.y = left.y + HexMetrics.wallHeight; walls.AddQuad(v1, v2, v3, v4); v1 = v3 = left + leftThicknessOffset; v2 = v4 = right + rightThicknessOffset; v3.y = v4.y = left.y + HexMetrics.wallHeight; walls.AddQuad(v2, v1, v4, v3); Walls with offsets. The quads are now offset, although it isn't that obvious. The shadows give it away. Is the wall thickness really uniform? It would be, if the near–far offset vectors would all point in the exact same direction. As the walls curve around cells, this clearly isn't the case. The vectors points away from or towards each other. As a result, the wall segment's base is a trapezoid, instead of a rectangle. Thus it ends up somewhat thinner than our configured thickness. Also, because the cells are perturbed, the angle between the vectors varies, which leads to nonuniform thickness. We'll improve this later.

Wall Tops To made the thickness of the walls visible from above, we have to add a quad on top of the wall. A simple way to add it is to remember the top two vertices of the first quad, and connect them with the top two of the second quad. Vector3 v1, v2, v3, v4; v1 = v3 = left - leftThicknessOffset; v2 = v4 = right - rightThicknessOffset; v3.y = v4.y = left.y + HexMetrics.wallHeight; walls.AddQuad(v1, v2, v3, v4); Vector3 t1 = v3, t2 = v4; v1 = v3 = left + leftThicknessOffset; v2 = v4 = right + rightThicknessOffset; v3.y = v4.y = left.y + HexMetrics.wallHeight; walls.AddQuad(v2, v1, v4, v3); walls.AddQuad(t1, t2, v3, v4); Walls with tops.

Turning Corners The remaining gaps are those at the corners of cells. To fill those, we have to add a segment in the triangular area between the cells. Each corner connects three cells. Each cell can be either walled or not. So there are eight possible configurations. Corner configurations. We only place walls in between cells with different walled states. That reduces the number of relevant configurations to six. In each of those, one of the cells lies on the inside of the wall curve. Let's consider this cell the pivot around which the wall curves. From the point of view of this cell, the wall starts at the edge shared with the left cell and ends at the edge shared with the right cell. Cell roles. So we have to create an AddWallSegment method which has the three corner vertices as parameters. While we could write the code to triangulate this segment, it's actually a special case of the other AddWallSegment method. The pivot plays the role of both near vertices. void AddWallSegment ( Vector3 pivot, HexCell pivotCell, Vector3 left, HexCell leftCell, Vector3 right, HexCell rightCell ) { AddWallSegment(pivot, left, pivot, right); } Next, create an AddWall method variant for three corner vertices and their cells. The job of this method is to figure out which corner is the pivot, if there is one. So it has to account for all eight possible configurations, and invoke AddWallSegment for six of them. public void AddWall ( Vector3 c1, HexCell cell1, Vector3 c2, HexCell cell2, Vector3 c3, HexCell cell3 ) { if (cell1.Walled) { if (cell2.Walled) { if (!cell3.Walled) { AddWallSegment(c3, cell3, c1, cell1, c2, cell2); } } else if (cell3.Walled) { AddWallSegment(c2, cell2, c3, cell3, c1, cell1); } else { AddWallSegment(c1, cell1, c2, cell2, c3, cell3); } } else if (cell2.Walled) { if (cell3.Walled) { AddWallSegment(c1, cell1, c2, cell2, c3, cell3); } else { AddWallSegment(c2, cell2, c3, cell3, c1, cell1); } } else if (cell3.Walled) { AddWallSegment(c3, cell3, c1, cell1, c2, cell2); } } To add the corner segments, invoke this method at the end of HexGridChunk.TriangulateCorner . void TriangulateCorner ( Vector3 bottom, HexCell bottomCell, Vector3 left, HexCell leftCell, Vector3 right, HexCell rightCell ) { … features.AddWall(bottom, bottomCell, left, leftCell, right, rightCell); } Walls with corners, but still gaps.