Cards from hexagons in Unity: water, objects of a relief and fortress walls
3r37342. 3r3-31. Parts 1-3: mesh, colors and cell heights. 3r333371.
3r37342.
3r37342.
Parts 4-7: bumps, rivers and roads. 3r333371.
3r37342.
3r37342. 3r360341. Part 8: water 3r36042.
3r37342. 3r360345. 3r37342. 3r36053. Add water to the cells. 3r36054. 3r37342. 3r36053. Triangulate the surface of the water. 3r36054. 3r37342. 3r36053. Create a surf with foam. 3r36054. 3r37342. 3r36053. Combine water and rivers. 3r36054. 3r37342. 3r36056.
3r37342. We have already added support for the rivers, and in this part we will completely submerge the cells in water.
3r37342.
3r37342.
3r340. 3r333378.
3r37342. Water is coming.
3r37342.
3r333371.
3r37342. Water level
3r37342. The easiest way to implement water support is to set it at the same level. All cells whose height is below this level are submerged in water. But a more flexible way would be to support the water at different heights, so let's make the water level changeable. For this it is necessary that HexCell
tracked your water level.
3r37342.
3r37342. 3r? 37308. 3r337379. public int WaterLevel {
get {3r37342. return waterLevel; 3r37342.}
set {
if (waterLevel == value) {3r373342. return; 3r37342.}
waterLevel = value; 3r37342. Refresh (); 3r37342.}
}
3r37342. int waterLevel;
3r37342. If desired, you can make sure that certain features of the relief do not exist under water. But for now I will not do this. Things like underwater roads suit me. They can be considered areas that have recently been flooded.
3r37342.
3r37342. Flooding of cells 3r3r72262.
3r37342. Now that we have water levels, the most important question is whether the cells are under water. The cell is under water if its water level is higher than its height. To get this information, we will add a property.
3r37342.
3r37342. 3r? 37308. 3r337379. public bool IsUnderwater {
get {3r37342. return waterLevel> elevation; 3r37342.}
}
3r37342. This means that when the water level and height are equal, the cell rises above the water. That is, the real surface of the water is below this height. As is the case with river surfaces, let's add the same offset - 3r-37302. HexMetrics.riverSurfaceElevationOffset . Let's change its name to more general.
3r37342.
3r37342. 3r? 37308. 3r337379. //public const float riverSurfaceElevationOffset = -0.5f; 3r37342. public const float waterElevationOffset = -0.5f;
3r37342. Change HexCell.RiverSurfaceY
so that he uses the new name. Then add a similar property to the surface of the water in the flooded cell.
3r37342.
3r37342. 3r? 37308. 3r337379. public float RiverSurfaceY {
get {3r37342. return
(elevation + HexMetrics.waterElevationOffset) *
HexMetrics.elevationStep; 3r37342.}
}
3r37342. public float WaterSurfaceY {
get {3r37342. return
(waterLevel + HexMetrics.waterElevationOffset) *
HexMetrics.elevationStep; 3r37342.}
}
3r37342. Editing water 3r3r72262.
3r37342. Editing the water level is similar to changing the height. Therefore, HexMapEditor
should monitor the active water level and whether it should be applied to cells.
3r37342.
3r37342. 3r? 37308. 3r337379. int activeElevation; 3r37342. int activeWaterLevel; 3r37342. 3r37342. …
3r37342. bool applyElevation = true; 3r37342. bool applyWaterLevel = true; 3r37342. 3r37342.
3r37342. Add methods to connect these parameters with the UI.
3r37342.
3r37342. 3r? 37308. 3r337379. public void SetApplyWaterLevel (bool toggle) {
applyWaterLevel = toggle; 3r37342.}
3r37342. public void SetWaterLevel (float level) {
activeWaterLevel = (int) level; 3r37342.}
3r37342. And add the water level at EditCell
.
3r37342.
3r37342. 3r? 37308. 3r337379. void EditCell (HexCell cell) {
if (cell) {
if (applyColor) {
cell.Color = activeColor; 3r37342.}
if (applyElevation) {3r373342. cell.Elevation = activeElevation; 3r37342.}
if (applyWaterLevel) {3r37342. cell.WaterLevel = activeWaterLevel; 3r37342.}
…
}
}
3r37342. To add a water level to the UI, duplicate the label and the height slider, and then change them. Do not forget to attach their events to the appropriate methods.
3r37342.
3r37342. 3r37342. Water level slider.
3r37342.
3r37342. unitypackage
3r37342.
3r37342. Water triangulation
3r37342. To triangulate water, we need a new mesh with new material. First, create a shader Water 3r3-33574. by duplicating the shader River . Change it to use the color property.
3r37342.
3r37342. 3r? 37308. 3r33737. Shader "Custom /Water" {3r37342. Properties {3r37342. _Color ("Color", Color) = (???1)
_MainTex ("Albedo (RGB)", 2D) = "white" {}
_Glossiness ("Smoothness", Range (?1)) = ???r3r37342. _Metallic ("Metallic", Range (?1)) = ???r3r37342.}
SubShader {
Tags {"RenderType" = "Transparent" "Queue" = "Transparent"}
LOD 200 3r373342. 3r37342. CGPROGRAM
#pragma surface surf Standard alpha
#pragma target ???r3r37342. 3r37342. sampler2D _MainTex; 3r37342. 3r37342. struct Input {3r37342. float2 uv_MainTex; 3r37342.}; 3r37342. 3r37342. half _Glossiness; 3r37342. half _Metallic; 3r37342. fixed4 _Color; 3r37342. 3r37342. void surf (Input IN, inout SurfaceOutputStandard o) {
fixed4 c = _Color; 3r37342. o.Albedo = c.rgb; 3r37342. o.Metallic = _Metallic; 3r37342. o.Smoothness = _Glossiness; 3r37342. o.Alpha = c.a; 3r37342.}
ENDCG 3r37342.}
FallBack "Diffuse"
}
3r37342. Create a new material with this shader, duplicating the material 3r335373. Water 3r3-33574. and replacing it with a shader. Let's leave the noise texture, because later we use it.
3r37342.
3r37342. 3r37342. Material Water.
3r37342.
3r37342. Add a new child object to the prefab by duplicating the child object 3r335373. Rivers 3r3-33574. . It does not need UV coordinates, and it should use material 3r335373. Water 3r3-33574. . As usual, let's do this by creating an instance of the prefab, changing it, and then applying the changes to the prefab. After that, get rid of the instance.
3r37342.
3r37342.
3r37342.
3r37342. Child object Water.
3r37342.
3r37342. Next, add HexGridChunk
water mesh support.
3r37342.
3r37342. 3r? 37308. 3r337379. public HexMesh terrain, rivers, roads, water; 3r37342. 3r37342. public void Triangulate () {
terrain.Clear (); 3r37342. rivers.Clear (); 3r37342. roads.Clear (); 3r37342. water.Clear (); 3r37342. for (int i = 0; i 3r33048. Triangulate (cells); 3r373342.} 3r37342. terrain.Apply ();
rivers. 3r332342.} 3r332322. 3r332373.
3r37342. And connect it with the child object of the prefab.
3r37342.
3r37342.
3r37342. [i] Water object is connected.
3r37342.
3r37342. Water hexagons
3r37342. Since water forms the second layer, let's give it its own triangulation method for each of the directions. We need to call it only when the cell is immersed in water.
3r37342.
3r37342. 3r? 37308. 3r337379. void Triangulate (HexDirection direction, HexCell cell) {
…
3r37342. if (cell.IsUnderwater) {3r373342. TriangulateWater (direction, cell, center); 3r37342.}
}
3r37342. void TriangulateWater (
HexDirection direction, HexCell cell, Vector3 center
) {
}
3r37342. As in the case of rivers, the height of the water surface does not vary greatly in cells with the same water level. Therefore, we do not seem to need complex edges. A simple triangle is enough.
3r37342.
3r37342. 3r? 37308. 3r337379. void TriangulateWater (
HexDirection direction, HexCell cell, Vector3 center
) {
center.y = cell.WaterSurfaceY; 3r37342. Vector3 c1 = center + HexMetrics.GetFirstSolidCorner (direction); 3r37342. Vector3 c2 = center + HexMetrics.GetSecondSolidCorner (direction); 3r37342. 3r37342. water.AddTriangle (center, c? c2); 3r37342.}
3r37342.
3r37342. Hexagons of water.
3r37342.
3r37342. Water connections 3r3r72262.
3r37342. We can connect adjacent cells with water with one quadrilateral.
3r37342.
3r37342. 3r? 37308. 3r337379. water.AddTriangle (center, c? c2); 3r37342. 3r37342. if (direction <= HexDirection.SE) {
HexCell neighbor = cell.GetNeighbor (direction);
if (neighbor == null || neighbor, IsUnderwater) {3r373342. return;
}
3r373342. );
Vector3 e1 = c1 + bridge;
Vector3 e2 = c2 + bridge;
3r37342. Water.AddQuad (c? c? e? e? e2);
} 3r37342.
3r37342. Joining the edges of the water. 5.
3r37342.
3r37342. And fill the corners with one triangle.
3r37342.
3r37342. 3r? 37308. 3r337379. if (direction <= HexDirection.SE) {
…
water.AddQuad (c? c? e? e2);
if (direction <= HexDirection.E) {
HexCell nextNeighbor = cell.GetNeighborr of 2014.a4r.31532323. HexCell nextNeighbor = cell.GTNeighborr of 2014 Idr-???. HexCell nextNeighbor = cell.GTNeighbor Idire 2014 Idr 31532323 nextNeighbor == null ||! nextNeighbor.IsUnderwater) {3r3-3?342 Cf. gost; 3r?3?742.} 3 c? e? c2 + HexMetrics.GetBridge (go .Next-ee).
}
3r37342.
3r37342. [i] Connectangles of water. 5.
3r37342.
3r37342. Now we have water cells connected when they are nearby. They leave a gap between themselves and dry cells with greater height, but we will leave that for later.
3r37342.
3r37342. Agreed water levels
3r37342. We assumed that the neighboring submarine cells have the same water level. If it is, then everything looks good, but if this assumption is violated, then errors occur.
3r37342.
3r37342. 3r37342. [i] Inconsistent water levels.
5.
3r37342.
3r37342. We can make the water stay level. For example, if the water level of a flooded cell changes, we can propagate the changes to neighboring cells to keep the levels in sync. However, this process must continue until it encounters cells that are not immersed in water. These cells set the boundaries of the water body.
3r37342.
3r37342. The danger of this approach is that it can quickly get out of control. If editing is unsuccessful, water can cover the entire map. Then all fragments will have to be triangulated at the same time, which will lead to a huge jump in delays.
3r37342.
3r37342. So let's not do it yet. This function can be added in a more complex editor. As long as the consistency of water levels, we leave on the conscience of the user.
3r37342.
3r37342. 3r33525. unitypackage
3r37342.
3r37342. Animating water
3r37342. Instead of a uniform color, we will create something resembling a wave. As in other shaders, we still will not strive for beautiful graphics, we only need to designate the waves.
3r37342.
3r37342.
3r37342. [i] Perfectly flat water. 5.
3r37342.
3r37342. Let's do the same thing we did with the rivers. Let's sample the noise with the position of the world and add it to the uniform color. To animate the surface, we add time to the V coordinate.
3r37342.
3r37342. 3r? 37308. 3r33737. struct Input {3r37342. float2 uv_MainTex; 3r37342. float3 worldPos; 3r37342.}; 3r37342. 3r37342. …
3r37342. void surf (Input IN, inout SurfaceOutputStandard o) {
float2 uv = IN.worldPos.xz; 3r37342. uv.y + = _Time.y; 3r37342. float4 noise = tex2D (_MainTex, uv * ???); 3r37342. float waves = noise.z; 3r37342. 3r37342. fixed4 c = saturate (_Color + waves); 3r37342. o.Albedo = c.rgb; 3r37342. o.Metallic = _Metallic; 3r37342. o.Smoothness = _Glossiness; 3r37342. o.Alpha = c.a; 3r37342.}
3r37342.
3r37342. [i] Water scrolling, time × 10. 5.
3r37342.
3r37342. Two directions
3r37342. So far this is not at all like waves. Let's complicate the picture by adding a second sample of the noise
3r37342. and this time adding the U coordinate. Use a different noise channel to get two different patterns as a result. The finished waves will be these two samples stacked together.
3r37342.
3r37342. 3r? 37308. 3r33737. float2 uv1 = IN.worldPos.xz; 3r37342. uv1.y + = _Time.y; 3r37342. float4 noise1 = tex2D (_MainTex, uv1 * ???); 3r37342. 3r37342. float2 uv2 = IN.worldPos.xz; 3r37342. uv2.x + = _Time.y; 3r37342. float4 noise2 = tex2D (_MainTex, uv2 * ???); 3r37342. 3r37342. float waves = noise1.z + noise2.x;
3r37342. When summing both samples, we get results in the range of 0–? so we need to scale it back to 0–1. Instead of simply dividing the waves in half, we can use the function. smoothstep
to create more interesting results. We will impose ¾ – 2 on 0–? so that there are no visible waves on the surface of the water.
3r37342.
3r37342. 3r? 37308. 3r33737. float waves = noise1.z + noise2.x; 3r37342. waves = smoothstep (0.7? ? waves);
3r37342.
3r37342. [i] Two directions, time × 10. 5.
3r37342.
3r37342. Blending waves
3r37342. It is still noticeable that we have two moving noise patterns that do not actually change. It would be plausible if the patterns were changed. We can accomplish this by performing interpolation between different channels of noise samples. But this cannot be done in the same way, otherwise the entire surface of the water will change simultaneously, and this is very noticeable. Instead, we will create a wave of mixing.
3r37342.
3r37342. We will create a mixing wave using a sine wave that moves diagonally across the surface of the water. We will do this by adding the coordinates of the world X and Z and using the sum as the input data for the function 3r37302. sin . Zoom out to get large enough bands. And of course, add the same value to animate them.
3r37342.
3r37342. 3r? 37308. 3r33737. float blendWave =
sin ((IN.worldPos.x + IN.worldPos.z) * 0.1 + _Time.y);
3r37342. Sinusoids range from -1 and ? and we need an interval of 0–1. You can get it by squaring the wave. To see the isolated result, use it instead of the changed color as the output value.
3r37342.
3r37342. 3r? 37308. 3r33737. sin ((IN.worldPos.x + IN.worldPos.z) * 0.1 + _Time.y); 3r37342. blendWave * = blendWave; 3r37342. 3r37342. float waves = noise1.z + noise2.x; 3r37342. waves = smoothstep (0.7? ? waves); 3r37342. 3r37342. fixed4 c = blendWave; //saturate (_Color + waves);
3r37342. 3r37342. [i] Waves of confusion.
5.
3r37342.
3r37342. To make the blending waves less noticeable, let's add a little noise from both samples to them.
3r37342.
3r37342. 3r? 37308. 3r33737. float blendWave = sin (
(IN.worldPos.x + IN.worldPos.z) * 0.1 +
(noise1.y + noise2.z) + _Time.y
); 3r37342. blendWave * = blendWave;
3r37342.
3r37342. [i] Distorted mixing waves. 5.
3r37342.
3r37342. Finally, we use a mixing wave to interpolate between the two channels in both samples of noise. For maximum variability, take four different channels.
3r37342.
3r37342. 3r? 37308. 3r33737. float waves = 3r373342. lerp (noise1.z, noise1.w, blendWave) +
lerp (noise2.x, noise2.y, blendWave); 3r37342. waves = smoothstep (0.7? ? waves); 3r37342. 3r37342. fixed4 c = saturate (_Color + waves);
3r37342.
3r37342. [i] The mixing of the waves, time × 2. 5.
3r37342.
3r37342. 3r3751. unitypackage
3r37342.
3r37342. The coast
3r37342. We ended up with open water, but now we need to fill the gap in the water along the coast. Since we have to comply with the land contours, the water of the coast requires a different approach. Let's split TriangulateWater
for two methods - one for open water, the second for the coast. To understand when we work with the coast, we need to look at the next cell. That is, TriangulateWater
we will get a neighbor. If there is a neighbor and he is not under water, then we are dealing with the coast.
3r37342.
3r37342. 3r? 37308. 3r337379. void TriangulateWater (
HexDirection direction, HexCell cell, Vector3 center
) {
center.y = cell.WaterSurfaceY; 3r37342. 3r37342. HexCell neighbor = cell. GetNeighbor (direction); 3r37342. if (neighbor! = null &&! neighbor.IsUnderwater) {
TriangulateWaterShore (direction, cell, neighbor, center); 3r37342.}
else {
TriangulateOpenWater (direction, cell, neighbor, center); 3r37342.}
}
3r37342. void TriangulateOpenWater (
HexDirection direction, HexCell cell, HexCell neighbor, Vector3 center
) {
Vector3 c1 = center + HexMetrics.GetFirstSolidCorner (direction); 3r37342. Vector3 c2 = center + HexMetrics.GetSecondSolidCorner (direction); 3r37342. 3r37342. water.AddTriangle (center, c? c2); 3r37342. 3r37342. if (direction <= HexDirection.SE && neighbor != null) {
//HexCell neighbor = cell.GetNeighbor (direction);
//if (neighbor == null null || Vector3 bridge = HexMetrics.GetBridge (direction);
3r37342. 3r37342. [i] There is no triangulation along the coast.
5.
3r37342.
3r37342. Since the coast is distorted, we must distort the triangles of water along the coast. Therefore, we need vertices of edges and a fan of triangles.
3r37342.
3r37342. 3r? 37308. 3r337379. void Triangulate WaterShore (
HexDirection direction, HexCell cell, HexCell neighbor, Vector3 center
) {
EdgeVertices e1 = new EdgeVertices (
Center + HexMetrics. GetFirstSolidCorner (direction),
Center + HexMetrics. GetSecondSolidCorner (direction)
); 3r37342. water.AddTriangle (center, e1.v? e1.v2); 3r37342. water.AddTriangle (center, e1.v? e1.v3); 3r37342. water.AddTriangle (center, e1.v? e1.v4); 3r37342. water.AddTriangle (center, e1.v? e1.v5); 3r37342.}
3r37342.
3r37342. [i] Fans of triangles along the coast. 5.
3r37342.
3r37342. Next comes a strip of edges, as in the usual relief. However, we are not obliged to limit ourselves to only certain directions, because we call TriangulateWaterShore
only when meeting with the coast, for which the band is always needed.
3r37342.
3r37342. 3r? 37308. 3r337379. water.AddTriangle (center, e1.v? e1.v5); 3r37342. 3r37342. Vector3 bridge = HexMetrics.GetBridge (direction); 3r37342. EdgeVertices e2 = new EdgeVertices (
E1.v1 + bridge,
E1.v5 + bridge
); 3r37342. water.AddQuad (e1.v? e1.v? e2.v? e2.v2); 3r37342. water.AddQuad (e1.v? e1.v? e2.v? e2.v3); 3r37342. water.AddQuad (e1.v? e1.v? e2.v? e2.v4); 3r37342. water.AddQuad (e1.v? e1.v? e2.v? e2.v5);
3r37342.
3r37342. [i] Ribs along the coast. 5.
3r37342.
3r37342. Similarly, we must also add an angular triangle every time.
3r37342.
3r37342. 3r? 37308. 3r337379. water.AddQuad (e1.v? e1.v? e2.v? e2.v5); 3r37342. 3r37342. HexCell nextNeighbor = cell.GetNeighbor (direction.Next ()); 3r37342. if (nextNeighbor! = null) {
water.AddTriangle (
e1.v? e2.v? e1.v5 + HexMetrics.GetBridge (direction.Next ())
); 3r37342.}
3r37342.
3r37342. [i] The angles of the ribs along the coast. 5.
3r37342.
3r37342. Now we have ready water coast. A part of it is always below the relief relief, so there are no holes.
3r37342.
3r37342. UV coast
3r37342. We can stayeverything is as it is, but it would be interesting if the coastal water had its own schedule. For example, the effect of foam, which becomes larger when approaching the coast. To implement it, the shader must know how close the fragment is to the coast. We can transmit this information via UV coordinates.
3r37342.
3r37342. Open water does not have UV coordinates, and does not need foam. It is needed only for water near the coast. Therefore, the requirements for both types of water are quite different. It will be logical to create your own mesh for each type. Therefore, we add to HexGridChunk
support for another mesh object.
3r37342.
3r37342. 3r? 37308. 3r337379. public HexMesh terrain, rivers, roads, water, waterShore; 3r37342. 3r37342. public void Triangulate () {
terrain.Clear (); 3r37342. rivers.Clear (); 3r37342. roads.Clear (); 3r37342. water.Clear (); 3r37342. waterShore.Clear (); 3r37342. for (int i = 0; i 3r33048. Triangulate (cells[i]); 3r373342.} 3r37342. terrain.Apply ();
rivers. ); R3r37342. WaterShore.Apply ();
}
3r37342. This new mesh will use TriangulateWaterShore
.
3r37342.
3r37342. 3r? 37308. 3r337379. void Triangulate WaterShore (
HexDirection direction, HexCell cell, HexCell neighbor, Vector3 center
) {
…
waterShore.AddQuad (e1.v? e1.v? e2.v? e2.v2); 3r37342. waterShore.AddQuad (e1.v? e1.v? e2.v? e2.v3); 3r37342. waterShore.AddQuad (e1.v? e1.v? e2.v? e2.v4); 3r37342. waterShore.AddQuad (e1.v? e1.v? e2.v? e2.v5); 3r37342. 3r37342. HexCell nextNeighbor = cell.GetNeighbor (direction.Next ()); 3r37342. if (nextNeighbor! = null) {
waterShore.AddTriangle (
e1.v? e2.v? e1.v5 + HexMetrics.GetBridge (direction.Next ())
); 3r37342.}
}
3r37342. Duplicate the water object, connect it with the prefab and configure it so that it uses the UV coordinates. We will also create a shader and material for coastal water, duplicating the existing shader and water material.
3r37342.
3r37342.
3r37342. [i] Water shore object and material with UV. 5.
3r37342.
3r37342. Change the shader Water Shore so that instead of water it displays the UV coordinates.
3r37342.
3r37342. 3r? 37308. 3r33737. fixed4 c = fixed4 (IN.uv_MainTex, ? 1);
3r37342. Since the coordinates are not yet specified, it will display a solid color. This makes it easy to see that the coast actually uses a separate mesh with material.
3r37342.
3r37342. 3r37342. [i] Separate mesh for the coast.
5.
3r37342.
3r37342. Let's put information about the coast in the coordinate V. On the water side, we assign it a value of ? on the land side - a value of 1. Since we don’t need to transmit anything anymore, all U coordinates will simply be equal to 0. 3r3-337328. 3r37342.
3r37342. 3r? 37308. 3r337379. waterShore.AddQuad (e1.v? e1.v? e2.v? e2.v2); 3r37342. waterShore.AddQuad (e1.v? e1.v? e2.v? e2.v3); 3r37342. waterShore.AddQuad (e1.v? e1.v? e2.v? e2.v4); 3r37342. waterShore.AddQuad (e1.v? e1.v? e2.v? e2.v5); 3r37342. waterShore.AddQuadUV (0f, 0f, 0f, 1f); 3r37342. waterShore.AddQuadUV (0f, 0f, 0f, 1f); 3r37342. waterShore.AddQuadUV (0f, 0f, 0f, 1f); 3r37342. waterShore.AddQuadUV (0f, 0f, 0f, 1f); 3r37342. 3r37342. HexCell nextNeighbor = cell.GetNeighbor (direction.Next ()); 3r37342. if (nextNeighbor! = null) {
waterShore.AddTriangle (
e1.v? e2.v? e1.v5 + HexMetrics.GetBridge (direction.Next ())
); 3r37342. waterShore.AddTriangleUV (
new Vector2 (0f, 0f),
new Vector2 (0f, 1f),
new Vector2 (0f, 0f)
); 3r37342.}
3r37342.
3r37342. [i] Transitions to the coasts, wrong. 5.
3r37342.
3r37342. The above code works for edges, but is erroneous in some angles. If the next neighbor is under water, then this approach will be correct. But when the next neighbor is not under water, the third vertex of the triangle will be under dry land.
3r37342.
3r37342. 3r? 37308. 3r337379. waterShore.AddTriangleUV (
new Vector2 (0f, 0f),
new Vector2 (0f, 1f),
new Vector2 (0f, nextNeighbor.IsUnderwater? 0f: 1f)
);
3r37342.
3r37342. [i] Transitions to the coast, correct. 5.
3r37342.
3r37342. Foam on the coast
3r37342. Now that the transitions to the coast have been implemented correctly, you can use them to create a foam effect. The easiest way to add the value of the coast to a uniform color.
3r37342.
3r37342. 3r? 37308. 3r33737. void surf (Input IN, inout SurfaceOutputStandard o) {
float shore = IN.uv_MainTex.y; 3r37342. 3r37342. float foam = shore; 3r37342. 3r37342. fixed4 c = saturate (_Color + foam); 3r37342. o.Albedo = c.rgb; 3r37342. o.Metallic = _Metallic; 3r37342. o.Smoothness = _Glossiness; 3r37342. o.Alpha = c.a; 3r37342.}
3r37342.
3r37342. [i] Linear foam. 5.
3r37342.
3r37342. To make the foam more interesting, multiply it by the square of the sine wave.
3r37342.
3r37342. 3r? 37308. 3r33737. float foam = sin (shore * 10); 3r37342. foam * = foam * shore;
3r37342.
3r37342. [i] Fading square sinusoid foam. 5.
3r37342.
3r37342. Let's make the foam front bigger as we get closer to shore. This can be done by taking its square root before using the value of the coast.
3r37342.
3r37342. 3r? 37308. 3r33737. float shore = IN.uv_MainTex.y; 3r37342. shore = sqrt (shore);
3r37342.
3r37342. [i] Foam becomes thicker near the shore. 5.
3r37342.
3r37342. Add distortion to make it look more natural. Let us make it so that when approaching the shore, the distortion becomes weaker. So it will better fit the coastline.
3r37342.
3r37342. 3r? 37308. 3r33737. float2 noiseUV = IN.worldPos.xz; 3r37342. float4 noise = tex2D (_MainTex, noiseUV * ???); 3r37342. 3r37342. float distortion = noise.x * (1 - shore); 3r37342. float foam = sin ((shore + distortion) * 10); 3r37342. foam * = foam * shore;
3r37342.
3r37342. [i] Distorted foam. 5.
3r37342.
3r37342. And, of course, all this is animated: a sine wave and distortion.
3r37342.
3r37342. 3r? 37308. 3r33737. float2 noiseUV = IN.worldPos.xz + _Time.y * ???; 3r37342. float4 noise = tex2D (_MainTex, noiseUV * ???); 3r37342. 3r37342. float distortion = noise.x * (1 - shore); 3r37342. float foam = sin ((shore + distortion) * 10 - _Time.y); 3r37342. foam * = foam * shore;
3r37342.
3r37342. [i] Animated foam. 5.
3r37342.
3r37342. In addition to foam arriving, there is a retreating. Let's add a second sinusoid for its simulation, which moves in the opposite direction. Make it weaker and add a time shift. The finished foam will be the maximum of these two sinusoids.
3r37342.
3r37342. 3r? 37308. 3r33737. float distortion1 = noise.x * (1 - shore); 3r37342. float foam1 = sin ((shore + distortion1) * 10 - _Time.y); 3r37342. foam1 * = foam1; 3r37342. 3r37342. float distortion2 = noise.y * (1 - shore); 3r37342. float foam2 = sin ((shore + distortion2) * 10 + _Time.y + 2); 3r37342. foam2 * = foam2 * 0.7; 3r37342. 3r37342. float foam = max (foam? foam2) * shore;
3r37342.
3r37342. [i] Inbound and receding foam. 5.
3r37342.
3r37342. Mixing waves and foam
3r37342. There is a sharp transition between open and coastal waters, because open water waves are not included in the coastal waters. To fix this, we need to include these waves in the shader Water Shore .
3r37342.
3r37342. Instead of copying the code of the waves, let's paste it into the include file Water.cginc . In fact, we insert into it a code for foam and for waves, each as a separate function.
3r37342.
3r37342. 3r337378. 3r37199. How do shader include files work? 3r3727200.
3r37342. 3r? 37308. 3r33737. float Foam (float shore, float2 worldXZ, sampler2D noiseTex) {
//float shore = IN.uv_MainTex.y; 3r37342. shore = sqrt (shore); 3r37342. 3r37342. float2 noiseUV = worldXZ + _Time.y * ???; 3r37342. float4 noise = tex2D (noiseTex, noiseUV * ???); 3r37342. 3r37342. float distortion1 = noise.x * (1 - shore); 3r37342. float foam1 = sin ((shore + distortion1) * 10 - _Time.y); 3r37342. foam1 * = foam1; 3r37342. 3r37342. float distortion2 = noise.y * (1 - shore); 3r37342. float foam2 = sin ((shore + distortion2) * 10 + _Time.y + 2); 3r37342. foam2 * = foam2 * 0.7; 3r37342. 3r37342. return max (foam? foam2) * shore; 3r37342.}
3r37342. float Waves (float2 worldXZ, sampler2D noiseTex) {
float2 uv1 = worldXZ; 3r37342. uv1.y + = _Time.y; 3r37342. float4 noise1 = tex2D (noiseTex, uv1 * ???); 3r37342. 3r37342. float2 uv2 = worldXZ; 3r37342. uv2.x + = _Time.y; 3r37342. float4 noise2 = tex2D (noiseTex, uv2 * ???); 3r37342. 3r37342. float blendWave = sin (
(worldXZ.x + worldXZ.y) * 0.1 +
(noise1.y + noise2.z) + _Time.y
); 3r37342. blendWave * = blendWave; 3r37342. 3r37342. float waves = 3r373342. lerp (noise1.z, noise1.w, blendWave) +
lerp (noise2.x, noise2.y, blendWave); 3r37342. return smoothstep (0.7? ? waves); 3r37342.}
3r37342. Change the shader Water 3r3-33574. so that it uses the new include file.
3r37342.
3r37342. 3r? 37308. 3r33737. #include "Water.cginc"
3r37342. sampler2D _MainTex; 3r37342. 3r37342. …
3r37342. void surf (Input IN, inout SurfaceOutputStandard o) {
float waves = Waves (IN.worldPos.xz, _MainTex); 3r37342. 3r37342. fixed4 c = saturate (_Color + waves); 3r37342. o.Albedo = c.rgb; 3r37342. o.Metallic = _Metallic; 3r37342. o.Smoothness = _Glossiness; 3r37342. o.Alpha = c.a; 3r37342.}
3r37342. In the shader Water Shore values are calculated for both foam and waves. Then we mute the waves as we approach the shore. The finished result will be a maximum of foam and waves.
3r37342.
3r37342. 3r? 37308. 3r33737. #include "Water.cginc"
3r37342. sampler2D _MainTex; 3r37342. 3r37342. …
3r37342. void surf (Input IN, inout SurfaceOutputStandard o) {
float shore = IN.uv_MainTex.y; 3r37342. float foam = Foam (shore, IN.worldPos.xz, _MainTex); 3r37342. float waves = Waves (IN.worldPos.xz, _MainTex); 3r37342. waves * = 1 - shore; 3r37342. 3r37342. fixed4 c = saturate (_Color + max (foam, waves)); 3r37342. o.Albedo = c.rgb; 3r37342. o.Metallic = _Metallic; 3r37342. o.Smoothness = _Glossiness; 3r37342. o.Alpha = c.a; 3r37342.}
3r37342.
3r37342. [i] Mixing foam and waves. 5.
3r37342.
3r37342. 3r31400. unitypackage
3r37342.
3r37342. Again about coastal water
3r37342. Part of the coastal mesh is hidden under the mesh of the relief. This is normal, but only a small part is hidden. Unfortunately, steep cliffs hide most of the coastal water, and therefore foam.
3r37342.
3r37342.
3r37342. [i] Almost hidden coastal water. 5.
3r37342.
3r37342. We can handle this by increasing the size of the coastline. This can be done by reducing the radius of the hexagons of water. For this, in addition to the integrity factor, we need to use 3r330302. HexMetrics coefficient of water, as well as methods for obtaining angles of water.
3r37342.
3r37342. The integrity factor is 0.8. To double the size of the water compounds, we need to assign a value of 0.6 to the water coefficient.
3r37342.
3r37342. 3r? 37308. 3r337379. public const float waterFactor = 0.6f; 3r37342. 3r37342. public static Vector3 GetFirstWaterCorner (HexDirection direction) {
return corners[(int)direction]* waterFactor; 3r37342.}
3r37342. public static Vector3 GetSecondWaterCorner (HexDirection direction) {3r37342. return corners[(int)direction + 1]* waterFactor; 3r37342.}
3r37342. Use these new methods in HexGridChunk
to find the corners of the water.
3r37342.
3r37342. 3r? 37308. 3r337379. void TriangulateOpenWater (
HexDirection direction, HexCell cell, HexCell neighbor, Vector3 center
) {
Vector3 c1 = center + HexMetrics.GetFirstWaterCorner (direction); 3r37342. Vector3 c2 = center + HexMetrics.GetSecondWaterCorner (direction); 3r37342. 3r37342. …
}
3r37342. void Triangulate WaterShore (
HexDirection direction, HexCell cell, HexCell neighbor, Vector3 center
) {
EdgeVertices e1 = new EdgeVertices (
Center + HexMetrics.GetFirstWaterCorner (direction),
Center + HexMetrics.GetSecondWaterCorner (direction)
); 3r37342. …
}
3r37342.
3r37342. [i] Using angles of water. 5.
3r37342.
3r37342. The distance between the hexagons of water has indeed doubled. Now HexMetrics
should also have a method of creating bridges in the water.
3r37342.
3r37342. 3r? 37308. 3r337379. public const float waterBlendFactor = 1f - waterFactor; 3r37342. 3r37342. public static Vector3 GetWaterBridge (HexDirection direction) {
return (corners[(int)direction]+ corners[(int)direction + 1]) *
waterBlendFactor; 3r37342.}
3r37342. Change HexGridChunk
so that he uses the new method.
3r37342.
3r37342. 3r? 37308. 3r337379. void TriangulateOpenWater (
HexDirection direction, HexCell cell, HexCell neighbor, Vector3 center
) {
…
3r37342. if (direction <= HexDirection.SE && neighbor != null) {
Vector3 bridge = HexMetrics.GetWaterBridge (direction);
if (direction <= HexDirection.E) {
water.AddTriangle (
c? e? c2 + HexMetrics.GetWaterBridge (direction.Next ( 3r37342.); 3r37342.} 3r37342.} 3r37342. .GetWaterBridge (direction); 3r37342.
3r3-37342. HexCell nextNeighbor = cell.GetNeighbor (direction.Next ());
If (nextNeighbor! = Null) {
.v? e1.v5 +
HexMetrics.GetWaterBridge (direction.Next ())
); 3r37342.
} 3r373342.} 3r37342.
3r37342. [i] Long bridges in the water. 5.
3r37342.
3r37342. Between the ribs of water and land
3r37342. Although this gives us more space for the foam, now even more is hidden under the relief. Ideally, we will be able to use the water edge from the water side, and the land edge from the land side.
3r37342.
3r37342. We cannot use a simple bridge to find the opposite edge of the land, if we start from the corners of the water. Instead, we can go in the opposite direction, from the center of the neighbor. Change TriangulateWaterShore
so to use this new approach.
3r37342.
3r37342. 3r? 37308. 3r337379. //Vector3 bridge = HexMetrics.GetWaterBridge (direction); 3r37342. Vector3 center2 = neighbor.Position; 3r37342. center2.y = center.y; 3r37342. EdgeVertices e2 = new EdgeVertices (
Center2 + HexMetrics.GetSecondSolidCorner (direction.Opposite ()),
Center2 + HexMetrics.GetFirstSolidCorner (direction.Opposite ())
3r37342. …
3r37342. HexCell nextNeighbor = cell.GetNeighbor (direction.Next ()); 3r37342. if (nextNeighbor! = null) {
Vector3 center3 = nextNeighbor.Position; 3r37342. center3.y = center.y; 3r37342. waterShore.AddTriangle (
e1.v? e2.v? center3 +
HexMetrics.GetFirstSolidCorner (direction.Previous ())
); 3r37342. …
}
3r37342.
3r37342. [i] Wrong corners of edges. 5.
3r37342.
3r37342. It worked, but now we again need to consider two cases for corner triangles.
3r37342.
3r37342. 3r? 37308. 3r337379. HexCell nextNeighbor = cell.GetNeighbor (direction.Next ()); 3r37342. if (nextNeighbor! = null) {
//Vector3 center3 = nextNeighbor.Position; 3r37342. //center3.y = center.y; 3r37342. Vector3 v3 = nextNeighbor.Position + (nextNeighbor.IsUnderwater?
HexMetrics. GetFirstWaterCorner (direction.Previous ()): 3r37342. HexMetrics.GetFirstSolidCorner (direction.Previous ())); 3r37342. v3.y = center.y; 3r37342. waterShore.AddTriangle (e1.v? e2.v? v3); 3r37342. waterShore.AddTriangleUV (
new Vector2 (0f, 0f),
new Vector2 (0f, 1f),
new Vector2 (0f, nextNeighbor.IsUnderwater? 0f: 1f)
); 3r37342.}
3r37342.
3r37342. [i] The correct angles of the edges. 5.
3r37342.
3r37342. It worked well, but now that most of the foam is visible, it becomes quite pronounced. To compensate for this, we will make the effect a bit weaker, reducing the scale of the coast in the shader.
3r37342.
3r37342. 3r? 37308. 3r33737. shore = sqrt (shore) * 0.9;
3r37342.
3r37342. [i] Ready foam. 5.
3r37342.
3r37342. 3r31673. unitypackage
3r37342.
3r37342. Submarine rivers
3r37342. We ended up with water, at least in those places where rivers do not flow into it. Since water and rivers do not yet notice each other, rivers will flow through and under the water.
3r37342.
3r37342.
3r37342. [i] Rivers flowing in water. 5.
3r37342.
3r37342. The order in which translucent objects are rendered depends on their distance from the camera. The nearest objects are rendered last, so they are at the top. When you move the camera, this will mean that sometimes rivers and sometimes water will appear over each other. Let's start by making the rendering order constant. Rivers must be drawn on top of the water so that waterfalls are displayed correctly. We can accomplish this by changing the shader queue River .
3r37342.
3r37342. 3r? 37308. 3r33737. Tags {"RenderType" = "Transparent" "Queue" = "Transparent + 1"}
3r37342.
3r37342. [i] Draw the river last. 5.
3r37342.
3r37342. Hide underwater rivers
3r37342. Although the river bed may well be under water, and water can actually flow through it, we should not see this water. And even more so it should not be rendered on top of the real surface of the water. We can get rid of the water of underwater rivers by adding river segments only when the current cell is not under water.
3r37342.
3r37342. 3r? 37308. 3r337379. void TriangulateWithRiverBeginOrEnd (
HexDirection direction, HexCell cell, Vector3 center, EdgeVertices e 3r37342.) {3r37342. …
3r37342. if (! cell.IsUnderwater) {3r373342. bool reversed = cell.HasIncomingRiver; 3r37342. …
}
}
3r37342. void TriangulateWithRiver (
HexDirection direction, HexCell cell, Vector3 center, EdgeVertices e
) {
…
3r37342. if (! cell.IsUnderwater) {3r373342. bool reversed = cell.IncomingRiver == direction; 3r37342. …
}
}
3r37342. For 3r? 37302. TriangulateConnection let's start by adding a river segment when neither the current cell nor the neighboring cell are under water.
3r37342.
3r37342. 3r? 37308. 3r337379. if (cell.HasRiverThroughEdge (direction)) {
e2.v3.y = neighbor.StreamBedY; 3r37342. 3r37342. if (! cell.IsUnderwater &&! neighbor.IsUnderwater) {3r3737342. TriangulateRiverQuad (
E1.v? e1.v? e2.v? e2.v?
cell.RiverSurfaceY, neighbor.RiverSurfaceY, 0.8f,
cell.HasIncomingRiver && cell.IncomingRiver == direction
); 3r37342.}
}
3r37342. 3r37342. [i] No more submarine rivers.
5.
3r37342.
3r37342. Waterfalls
3r37342. There are no more submarine rivers, but now we have holes in those parts of the rivers where they meet the surface of the water. Rivers flush with water create small holes or overlaps. But the most noticeable are the missing waterfalls for rivers flowing from a greater height. Let's do it first.
3r37342.
3r37342. The river segment with a waterfall used to pass through the surface of the water. As a result, it was partially above, and partly under water. We need to keep a part above the water level, discarding everything else. We will have to work for this, so we will create a separate method.
3r37342.
3r37342. The new method requires four peaks, two levels of rivers and water level. We will set it up so that we look in the direction of the current, down the waterfall. Therefore, the first two vertices and the left and right sides will be on top, followed by the lower ones.
3r37342.
3r37342. 3r? 37308. 3r337379. void TriangulateWaterfallInWater (
Vector3 v? Vector3 v? Vector3 v? Vector3 v?
float y? float y? float waterY 3r37342.) {
v1.y = v2.y = y1; 3r37342. v3.y = v4.y = y2; 3r37342. rivers.AddQuad (v? v? v? v4); 3r37342. rivers.AddQuadUV (0f, 1f, 0.8f, 1f); 3r37342.}
3r37342. Call this method in TriangulateConnection
when the neighbor is under water and we have a waterfall.
3r37342.
3r37342. 3r? 37308. 3r337379. if (! cell.IsUnderwater) {3r373342. if (! neighbor.IsUnderwater) {
TriangulateRiverQuad (
E1.v? e1.v? e2.v? e2.v?
Cell.RiverSurfaceY, neighbor.RiverSurfaceY, 0.8f,
Cell.HasIncomingRiver && cell.IncomingRiver == direction 3rr3r3r427342. Cell.HasIncomingRiver && cell.IncomingRiver = 3rr
3r37342.}
else if (cell.Elevation> neighbor.WaterLevel) {
TriangulateWaterfallInWater (
E1.v? e1.v? e2.v? e2.v?
Cell.RiverSurfaceY, neighbor.RiverSurfaceY,
Neighbor.WaterSurfaceY
); 3r37342.}
}
3r37342. We also need to process waterfalls in the opposite direction, when the current cell is under water and the next cell is not.
3r37342.
3r37342. 3r? 37308. 3r337379. if (! cell.IsUnderwater) {3r373342. …
}
else if (
! neighbor.IsUnderwater &&
neighbor.Elevation> cell.WaterLevel
) {
TriangulateWaterfallInWater (
E2.v? e2.v? e1.v? e1.v?
Neighbor.RiverSurfaceY, cell.RiverSurfaceY,
Cell.WaterSurfaceY
); 3r37342.}
3r37342. So we again get the quad of the source river. Next we need to change TriangulateWaterfallInWater
so that he raises the lower peaks to the water level. Unfortunately, changing only the Y coordinates will not be enough. This can move the waterfall away from the cliff, which may cause holes to form. Instead, you have to move the bottom vertices to the top using interpolation.
3r37342.
3r37342.
3r37342. [i] We interpolate. 5.
3r37342.
3r37342. To move the lower peaks up, divide their distance below the water surface by the height of the waterfall. This will give us the value of the interpolator.
3r37342.
3r37342. 3r? 37308. 3r337379. v1.y = v2.y = y1; 3r37342. v3.y = v4.y = y2; 3r37342. float t = (waterY - y2) /(y1 - y2); 3r37342. v3 = Vector3.Lerp (v? v? t); 3r37342. v4 = Vector3.Lerp (v? v? t); 3r37342. rivers.AddQuad (v? v? v? v4); 3r37342. rivers.AddQuadUV (0f, 1f, 0.8f, 1f);
3r37342. As a result, we get a shortened waterfall, having the same orientation. However, since the positions of the lower vertices have changed, they are distorted not in the same way as the original vertices. This means that the end result will still not coincide with the original waterfall. To solve this problem, we need to manually distort the vertices before interpolation, and then add an undistorted quad.
3r37342.
3r37342. 3r? 37308. 3r337379. v1.y = v2.y = y1; 3r37342. v3.y = v4.y = y2; 3r37342. v1 = HexMetrics.Perturb (v1); 3r37342. v2 = HexMetrics.Perturb (v2); 3r37342. v3 = HexMetrics.Perturb (v3); 3r37342. v4 = HexMetrics.Perturb (v4); 3r37342. float t = (waterY - y2) /(y1 - y2); 3r37342. v3 = Vector3.Lerp (v? v? t); 3r37342. v4 = Vector3.Lerp (v? v? t); 3r37342. rivers.AddQuadUnperturbed (v? v? v? v4); 3r37342. rivers.AddQuadUV (0f, 1f, 0.8f, 1f);
3r37342. Since we already have a method for adding undistorted triangles, we really do not need to create it for quad-s. Therefore, add the required method HexMesh.AddQuadUnperturbed
.
3r37342.
3r37342. 3r? 37308. 3r337379. public void AddQuadUnperturbed (
Vector3 v? Vector3 v? Vector3 v? Vector3 v4
) {
int vertexIndex = vertices.Count; 3r37342. vertices.Add (v1); 3r37342. vertices.Add (v2); 3r37342. vertices.Add (v3); 3r37342. vertices.Add (v4); 3r37342. triangles.Add (vertexIndex); 3r37342. triangles.Add (vertexIndex + 2); 3r37342. triangles.Add (vertexIndex + 1); 3r37342. triangles.Add (vertexIndex + 1); 3r37342. triangles.Add (vertexIndex + 2); 3r37342. triangles.Add (vertexIndex + 3); 3r37342.}
3r37342.
3r37342. [i] Waterfalls end on the surface of the water. 5.
3r37342.
3r37342. 3r31957. unitypackage
3r37342.
3r37342. The mouths of 3r36833.
3r37342. When rivers flow at the same height as the surface of the water, the mesh of the river touches the mesh of the coast. If it were a river flowing into the sea or into the ocean, then there would be a current of the river with a surf. Therefore, we will call these areas mouths.
3r37342.
3r37342.
3r37342. [i] The river meets the coast without distorting the peaks. 5.
3r37342.
3r37342. Now we have two problems with the mouths. Firstly, the quad rivers join the second and fourth tops of the ribs, passing the third. Since the coast of water does not use the third peak, it can create a hole or overlap. We can solve this problem by changing the mouth geometry.
3r37342.
3r37342. The second problem is that there is a sharp transition between foam and river materials. To solve it, we need another material that mixes the effects of the river and water.
3r37342.
3r37342. This means that the mouths require a special approach, so let's create a separate method for them. It should be called at TriangulateWaterShore
when there is a river moving in the current direction.
3r37342.
3r37342. 3r? 37308. 3r337379. void Triangulate WaterShore (
HexDirection direction, HexCell cell, HexCell neighbor, Vector3 center
) {
…
3r37342. if (cell.HasRiverThroughEdge (direction)) {
TriangulateEstuary (e? e2); 3r37342.}
else {
waterShore.AddQuad (e1.v? e1.v? e2.v? e2.v2); 3r37342. waterShore.AddQuad (e1.v? e1.v? e2.v? e2.v3); 3r37342. waterShore.AddQuad (e1.v? e1.v? e2.v? e2.v4); 3r37342. waterShore.AddQuad (e1.v? e1.v? e2.v? e2.v5); 3r37342. waterShore.AddQuadUV (0f, 0f, 0f, 1f); 3r37342. waterShore.AddQuadUV (0f, 0f, 0f, 1f); 3r37342. waterShore.AddQuadUV (0f, 0f, 0f, 1f); 3r37342. waterShore.AddQuadUV (0f, 0f, 0f, 1f); 3r37342.}
3r37342. …
}
3r37342. void TriangulateEuary (EdgeVertices e? EdgeVertices e2) {
}
3r37342. A region that mixes both effects is not required to fill the entire band. It will be enough for us to form trapezoid. Therefore, we can use two coastal triangles on the sides.
3r37342.
3r37342. 3r? 37308. 3r337379. void TriangulateEuary (EdgeVertices e? EdgeVertices e2) {
waterShore.AddTriangle (e2.v? e1.v? e1.v1); 3r37342. waterShore.AddTriangle (e2.v? e1.v? e1.v4); 3r37342. waterShore.AddTriangleUV (
new Vector2 (0f, 1f), new Vector2 (0f, 0f), new Vector2 (0f, 0f)
); 3r37342. waterShore.AddTriangleUV (
new Vector2 (0f, 1f), new Vector2 (0f, 0f), new Vector2 (0f, 0f)
); 3r37342.}
3r37342.
3r37342. [i] Trapezoidal hole for the mixing area. 5.
3r37342.
3r37342. UV2 coordinates
3r37342. To create the effect of the river, we need UV-coordinates. But to create a foam effect, we also need UV coordinates. That is, when mixing them, we need two sets of UV-coordinates. Fortunately, Unity engine meshes can support up to four sets of UV. We just need to add in HexMesh
support second set.
3r37342.
3r37342. 3r? 37308. 3r337379. public bool useCollider, useColors, useUVCoordinates, useUV2Coordinates; 3r37342. 3r37342.[NonSerialized]List uvs, uv2s; 3r37342. 3r37342. public void Clear () {
…
if (useUVCoordinates) {3r373342. uvs = ListPool .Get (); 3r37342.}
if (useUV2Coordinates) {3r373342. uv2s = ListPool .Get (); 3r37342.}
triangles = ListPool .Get (); 3r37342.}
3r37342. public void Apply () {
…
if (useUVCoordinates) {3r373342. hexMesh.SetUVs (? uvs); 3r37342. ListPool .Add (uvs); 3r37342.}
if (useUV2Coordinates) {3r373342. hexMesh.SetUVs (? uv2s); 3r37342. ListPool .Add (uv2s); 3r37342.}
…
}
3r37342. To add a second set of UV, we duplicate the methods of working with UV and change the image we need.
3r37342.
3r37342. 3r? 37308. 3r337379. public void AddTriangleUV2 (Vector2 uv? Vector2 uv? Vector3 uv3) {
uv2s.Add (uv1); 3r37342. uv2s.Add (uv2); 3r37342. uv2s.Add (uv3); 3r37342.}
3r37342. public void AddQuadUV2 (Vector2 uv? Vector2 uv? Vector3 uv? Vector3 uv4) {
uv2s.Add (uv1); 3r37342. uv2s.Add (uv2); 3r37342. uv2s.Add (uv3); 3r37342. uv2s.Add (uv4); 3r37342.}
3r37342. public void AddQuadUV2 (float uMin, float uMax, float vMin, float vMax) {
uv2s.Add (new Vector2 (uMin, vMin)); 3r37342. uv2s.Add (new Vector2 (uMax, vMin)); 3r37342. uv2s.Add (new Vector2 (uMin, vMax)); 3r37342. uv2s.Add (new Vector2 (uMax, vMax)); 3r37342.}
3r37342. The shader function of the river is
3r37342. Since we will use the river effect in two shaders, we will move the code from the shader 3r3-335373. River in the new function of the include file Water 3r3-33574. .
3r37342.
3r37342. 3r? 37308. 3r33737. float River (float2 riverUV, sampler2D noiseTex) {3r373342. float2 uv = riverUV; 3r37342. uv.x = uv.x * ??? + _Time.y * ???; 3r37342. uv.y - = _Time.y * ???; 3r37342. float4 noise = tex2D (noiseTex, uv); 3r37342. 3r37342. float2 uv2 = riverUV; 3r37342. uv2.x = uv2.x * ??? - _Time.y * ???; 3r37342. uv2.y - = _Time.y * ???; 3r37342. float4 noise2 = tex2D (noiseTex, uv2); 3r37342. 3r37342. return noise.x * noise2.w; 3r37342.}
3r37342. Change the shader River so to use this new feature.
3r37342.
3r37342. 3r? 37308. 3r33737. #include "Water.cginc"
3r37342. sampler2D _MainTex; 3r37342. 3r37342. …
3r37342. void surf (Input IN, inout SurfaceOutputStandard o) {
float river = River (IN.uv_MainTex, _MainTex); 3r37342. 3r37342. fixed4 c = saturate (_Color + river); 3r37342. …
}
3r37342. Objects mouth
3r37342. Add to HexGridChunk
support object mouth mesh.
3r37342.
3r37342. 3r? 37308. 3r337379. public HexMesh terrain, rivers, roads, water, waterShore, estuaries; 3r37342. 3r37342. public void Triangulate () {
terrain.Clear (); 3r37342. rivers.Clear (); 3r37342. roads.Clear (); 3r37342. water.Clear (); 3r37342. waterShore.Clear (); 3r37342. estuaries.Clear (); 3r37342. for (int i = 0; i 3r33048. Triangulate (cells[i]); 3r373342.} 3r37342. terrain.Apply ();
rivers. ); 3r373342. WaterShore.Apply ();
Estuaries.Apply ();
} 3r3-337322.
3r37342. Create a shader, material and object mouth, duplicating the coast and changing it. Connect it to the fragment, and make it use the UV and UV2 coordinates.
3r37342.
3r37342. 3r37342. [i] Estuarties object.
5.
3r37342.
3r37342. Mouth triangulation
3r37342. We can solve the problem of a hole or overlay by placing a triangle between the end of the river and the middle edge of the water. Since our mouth shader is a duplicate of the coast shader, let's set the UV coordinates that match the foam effect.
3r37342.
3r37342. 3r? 37308. 3r337379. void TriangulateEuary (EdgeVertices e? EdgeVertices e2) {
…
3r37342. estuaries.AddTriangle (e1.v? e2.v? e2.v4); 3r37342. estuaries. AddTriangleUV (
new Vector2 (0f, 0f), new Vector2 (0f, 1f), new Vector2 (0f, 1f)
); 3r37342.}
3r37342. 3r37342. [i] Middle triangle.
5.
3r37342.
3r37342. We can fill the entire trapezoid by adding a quad on both sides of the middle triangle.
3r37342.
3r37342. 3r? 37308. 3r337379. estuaries.AddQuad (e1.v? e1.v? e2.v? e2.v2); 3r37342. estuaries.AddTriangle (e1.v? e2.v? e2.v4); 3r37342. estuaries.AddQuad (e1.v? e1.v? e2.v? e2.v5); 3r37342. 3r37342. estuaries.AddQuadUV (0f, 0f, 0f, 1f); 3r37342. estuaries. AddTriangleUV (
new Vector2 (0f, 0f), new Vector2 (0f, 1f), new Vector2 (0f, 1f)
); 3r37342. estuaries.AddQuadUV (0f, 0f, 0f, 1f);
3r37342. 3r37342. [i] Ready trapezoid.
5.
3r37342.
3r37342. Let's rotate the quad orientation to the left so that it has a shortened diagonal connection, and as a result we get a symmetric geometry.
3r37342.
3r37342. 3r? 37308. 3r337379. estuaries.AddQuad (e2.v? e1.v? e2.v? e1.v3); 3r37342. estuaries.AddTriangle (e1.v? e2.v? e2.v4); 3r37342. estuaries.AddQuad (e1.v? e1.v? e2.v? e2.v5); 3r37342. 3r37342. estuaries.AddQuadUV (
new Vector2 (0f, 1f), new Vector2 (0f, 0f),
new Vector2 (0f, 1f), new Vector2 (0f, 0f)
); 3r37342. //estuaries.AddQuadUV (0f, 0f, 0f, 1f);
3r37342. 3r37342. [i] Rotated quad, symmetric geometry
3r37342.
3r37342. The flow of the river is 3r3727262.
3r37342. To support the river effect, we need to add UV2 coordinates. The bottom of the middle triangle is in the middle of the river, so its U coordinate should be equal to 0.5. As the river flows towards the water, the left point receives the U coordinate equal to ? and the right one - the U coordinate with the value 0. Let the Y coordinates be 0 and ? corresponding to the direction of flow.
3r37342.
3r37342. 3r? 37308. 3r337379. estuaries.AddTriangleUV2 (
new Vector2 (0.5f, 1f), new Vector2 (1f, 0f), new Vector2 (0f, 0f)
);
3r37342. The quadrilaterals on both sides of the triangle must coincide with this orientation. Let's keep the same U coordinates for points that exceed the width of the river.
3r37342.
3r37342. 3r? 37308. 3r337379. estuaries.AddQuadUV2 (
new Vector2 (1f, 0f), new Vector2 (1f, 1f),
new Vector2 (1f, 0f), new Vector2 (0.5f, 1f) 3r334242.); 3r37342. estuaries.AddTriangleUV2 (
new Vector2 (0.5f, 1f), new Vector2 (1f, 0f), new Vector2 (0f, 0f)
); 3r37342. estuaries.AddQuadUV2 (
new Vector2 (0.5f, 1f), new Vector2 (0f, 1f),
new Vector2 (0f, 0f), new Vector2 (0f, 0f) 3r3r42.);
3r37342. 3r37342. [i] UV2 trapezoid.
5.
3r37342.
3r37342. To make sure that we set the UV2 coordinates correctly, let's make the shader Estuary visualize them. We can access these coordinates by adding to the input structure. float2 uv2_MainTex
.
3r37342.
3r37342. 3r? 37308. 3r33737. struct Input {3r37342. float2 uv_MainTex; 3r37342. float2 uv2_MainTex; 3r37342. float3 worldPos; 3r37342.}; 3r37342. 3r37342. …
3r37342. void surf (Input IN, inout SurfaceOutputStandard o) {
float shore = IN.uv_MainTex.y; 3r37342. float foam = Foam (shore, IN.worldPos.xz, _MainTex); 3r37342. float waves = Waves (IN.worldPos.xz, _MainTex); 3r37342. waves * = 1 - shore; 3r37342. 3r37342. fixed4 c = fixed4 (IN.uv2_MainTex, ? 1); 3r37342. …
}
3r37342. 3r37342. [i] UV2 coordinates.
5.
3r37342.
3r37342. Everything looks good, you can use a shader to create a river effect.
3r37342.
3r37342. 3r? 37308. 3r33737. void surf (Input IN, inout SurfaceOutputStandard o) {
…
3r37342. float river = River (IN.uv2_MainTex, _MainTex); 3r37342. 3r37342. fixed4 c = saturate (_Color + river); 3r37342. …
}
3r37342. 3r37342. [i] Use UV2 to create a river effect.
5.
3r37342.
3r37342. We created rivers in such a way that when triangulating the connections between the cells, the V coordinates of the river change from 0.8 to 1. Therefore, here we should also use this interval, and not values from 0 to 1. However, the coast connection is 50% larger than normal cell connections . Therefore, for best fit with the course of the river, we must change the values from 0.8 to 1.1.
3r37342.
3r37342. 3r? 37308. 3r337379. estuaries.AddQuadUV2 (
new Vector2 (1f, 0.8f), new Vector2 (1f, 1.1f),
new Vector2 (1f, 0.8f), new Vector2 (0.5f, 1.1f)
); 3r37342. estuaries.AddTriangleUV2 (
new Vector2 (0.5f, 1.1f),
new Vector2 (1f, 0.8f),
new Vector2 (0f, 0.8f)
); 3r37342. estuaries.AddQuadUV2 (
new Vector2 (0.5f, 1.1f), new Vector2 (0f, 1.1f),
new Vector2 (0f, 0.8f), new Vector2 (0f, 0.8f) 3r3r4273.);
3r37342. 3r37342.
3r37342. [i] Synchronized flow of the river and the mouth.
5.
3r37342.
3r37342. Adjust the flow
3r37342. While the current of the river moves in a straight line. But when water flows into a larger area, it expands. The flow will be bent. We can simulate this by folding the UV2 coordinates.
3r37342.
3r37342. Instead of keeping the upper coordinates U constant beyond the width of the river, let's shift them by 0.5. The leftmost point is set to 1.? the rightmost one is −0.5.
3r37342.
3r37342. At the same time, we will expand the flow by shifting the coordinates U of the left and right points of the bottom. Change the left one from 1 to 0.? and the right one from 0 to 0.3.
3r37342.
3r37342. 3r? 37308. 3r337379. estuaries.AddQuadUV2 (
new Vector2 (1.5f, 0.8f), new Vector2 (0.7f, 1.1f),
new Vector2 (1f, 0.8f), new Vector2 (0.5f, 1.1f)
); 3r37342. …
estuaries.AddQuadUV2 (
new Vector2 (0.5f, 1.1f), new Vector2 (0.3f, 1.1f),
new Vector2 (0f, 0.8f), new Vector2 (-0.5f, 0.8f)
) ;
3r37342. 3r37342.
3r37342. [i] The expansion of the flow of the river.
5.
3r37342.
3r37342. To complete the curvature effect, change the V coordinates of the same four points. As the water flows away from the end of the river, we will increase the V coordinates of the upper points to 1. And in order to create a better curve, we will increase the V coordinates of the two lower points to ???.
3r37342.
3r37342. 3r? 37308. 3r337379. estuaries.AddQuadUV2 (
new Vector2 (1.5f, 1f), new Vector2 (0.7f, ???f),
new Vector2 (1f, 0.8f), new Vector2 (0.5f, 1.1f)
); 3r37342. estuaries.AddTriangleUV2 (
new Vector2 (0.5f, 1.1f),
new Vector2 (1f, 0.8f),
new Vector2 (0f, 0.8f)
); 3r37342. estuaries.AddQuadUV2 (
new Vector2 (0.5f, 1.1f), new Vector2 (0.3f, ???f),
new Vector2 (0f, 0.8f), new Vector2 (-0.5f, 1f)
);
3r37342. 3r37342.
3r37342. [i] Curved river flow.
5.
3r37342.
3r37342. The mixing of the river and the coast
3r37342. All we have left is to mix the effects of the coast and the river. To do this, we use linear interpolation, taking the value of the coast as an interpolator.
3r37342.
3r37342. 3r? 37308. 3r33737. float shoreWater = max (foam, waves); 3r37342. 3r37342. float river = River (IN.uv2_MainTex, _MainTex); 3r37342. 3r37342. float water = lerp (shoreWater, river, IN.uv_MainTex.x); 3r37342. 3r37342. fixed4 c = saturate (_Color + water);
3r37342. Although this should work, you may get a compilation error. The compiler complains about the override _MainTex_ST
. The reason is an error inside the Unity surface shader compiler caused by the simultaneous use of 3r37302. uv_MainTex and 3r37302. uv2_MainTex . We need to find a workaround.
3r37342.
3r37342. Instead of using 3r3303730. uv2_MainTex we will have to transfer the secondary UV coordinates manually. To do this, rename uv2_MainTex
in 3r37302. riverUV . Then we add a vertex function to the shader, which assigns coordinates to it.
3r37342.
3r37342. 3r? 37308. 3r33737. #pragma surface surf Standard alpha vertex: vert
…
3r37342. struct Input {3r37342. float2 uv_MainTex; 3r37342. float2 riverUV; 3r37342. float3 worldPos; 3r37342.}; 3r37342. 3r37342. …
3r37342. void vert (inout appdata_full v, out Input o) {
UNITY_INITIALIZE_OUTPUT (Input, o); 3r37342. o.riverUV = v.texcoord1.xy; 3r37342.}
3r37342. void surf (Input IN, inout SurfaceOutputStandard o) {
…
3r37342. float river = River (IN.riverUV, _MainTex); 3r37342. 3r37342. …
}
3r37342. 3r37342. [i] Interpolation based on the value of the coast.
5.
3r37342.
3r37342. Interpolation works, except for the left and right vertex at the top. At these points, the river should disappear. Therefore, we can not use the value of the coast. We have to use a different value, which in these two vertices is 0. Fortunately, we still have the U coordinate of the first UV set, so we can store this value there.
3r37342.
3r37342. 3r? 37308. 3r337379. estuaries.AddQuadUV (
new Vector2 (0f, 1f), new Vector2 (0f, 0f),
new Vector2 (1f, 1f), new Vector2 (0f, 0f)
); 3r37342. estuaries.AddTriangleUV (
new Vector2 (0f, 0f), new Vector2 (1f, 1f), new Vector2 (1f, 1f)
); 3r37342. estuaries.AddQuadUV (
new Vector2 (0f, 0f), new Vector2 (0f, 0f),
new Vector2 (1f, 1f), new Vector2 (0f, 1f)
); 3r37342. //estuaries.AddQuadUV (0f, 0f, 0f, 1f);
3r37342. 3r37342. [i] Proper mixing.
5.
3r37342.
3r37342. Now the mouths have a good mix between the expanding river, the coastal water and the foam. Although it does not create an exact match with waterfalls, this effect looks good with waterfalls.
3r37342.
3r37342.
3r37342. [i] Mouths in action 3r3727275.
3r37342.
3r37342. unitypackage
3r37342.
3r37342. Rivers flowing from reservoirs
3r37342. We already have rivers flowing into reservoirs, but there is no support for rivers flowing in a different direction. There are lakes from which rivers flow, so we need to add them too.
3r37342.
3r37342. When a river flows out of a reservoir, it actually flows towards a greater height. This is currently not possible. We need to make an exception and allow this situation if the water level corresponds to the height of the target point. Let's add in HexCell
A private method that checks by our new criterion whether the neighbor is the correct target point for the outgoing river.
3r37342.
3r37342. 3r? 37308. 3r337379. bool IsValidRiverDestination (HexCell neighbor) {
return neighbor && (
elevation> = neighbor.elevation || waterLevel == neighbor.elevation
); 3r37342.}
3r37342. We will use our new method to determine whether it is possible to create an outgoing river.
3r37342.
3r37342. 3r? 37308. 3r337379. public void SetOutgoingRiver (HexDirection direction) {
if (hasOutgoingRiver && outgoingRiver == direction) {
return; 3r37342.}
3r37342. HexCell neighbor = GetNeighbor (direction); 3r37342. //if (! neighbor || elevation < neighbor.elevation) {
if (! IsValidRiverDestination (neighbor)) {
return; 3r3737342.}
3r37342. RemoveOutgoingRiver ();
3r37342. 3r37342. Also there you need to check the river when the height of the cell or the water level changes. Create a private method that will take care of this task.
3r37342.
3r37342. 3r? 37308. 3r337379. void ValidateRivers () {
if (
hasOutgoingRiver &&
! IsValidRiverDestination (GetNeighbor (outgoingRiver)) 3r373342.) {3r37342. RemoveOutgoingRiver (); 3r37342.}
if (
hasIncomingRiver &&
! GetNeighbor (incomingRiver) .IsValidRiverDestination (this) 3r37342.) {3r37342. RemoveIncomingRiver (); 3r37342.}
}
3r37342. We use this new method in the properties Elevation
and 3r37302. WaterLevel .
3r37342.
3r37342. 3r? 37308. 3r337379. public int Elevation {
…
set {
…
3r37342. //if (
//hasOutgoingRiver &&
//elevation < GetNeighbor(outgoingRiver).elevation
//) {
//RemoveOutgoingRiver (); 3r37342. //} 3r373342. //if (3r373342. //hasIncomingRiver && 3r3-37342. //elevation> GetNeighbor (incomingRiver) .elevation 3r373342. //) {3r373342. //RemoveIncomingRiver (); 3r37342. //} 3r373342. ValidateRivers (); 3r37342. 3r37342. …
}
}
3r37342. public int WaterLevel {
…
set {
if (waterLevel == value) {3r373342. return; 3r37342.}
waterLevel = value; 3r37342. ValidateRivers (); 3r37342. Refresh (); 3r37342.}
}
3r37342. 3r37342. [i] Outgoing and entering the lakes of the river.
5.
3r37342.
3r37342. Deploy over
3r37342. We have created 3r37302. HexGridChunk.TriangulateEstuary , assuming that rivers can only flow into reservoirs. Therefore, as a result, the flow of a river always moves in one direction. We need to reverse the flow when dealing with a river flowing from a reservoir. For this you need to TriangulateEstuary
knew about the direction of the current. Therefore, we give it a boolean parameter that determines whether we are dealing with an incoming river.
3r37342.
3r37342. 3r? 37308. 3r337379. void TriangulateEuary (
EdgeVertices e? EdgeVertices e? bool incomingRiver
) {
…
}
3r37342. We will transmit this information when calling this method from 3r37302. TriangulateWaterShore .
3r37342.
3r37342. 3r? 37308. 3r337379. if (cell.HasRiverThroughEdge (direction)) {
TriangulateEstuary (e? e? cell.IncomingRiver == direction); 3r37342.}
3r37342. Now we need to turn the flow of the river, changing the coordinates of UV2. The coordinates of U for outgoing rivers need to be mirrored: −0.5 becomes 1.? 0 becomes ? 1 becomes ? and 1.5 becomes −0.5.
3r37342.
3r37342. With V coordinates, things are a little more complicated. If you look at how we worked with inverted river connections, then 0.8 should be ? and 1 should be −0.2. This means that 1.1 becomes −0.? and ??? becomes −???.
3r37342.
3r37342. Since in each case the coordinates of UV2 are very different, let's write a separate code for them.
3r37342.
3r37342. 3r? 37308. 3r337379. void TriangulateEuary (
EdgeVertices e? EdgeVertices e? bool incomingRiver
) {
…
3r37342. if (incomingRiver) {
estuaries.AddQuadUV2 (
new Vector2 (1.5f, 1f), new Vector2 (0.7f, ???f),
new Vector2 (1f, 0.8f), new Vector2 (0.5f, 1.1f)
); 3r37342. estuaries.AddTriangleUV2 (
new Vector2 (0.5f, 1.1f),
new Vector2 (1f, 0.8f),
new Vector2 (0f, 0.8f)
); 3r37342. estuaries.AddQuadUV2 (
new Vector2 (0.5f, 1.1f), new Vector2 (0.3f, ???f),
new Vector2 (0f, 0.8f), new Vector2 (-0.5f, 1f)
); 3r37342.}
else {
estuaries.AddQuadUV2 (
new Vector2 (-0.5f, -0.2f), new Vector2 (0.3f, -???f),
new Vector2 (0f, 0f), new Vector2 (0.5f, -0.3f)
.) 3r37342. estuaries.AddTriangleUV2 (
new Vector2 (0.5f, -0.3f),
new Vector2 (0f, 0f),
new Vector2 (1f, 0f)
); 3r37342. estuaries.AddQuadUV2 (
new Vector2 (0.5f, -0.3f), new Vector2 (0.7f, -???f),
new Vector2 (1f, 0f), new Vector2 (1.5f, -0.2f)
); 3r37342.}
}
3r37342.
3r37342. [i] The correct flow of the rivers. 5.
3r37342.
3r37342. unitypackage
3r37342.
3r37342. 3r360341. Part 9: relief objects
3r37342. 3r360345. 3r37342. 3r36053. Add objects to the relief. 3r36054. 3r37342. 3r36053. We create support for object density levels. 3r36054. 3r37342. 3r36053. We use various objects in the level. 3r36054. 3r37342. 3r36053. We mix three different types of objects. 3r36054. 3r37342. 3r36056.
3r37342. In this part we will talk about adding objects to the relief. We will create objects such as buildings and trees.
3r37342.
3r37342. 3r37342. [i] Conflict between forests, agricultural land and urbanization.
5.
3r37342.
3r37342. Adding object support
3r37342. Although the shape of the relief has variations, while nothing happens on it. It is a lifeless land. To breathe life into it, you need to add such objects. like trees and houses. These objects are not part of the relief mesh, but will be separate objects. But this will not prevent us from adding them when triangulating the terrain.
3r37342.
3r37342. 3r33737. HexGridChunk don't care how the mesh works. He simply orders one of his children HexMesh
add triangle or quad. Likewise, it can have a child element that places objects on them.
3r37342.
3r37342. Object Manager 3r3727262.
3r37342. Let's create the component. HexFeatureManager
Which will be engaged in objects within one fragment. We use the same scheme as in 3r37302. HexMesh - give him methods Clear
, 3r37302. Apply and 3r37302. AddFeature . Since the object needs to be placed somewhere, method AddFeature
gets position parameter.
3r332378. 3r37342. We will start with the implementation-procurement, which so far will not do anything.
3r37342.
3r37342. 3r? 37308. 3r337379. using UnityEngine; 3r37342. 3r37342. public class HexFeatureManager: MonoBehavior {
3r37342. public void Clear () {}
3r37342. public void Apply () {}
3r37342. public void AddFeature (Vector3 position) {}
}
3r37342. Now we can add a link to such a component in HexGridChunk
. Then you can include it in the process of triangulation, as well as all child elements 3r37302. HexMesh .
3r37342.
3r37342. 3r? 37308. 3r337379. public HexFeatureManager features; 3r37342. 3r37342. public void Triangulate () {
terrain.Clear (); 3r37342. rivers.Clear (); 3r37342. roads.Clear (); 3r37342. water.Clear (); 3r37342. waterShore.Clear (); 3r37342. estuaries.Clear (); 3r37342. features.Clear (); 3r37342. for (int i = 0; i 3r33048. Triangulate (cells[i]); 3r373342.} 3r37342. terrain.Apply ();
rivers. Apply (); 3r37342. roads.Apply (); 3r373. 324. 324. ???.). ); 3r373342. WaterShore.Apply (); 3r3-37342. Estuaries.Apply ();
Features.Apply (); 3r373342.} 3r3323732. 3r33233. 3r3-337328. 3r37342. Let's start by placing one object in the center of each cell
3r37342.
3r37342. 3r? 37308. 3r337379. void Triangulate (HexCell cell) {
for (HexDirection d = HexDirection.NE; d <= HexDirection.NW; d++) {
Triangulate (d, cell); 3r37342.} 3r3-37342. features.AddFeature (cell.Position);
}
}
} 3r37342. Now we need a real object manager. Add to the prefab Hex Grid Chunk another child object and give it a component 3r337372. HexFeatureManager . Then you can connect a fragment with it.
3r37342.
3r37342.
3r37342.
3r37342.
3r37342. [i] Object manager added to fragment prefab. 5.
3r37342.
3r37342. Prefab facilities
3r37342. What terrain object will we create? For the first test, the cube is quite suitable. Create a sufficiently large cube, say, with a scale (? ? 3), and turn it into a prefab. Also create a material for it. I used the default material with red color. Remove it from the collider, because we don’t need it.
3r37342.
3r37342.
3r37342. [i] Prefab Cube. 5.
3r37342.
3r37342. Object managers will need a link to this prefab, so we will add it to 3r-37302. HexFeatureManager and then connect them. Since the placement of the object requires access to the transform component, we use it as a reference type.
3r37342.
3r37342. 3r? 37308. 3r337379. public Transform featurePrefab;
3r37342.
3r37342. [i] Object manager with prefab. 5.
3r37342.
3r37342. Creating instances of objects 3r3727262.
3r37342. The structure is ready, and we can begin to add relief objects! It is enough just to create an instance of the prefab in r3r37302. HexFeatureManager.AddFeature and set his position.
3r37342.
3r37342. 3r? 37308. 3r337379. public void AddFeature (Vector3 position) {
Transform instance = Instantiate (featurePrefab); 3r37342. instance.localPosition = position; 3r37342.}
3r37342.
3r37342. [i] Instances of relief objects. 5.
3r37342.
3r37342. From this point on, the relief will be filled with cubes. At the very least, the upper halves of the cubes, because the local origin point for the cube mesh in Unity is in the center of the cube, and the lower part is below the surface of the relief. To place the cubes on topography, we need to move them up half their height.
3r37342.
3r37342. 3r? 37308. 3r337379. public void AddFeature (Vector3 position) {
Transform instance = Instantiate (featurePrefab); 3r37342. position.y + = instance.localScale.y * 0.5f; 3r37342. instance.localPosition = position; 3r37342.}
3r37342.
3r37342. [i] Cubes on the surface of the relief. 5.
3r37342.
3r37342. 3r337378. 3r37199. What if we use another mesh? 3r3727200.
3r37342. Of course, our cells are distorted, so we need to distort the positions of objects. So we get rid of the perfect repeatability of the grid.
3r37342.
3r37342. 3r? 37308. 3r337379. instance.localPosition = HexMetrics.Perturb (position);
3r37342.
3r37342. [i] Distorted positions of objects. 5.
3r37342.
3r37342. Destruction of relief objects
3r37342. With each update of the fragment, we create new relief objects. This means that while we are creating more and more objects in the same positions. To avoid duplicates, we need to get rid of old objects when cleaning up a fragment.
3r37342.
3r37342. The fastest way to do this is by creating a game container object and turning all the relief objects into its children. Then when you call Clear
we will destroy this container and create a new one. The container itself will be a child of its manager.
3r37342.
3r37342. 3r? 37308. 3r337379. Transform container; 3r37342. 3r37342. public void Clear () {
if (container) {
Destroy (container.gameObject); 3r37342.}
container = new GameObject ("Features Container"). transform; 3r37342. container.SetParent (transform, false); 3r37342.}
3r37342. …
3r37342. public void AddFeature (Vector3 position) {
Transform instance = Instantiate (featurePrefab); 3r37342. position.y + = instance.localScale.y * 0.5f; 3r37342. instance.localPosition = HexMetrics.Perturb (position); 3r37342. instance.SetParent (container, false); 3r37342.}
3r37342. 3r337378. 3r37199. Probably inefficient every time to create and destroy objects of relief. 3r3727200. method. HexFeatureManager.Apply
. But let's leave it for the future tutorial. Fortunately, everything is not so bad, because we divided the relief into fragments. 3r333378. 3r333378.
3r37342. 3r3323279. unitypackage
3r37342.
3r37342. Placement of terrain objects
3r37342. For now, we place objects in the center of each cell. For empty cells, this looks normal, but on cells containing rivers and roads, as well as flooded with water, it seems strange.
3r37342.
3r37342.
3r333378.
3r37342. [i] Objects are located everywhere. 5.
3r37342.
3r37342. Therefore, before placing an object, let's check in 3r337372. HexGridChunk.Triangulate whether the cell is empty.
3r37342.
3r37342. 3r? 37308. 3r337379. if (! cell.IsUnderwater &&! cell.HasRiver &&! cell.HasRoads) {
features.AddFeature (cell.Position); 3r37342.}
3r37342.
3r37342. [i] Limited accommodation. 5.
3r37342.
3r37342. One object per direction
3r37342. Only one object per cell is not too much. There is plenty of room for heaps of objects. Therefore, we add an additional object to the center of each of the six triangles of the cell, that is, one per direction.
3r37342.
3r37342. We will do this in another method Triangulate
when we know that there is no river in the cell. We still need to check whether we are under water and whether there is a road in the cell. But in this case, we are only interested in the roads going in the current direction.
3r37342.
3r37342. 3r? 37308. 3r337379. void Triangulate (HexDirection direction, HexCell cell) {
…
3r37342. if (cell.HasRiver) {3r373342. …
}
else {
TriangulateWithoutRiver (direction, cell, center, e); 3r37342. 3r37342. if (! cell.IsUnderwater &&! cell.HasRoadThroughEdge (direction)) {
features.AddFeature ((center + e.v1 + e.v5) * (1f /3f)); 3r37342.}
}
3r37342. …
}
3r37342.
3r37342. [i] Many facilities, but not in the vicinity of the rivers. 5.
3r37342.
3r37342. This creates many more objects! They appear alongside the roads, but they still avoid the rivers. To place objects along rivers we can also add them inside 3r37302. TriangulateAdjacentToRiver . But again only when the triangle is not under water and there is no road on it.
3r37342.
3r37342. 3r? 37308. 3r337379. void TriangulateAdjacentToRiver (
HexDirection direction, HexCell cell, Vector3 center, EdgeVertices e
) {
…
3r37342. if (! cell.IsUnderwater &&! cell.HasRoadThroughEdge (direction)) {
features.AddFeature ((center + e.v1 + e.v5) * (1f /3f)); 3r37342.}
}
3r37342.
3r37342. [i] Objects appeared alongside the rivers. 5.
3r37342.
3r37342. 3r337378. 3r37199. Is it possible to render so many objects? 3r3727200.
3r37342. 3r333414. unitypackage
3r37342.
3r37342. Variety of facilities
3r37342. All our objects of relief have the same orientation, which looks completely unnatural. Let's give each of them a random turn.
3r37342.
3r37342. 3r? 37308. 3r337379. public void AddFeature (Vector3 position) {
Transform instance = Instantiate (featurePrefab); 3r37342. position.y + = instance.localScale.y * 0.5f; 3r37342. instance.localPosition = HexMetrics.Perturb (position); 3r37342. instance.localRotation = Quaternion.Euler (0f, 360f * Random.value, 0f); 3r37342. instance.SetParent (container, false); 3r37342.}
3r37342. 3r37342. [i] Random turns.
5.
3r37342.
3r37342. So the result becomes much more diverse. Unfortunately, with each update of the fragment, the objects receive a new random rotation. Editing cells should not change the objects in the neighborhood, so we need a different approach.
3r332378. 3r37342. We have a noise texture that is always the same. However, this texture contains Perlin's gradient noise, and it is locally matched. This is what we need when we distort the positions of the vertices in the cells. But the turns do not have to be coordinated. All turns should be equally probable and mixed. Therefore, we need a texture with non-gradient random values that can be sampled without bilinear filtering. In essence, this is a hash grid that creates the basis of gradient noise.
3r37342.
3r37342. Creating a hash table
3r37342. We can create a hash table from an array of float values and fill it once with random values. Thanks to this we don’t need a texture at all. Let's add it to HexMetrics
. A size of 256 by 256 is enough for sufficient variability.
3r37342.
3r37342. 3r? 37308. 3r337379. public const int hashGridSize = 256; 3r37342. 3r37342. static float[]hashGrid; 3r37342. 3r37342. public static void InitializeHashGrid () {
hashGrid = new float[hashGridSize * hashGridSize]; 3r37342. for (int i = 0; i < hashGrid.Length; i++) {
hashGrid[i]= Random.value;
} 3r373342.} 3r332323.
3r37342. Random values are generated by a mathematical formula that always gives the same results. The resulting sequence depends on the number of seed, which by default is equal to the current time value. That is why in each game session we will receive different results.
3r37342.
3r37342. To ensure the re-creation of always the same objects, we need to add the seed parameter to the initialization method.
3r37342.
3r37342. 3r? 37308. 3r337379. public static void InitializeHashGrid (int seed) {
hashGrid = new float[hashGridSize * hashGridSize]; 3r37342. Random.InitState (seed); 3r37342. for (int i = 0; i < hashGrid.Length; i++) {
hashGrid[i]= Random.value;
} 3r373342.} 3r332323.
3r37342. Now that we have initialized a stream of random numbers, we will always get the same sequence from it. Therefore, seemingly random events occurring after the map generation will also always be the same. We can avoid this by maintaining the state of the random number generator before it is initialized. After completing the work, we can ask him the old state.
3r37342.
3r37342. 3r? 37308. 3r337379. Random.State currentState = Random.state; 3r37342. Random.InitState (seed); 3r37342. for (int i = 0; i < hashGrid.Length; i++) {
hashGrid[i]= Random.value;
} 3r373342. Random.state = currentState; 3r332322. 3r332373.
3r37342. The hash table initialization is done in 3r337302. HexGrid at the same time that he assigns a noise texture. That is, in the methods HexGrid.Start
and 3r37302. HexGrid.Awake . Let's do it so that values are generated no more often than necessary.
3r37342.
3r37342. 3r? 37308. 3r337379. public int seed; 3r37342. 3r37342. void Awake () {
HexMetrics.noiseSource = noiseSource; 3r37342. HexMetrics.InitializeHashGrid (seed); 3r37342. 3r37342. …
}
3r37342. void OnEnable () {
if (! HexMetrics.noiseSource) {
HexMetrics.noiseSource = noiseSource; 3r37342. HexMetrics.InitializeHashGrid (seed); 3r37342.}
}
3r37342. The common seed variable allows us to choose the seed value for the card. Any value will do. I chose 1234.
3r37342.
3r37342.
3r37342. [i] Selection of seed. 5.
3r37342.
3r37342. Using the hash table
3r37342. To use the hash table, add in 3r37302. HexMetrics sampling method. Like 3r37302. SampleNoise , it uses to get the xz position coordinate value. The hash index is found by limiting the coordinates to integer values, and then obtaining the remainder of the integer division by the size of the table.
3r37342.
3r37342. 3r? 37308. 3r337379. public static float SampleHashGrid (Vector3 position) {
int x = (int) position.x% hashGridSize; 3r37342. int z = (int) position.z% hashGridSize; 3r37342. return hashGrid[x + z * hashGridSize]; 3r37342.}
3r37342. 3r337378. 3r37199. What is he doing %? 3r3727200.
3r37342. This works for positive coordinates, but not for negative ones, because for such numbers the remainder will be negative. We can fix this by adding a table size to negative results.
3r37342.
3r37342. 3r? 37308. 3r337379. int x = (int) position.x% hashGridSize; 3r37342. if (x < 0) {
x + = hashGridSize;
}
int z = (int) position.z% hashGridSize;
if (z < 0) {
z + = hashGridSize;
3r37342. Now for each square unit we create our own value. However, in reality, we do not need such a density table. Objects are spaced farther apart. We can stretch the table by reducing the scale of the position before calculating the index. One unique value for a 4 by 4 square will suffice.
3r37342.
3r37342. 3r? 37308. 3r337379. public const float hashGridScale = ???f; 3r37342. 3r37342. public static float SampleHashGrid (Vector3 position) {
int x = (int) (position.x * hashGridScale)% hashGridSize; 3r37342. if (x < 0) {
x + = hashGridSize;
}
int z = (int) (position.z * hashGridScale)% hashGridSize; 3r37342. if (z < 0) {
25. z + = hashGridSize; 3r3r3r3r426422. zr)[x + z * hashGridSize];
}
3r37342. Let's go back to r3r37302. HexFeatureManager.AddFeature and use our new hash table to get the value. After we apply it to set the rotation, the objects while editing the relief will remain motionless.
3r37342.
3r37342. 3r? 37308. 3r337379. public void AddFeature (Vector3 position) {
float hash = HexMetrics.SampleHashGrid (position); 3r37342. Transform instance = Instantiate (featurePrefab); 3r37342. position.y + = instance.localScale.y * 0.5f; 3r37342. instance.localPosition = HexMetrics.Perturb (position); 3r37342. instance.localRotation = Quaternion.Euler (0f, 360f * hash, 0f); 3r37342. instance.SetParent (container, false); 3r37342.}
3r37342. Threshold placement 3r37262.
3r37342. Although the objects have a different turn, the pattern is still noticeable in their placement. There are seven objects in each cell. We can add chaos to this scheme by arbitrarily omitting some of the objects. How do we decide whether to add an object or not? Of course, testing another random value!
3r37342.
3r37342. That is, now instead of one hash value we need two. Their support can be added by using the hash table as the array type instead of 3r-37302. float variable Vector2
. But vector operations do not make sense for hash values, so let's create a special structure for this purpose. She will need only two float values. And let's add a static method to create a pair of randomized values.
3r37342.
3r37342. 3r? 37308. 3r337379. using UnityEngine; 3r37342. 3r37342. public struct HexHash {
3r37342. public float a, b; 3r37342. 3r37342. public static HexHash Create () {
HexHash hash; 3r37342. hash.a = Random.value; 3r37342. hash.b = Random.value; 3r37342. return hash; 3r37342.}
}
3r37342. 3r337378. 3r37199. Shouldn't it be serialized? 3r3727200.
3r37342. Change HexMetrics
so that it uses the new structure.
3r37342.
3r37342. 3r? 37308. 3r337379. static HexHash[]hashGrid; 3r37342. 3r37342. public static void InitializeHashGrid (int seed) {
hashGrid = new HexHash[hashGridSize * hashGridSize]; 3r37342. Random.State currentState = Random.state; 3r37342. Random.InitState (seed); 3r37342. for (int i = 0; i < hashGrid.Length; i++) {
hashGrid[i]= HexHash.Create ();
} 3r373342. Random.state = currentState; 3r373342.}
3r3733424.
}
3r37342. Now HexFeatureManager.AddFeature
has access to two hash values. Let's use the first to decide whether to add an object, or skip it. If the value is equal to or greater than 0.? then skip. In this case, we get rid of about half of the objects. The second value will normally be used to determine the turn.
3r37342.
3r37342. 3r? 37308. 3r337379. public void AddFeature (Vector3 position) {
HexHash hash = HexMetrics.SampleHashGrid (position); 3r37342. if (hash.a> = 0.5f) {
return; 3r37342.}
Transform instance = Instantiate (featurePrefab); 3r37342. position.y + = instance.localScale.y * 0.5f; 3r37342. instance.localPosition = HexMetrics.Perturb (position); 3r37342. instance.localRotation = Quaternion.Euler (0f, 360f * hash.b, 0f); 3r37342. instance.SetParent (container, false); 3r37342.}
3r37342.
3r37342. [i] The density of objects is reduced by 50%. 5.
3r37342.
3r37342. unitypackage
3r37342.
3r37342. Drawing objects 3r36833.
3r37342. Instead of placing objects everywhere, let's make them editable. But we will not draw separate objects, but add the level of objects to each cell. This level will control the likelihood of objects in the cell. By default, the value is zero, that is, the objects will be absent.
3r37342.
3r37342. Since red cubes do not look like natural objects on our relief, let's call them buildings. They will represent urbanization. Let's add in HexCell
level of urbanization.
3r37342.
3r37342. 3r? 37308. 3r337379. public int UrbanLevel {
get {3r37342. return urbanLevel; 3r37342.}
set {
if (urbanLevel! = value) {3r37342. urbanLevel = value; 3r37342. RefreshSelfOnly (); 3r37342.}
}
}
3r37342. int urbanLevel;
3r37342. We can make it so that the level of urbanization for the underwater cell is zero, but this is not necessary, we already skip the creation of underwater objects. And perhaps at some stage we will add water objects of urbanization, such as docks and underwater structures.
3r37342.
3r37342. Density slider
3r37342. To change the level of urbanization, add 3r337372. HexMapEditor support for another slider.
3r37342.
3r37342. 3r? 37308. 3r337379. int activeUrbanLevel; 3r37342. 3r37342. …
3r37342. bool applyUrbanLevel; 3r37342. 3r37342. …
3r37342. public void SetApplyUrbanLevel (bool toggle) {
applyUrbanLevel = toggle; 3r37342.}
3r37342. public void SetUrbanLevel (float level) {
activeUrbanLevel = (int) level; 3r37342.}
3r37342. void EditCell (HexCell cell) {
if (cell) {
…
if (applyWaterLevel) {3r37342. cell.WaterLevel = activeWaterLevel; 3r37342.}
if (applyUrbanLevel) {3r37342. cell.UrbanLevel = activeUrbanLevel; 3r37342.}
if (riverMode == OptionalToggle.No) {
cell.RemoveRiver (); 3r37342.}
…
}
}
3r37342. Add another slider to the UI and connect it with the corresponding metodes I will place a new panel on the right side of the screen to avoid overflowing the left panel.
3r37342.
3r37342. How many levels will we need? Let's look at four, denoting zero, low, medium and high density.
3r37342.
3r37342.
3r37342.
3r37342. [i] Urbanization slider. 5.
3r37342.
3r37342. Change threshold
3r37342. Now that we have a level of urbanization, we need to use it to determine whether to place objects. To do this, we need to add the level of urbanization as an additional parameter in 3r337302. HexFeatureManager.AddFeature . Let's take one more step and just pass the cell itself. In the future we will be more comfortable.
3r37342.
3r37342. The fastest way to take advantage of the level of urbanization is by multiplying it by ??? and using the value as a new threshold for skipping objects. Due to this, the probability of an object will increase with each level by 25%.
3r37342.
3r37342. 3r? 37308. 3r337379. public void AddFeature (HexCell cell, Vector3 position) {
HexHash hash = HexMetrics.SampleHashGrid (position); 3r37342. if (hash.a> = cell.UrbanLevel * ???f) {3r373342. return; 3r37342.}
…
}
3r37342. In order for this to work, we will transfer the cells to HexGridChunk
.
3r37342.
3r37342. 3r? 37308. 3r337379. void Triangulate (HexCell cell) {
…
if (! cell.IsUnderwater &&! cell.HasRiver &&! cell.HasRoads) {
features.AddFeature (cell, cell.Position); 3r37342.}
}
3r37342. void Triangulate (HexDirection direction, HexCell cell) {
…
if (! cell.IsUnderwater &&! cell.HasRoadThroughEdge (direction)) {
features.AddFeature (cell, (center + e.v1 + e.v5) * (1f /3f)); 3r37342.}
…
}
3r37342. …
3r37342. void TriangulateAdjacentToRiver (
HexDirection direction, HexCell cell, Vector3 center, EdgeVertices e
) {
…
3r37342. if (! cell.IsUnderwater &&! cell.HasRoadThroughEdge (direction)) {
features.AddFeature (cell, (center + e.v1 + e.v5) * (1f /3f)); 3r37342.}
}
3r37342.
3r37342. [i] Drawing density levels of urbanization. 5.
3r37342.
3r37342. 3r3333939. unitypackage
3r37342.
3r37342. Several prefabs of relief objects
3r37342. The differences in the probability of objects appearing are not enough to create a clear separation between low and high levels of urbanization. In some cells, there will simply be more or less than the expected number of buildings. We can make the difference clearer by using our own prefab for each level.
3r37342.
3r37342. Freed from the field featurePrefab
in 3r37302. HexFeatureManager and replace it with an array for prefabs urbanization. To obtain the corresponding prefab, we will subtract one from the level of urbanization and use the value as an index.
3r37342.
3r37342. 3r? 37308. 3r337379. 3r333963. //public Transform featurePrefab; 3r37342. public Transform[]urbanPrefabs; 3r37342. 3r37342. public void AddFeature (HexCell cell, Vector3 position) {
…
Transform instance = Instantiate (urbanPrefabs[cell.UrbanLevel - 1]); 3r37342. …
}
3r37342. We will create two duplicates of the prefab of the object, rename and change them so that they denote three different levels of urbanization. Level 1 is low density, so we use a cube with a unit edge length, which denotes a shack. I will scale the prefab level 2 to (1.? ? 1.5) to make it look like a two-story building. For tall buildings of level ? I used the scale (? ? 2).
3r37342.
3r37342. 3r37342.
3r37342. [i] Using different prefabs for each level of urbanization.
5.
3r37342.
3r37342. The mixing of prefabs
3r37342. We are not obliged to limit ourselves to strict separation of building types. You can mix them up a bit, as it happens in the real world. Instead of one threshold per level, let's use three, one for each type of building.
3r37342.
3r37342. At level ? we use shackling in 40% of cases. There will be no other buildings here at all. For the level we use three values (0.? ? 0).
3r37342.
3r37342. At level ? we replace the shacks with more buildings, and add a 20% probability for additional shacks. High buildings will not do. That is, we use the threshold three values (0.? 0.? 0).
3r37342.
3r37342. At level ? we replace medium buildings with high ones, replace shacks again, and add another probability of 20% shacks. The threshold values will be equal to (0.? 0.? 0.4).
3r37342.
3r37342. That is, the idea is that as the level of urbanization increases, we will upgrade existing buildings and add new ones to empty places. To delete an existing building, we need to use the same hash value intervals. If the hashes between 0 and 0.4 at level 1 were shacks, then at level 3 the same spacing will create tall buildings. At level ? tall buildings should be created with hash values in the range of 0–0.? two-story buildings in the range of 0.4–0.? and shacks in the range of 0.6–0.8. If you check them from highest to lowest, then this can be done with the help of a triple of thresholds (0.? 0.? 0.8). The thresholds of level 2 will then become (? 0.? 0.6), and the thresholds of level 1 will become (? ? 0.4).
3r37342.
3r37342. Let's save these thresholds in HexMetrics
as a collection of arrays with a method that allows you to get thresholds for a certain level. Since we are only interested in levels with objects, we ignore level 0.
3r37342.
3r37342. 3r? 37308. 3r337379. static float[] []featureThresholds = {
new float[]{0.0f, 0.0f, 0.4f},
new float[]{0.0f, 0.4f, 0.6f}, 3r37342. new float[]{0.4f, 0.6f, 0.8f}
}; 3r37342. 3r37342. public static float[]GetFeatureThresholds (int level) {
return featureThresholds[level]; 3r37342.}
3r37342. Next, add HexFeatureManager
A method that uses the level and hash value to select a prefab. If the level is greater than zero, then we get the thresholds using the level reduced by one. Then we cycle through the thresholds until one of them exceeds the value of the hash. This will mean that we have found the prefab. If not found, then return null.
3r37342.
3r37342. 3r? 37308. 3r337379. Transform PickPrefab (int level, float hash) {
if (level> 0) {
float[]thresholds = HexMetrics.GetFeatureThresholds (level - 1); 3r37342. for (int i = 0;. i < thresholds.Length; i++) {
if (hash
. 3r37342. This approach requires changing the order of references to prefabs so that they go from high to low density.
3r37342.
3r37342.
3r37342. [i] Inverted prefab order. 5.
3r37342.
3r37342. Use our new method in AddFeature
to select prefab. If we do not receive it, then skip the object. Otherwise, create an instance of it and continue as before.
3r37342.
3r37342. 3r? 37308. 3r337379. public void AddFeature (HexCell cell, Vector3 position) {
HexHash hash = HexMetrics.SampleHashGrid (position); 3r37342. //if (hash.a> = cell.UrbanLevel * ???f) {3r373342. //return; 3r37342. //} 3r373342. //Transform instance = Instantiate (urbanPrefabs[cell.UrbanLevel - 1]); 3r37342. Transform prefab = PickPrefab (cell.UrbanLevel, hash.a); 3r37342. if (! prefab) {3r373342. return; 3r37342.}
Transform instance = Instantiate (prefab); 3r37342. position.y + = instance.localScale.y * 0.5f; 3r37342. instance.localPosition = HexMetrics.Perturb (position); 3r37342. instance.localRotation = Quaternion.Euler (0f, 360f * hash.b, 0f); 3r37342. instance.SetParent (container, false); 3r37342.}
3r37342.
3r37342. [i] Mix prefabs. 5.
3r37342.
3r37342. Variations at the level of 3r3727262.
3r37342. Now we have well-mixed buildings, but so far there are only three of them. We can further increase the variability by tying a collection of prefabs to each level of urbanization density. After that, you can choose one of them randomly. This will require a new random value, so add the third one in 3r330302. HexHash .
3r37342.
3r37342. 3r? 37308. 3r337379. public float a, b, c; 3r37342. 3r37342. public static HexHash Create () {
HexHash hash; 3r37342. hash.a = Random.value; 3r37342. hash.b = Random.value; 3r37342. hash.c = Random.value; 3r37342. return hash; 3r37342.}
3r37342. Turn HexFeatureManager.urbanPrefabs
into an array of arrays, and add to the method PickPrefab
option choice
. Use it to select the index of the built-in array, multiplying it by the length of this array and converting it into an integer.
3r37342.
3r37342. 3r? 37308. 3r337379. public Transform[] []urbanPrefabs; 3r37342. 3r37342. …
3r37342. Transform PickPrefab (int level, float hash, float choice) {
if (level> 0) {
float[]thresholds = HexMetrics.GetFeatureThresholds (level - 1); 3r37342. for (int i = 0; i < thresholds.Length; i++) {
if (hash
3r37342. Let's justify our choice on the value of the second hash (B). It will then require that the rotation change from B to C.
3r37342.
3r37342. 3r? 37308. 3r337379. public void AddFeature (HexCell cell, Vector3 position) {
HexHash hash = HexMetrics.SampleHashGrid (position); 3r37342. Transform prefab = PickPrefab (cell.UrbanLevel, hash.a, hash.b); 3r37342. if (! prefab) {3r373342. return; 3r37342.}
Transform instance = Instantiate (prefab); 3r37342. position.y + = instance.localScale.y * 0.5f; 3r37342. instance.localPosition = HexMetrics.Perturb (position); 3r37342. instance.localRotation = Quaternion.Euler (0f, 360f * hash.c, 0f); 3r37342. instance.SetParent (container, false); 3r37342.}
3r37342. Before proceeding, we need to consider that Random.value
may return the value 1. Because of this, the array index may exceed the limits. To prevent this from happening, we slightly change the scale of the hash values. Simply scale them all so as not to worry about the particular we use.
3r37342.
3r37342. 3r? 37308. 3r337379. public static HexHash Create () {
HexHash hash; 3r37342. hash.a = Random.value * ???f; 3r37342. hash.b = Random.value * ???f; 3r37342. hash.c = Random.value * ???f; 3r37342. return hash; 3r37342.}
3r37342. Unfortunately, the arrays are not displayed in the inspector. Therefore, we can not configure them. To circumvent this limitation, create a serializable structure in which we encapsulate the embedded array. Give it a method that converts from choice to an array index and returns the prefab.
3r37342.
3r37342. 3r? 37308. 3r337379. using UnityEngine; 3r37342. 3r37342.[System.Serializable]3r37342. public struct HexFeatureCollection {
3r37342. public Transform[]prefabs; 3r37342. 3r37342. public Transform Pick (float choice) {3r37342. return prefabs[(int)(choice * prefabs.Length)]; 3r37342.}
}
3r37342. We use in HexFeatureManager
instead of embedded arrays, an array of such collections.
3r37342.
3r37342. 3r? 37308. 3r337379. //public Transform[] []urbanPrefabs; 3r37342. public HexFeatureCollection[]urbanCollections; 3r37342. 3r37342. …
3r37342. Transform PickPrefab (int level, float hash, float choice) {
if (level> 0) {
float[]thresholds = HexMetrics.GetFeatureThresholds (level - 1); 3r37342. for (int i = 0; i 3r344462. if (hash
3r37342. Now we can assign several buildings to each density level. Since they are independent, we do not have to use the same amount per level. I simply used two options per level, adding a longer lower version to each one. I chose for them the scales (3.? ? 2), (2.7? 1.? 1.5) and (1.7? ? 1).
3r37342.
3r37342.
3r37342.
3r37342. [i] Two types of buildings per level of density. 5.
3r37342.
3r37342. 3r334275. unitypackage
3r37342.
3r37342. Several types of objects 3r36833.
3r37342. In the existing scheme, we can create quite decent urban structures. But the relief may contain not only buildings. How about farms or plants? Let's add in HexCell
levels and for them. They are not mutually exclusive and can be mixed.
3r37342.
3r37342. 3r? 37308. 3r337379. public int FarmLevel {
get {3r37342. return farmLevel; 3r37342.}
set {
if (farmLevel! = value) {3r373342. farmLevel = value; 3r37342. RefreshSelfOnly (); 3r37342.}
}
}
3r37342. public int PlantLevel {
get {3r37342. return plantLevel; 3r37342.}
set {
if (plantLevel! = value) {
plantLevel = value; 3r37342. RefreshSelfOnly (); 3r37342.}
}
}
3r37342. int urbanLevel, farmLevel, plantLevel;
3r37342. Of course, this requires support in r3r37302. HexMapEditor two additional sliders.
3r37342.
3r37342. 3r? 37308. 3r337379. int activeUrbanLevel, activeFarmLevel, activePlantLevel; 3r37342. 3r37342. bool applyUrbanLevel, applyFarmLevel, applyPlantLevel; 3r37342. 3r37342. …
3r37342. public void SetApplyFarmLevel (bool toggle) {
applyFarmLevel = toggle; 3r37342.}
3r37342. public void SetFarmLevel (float level) {
activeFarmLevel = (int) level; 3r37342.}
3r37342. public void SetApplyPlantLevel (bool toggle) {
applyPlantLevel = toggle; 3r37342.}
3r37342. public void SetPlantLevel (float level) {
activePlantLevel = (int) level; 3r37342.}
3r37342. …
3r37342. void EditCell (HexCell cell) {
if (cell) {
…
if (applyUrbanLevel) {3r37342. cell.UrbanLevel = activeUrbanLevel; 3r37342.}
if (applyFarmLevel) {3r373342. cell.FarmLevel = activeFarmLevel; 3r37342.}
if (applyPlantLevel) {3r373342. cell.PlantLevel = activePlantLevel; 3r37342.}
…
}
}
3r37342. Add them to the UI.
3r37342.
3r37342.
3r37342. [i] Three sliders. 5.
3r37342.
3r37342. Also additional collections will need HexFeatureManager
.
3r37342.
3r37342. 3r? 37308. 3r337379. public HexFeatureCollection[]3r37342. urbanCollections, farmCollections, plantCollections;
3r37342.
3r37342. [i] Three collections of objects of relief. 5.
3r37342.
3r37342. I created both prefabs for density and for farms and plants, as well as for collections of buildings. For all of them, I used cubes. Farms have a light green material, plants - dark green.
3r37342.
3r37342. I made cubes of truss with a height of 0.1 units to designate square allotments of agricultural land. I chose (2.? 0.? 2.5) and (3.? 0.? 2) as high density scales. On average, the sites have an area of ??? and a size of 2.5 by ???. The low level of density received an area of 1 and a size of 1.5 by ???.
3r37342.
3r37342. Plant prefabs denote tall trees and large shrubs. High-density prefabs are the largest, (1.2? 4.? ???) and (1.? ? 1.5). The average scales are (0.7? ? ???) and (? 1.? 1). The smallest plants have dimensions (0.? 1.? 0.5) and (0.7? ? ???).
3r37342.
3r37342. Selection of objects of relief
3r37342. Each type of object must receive its own hash value so that they have different creation patterns and can be mixed. Add to HexHash
two additional values.
3r37342.
3r37342. 3r? 37308. 3r337379. public float a, b, c, d, e; 3r37342. 3r37342. public static HexHash Create () {
HexHash hash; 3r37342. hash.a = Random.value * ???f; 3r37342. hash.b = Random.value * ???f; 3r37342. hash.c = Random.value * ???f; 3r37342. hash.d = Random.value * ???f; 3r37342. hash.e = Random.value * ???f; 3r37342. return hash; 3r37342.}
3r37342. Now HexFeatureManager.PickPrefab
will have to work with different collections. Add a parameter to simplify the process. We will also change the hash used by the option of the selected prefab to D, and the hash to rotate to E.
3r37342.
3r37342. 3r? 37308. 3r337379. Transform PickPrefab (
HexFeatureCollection[]Collection, 3r37342. Int level, float hash, float choice
) {
if (level> 0) {
float[]thresholds = HexMetrics.GetFeatureThresholds (level - 1); 3r37342. for (int i = 0; i 3r34462. if (hash 3r3323.3.
3r37342. Currently AddFeature
selects prefab urbanization. This is normal, we need more options. Therefore, add another prefab from the farm. We use B. as the hash value. Choosing the option again will be D. 3r3-337328. 3r37342.
3r37342. 3r? 37308. 3r337379. Transform prefab = PickPrefab (3r373342. UrbanCollections, cell.UrbanLevel, hash.a, hash.d
); 3r37342. Transform otherPrefab = PickPrefab (
FarmCollections, cell.FarmLevel, hash.b, hash.d
); 3r37342. if (! prefab) {3r373342. return; 3r37342.}
3r37342. What copy of the prefab will we create as a result? If one of them turns out to be null, then the choice is obvious. However, if both exist, then we need to make a decision. Let's just add the prefab with the lowest hash value.
3r37342.
3r37342. 3r? 37308. 3r337379. Transform otherPrefab = PickPrefab (
FarmCollections, cell.FarmLevel, hash.b, hash.d
); 3r37342. if (prefab) {3r3r7373. . If (otherPrefab && hash.b < hash.a) {
prefab = otherPrefab;
}
}
else if (otherPrefab) {
prefab = otherPrefab;
}
else {
return;
}
3r37342. 3r37342. [i] Mixing urban and rural sites.
5.
3r37342. Next, do the same with the plants, using the value of the hash C.
3r37342.
3r37342. 3r? 37308. 3r337379. if (prefab) {3r3r7373. rrrrrrrrrrrrrrrrrrrrrrrrr 34 hash. = otherPrefab;
}
else {
return; 3r37342.} 3r332328. 3r37342. However, we cannot just copy the code. When we choose a rural object instead of an urban object, then we need to compare the hash of the plants with the hash of the farms, and not with the urban one. Therefore, we need to track the hash that we decided to select and compare with it.
3r37342.
3r37342. 3r? 37308. 3r337379. float usedHash = hash.a; 3r37342. if (prefab) {3r3r7373. if (otherPrefab && hash.b < hash.a) {
prefab = otherPrefab;
usedHash = hash.b;
}
}
else if (otherPrefab) {
}
otherPrefab = PickPrefab (
plantCollections, cell.PlantLevel, hash.c, hash.d
.);.
if (prefab) {
if (otherPrefab && hash.c < usedHash) {
prefab = otherPrefab;
.}
}
Else if (otherPrefab) {3r3r.37342. Prefab = otherPrefab; 3r37342.} 3r373342. Else {3r37342. Return; 3r373342.}
3r37342.
3r37342. [i] A mixture of urban, rural and vegetable objects. 5.
3r37342.
3r37342. 3r34611. unitypackage
3r37342.
3r37342. 3r360341. Part 10: The walls of 3r36042.
3r37342. 3r360345. 3r37342. 3r36053. We enclose the cell. 3r36054. 3r37342. 3r36053. We build the walls along the edges of the cells. 3r36054. 3r37342. 3r36053. Allowed to pass through rivers and roads. 3r36054. 3r37342. 3r36053. Avoid water and connect with cliffs. 3r36054. 3r37342. 3r36056.
3r37342. In this section we will add walls between the cells.
3r37342.
3r37342.
3r37342. [i] There is nothing more welcoming than the high wall. 5.
3r37342.
3r37342. Editing the walls
3r37342. To support the walls, we need to know where to place them. We will place them between cells along the edges connecting them. Since the existing objects are located in the central part of the cells, we do not need to worry that the walls will pass through them.
3r37342.
3r37342.
3r37342. [i] The walls are located along the edges. 5.
3r37342.
3r37342. The walls are objects of relief, although they are large. Like other objects, we will not edit them directly. Instead, we will change the cells. We will not have separate segments of the walls, but we will be engaged in enclosing the cells as a whole.
3r37342.
3r37342. Property Walled
3r37342. To support fenced cells add in 3r37302. HexCell property 3r37302. Walled . This is a simple switch. Since the walls are located between the cells, we need to update both the edited cells and their neighbors.
3r37342.
3r37342. 3r? 37308. 3r337379. public bool Walled {
get {3r37342. return walled; 3r37342.}
set {
if (walled! = value) {
walled = value; 3r37342. Refresh (); 3r37342.}
}
}
3r37342. bool walled;
3r37342. Switch editor
3r37342. To switch the state of “fenced” cells, we need to add in 3r337372. HexMapEditor switch support. Therefore, add another field OptionalToggle
and the method for setting it.
3r37342.
3r37342. 3r? 37308. 3r337379. OptionalToggle riverMode, roadMode, walledMode; 3r37342. 3r37342. …
3r37342. public void SetWalledMode (int mode) {
walledMode = (OptionalToggle) mode; 3r37342.}
3r37342. Unlike rivers and roads, the walls do not go from cell to cell, but lie between them. Therefore, we do not need to think about dragging. When the wall switch is active, we simply set the state of “fencing” of the current cell based on the state of this switch.
3r37342.
3r37342. 3r? 37308. 3r337379. void EditCell (HexCell cell) {
if (cell) {
…
if (roadMode == OptionalToggle.No) {
cell.RemoveRoads (); 3r37342.}
if (walledMode! = OptionalToggle.Ignore) {
cell.Walled = walledMode == OptionalToggle.Yes; 3r37342.}
if (isDrag) {
…
}
}
}
3r37342. We duplicate one of the previous elements of the UI switches and change them so that they control the state of "fencing". I will put them in the UI panel along with other objects.
3r37342.
3r37342.
3r37342. [i] Switch "fence". 5.
3r37342.
3r37342. unitypackage
3r37342.
3r37342. Creating walls 3r36833.
3r37342. Since the walls follow the contours of the cells, they should not have a permanent shape. Therefore, we cannot simply use a prefab for them, as was done with other objects of relief. Instead, we need to build a mesh, as we did with the relief. This means that our fragment prefab needs another child element HexMesh
. Duplicate one of the remaining child meshes and make new objects 3r3-335373. Walls cast shadows. They need nothing but vertices and triangles, so all options are HexMesh
need to turn off.
3r37342.
3r37342.
3r37342.
3r37342. [i] Child Prefab Walls. 5.
3r37342.
3r37342. It will be logical that the walls are an urban object, so for them I used the red building material.
3r37342.
3r37342. Office of the walls 3r3727262.
3r37342. Since the walls are objects of relief, they should deal with r3r37302. HexFeatureManager . Therefore, we give the object manager of the relief a link to the object Walls , and make sure that it calls methods 3r337372. Clear and 3r37302. Apply .
3r37342.
3r37342. 3r? 37308. 3r337379. public HexMesh walls; 3r37342. 3r37342. …
3r37342. public void Clear () {
…
walls.Clear (); 3r37342.}
3r37342. public void Apply () {
walls.Apply (); 3r37342.}
3r37342.
3r37342. [i] Walls connected to the manager of the relief objects. 5.
3r37342.
3r37342. 3r337378. 3r37199. Shouldn't Walls be a child of Features? 3r3727200.
3r37342. Now we need to add a method to the manager that allows adding walls to it. Since the walls are along the edges between the cells, he needs to know the corresponding vertices of the edges and cells. 3r33737. HexGridChunk will call it through TriangulateConnection
, at the time of the triangulation of the cell and one of its neighbors. From this point of view, the current cell is located on the near side of the wall, and the other is on the far side.
3r37342.
3r37342. 3r? 37308. 3r337379. public void AddWall (
EdgeVertices near, HexCell nearCell,
EdgeVertices far, HexCell farCell 3r373342.) {
}
3r37342. Call this new method in HexGridChunk.TriangulateConnection
after completing all the other connective work and immediately before going to the corner triangle. We will provide the manager of the terrain objects to decide for themselves where the wall should actually be located.
3r37342.
3r37342. 3r? 37308. 3r337379. void TriangulateConnection (
HexDirection direction, HexCell cell, EdgeVertices e1
) {
…
3r37342. if (cell.GetEdgeType (direction) == HexEdgeType.Slope) {
…
}
else {
…
}
3r37342. features.AddWall (e? cell, e? neighbor); 3r37342. 3r37342. HexCell nextNeighbor = cell.GetNeighbor (direction.Next ()); 3r37342. if (direction <= HexDirection.E && nextNeighbor != null) {
}
}
3r37342. Constructing a wall segment
3r37342. The entire wall will wriggle through several cell edges. Each edge contains only one wall element. From the point of view of the near cell, the segment begins on the left side of the edge and ends on the right. Let's add in HexFeatureManager
a separate method using four vertices in the corners of the edge.
3r37342.
3r37342. 3r? 37308. 3r337379. void AddWallSegment (
Vector3 nearLeft, Vector3 farLeft, Vector3 nearRight, Vector3 farRight
) {
}
3r37342. 3r37342. [i] Near and far side.
5.
3r37342.
3r37342. 3r33737. AddWall may call this method with the first and last vertices of the edges. But walls should be added only when we have a connection between a fenced cell and a non-enclosed cell. It does not matter which of the cells is inside and which is outside; only the difference between their states is taken into account.
3r37342.
3r37342. 3r? 37308. 3r337379. public void AddWall (
EdgeVertices near, HexCell nearCell,
EdgeVertices far, HexCell farCell 3r373342.) {
if (nearCell.Walled! = farCell.Walled) {
AddWallSegment (near.v? far.v? near.v? far.v5); 3r37342.}
}
3r37342. The simplest segment of the wall is one quad, standing in the middle of the edge. We will find its lower peaks, interpolating to the middle of the closest to the distant peaks.
3r37342.
3r37342. 3r? 37308. 3r337379. void AddWallSegment (
Vector3 nearLeft, Vector3 farLeft, Vector3 nearRight, Vector3 farRight
) {
Vector3 left = Vector3.Lerp (nearLeft, farLeft, 0.5f); 3r37342. Vector3 right = Vector3.Lerp (nearRight, farRight, 0.5f); 3r37342.}
3r37342. How tall should the wall be? Let's set its height to HexMetrics
. I made them the size of one level of height of cells.
3r37342.
3r37342. 3r? 37308. 3r337379. public const float wallHeight = 3f;
3r37342. 3r33737. HexFeatureManager.AddWallSegment can use this height to position the third and fourth vertex of the quad, and also add it to the mesh walls
.
3r37342.
3r37342. 3r? 37308. 3r337379. Vector3 left = Vector3.Lerp (nearLeft, farLeft, 0.5f); 3r37342. Vector3 right = Vector3.Lerp (nearRight, farRight, 0.5f); 3r37342. 3r37342. Vector3 v? v? v? v4; 3r37342. v1 = v3 = left; 3r37342. v2 = v4 = right; 3r37342. v3.y = v4.y = left.y + HexMetrics.wallHeight; 3r37342. walls.AddQuad (v? v? v? v4);
3r37342. Now we can edit the walls and they will be displayed as quad bands. However, we will not see a continuous wall. Each quad is visible only from one side. Its face is directed towards the cell from which it was added.
3r37342.
3r37342.
3r37342. [i] One-sided quad walls. 5.
3r37342.
3r37342. We can quickly solve this problem by adding a second quad, pointing in the other direction.
3r37342.
3r37342. 3r? 37308. 3r337379. walls.AddQuad (v? v? v? v4); 3r37342. walls.AddQuad (v? v? v? v3);
3r37342.
3r37342. [i] Double-sided walls. 5.
3r37342.
3r37342. Now all the walls are visible entirely, but in the corners of the cells, where there are three cells, all the same until there are holes. We will fill them in later.
3r37342.
3r37342. Thick walls
3r37342. Although the walls are already visible on both sides, they have no thickness. In fact, the walls are thin, like paper, and almost invisible at a certain angle. So let's make them solid by adding thickness. Let's set their thickness in HexMetrics
. I chose the value of ??? units, it seemed to me suitable.
3r37342.
3r37342. 3r? 37308. 3r337379. public const float wallThickness = ???f;
3r37342. To make two walls thick, you need to separate the two quad-side. They must move in opposite directions. One side should move to the near edge, the other - to the far. The displacement vector for this is far - near
, but to keep the upper part of the wall flat, we need to assign its Y value to 0.
3r37342.
3r37342. Since this should be done both for the left and for the right part of the wall segment, let's add to HexMetrics
method to calculate this displacement vector.
3r37342.
3r37342. 3r? 37308. 3r337379. public static Vector3 WallThicknessOffset (Vector3 near, Vector3 far) {
Vector3 offset; 3r37342. offset.x = far.x - near.x; 3r37342. offset.y = 0f; 3r37342. offset.z = far.z - near.z; 3r37342. return offset; 3r37342.}
3r37342. In order for the wall to remain in the center of the edge, the present distance of movement along this vector must be equal to half the thicknesss for each of the parties. And to make sure that we really moved to the desired distance, we normalize the displacement vector before scaling it.
3r37342.
3r37342. 3r? 37308. 3r337379. return offset.normalized * (wallThickness * 0.5f);
3r37342. Use this method in HexFeatureManager.AddWallSegment
to change the position of the quad. Since the displacement vector goes from the nearest to the far cell, we subtract it from the near quad and add it to the far one.
3r37342.
3r37342. 3r? 37308. 3r337379. Vector3 left = Vector3.Lerp (nearLeft, farLeft, 0.5f); 3r37342. Vector3 right = Vector3.Lerp (nearRight, farRight, 0.5f); 3r37342. 3r37342. Vector3 leftThicknessOffset =
HexMetrics.WallThicknessOffset (nearLeft, farLeft); 3r37342. Vector3 rightThicknessOffset =
HexMetrics.WallThicknessOffset (nearRight, farRight); 3r37342. 3r37342. Vector3 v? v? v? v4; 3r37342. v1 = v3 = left - leftThicknessOffset; 3r37342. v2 = v4 = right - rightThicknessOffset; 3r37342. v3.y = v4.y = left.y + HexMetrics.wallHeight; 3r37342. walls.AddQuad (v? v? v? v4); 3r37342. 3r37342. v1 = v3 = left + leftThicknessOffset; 3r37342. v2 = v4 = right + rightThicknessOffset; 3r37342. v3.y = v4.y = left.y + HexMetrics.wallHeight; 3r37342. walls.AddQuad (v? v? v? v3);
3r37342. 3r37342. [i] Walls with offsets.
5.
3r37342.
3r37342. Quads are now shifted, although this is not entirely noticeable.
3r37342.
3r37342. 3r337378. 3r37199. Is the wall thickness the same? 3r3727200.
3r37342. The tops of the walls
3r37342. To make the wall thickness visible from above, we need to add quad to the top of the wall. The easiest way to do this is by memorizing the top two vertices of the first quad and connecting them to the top two vertices of the second quad.
3r37342.
3r37342. 3r? 37308. 3r337379. Vector3 v? v? v? v4; 3r37342. v1 = v3 = left - leftThicknessOffset; 3r37342. v2 = v4 = right - rightThicknessOffset; 3r37342. v3.y = v4.y = left.y + HexMetrics.wallHeight; 3r37342. walls.AddQuad (v? v? v? v4); 3r37342. 3r37342. Vector3 t1 = v? t2 = v4; 3r37342. 3r37342. v1 = v3 = left + leftThicknessOffset; 3r37342. v2 = v4 = right + rightThicknessOffset; 3r37342. v3.y = v4.y = left.y + HexMetrics.wallHeight; 3r37342. walls.AddQuad (v? v? v? v3); 3r37342. 3r37342. walls.AddQuad (t? t? v? v4);
3r37342. 3r37342. [i] Walls with tops.
5.
3r37342.
3r37342. Cornering
3r37342. We still have holes in the corners of the cells. To fill them, we need to add a segment to the triangular area between the cells. Each corner connects three cells. Each cell may or may not have a wall. That is, eight configurations are possible.
3r37342.
3r37342.
3r37342. [i] Configuration angles. 5.
3r37342.
3r37342. We place walls only between cells with different “fenced” conditions. This reduces the number of configurations to six. In each of them, one of the cells is inside the curved walls. Let's take this cell as the pivot point around which the wall is bent. From the point of view of this cell, the wall begins with an edge that is common to the left cell, and ends with an edge that is common to the right cell.
3r37342.
3r37342.
3r37342. [i] The roles of the cells. 5.
3r37342.
3r37342. That is, we need to create a method AddWallSegment
whose parameters will be the three vertices of the angle. Although we can write code to triangulate this segment, in fact it is a special case of the method. AddWallSegment
. The pivot point plays the role of both near vertices.
3r37342.
3r37342. 3r? 37308. 3r337379. void AddWallSegment (
Vector3 pivot HexCell pivotCell,
Vector3 left, HexCell leftCell,
Vector3 right, HexCell rightCell
) {
AddWallSegment (pivot, left, pivot, right); 3r37342.}
3r37342. Next, create a variant of the method AddWall
for three vertices of an angle and their cells. The task of this method is to determine the angle, which is the reference point, if it exists. Therefore, it should consider all eight possible configurations and call 3r330302. AddWallSegment for six of them.
3r37342.
3r37342. 3r? 37308. 3r337379. public void AddWall (
Vector3 c? HexCell cell?
Vector3 c? HexCell cell?
Vector3 c? HexCell cell3
) {
if (cell1.Walled) {
if (cell2.Walled) {3r373342. if (! cell3.Walled) {3r373342. AddWallSegment (c? cell? c? cell? c? cell2); 3r37342.}
}
else if (cell3.Walled) {
AddWallSegment (c? cell? c? cell? c? cell1); 3r37342.}
else {
AddWallSegment (c? cell? c? cell? c? cell3); 3r37342.}
}
else if (cell2.Walled) {
if (cell3.Walled) {
AddWallSegment (c? cell? c? cell? c? cell3); 3r37342.}
else {
AddWallSegment (c? cell? c? cell? c? cell1); 3r37342.}
}
else if (cell3.Walled) {
AddWallSegment (c? cell? c? cell? c? cell2); 3r37342.}
}
3r37342. To add corner segments, call this method at the end of r3r37302. HexGridChunk.TriangulateCorner .
3r37342.
3r37342. 3r? 37308. 3r337379. void TriangulateCorner (
Vector3 bottom, HexCell bottomCell,
Vector3 left, HexCell leftCell,
Vector3 right, HexCell rightCell
) {
…
3r37342. features.AddWall (bottom, bottomCell, left, leftCell, right, rightCell); 3r37342.}
3r37342.
3r37342. [i] The walls are with corners, but there are still holes. 5.
3r37342.
3r37342. Close the hole
3r37342. There are still holes in the walls, because the height of the wall segments is not constant. While the segments along the edges have a constant height, the corner segments are between two different edges. Since each edge can have its own height, holes appear at the corners.
3r37342.
3r37342. To fix this, change AddWallSegment
so that it stores separately the Y coordinates of the left and right upper vertices.
3r37342.
3r37342. 3r? 37308. 3r337379. float leftTop = left.y + HexMetrics.wallHeight; 3r37342. float rightTop = right.y + HexMetrics.wallHeight; 3r37342. 3r37342. Vector3 v? v? v? v4; 3r37342. v1 = v3 = left - leftThicknessOffset; 3r37342. v2 = v4 = right - rightThicknessOffset; 3r37342. v3.y = leftTop; 3r37342. v4.y = rightTop; 3r37342. walls.AddQuad (v? v? v? v4); 3r37342. 3r37342. Vector3 t1 = v? t2 = v4; 3r37342. 3r37342. v1 = v3 = left + leftThicknessOffset; 3r37342. v2 = v4 = right + rightThicknessOffset; 3r37342. v3.y = leftTop; 3r37342. v4.y = rightTop; 3r37342. walls.AddQuad (v? v? v? v3);
3r37342.
3r37342. [i] Enclosed walls. 5.
3r37342.
3r37342. The walls are now closed, but you probably still see holes in the shadows of the wall. This is caused by parameter Normal Bias settings for shadows of directional light. When it is greater than zero, the triangles of the shadow-casting objects move along the surface normal. This avoids self-shadowing, but at the same time creates holes in cases where the triangles look in different directions. At the same time, holes can be created in the shadows of fine geometry, for example, such as our walls.
3r37342.
3r37342. You can get rid of these shadow artifacts by reducing normal bias to zero. Or change the mode Cast Shadows mesh wall renderer on Two Sided . This will cause the shadow-casting object to render both sides of each triangle wall to render, which closes all the holes.
3r37342.
3r37342. 3r37342. [i] There are no more shadow holes.
5.
3r37342.
3r37342. unitypackage
3r37342.
3r37342. Walls on ledges
3r37342. As long as our walls are fairly straight. For flat terrain, this is not bad at all, but it looks strange when the walls coincide with the ledges. This happens when there is a difference of one height between the cells on opposite sides of the wall.
3r37342.
3r37342.
3r37342. [i] Straight walls on ledges. [h2] 5.
3r37342.
3r37342. Follow the edge of
3r37342. Instead of creating one segment for the whole edge, we will create one for each part of the edge strip. We can do this by calling four times AddWallSegment
in version AddWall
for the ribs.
3r37342.
3r37342. 3r? 37308. 3r337379. public void AddWall (
EdgeVertices near, HexCell nearCell,
EdgeVertices far, HexCell farCell 3r373342.) {
if (nearCell.Walled! = farCell.Walled) {
AddWallSegment (near.v? far.v? near.v? far.v2); 3r37342. AddWallSegment (near.v? far.v? near.v? far.v3); 3r37342. AddWallSegment (near.v? far.v? near.v? far.v4); 3r37342. AddWallSegment (near.v? far.v? near.v? far.v5); 3r37342.}
}
3r37342.
3r37342. [i] Curving walls. 5.
3r37342.
3r37342. The walls now replicate the shapes of distorted edges. In combination with the ledges it looks much better. In addition, it creates more interesting walls on flat terrain.
3r37342.
3r37342. Placing walls on the ground
3r37342. Looking at the walls on the ledges, you can find a problem. The walls are hanging above the ground! This is true for inclined flat edges, but usually not so noticeable.
3r37342.
3r37342. 3r37342. [i] Walls hanging in the air.
5.
3r37342.
3r37342. To solve the problem, we need to lower the walls. The easiest way is to lower the entire wall so that its top remains flat. At the same time, part of the wall on the upper side will slightly sink into the relief, but this will suit us.
3r37342.
3r37342. To lower the wall, we need to determine which of the sides is lower - near or far. We can simply use the height of the lowest side, but we do not need to go down so low. You can interpol the Y coordinate from low to high with an offset of just under 0.5. Since the walls only occasionally become higher than the lower step of the step, we can useI am the vertical step of the ledge. Different wall thickness of the ledge configuration may require a different offset.
3r37342.
3r37342.
It may be interesting
weber
Author13-10-2018, 10:32
Publication DateGame development / Unity3D
Category- Comments: 0
- Views: 343
davidphilp
Really thanks and appreciated by Singapore Immigration Consultant
davidphilp
raymond weber
taxiseo2
nursing test bank
taxiseo2