Unity Hexagon Cards: Fog of War, Map Exploration, Procedural Generation

 3r37249. 3r3-31. Parts 1-3: mesh, colors and cell heights
 3r37249.
 3r37249. Parts 4-7: bumps, rivers and roads
 3r37249.
 3r37249. 3r314. Parts 8-11: water, landforms, and fortifications
 3r37249.
 3r37249. Parts 12-15: saving and loading, textures, distances
 3r37249.
 3r37249. Parts 16-19: search for the way, player squads, animations
 3r37249.
 3r37249. 3r335561. Part 20: the fog of war
 3r37249. 3r335565.  3r37249. 3r335579. Store cell data in a texture. 3r? 35580.  3r37249. 3r335579. Change terrain types without triangulation. 3r? 35580.  3r37249. 3r335579. We trace visibility. 3r? 35580.  3r37249. 3r335579. We darken everything invisible. 3r? 35580.  3r37249. 3r335582.
 3r37249. In this section we will add to the map the effect of the fog of war.
 3r37249.
 3r37249. Now the series will be created on Unity ???.
 3r37249.
 3r37249. 3r3727226. 3r362.
 3r37249. Now we see that we can and cannot see.
 3r37249. unitypackage
 3r37249.
 3r37249. 3r370767. Objects and visibility
 3r37249. Now visibility works for the whole procedurally generated relief, but it does not affect the relief objects yet. Buildings, farms and trees are created from prefabs, not from procedural geometry, so we cannot add cell indices and mix weights with their vertices. Since each of these objects belongs to only one cell, we need to determine which cell they are in. If we can do this, we will access the data of the corresponding cells and apply visibility.
 3r37249.
 3r37249. We can already convert the XZ positions of the world into cell indices. This transformation was used to edit the relief and control units. However, the corresponding code is non-trivial. It uses integer operations and requires logic to work with edges. For a shader, this is impractical to apply, so we can bake the main part of the logic into a texture and use it.
 3r37249.
 3r37249. We already use a hexagonal-patterned texture to project a grid over the topography. This texture defines a 2 × 2 cell area. Therefore, it is easy to calculate in which area we are. After that, you can apply a texture containing the X and Z offsets to the cells in this area and use this data to calculate the cell in which we are.
 3r37249.
 3r37249. Here is a similar texture. The offset by X is stored in its red channel, and the offset by Z is in the green channel. Since it covers a 2 × 2 cell area, we need offsets from 0 and 2. Such data cannot be stored in the color channel, so displacements are reduced by half. We do not need clear edges of cells, so a small texture is enough.
 3r37249.
 3r37249. 3r3727226. Unity Hexagon Cards: Fog of War, Map Exploration, Procedural Generation
 3r37249. Grid coordinates texture.
 3r37249.
 3r37249. Add texture to the project. Set it for
Wrap Mode
Repeat
Like the other mesh textures. We do not need any mixing, therefore for
Blend Mode
choose the value
Point
. Also disable
Compression
so that the data is not distorted. Disable mode
sRGB
so that when rendering in linear mode no color space transformations are performed. And finally, we do not need mip-textures.
 3r37249.
 3r37249. 3r3727226.
 3r37249. Texture import options.
 3r37249.
 3r37249. 3r337373. Object shader with visibility
 3r37249. Create a new shader
Feature
to add object visibility support. This is a simple surface shader with a vertex program. Add
to it. HexCellData
and pass the visibility index to the fragment program, and as usual we take it into account in color. The difference here is that we cannot use r3r37198. GetCellData , because the required data meshes do not exist. Instead, we have a position in the world. But for now let's keep the visibility equal to 1.
 3r37249.
 3r37249.
3r337378. Shader "Custom /Feature" {
Properties {3r324249. _Color ("Color", Color) = (???1)
_MainTex ("Albedo (RGB)", 2D) = "white" {}
_Glossiness ("Smoothness", Range (?1)) = ???r3r37249. _Metallic ("Metallic", Range (?1)) = ???r3r37249.[NoTilingOffset]_GridCoordinates ("Grid Coordinates", 2D) = "white" {}
}
SubShader {
Tags {"RenderType" = "Opaque"}
LOD 200
3r37249. CGPROGRAM
#pragma surface surf Standard fullforwardshadows vertex: vert
#pragma target ???r3r37249. 3r37249. #include "/HexCellData.cginc"
3r37249. sampler2D _MainTex, _GridCoordinates; 3r37249. 3r37249. half _Glossiness; 3r37249. half _Metallic; 3r37249. fixed4 _Color; 3r37249. 3r37249. struct Input {3r337249. float2 uv_MainTex; 3r37249. float visibility; 3r37249.}; 3r37249. 3r37249. void vert (inout appdata_full v, out Input data) {
UNITY_INITIALIZE_OUTPUT (Input, data); 3r37249. float3 pos = mul (unity_ObjectToWorld, v.vertex); 3r37249. 3r37249. data.visibility = 1; 3r37249.}
3r37249. void surf (Input IN, inout SurfaceOutputStandard o) {
fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color; 3r37249. o.Albedo = c.rgb * IN.visibility; 3r37249. o.Metallic = _Metallic; 3r37249. o.Smoothness = _Glossiness; 3r37249. o.Alpha = c.a; 3r37249.}
ENDCG
}
FallBack "Diffuse"
}
 3r37249. Let's change all materials of the objects so that they use the new shader, and assign them the texture of the grid coordinates.
 3r37249.
 3r37249. 3r3727226. 3r32993.
 3r37249. Urban with mesh texture.
 3r37249.
 3r37249. 3r337373. Access data cells
 3r37249. To sample the grid coordinate texture in the vertex program, we again need 3r -37198. tex2Dlod with a four-component vector texture coordinates. The first two coordinates are the position of the XZ world. The other two are still zero.
 3r37249.
 3r37249.
3r337378. void vert (inout appdata_full v, out Input data) {
UNITY_INITIALIZE_OUTPUT (Input, data); 3r37249. float3 pos = mul (unity_ObjectToWorld, v.vertex); 3r37249. 3r37249. float4 gridUV = float4 (pos.xz, ? 0); 3r37249. 3r37249. data.visibility = 1; 3r37249.}
 3r37249. As in the shader
Terrain
, stretch the UV coordinates so that the texture has the correct aspect ratio corresponding to the grid of hexagons.
 3r37249.
 3r37249.
3r337378. float4 gridUV = float4 (pos.xz, ? 0); 3r37249. gridUV.x * = 1 /(4 * ???); 3r37249. gridUV.y * = 1 /(2 * 15.0);
 3r37249. We can find out which part of the 2 × 2 cells we are in by taking the value of the UV coordinates rounded down. This forms the basis of the cell coordinates.
 3r37249.
 3r37249.
3r337378. float4 gridUV = float4 (pos.xz, ? 0); 3r37249. gridUV.x * = 1 /(4 * ???); 3r37249. gridUV.y * = 1 /(2 * 15.0); 3r37249. float2 cellDataCoordinates = floor (gridUV.xy);
 3r37249. To find the coordinates of the cell in which we are, we add the offsets stored in the texture.
 3r37249.
 3r37249.
3r337378. float2 cellDataCoordinates =
floor (gridUV.xy) + tex2Dlod (_GridCoordinates, gridUV) .rg;
 3r37249. Since part of the grid is 2 × ? and the offsets are halved, we need to double the result to get the final coordinates.
 3r37249.
 3r37249.
3r337378. float2 cellDataCoordinates =
floor (gridUV.xy) + tex2Dlod (_GridCoordinates, gridUV) .rg; 3r37249. cellDataCoordinates * = 2;
 3r37249. Now we have the coordinates of the XZ grid of cells that need to be converted to the UV coordinates of the cells. This can be done simply by moving to the pixel centers, and then divided by the size of the textures. So let's add
to the include file. HexCellData
function for this, which will also be sampling.
 3r37249.
 3r37249.
3r337378. float4 GetCellData (float2 cellDataCoordinates) {
float2 uv = cellDataCoordinates + 0.5; 3r37249. uv.x * = _HexCellData_TexelSize.x; 3r37249. uv.y * = _HexCellData_TexelSize.y; 3r37249. return tex2Dlod (_HexCellData, float4 (uv, ? 0)); 3r37249.}
 3r37249. Now we can use this function in the vertex shader program
Feature
.
 3r37249.
 3r37249.
3r337378. cellDataCoordinates * = 2; 3r37249. 3r37249. data.visibility = GetCellData (cellDataCoordinates) .x; 3r37249. data.visibility = lerp (0.2? ? data.visibility);
 3r37249. 3r3727226.
 3r37249. Objects with visibility.
 3r37249.
 3r37249. Finally, visibility affects the entire map, with the exception of units that are always visible. Since we determine the visibility of objects for each vertex, then for an object intersecting the border of the cell, the visibility of the cells being closed by it will be mixed. But objects are so small that they constantly remain inside their cells, even taking into account position distortions. However, some may be part of the vertices in another cell. Therefore, our approach is cheap, but imperfect. This is most noticeable in the case of walls, the visibility of which fluctuates between the appearances of neighboring cells.
 3r37249.
 3r37249. 3r3727226. 3r333334.
 3r37249. Walls with changing visibility.
 3r37249.
 3r37249. Since wall segments are generated procedurally, we can add cell data to their mesh and use the approach we used for the terrain. Unfortunately, the towers are prefabs, so we will still have inconsistencies. In general, the existing approach looks quite good for the simple geometry we use. In the future, we will look at more detailed models and walls, so we will improve the method of mixing their visibility.
 3r37249.
 3r37249. 3r333338. unitypackage
 3r37249.
 3r37249. 3r335561. Part 21: study maps of 3r335562.
 3r37249. 3r335565.  3r37249. 3r335579. Display everything while editing. 3r? 35580.  3r37249. 3r335579. We trace the investigated cells. 3r? 35580.  3r37249. 3r335579. Hiding what is not yet known. 3r? 35580.  3r37249. 3r335579. Force squads to avoid unexplored areas. 3r? 35580.  3r37249. 3r335582.
 3r37249. In the previous section, we added the fog of war, which we will now improve to implement a map exploration.
 3r37249.
 3r37249. 3r3727226. 3r333170.
 3r37249. We are ready to explore the world.
 3r37249.
 3r37249. 3r370767. Display the entire map in 3r37068 edit mode.
 3r37249. The meaning of the study lies in the fact that the cells that are not yet seen are considered to be unknown, and therefore invisible. They should not be shaded, but generally not displayed. Therefore, before adding research support, we will turn on visibility in edit mode.
 3r37249.
 3r37249. 3r337373. Toggle visibility mode
 3r37249. We can control whether shaders use visibility, using a keyword, as was done with overlaying the grid. Let's use the keyword
HEX_MAP_EDIT_MODE
indicating the state of the edit mode. Since several shaders should know about this keyword, we define it globally using static methods. Shader.EnableKeyWord and 3r31937. Shader.DisableKeyword . We will call the corresponding method in HexGameUI.SetEditMode when changing the edit mode.
 3r37249.
 3r37249.
    public void SetEditMode (bool toggle) {
enabled =! toggle; 3r37249. grid.ShowUI (! toggle); 3r37249. grid.ClearPath (); 3r37249. if (toggle) {
Shader.EnableKeyword ("HEX_MAP_EDIT_MODE"); 3r37249.}
else {
Shader.DisableKeyword ("HEX_MAP_EDIT_MODE"); 3r37249.}
}

 3r37249. 3r337373. Shaders editing mode
 3r37249. When HEX_MAP_EDIT_MODE defined, shaders will ignore visibility. This boils down to the fact that the cell visibility will always be considered equal to 1. Let's add to the beginning of the include file. HexCellData function to filter cell data depending on the keyword.
 3r37249.
 3r37249.
  3r337378. sampler2D _HexCellData; 3r37249. float4 _HexCellData_TexelSize; 3r37249. 3r37249. float4 FilterCellData (float4 data) {
#if defined (HEX_MAP_EDIT_MODE)
data.x = 1; 3r37249. #endif
return data; 3r37249.}

 3r37249. Pass through this function the result of both functions GetCellData before returning it.
 3r37249.
 3r37249.
  3r337378. float4 GetCellData (appdata_full v, int index) {

return FilterCellData (data); 3r37249.}
3r37249. float4 GetCellData (float2 cellDataCoordinates) {

return FilterCellData (tex2Dlod (_HexCellData, float4 (uv, ? 0))); 3r37249.}

 3r37249. For everything to work, all the relevant shaders must receive a multi_compile directive to create variants in case the keyword 3r317173 is used. HEX_MAP_EDIT_MODE defined. Add the appropriate line to the shaders Estuary , 3r31734. Feature , 3r31734. River , 3r31734. Road , 3r31734. Terrain , 3r31734. Water 3r31734. and 3r334173. Water Shore , between the target directive and the first include directive.
 3r37249.
 3r37249.
  3r337378. #pragma multi_compile _ HEX_MAP_EDIT_MODE    

 3r37249. Now, when you switch to map editing mode, the fog of war will disappear.
 3r37249.
 3r37249. 3r3323296. unitypackage
 3r37249.
 3r37249. 3r370767. Examination of cells
 3r37249. By default, cells should be considered unexplored. They become researched when a squad sees them. After that, they continue to be investigated if a detachment can see them.
 3r37249.
 3r37249. 3r337373. Tracking research status
 3r37249. To add support for tracking the status of the study, add in r3r37198. HexCell general property 3r319378. IsExplored .
 3r37249.
 3r37249.
    public bool IsExplored {get; set;}    

   3r37249. The state of the study is determined by the cell itself. Therefore, this property should be set only HexCell . To add such a restriction, let's make the setter private.
 3r37249.
 3r37249.
    public bool IsExplored {get; private set;}    

 3r37249. The first time, when the visibility of the cell becomes greater than zero, the cell begins to be considered investigated, and therefore IsExplored should be assigned. true . In fact, it will be enough for us to simply mark the cell as explored, when the visibility increases to 1. This must be done before calling 3r37198. RefreshVisibility .
 3r37249.
 3r37249.
    public void IncreaseVisibility () {
visibility + = 1; 3r37249. if (visibility == 1) {
IsExplored = true; 3r37249. ShaderData.RefreshVisibility (this); 3r37249.}
}

 3r37249. 3r337373. Transferring probe status to shaders
 3r37249. As in the case of the visibility of the cells, we transfer their state of the study to the shaders through the shader data. In the end, it's just another type of visibility. 3r337378. HexCellShaderData.RefreshVisibility stores visibility state in channel R data. Let's keep the study state in channel G data.
 3r37249.
 3r37249.
    public void RefreshVisibility (HexCell cell) {
int index = cell.Index; 3r37249. cellTextureData[index].r = cell.IsVisible? (byte) 255: (byte) 0; 3r37249. cellTextureData[index].g = cell.IsExplored? (byte) 255: (byte) 0; 3r37249. enabled = true; 3r37249.}

 3r37249. 3r337373. Black unexplored relief
 3r37249. Now we can use shaders to visualize the state of the study cells. To make sure that everything works as it should, we simply make the unexplored relief black. But first, to make the edit mode work, we change 3r337378. FilterCellData so that it filters the research data.
 3r37249.
 3r37249.
  3r337378. float4 FilterCellData (float4 data) {
#if defined (HEX_MAP_EDIT_MODE)
data.xy = 1; 3r37249. #endif
return data; 3r37249.}

 3r37249. Shader Terrain transfers the visibility data of all three possible cells to the fragment program. In the case of the study state, we combine them in the vertex program and transfer the single value to the fragment program. Add to the incoming data visibility the fourth component, so that we have a place for it.
 3r37249.
 3r37249.
  3r337378. struct Input {3r337249. float4 color: COLOR; 3r37249. float3 worldPos; 3r37249. float3 terrain; 3r37249. float4 visibility; 3r37249.};    

 3r37249. Now, in the vertex program, when we change the visibility index, we must explicitly get access to r3r37198. data.visibility.xyz .
 3r37249.
 3r37249.
  3r337378. void vert (inout appdata_full v, out Input data) {

data.visibility.xyz = lerp (0.2? ? data.visibility.xyz); 3r37249.}

 3r37249. After that, we combine the study states and write the result in 3r37198. data.visibility.w . This is done in the same way as combining visibility in other shaders, but using the Y data component of the cells.
 3r37249.
 3r37249.
  3r337378. data.visibility.xyz = lerp (0.2? ? data.visibility.xyz); 3r37249. data.visibility.w =
cell0.y * v.color.x + cell1.y * v.color.y + cell2.y * v.color.z;

 3r37249. The status of the study is now available in the fragment program through IN.visibility.w . We take it into account in the calculation of albedo.
 3r37249.
 3r37249.
  3r337378. void surf (Input IN, inout SurfaceOutputStandard o) {

3r37249. float explored = IN.visibility.w; 3r37249. o.Albedo = c.rgb * grid * _Color * explored; 3r37249. o.Metallic = _Metallic; 3r37249. o.Smoothness = _Glossiness; 3r37249. o.Alpha = c.a; 3r37249.}

 3r37249. 3r3727226. 3r333333.
 3r37249. The unexplored relief is now black.
 3r37249.
 3r37249. The relief of the unexplored cells is now black. But the objects, roads and water is not affected. However, this is enough to make sure that the study is working.
 3r37249.
 3r37249. 3r337373. Saving and loading the study state
 3r37249. Now that we have added study support, we need to make sure that the state of the study is taken into account when saving and loading maps. Therefore, we need to increase the version of the map files to 3. To make these changes more convenient, let's add for this in 3r319378. SaveLoadMenu constant.
 3r37249.
 3r37249.
    const int mapFileVersion = 3;    

 3r37249. We will use this constant when writing a file version to r3r37198. Save and when checking file support in Load .
 3r37249.
 3r37249.
    void Save (string path) {
using (
BinaryWriter writer =
new BinaryWriter (File.Open (path, FileMode.Create))
) {
writer.Write (mapFileVersion); 3r37249. hexGrid.Save (writer); 3r37249.}
}
3r37249. void Load (string path) {
if (! File.Exists (path)) {
Debug.LogError ("File does not exist" + path); 3r37249. return; 3r37249.}
using (BinaryReader reader = new BinaryReader (File.OpenRead (path))) {
int header = reader.ReadInt32 (); 3r37249. if (header <= mapFileVersion) {
hexGrid.Load (reader, header);
HexMapCamera.ValidatePosition ();
}
else {
Debug.
}

 3r37249. As a last step, HexCell.Save we will write the state of the study.
 3r37249.
 3r37249.
    public void Save (BinaryWriter writer) {

writer.Write (IsExplored); 3r37249.}

 3r37249. And we will read it at the end of Load . After that we will call RefreshVisibility in case the state of the study is different from the previous one
 3r37249.
 3r37249.
    public void Load (BinaryReader reader) {

3r37249. IsExplored = reader.ReadBoolean (); 3r37249. ShaderData.RefreshVisibility (this); 3r37249.}

 3r37249. To preserve backward compatibility with old save files, we need to skip reading the save state if the file version is less than 3. Let's in this case, by default, the cells will have the status “unexamined”. To do this, we need to add as a parameter. Load header data.
 3r37249.
 3r37249.
    public void Load (BinaryReader reader, int header) {

3r37249. IsExplored = header> = 3? reader.ReadBoolean (): false; 3r37249. ShaderData.RefreshVisibility (this); 3r37249.}

 3r37249. Now HexGrid.Load should transfer to HexCell.Load header data.
 3r37249.
 3r37249.
    public void Load (BinaryReader reader, int header) {

3r37249. for (int i = 0; i 3r3–35172. cells.Load (reader, header);
}
 3r37249. Now, when saving and loading maps, the state of cell exploration will be taken into account.
 3r37249.
 3r37249. unitypackage
 3r37249.
 3r37249. 3r370767. Hide unknown cells
 3r37249. At the current stage, the unexplored cells are visually indicated by black relief. But in reality, we want these cells to be invisible, because they are unknown. We can make opaque geometry transparent so that it is not visible. However, the Unity surface shader framework was developed without this possibility. Instead of using true transparency, we change the shaders to match the background, which also makes them invisible.
 3r37249.
 3r37249. 3r337373. Making relief truly black
 3r37249. Although the relief is black, we can still recognize it because it still has specular lighting. To get rid of the lighting, we need to make it perfectly matt black. In order not to affect other surface properties, the easiest way is to change the specular color to black. This is possible if you use a surface shader that works with specular, but now we use standard metallic. Therefore, let's start by switching the shader Terrain on specular.
 3r37249.
 3r37249. Replace the color property. _Metallic on the property 3r317173. _Specular . By default, its color value should be (0.? 0.? 0.2). So we guarantee that it will match the appearance of the metallic version.
 3r37249.
 3r37249.
  3r337378. Properties {3r324249. _Color ("Color", Color) = (???1)
_MainTex ("Terrain Texture Array", 2DArray) = "white" {}
_GridTex ("Grid Texture", 2D) = "white" {}
_Glossiness ("Smoothness", Range (?1)) = ???r3r37249. //_Metallic ("Metallic", Range (?1)) = ???r3r37249. _Specular ("Specular", Color) = (0.? 0.? 0.2)
}

 3r37249. We will also modify the corresponding shader variables. The color of the specular surface shader is defined as fixed3 so let's use it.
 3r37249.
 3r37249.
  3r337378. half _Glossiness; 3r37249. //half _Metallic; 3r37249. fixed3 _Specular; 3r37249. fixed4 _Color;    

 3r37249. Change the pragma surface surf from Standard on 3r317173. StandardSpecular . This will force Unity to generate shaders using specular.
 3r37249.
 3r37249.
  3r337378. #pragma surface surf StandardSpecular fullforwardshadows vertex: vert    

 3r37249. Now functions surf you need the second parameter to have type SurfaceOutputStandardSpecular . In addition, you now need to assign a value not o.Metallic , and 3r337378. o.Specular .
 3r37249.
 3r37249.
  3r337378. void surf (Input IN, inout SurfaceOutputStandardSpecular o) {

3r37249. float explored = IN.visibility.w; 3r37249. o.Albedo = c.rgb * grid * _Color * explored; 3r37249. //o.Metallic = _Metallic; 3r37249. o.Specular = _Specular; 3r37249. o.Smoothness = _Glossiness; 3r37249. o.Alpha = c.a; 3r37249.}

 3r37249. Now we can obscure the reflections by taking into account 3r319378. explored in specular color.
 3r37249.
 3r37249.
  3r337378. o.Specular = _Specular * explored;    

 3r37249. 3r3727226.
 3r37249. [i] Uncharted relief without reflected light.

 3r37249.
 3r37249. As you can see in the picture, now the unexplored relief looks dull black. However, when viewed at a tangent angle, the surfaces turn into a mirror, due to which the reliefIt reflects the environment, that is, the skybox.
 3r37249.
 3r37249. 3r3r6464. 3r36465. Why do surfaces become mirrors? [/b]
This is called the Fresnel effect. For details, see the tutorial series. Rendering .

 3r37249. 3r3727226.
 3r37249. The unexplored areas still reflect the environment.
 3r37249.
 3r37249. To get rid of these reflections, we will assume that the unexplored relief is completely shaded. This is implemented by assigning the value explored the occlusion parameter, which we use as a reflection mask.
 3r37249.
 3r37249.
  3r337378. float explored = IN.visibility.w; 3r37249. o.Albedo = c.rgb * grid * _Color * explored; 3r37249. o.Specular = _Specular * explored; 3r37249. o.Smoothness = _Glossiness; 3r37249. o.Occlusion = explored; 3r37249. o.Alpha = c.a;    

 3r37249. 3r3727226.
 3r37249. Investigated without reflections.
 3r37249.
 3r37249. 3r337373. Matching background
 3r37249. Now that the unexplored relief ignores all the lighting, you need to make it match the background. Since our camera is always looking from above, the background always remains gray. To inform the shader Terrain What color to use, add to it the property _BackgroundColor , the default value is black.
 3r37249.
 3r37249.
  3r337378. Properties {3r324249. …
_BackgroundColor ("Background Color", Color) = (??0)
}
3r37249. …
3r37249. half _Glossiness; 3r37249. fixed3 _Specular; 3r37249. fixed4 _Color; 3r37249. half3 _BackgroundColor;

 3r37249. To use this color, we will add it as an emissive light. This is implemented by assignment o.Emission background color values ​​multiplied by one minus explored.
 3r37249.
 3r37249.
  3r337378. o.Occlusion = explored; 3r37249. o.Emission = _BackgroundColor * (1 - explored);    

 3r37249. Since we use the default skybox, the visible background color is actually not the same. In general, a slightly reddish gray will be the best color. When setting the terrain material you can use for Hex Color code 68615BFF.
 3r37249.
 3r37249. 3r3727226. 3r333843.
 3r37249. Relief material with gray background color.
 3r37249.
 3r37249. In general, it works, although if you know where to look, you can see very weak silhouettes. So that the player could not see them, you can assign a uniform background color of 68615BFF to the camera instead of the skybox.
 3r37249.
 3r37249. 3r3727226. 3r? 33858.
 3r37249. Camera with a uniform background color.
 3r37249.
 3r37249. 3r3r6464. 3r36465. Why not remove the skybox? [/b]
This can be done, but do not forget that it is used for environmental lighting cards. If you switch to a uniform color, the lighting of the map also changes.

 3r37249. Now we can not find the differences between the background and unexplored cells. The high unexplored relief can still obscure the low relief studied at low camera angles. In addition, the unexplored parts still cast shadows on the investigated. But these minimal hints can be neglected.
 3r37249.
 3r37249. 3r3727226. 3r333331.
 3r37249. Unexplored cells are no longer visible.
 3r37249.
 3r37249. 3r3r6464. 3r36465. What if you don't use a uniform background color? [/b]
You can create your own shader, which will actually make the relief transparent, but continue writing to the depth buffer. This may require tricks with a shader queue. If you are using a screen space texture, you can simply sample this texture instead of the background color. If you use a texture in the space of the world, then you need to perform calculations to determine which UV coordinates of the texture to use based on the viewing angle and position of the fragment in the world.

 3r37249. 3r337373. Hide terrain objects
 3r37249. Now we have hidden only the mesh of the relief. It does not act on the rest of the research.
 3r37249.
 3r37249. 3r3727226. 3r333338.
 3r37249. While only the relief is hidden.
 3r37249.
 3r37249. Let's now change the shader Feature which is an opaque shader like Terrain . Turn it into a specular shader and add a background color to it. Let's start with the properties.
 3r37249.
 3r37249.
  3r337378. Properties {3r324249. _Color ("Color", Color) = (???1)
_MainTex ("Albedo (RGB)", 2D) = "white" {}
_Glossiness ("Smoothness", Range (?1)) = ???r3r37249. //_Metallic ("Metallic", Range (?1)) = ???r3r37249. _Specular ("Specular", Color) = (0.? 0.? 0.2)
_BackgroundColor ("Background Color", Color) = (??0)
[NoScaleOffset]_GridCoordinates ("Grid Coordinates", 2D) = "white" {}
}

 3r37249. Further, the pragma surface and variables, as before.
 3r37249.
 3r37249.
  3r337378. #pragma surface surf StandardSpecular fullforwardshadows vertex: vert
3r37249. …
3r37249. half _Glossiness; 3r37249. //half _Metallic; 3r37249. fixed3 _Specular; 3r37249. fixed4 _Color; 3r37249. half3 _BackgroundColor;

 3r37249. 3r337378. visibility also need another component. Since Feature combines visibility for each vertex; it only needed one float value. Now we need two.
 3r37249.
 3r37249.
  3r337378. struct Input {3r337249. float2 uv_MainTex; 3r37249. float2 visibility; 3r37249.};    

 3r37249. Change vert so that it explicitly uses for data visibility data.visibility.x and then assign data.visibility.y value of research data.
 3r37249.
 3r37249.
  3r337378. void vert (inout appdata_full v, out Input data) {

3r37249. float4 cellData = GetCellData (cellDataCoordinates); 3r37249. data.visibility.x = cellData.x; 3r37249. data.visibility.x = lerp (0.2? ? data.visibility.x); 3r37249. data.visibility.y = cellData.y; 3r37249.}

 3r37249. Change surf so that it uses the new data, as Terrain .
 3r37249.
 3r37249.
  3r337378. void surf (Input IN, inout SurfaceOutputStandardSpecular o) {
fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color; 3r37249. float explored = IN.visibility.y; 3r37249. o.Albedo = c.rgb * (IN.visibility.x * explored); 3r37249. //o.Metallic = _Metallic; 3r37249. o.Specular = _Specular * explored; 3r37249. o.Smoothness = _Glossiness; 3r37249. o.Occlusion = explored; 3r37249. o.Emission = _BackgroundColor * (1 - explored); 3r37249. o.Alpha = c.a; 3r37249.}

 3r37249. 3r3727226. 3r34023.
 3r37249. Hidden objects of relief.
 3r37249.
 3r37249. 3r337373. We hide the water
 3r37249. Next come the shaders Water 3r31734. and 3r334173. Water Shore . We start by converting them into specular shaders. However, they do not need the background color because they are transparent shaders.
 3r37249.
 3r37249. After the conversion, add to visibility one more component and change accordingly. vert . Both shaders combine these three cells.
 3r37249.
 3r37249.
  3r337378. struct Input {3r337249. …
float2 visibility; 3r37249.}; 3r37249. 3r37249. …
3r37249. void vert (inout appdata_full v, out Input data) {

3r37249. data.visibility.x =
cell0.x * v.color.x + cell1.x * v.color.y + cell2.x * v.color.z; 3r37249. data.visibility.x = lerp (0.2? ? data.visibility.x); 3r37249. data.visibility.y =
cell0.y * v.color.x + cell1.y * v.color.y + cell2.y * v.color.z; 3r37249.}

 3r37249. 3r31734. Water 3r31734. and 3r334173. Water Shore Perform in 3r37198. surf different operations, but set their surface properties in the same way. Since they are transparent, we take into account explore in the alpha channel, but we will not set the emission.
 3r37249.
 3r37249.
  3r337378. void surf (Input IN, inout SurfaceOutputStandardSpecular o) {

3r37249. float explored = IN.visibility.y; 3r37249. o.Albedo = c.rgb * IN.visibility.x; 3r37249. o.Specular = _Specular * explored; 3r37249. o.Smoothness = _Glossiness; 3r37249. o.Occlusion = explored; 3r37249. o.Alpha = c.a * explored; 3r37249.}

 3r37249. 3r3727226. 3r31034.
 3r37249. Hidden water.
 3r37249.
 3r37249. 3r337373. Hiding mouths, rivers and roads
 3r37249. We still have shaders Estuary , 3r31734. River and 3r334173. Road . All three are transparent and combine the data of the two cells. Switch them all to specular, and then add them to r3r37198. visibility research data.
 3r37249.
 3r37249.
  3r337378. struct Input {3r337249. …
float2 visibility; 3r37249.}; 3r37249. 3r37249. …
3r37249. void vert (inout appdata_full v, out Input data) {

3r37249. data.visibility.x = cell0.x * v.color.x + cell1.x * v.color.y; 3r37249. data.visibility.x = lerp (0.2? ? data.visibility.x); 3r37249. data.visibility.y = cell0.y * v.color.x + cell1.y * v.color.y; 3r37249.}

 3r37249. Change the function surf shaders Estuary and 3r334173. River so that she uses the new data. Both need to make the same changes.
 3r37249.
 3r37249.
  3r337378. void surf (Input IN, inout SurfaceOutputStandardSpecular o) {

3r37249. float explored = IN.visibility.y; 3r37249. fixed4 c = saturate (_Color + water); 3r37249. o.Albedo = c.rgb * IN.visibility.x; 3r37249. o.Specular = _Specular * explored; 3r37249. o.Smoothness = _Glossiness; 3r37249. o.Occlusion = explored; 3r37249. o.Alpha = c.a * explored; 3r37249.}

 3r37249. Shader Road slightly different because it uses an extra blend indicator.
 3r37249.
 3r37249.
  3r337378. void surf (Input IN, inout SurfaceOutputStandardSpecular o) {
float4 noise = tex2D (_MainTex, IN.worldPos.xz * ???); 3r37249. fixed4 c = _Color * ((noise.y * ??? + ???) * IN.visibility.x); 3r37249. float blend = IN.uv_MainTex.x; 3r37249. blend * = noise.x + 0.5; 3r37249. blend = smoothstep (0.? 0.? blend); 3r37249. 3r37249. float explored = IN.visibility.y; 3r37249. o.Albedo = c.rgb; 3r37249.o.Specular = _Specular * explored; 3r37249. o.Smoothness = _Glossiness; 3r37249. o.Occlusion = explored; 3r37249. o.Alpha = blend * explored; 3r37249.}

 3r37249. 3r3727226. 3r334199.
 3r37249. Everything is hidden.
 3r37249.
 3r37249. unitypackage
 3r37249.
 3r37249. 3r370767. Avoiding uncharted cells
 3r37249. Although the unknown is hidden visually, so far the state of the research is not taken into account when searching for a path. As a result, the detachments can be ordered to move to the unexplored cells and through them, magically determining which way to go. We need to force units to avoid unexplored cells.
 3r37249.
 3r37249. 3r3727226. 3r334224.
 3r37249. Move through unexplored cells.
 3r37249.
 3r37249. 3r337373. Troops determine the cost of moving
 3r37249. Before we go into the unexplored cells, let's redo the code to transfer the cost of movement from 3r37198. HexGrid in 3r37198. HexUnit . This will simplify the support of units with different rules of movement.
 3r37249.
 3r37249. Add to HexUnit general method 3r319378. GetMoveCost to determine the cost of moving. He needs to know which cells are moving between, as well as the direction. Copy the corresponding relocation cost code from 3r37198. HexGrid.Search in this method and change the variable names.
 3r37249.
 3r37249.
    public int GetMoveCost (
HexCell fromCell, HexCell toCell, HexDirection direction)
{
HexEdgeType edgeType = fromCell.GetEdgeType (toCell); 3r37249. if (edgeType == HexEdgeType.Cliff) {
continue; 3r37249.}
int moveCost; 3r37249. if (fromCell.HasRoadThroughEdge (direction)) {
moveCost = 1; 3r37249.}
else if (fromCell.Walled! = toCell.Walled) {
continue; 3r37249.}
else {
moveCost = edgeType == HexEdgeType.Flat? 5:10; 3r37249. moveCost + =
toCell.UrbanLevel + toCell.FarmLevel + toCell.PlantLevel; 3r37249.}
}

 3r37249. The method should return the cost of moving. The old code for skipping invalid moves used continue , but this approach will not work here. If movement is not possible, then we will return the negative costs of moving.
 3r37249.
 3r37249.
    public int GetMoveCost (
HexCell fromCell, HexCell toCell, HexDirection direction)
{
HexEdgeType edgeType = fromCell.GetEdgeType (toCell); 3r37249. if (edgeType == HexEdgeType.Cliff) {
return -1; 3r37249.}
int moveCost; 3r37249. if (fromCell.HasRoadThroughEdge (direction)) {
moveCost = 1; 3r37249.}
else if (fromCell.Walled! = toCell.Walled) {
return -1; 3r37249.}
else {
moveCost = edgeType == HexEdgeType.Flat? 5:10; 3r37249. moveCost + =
toCell.UrbanLevel + toCell.FarmLevel + toCell.PlantLevel; 3r37249.}
return moveCost; 3r37249.}

 3r37249. Now we need to know when finding the way not only the speed, but also the selected squad. Change accordingly. HexGameUI.DoPathFinding .
 3r37249.
 3r37249.
    void DoPathfinding () {
if (UpdateCurrentCell ()) {3r324249. if (currentCell && selectedUnit.IsValidDestination (currentCell)) {
grid.FindPath (selectedUnit.Location, currentCell, selectedUnit); 3r37249.}
else {
grid.ClearPath (); 3r37249.}
}
}

 3r37249. Since we still need access to the squadron’s speed, we’ll add to r3r37198. HexUnit property 3r319378. Speed ​​ . For now, it will return a constant value of 24.
 3r37249.
 3r37249.
    public int Speed ​​{
get {
return 24; 3r37249.}
}

 3r37249. In 3r337378. HexGrid change FindPath 3r3727213. and 3r31937. Search so that they can work with our new approach.
 3r37249.
 3r37249.
    public void FindPath (HexCell fromCell, HexCell toCell, HexUnit unit) {
ClearPath (); 3r37249. currentPathFrom = fromCell; 3r37249. currentPathTo = toCell; 3r37249. currentPathExists = Search (fromCell, toCell, unit); 3r37249. ShowPath (unit.Speed); 3r37249.}
3r37249. bool Search (HexCell fromCell, HexCell toCell, HexUnit unit) {
int speed = unit.Speed; 3r37249. …
}

 3r37249. Now remove from Search the old code that determined whether it was possible to move to the next cell and what are the costs of moving. Instead, we will call HexUnit.IsValidDestination and 3r31937. HexUnit.GetMoveCost . We will skip the cell if the cost of movement turns out to be negative.
 3r37249.
 3r37249.
    for (HexDirection d = HexDirection.NE; d  <= HexDirection.NW; d++) {
extraccccccccq.htw.ph.ax.ph.co.ukhhhhhhhhhhhhhhhhhhhhhhhhhhhh = hryo ıs ao h h h h h h r h a r h y h y h y l l p h y p l p y h y l p p p p p e d p p y h y p p p a p p h o p h e p l e f l ha ab g a b p l p h p l p p p p paaa InITITITIT ability of in to to ours in the region of partners, for partners to to investors to toxus intoxus, forxors, forts (HexDirection d = HexDirection.NE; d <= HexDirection.NW; d++) {
k.t. .}
//if (neighbor.IsUnderwater || neighbor.Unit) {
//continue;
//}
//HexEdgeType == HexEdgeType.Cliff) {3r337379. ////continue;
//}
//int moveCost;
//if (current.HasRoadThroughEdge (d)) {
//moveCost = 1. /}
//else if (current.Walled! = Neighbor.Walled) {
//continue;
//}
//else {
//moveCost = edgeType == HexEdgeType.Flat? 5 : 10;
//moveCost + = neighbor.UrbanLevel + neighbor.FarmLevel +
//neighbor.Pl antLevel; 3r37249. //} 3r324249. if (! unit.IsValidDestination (neighbor)) {3r-37249. continue; 3r37249.}
int moveCost = unit.GetMoveCost (current, neighbor, d); 3r37249. if (moveCost < 0) {
continue;
}
int distance = current.Distance + moveCost;
int turn = (distance - 1) /speed;
if (turn> currentTurn) {3r324379. * speed + moveCost;
}
}
3r3721414. 3r37235.  3r37249. 3r337373. Bypassing unexplored areas
 3r37249. To avoid unexplored cells, it is enough for us to make 3r337378. HexUnit.IsValidDestination checked if the cell was examined.
 3r37249.
 3r37249.
    public bool IsValidDestination (HexCell cell) {
return cell.IsExplored &&! cell.IsUnderwater &&! cell.Unit; 3r37249.}

 3r37249. 3r3727226. 3r34456.
 3r37249. No more units will be able to fall on unexplored cells.
 3r37249.
 3r37249. Since the unexplored cells are no longer the correct endpoints, detachments will avoid them when moving to the endpoint. That is, unexplored areas act as barriers that lengthen the path or even make it impossible. We will have to bring the detachments closer to the unknown relief in order to first explore the area.
 3r37249.
 3r37249. 3r3r6464. 3r36465. What if a shorter path appears during the move? [/b]
In our case, the path is determined only once and it is impossible to deviate from it during the movement. We can change this and look for a new path at every step, but this will make the detachment’s movement unpredictable and chaotic. It is better to stick to the chosen path and not to be wise.
 3r37249.
 3r37249. On the other hand, if we are strictly limited to movement in one turn, then for long journeys we will have to calculate a new path at each turn. In this case, the units may choose a shorter path in the next turn if it suddenly appeared.

 3r37249. 3r334482. unitypackage
 3r37249.
 3r37249. 3r335561. Part 22: improved visibility 3r335562.
 3r37249. 3r335565.  3r37249. 3r335579. Smoothly change the visibility. 3r? 35580.  3r37249. 3r335579. Use the height of the cell to determine the scope. 3r? 35580.  3r37249. 3r335579. Hide the edge of the map. 3r? 35580.  3r37249. 3r335582.
 3r37249. Adding support for the study of the map, we will improve the calculations and transitions of the scope.
 3r37249.
 3r37249. 3r3727226.
 3r37249. To see further, climb higher.
 3r37249.
 3r37249. 3r370767. Transitions visibility
 3r37249. The cell is either visible or invisible, because it is either in the field of view of the detachment or not. Even if it seems that a detachment takes some time to move between cells, its field of view jumps from cell to cell instantly. As a result, the visibility of the surrounding cells changes dramatically. The movement of the detachment seems smooth, but the changes in visibility are sudden.
 3r37249.
 3r37249. Ideally, visibility should also change smoothly. Getting into the field of visibility, the cells should be illuminated gradually, and leaving it, gradually darken. Or maybe you prefer instant transitions? Let's add in HexCellShaderData property switching instant transitions. By default, the transitions will be smooth.
 3r37249.
 3r37249.
    public bool ImmediateMode {get; set;}    

 3r37249. 3r337373. Track transition cells
 3r37249. Even when displaying smooth transitions, the true visibility data still remains binary, that is, the effect is only visual. This means that the transitions of visibility should deal with r3r37198. HexCellShaderData . Give him the list of cells in which the transition is performed. Let's make it so that with each initialization it is empty.
 3r37249.
 3r37249.
    using System.Collections.Generic; 3r37249. using UnityEngine; 3r37249. 3r37249. public class HexCellShaderdаta: MonoBehaviour {
3r37249. Texture2D cellTexture; 3r37249. Color32[]cellTextureData; 3r37249. 3r37249. List transitioningCells = new List (); 3r37249. 3r37249. public bool ImmediateMode {get; set;}
3r37249. public void Initialize (int x, int z) {

3r37249. transitioningCells.Clear (); 3r37249. enabled = true; 3r37249.}
3r37249. …
}

 3r37249. At the moment, we are setting the cell data to 3r337378. RefreshVisibility directly. This is still correct for instant transition mode, but when it is disabled, we have to add a cell to the list of transition cells.
 3r37249.
 3r37249.
    public void RefreshVisibility (HexCell cell) {
int index = cell.Index; 3r37249. if (ImmediateMode) {
cellTextureData[index].r = cell.IsVisible? (byte) 255: (byte) 0; 3r37249. cellTextureData[index].g = cell.IsExplored? (byte) 255: (byte) 0; 3r37249.}
else {
transitioningCells.Add (cell); 3r37249.}
enabled = true; 3r37249.}

 3r37249. It seems that visibility no longer works, because while we do nothing with the cells in the list.
 3r37249.
 3r37249. 3r337373. Bypassing transition cells in a cycle.
 3r37249. Instead of instantly setting the corresponding values ​​of 255 or ? we will increase /decrease these values ​​gradually. The smoothness of the transition depends on the rate of change. It should not be very fast and not very slow. A good compromise between beautiful transitions and the convenience of the game is to change within one second. Let's set a constant for this to make it easier to change.
 3r37249.
 3r37249.
    const float transitionSpeed ​​= 255f;    

 3r37249. Now in 3r337378. LateUpdate we can determine the delta applied to the values. To do this, multiply the time delta by the speed. It must be an integer value, because we do not know how big it can be. A sharp drop in the frame rate can make the delta greater than 255.
 3r37249.
 3r37249. In addition, we need to perform the update while there are transitional cells. Therefore, the code should be included while there is something in the list.
 3r37249.
 3r37249.
    void LateUpdate () {
int delta = (int) (Time.deltaTime * transitionSpeed); 3r37249. 3r37249. cellTexture.SetPixels32 (cellTextureData); 3r37249. cellTexture.Apply (); 3r37249. enabled = transitioningCells.Count> 0; 3r37249.}

 3r37249. Also theoretically, very high frame rates are possible. In combination with a low transition speed, this can give us a delta of 0. To make the change, we will force the minimum of the delta to be 1.
 3r37249.
 3r37249.
    int delta = (int) (Time.deltaTime * transitionSpeed); 3r37249. if (delta == 0) {
delta = 1; 3r37249.}

 3r37249. Having received the delta, we can bypass in the cycle all transition cells and update their data. Suppose that we have for this a method UpdateCellData whose parameters are the corresponding cell and delta.
 3r37249.
 3r37249.
    int delta = (int) (Time.deltaTime * transitionSpeed); 3r37249. if (delta == 0) {
delta = 1; 3r37249.}
for (int i = 0; i < transitioningCells.Count; i++) {
UpdateCellData (transitioningCells, delta);
} 3r3727213. 3r37214. 3r37235.  3r37249. At some point, the cell transition should complete. Suppose the method returns information about whether the transition is still ongoing. When it stops going, we can remove the cell from the list. After that, we must execute the decrement of the iterator in order not to skip the cells.
 3r37249.
 3r37249.
    for (int i = 0; i  < transitioningCells.Count; i++) {
if (! UpdateCellData (transitioningCells[i], delta)) {3r37249. transitioningCells.RemoveAt (i--);
} 3r3r.  3r37249. The processing order of the transient cells is not important. Therefore, it is not necessary for us to delete a cell at the current index, which would cause 3r -37198. RemoveAt 3r3727213. Move all the cells after it. Instead, we move the last cell to the current index, and then delete the last one.
 3r37249.
 3r37249.
    if (! UpdateCellData (transitioningCells[i], delta)) {
transitioningCells[i--]=
transitioningCells[transitioningCells.Count - 1]; 3r37249. transitioningCells.RemoveAt (transitioningCells.Count - 1); 3r37249.}

 3r37249. Now we have to create the method UpdateCellData . To do his job, he will need an index and data cells, so let's start by getting them. It must also determine whether to continue updating the cell. By default, we will assume that it is not necessary. After completion of the work, it is necessary to apply the changed data and return the status “update continues”.
 3r37249.
 3r37249.
    bool UpdateCellData (HexCell cell, int delta) {
int index = cell.Index; 3r37249. Color32 data = cellTextureData[index]; 3r37249. bool stillUpdating = false; 3r37249. 3r37249. cellTextureData[index]= data; 3r37249. return stillUpdating; 3r37249.}

 3r37249. 3r337373. Updating cell data
 3r37249. At this stage, we have a cell that is in the process of transition or has already completed it. First, let's check the state of the cell study. If a cell is examined, but its G value is not yet equal to 25? then it is in the process of transition, so we will track it.
 3r37249.
 3r37249.
    bool stillUpdating = false; 3r37249. 3r37249. if (cell.IsExplored && data.g 3r334341. stillUpdating = true;
}
cellTextureData[index]= data; 3r3721313.

 3r37249. To perform the transition, we will add a delta cell to the G value. Arithmetic operations do not work with bytes, they are first converted to integer. Therefore, the sum will be in integer format, which needs to be converted to byte.
 3r37249.
 3r37249.
    if (cell.IsExplored && data.g  < 255) {
stillUpdating = true;
int t = data.g + delta;
data.g = (byte) t;
} 3r372133. 3r37234.
 3r37249. But before converting you need to make sure that the value does not exceed 255.
 3r37249.
 3r37249.
    int t = data.g + delta; 3r37249. data.g = t> = 255? (byte) 255: (byte) t;    

 3r37249. Next, we need to do the same for visibility, which uses the value of R.
 3r37249.
 3r37249.
    if (cell.IsExplored && data.g  < 255) {
}
if (cell.IsVisible && data.r 3r334341. = t> = 255? (byte) 255: (byte) t;
}

 3r37249. Since the cell may become invisible again, we need to check whether it is necessary to decrease the value of R. This happens in the case when the cell is invisible, but R is greater than zero.
 3r37249.
 3r37249.
    if (cell.IsVisible) {
if (data.r < 255) {
stillUpdating = true;
int t = data.r + delta;
data.r = t> = 255? (byte) 255: (byte) t; 3rr24249.} 3r324379.}
. else if (data.r> 0) {
stillUpdating = true;
int t = data.r - delta; 3rrr24249. data.r = t < 0 ? (byte)0 : (byte)t;
} 3r3722813. 3r3721414.  3r37249. Now UpdateCellData ready and visibility transitions are true.
 3r37249.
 3r37249.

3r34878. 3r34814.
0.

 3r37249. [i] Transitions of visibility.

 3r37249.
 3r37249. 3r337373. Protection against duplicate transition elements
 3r37249. Transitions work, but there may be duplicate items in the list. This is obtained if the visibility state of the cell changes while it is still in transition. For example, when a cell is visible while a unit is moving, only for a short time.
 3r37249.
 3r37249. As a result of the appearance of duplicate elements, the cell transition is updated several times per frame, which leads to faster transitions and extra work. We can prevent this by checking before adding a cell whether it is already in the list. However, search the list for each call 3r-3r19198. RefreshVisibility
costly, especially when the transition of a set of cells. Instead, let's use another yet unused channel to indicate whether a cell is in the process of transition, for example, value B. When adding a cell to the list, we will assign it a value of 25? and add to the list only those cells whose value is not equal to 255.
 3r37249.
 3r37249.
    public void RefreshVisibility (HexCell cell) {
int index = cell.Index; 3r37249. if (ImmediateMode) {
cellTextureData[index].r = cell.IsVisible? (byte) 255: (byte) 0; 3r37249. cellTextureData[index].g = cell.IsExplored? (byte) 255: (byte) 0; 3r37249.}
else if (cellTextureData[index].b! = 255) {
cellTextureData[index].b = 255; 3r37249. transitioningCells.Add (cell); 3r37249.}
enabled = true; 3r37249.}

 3r37249. For this to work, we need to reset the value of B after the completion of the cell transition.
 3r37249.
 3r37249.
    bool UpdateCellData (HexCell cell, int delta) {

3r37249. if (! stillUpdating) {
data.b = 0; 3r37249.}
cellTextureData[index]= data; 3r37249. return stillUpdating; 3r37249.}

 3r37249.

3r34878. 3r34879.
0.

 3r37249. Transitions without duplicates.
 3r37249.
 3r37249. 3r337373. Instantly loading visibility
 3r37249. Visibility changes are now gradual, even when loading a map. This is illogical, because the map describes the state in which the cells are already visible, so the transition is irrelevant here. In addition, the execution of transitions for a set of visible cells of a large map may slow down the game after loading. Therefore, before loading cells and troops, let's switch to HexGrid.Load in the mode of instant transitions.
 3r37249.
 3r37249.
    public void Load (BinaryReader reader, int header) {

3r37249. cellShaderData.ImmediateMode = true; 3r37249. 3r37249. for (int i = 0; i 3r3–35172. cells.Load (reader, header);
}
 3r37249. So we redefine the initial setting of the instant transition mode, whatever it is. Perhaps it is already disabled, or made configuration option, so remember the original mode and will switch to it after the completion of the work.
 3r37249.
 3r37249.
    public void Load (BinaryReader reader, int header) {

3r37249. bool originalImmediateMode = cellShaderData.ImmediateMode; 3r37249. cellShaderData.ImmediateMode = true; 3r37249. 3r37249. …
3r37249. cellShaderData.ImmediateMode = originalImmediateMode; 3r37249.}

 3r37249. 3r34936. unitypackage
 3r37249.
 3r37249. 3r370767. The field of view is dependent on the height of
 3r37249. For the time being we used a constant field of view equal to three for all units, but in reality it is more complicated. In general, we cannot see an object for two reasons: either we are hampered by some kind of obstacle, or the object is too small or far. In our game we implement only the limitation of the scope.
 3r37249.
 3r37249. We cannot see what is on the opposite side of the Earth, because the planet is obscuring our view. We can only see to the horizon. Since the planet can be approximately considered as a sphere, the higher the viewpoint, the more surface we can see, that is, the horizon depends on the height.
 3r37249.
 3r37249. 3r3727226.
 3r37249. [i] The horizon depends on the height of the viewpoint.

 3r37249.
 3r37249. The limited scope of our units imitates the horizon effect created by the curvature of the Earth. The range of their review depends on the size of the planet and the scale of the map. At least this is the logical explanation. But The main reason for reducing visibility is gameplay, a limitation called fog of war. However, understanding the physics underlying the field of view, we can conclude that a high point of view should have strategic value, because it postpones the horizon and allows you to look beyond lower obstacles. But so far we have not implemented it.
 3r37249.
 3r37249. 3r337373. Height for review
 3r37249. To take altitude into account when determining the scope, we need to know altitude. This will be the usual height or level of water, depending on whether the land cell or the water cell. Let's add for this in HexCell property.
 3r37249.
 3r37249.
    public int ViewElevation {
get {
return elevation> = waterLevel? elevation: waterLevel; 3r37249.}
}

 3r37249. But if height affects the field of visibility, then when the cell's height changes, the situation with visibility can also change. Since the cell has blocked or now blocks the scope of several units, it is not so easy to determine what needs to be changed. The cell itself will not be able to solve this problem, so let it report a change in the situation 3r -37198. HexCellShaderData
. Suppose, HexCellShaderData There is for this method ViewElevationchanged . We will call it when we specify HexCell.Elevation if necessary.
 3r37249.
 3r37249.
    public int Elevation {
get {
return elevation; 3r37249.}
set {
if (elevation == value) {
return; 3r37249.}
int originalViewElevation = ViewElevation; 3r37249. elevation = value; 3r37249. if (ViewElevation! = originalViewElevation) {
ShaderData.ViewElevationchanged (); 3r37249.}

}
}

 3r37249. The same applies to WaterLevel .
 3r37249.
 3r37249.
    public int WaterLevel {
get {
return waterLevel; 3r37249.}
set {
if (waterLevel == value) {
return; 3r37249.}
int originalViewElevation = ViewElevation; 3r37249. waterLevel = value; 3r37249. if (ViewElevation! = originalViewElevation) {
ShaderData.ViewElevationchanged (); 3r37249.}
ValidateRivers (); 3r37249. Refresh (); 3r37249.}
}

 3r37249. 3r337373. Drop visibility
 3r37249. Now we need to create the method HexCellShaderData.ViewElevationchanged . Determining the change in the overall visibility situation is a difficult task, especially when simultaneously changing several cells. Therefore, we will not invent any tricks, but simply plan to reset the visibility of all the cells. Add a boolean field to see if it needs to be done. Inside the method, we will simply assign it a true and include the component. Regardless of the number of simultaneously changing cells, this will result in a single reset of values.
 3r37249.
 3r37249.
    bool needsVisibilityReset; 3r37249. 3r37249. …
3r37249. public void ViewElevationchanged () {
needsVisibilityReset = true; 3r37249. enabled = true; 3r37249.}

 3r37249. To reset the visibility of all cells, you need to have access to them, which is at 3r-37198. HexCellShaderData
not. So let's delegate this responsibility to HexGrid . For this you need to add in HexCellShaderData property that will allow to reference the grid. Then we can use it in LateUpdate to request a reset.
 3r37249.
 3r37249.
    public HexGrid Grid {get; set;}
3r37249. …
3r37249. void LateUpdate () {
if (needsVisibilityReset) {
needsVisibilityReset = false; 3r37249. Grid.ResetVisibility (); 3r37249.}
3r37249. …
}

 3r37249. We turn to HexGrid : set the link to the grid in HexGrid.Awake after creating the shader data.
 3r37249.
 3r37249.
    void Awake () {
HexMetrics.noiseSource = noiseSource; 3r37249. HexMetrics.InitializeHashGrid (seed); 3r37249. HexUnit.unitPrefab = unitPrefab; 3r37249. cellShaderData = gameObject.AddComponent (); 3r37249. cellShaderData.Grid = this; 3r37249. CreateMap (cellCountX, cellCountZ); 3r37249.}

 3r37249. 3r337378. HexGrid
should also get the method ResetVisibility to reset all cells. Just make it loop around all the cells and delegate the execution to itself.
 3r37249.
 3r37249.
    public void ResetVisibility () {
for (int i = 0; i 3r317172. cells.ResetVisibility ();
}
} 3r3727213. 3r37214. 3r37235.  3r37249. Now we need to add in HexCell method ResetVisibilty . It will simply reset the visibility and launch the visibility update. This should be done when the visibility of the cell is greater than zero.
 3r37249.
 3r37249.
    public void ResetVisibility () {
if (visibility> 0) {
visibility = 0; 3r37249. ShaderData.RefreshVisibility (this); 3r37249.}
}

 3r37249. After resetting all visibility data HexGrid.ResetVisibility must again apply visibility to all units, for which he needs to know the scope of each unit. Suppose you can get it using the property. VisionRange .
 3r37249.
 3r37249.
    public void ResetVisibility () {
for (int i = 0; i 3r3–35172. cells[i].ResetVisibility ();
}
for (int i = 0; i 3r3–35175. HexUnit unit = units[i]; 3r337379. raft. VisionRange); 3r324249.}
} 3r3727213. 3r37214. 3r3727235.  3r37249. To make this work, we will perform a refactoring-rename HexUnit.visionRange in 3r37198. HexUnit.VisionRange
and turn it into a property. For now, it will receive a constant value of ? but this will change in the future.
 3r37249.
 3r37249.
    public int VisionRange {
get {
return 3; 3r37249.}
}

 3r37249. Due to this, the visibility data will be reset and remain correct after changing the height of the cell view. But it is likely that we will change the rules for defining the scope and launch recompilation in Play mode. To make the scope change independently, let's start the reset 3r337378. HexGrid.OnEnable
when recompilation is detected.
 3r37249.
 3r37249.
    void OnEnable () {
if (! HexMetrics.noiseSource) {

ResetVisibility (); 3r37249.}
}

 3r37249. Now you can change the scope code and see the results while remaining in Play mode.
 3r37249.
 3r37249. 3r337373. Expand the horizon
 3r37249. The calculation of the scope is determined HexGrid.GetVisibleCells . In order for the field of view to be affected by height, we can simply use the viewing height of fromCell temporarily overriding the transferred area. So we can easily check if it works.
 3r37249.
 3r37249.
    List    GetVisibleCells (HexCell fromCell, int range) {

3r37249. range = fromCell.ViewElevation; 3r37249. fromCell.SearchPhase = searchFrontierPhase; 3r37249. fromCell.Distance = 0; 3r37249. searchFrontier.Enqueue (fromCell); 3r37249. …
}

 3r37249. 3r3727226. 3r? 35250.
 3r37249. [i] Use altitude as a scope.

 3r37249.
 3r37249. 3r337373. Visibility barriers
 3r37249. Applying the viewing height as an area of ​​view correctly works only when all other cells are at zero height. But if all the cells have the same height as the viewpoint, then the scope should be zero. In addition, cells with high heights should block the visibility of low cells behind them. So far, none of this has been implemented.
 3r37249.
 3r37249. 3r3727226. 3r335269.
 3r37249. The scope is not obstructed.
 3r37249.
 3r37249. The most correct way to determine the scope would be to test by emitting rays, but it would quickly become expensive and still produce strange results. We need a quick solution that creates fairly good results that are not required to be perfect. In addition, it is important that the rules for determining the scope are simple, intuitive and predictable for players.
 3r37249.
 3r37249. Our solution will be the following: when determining the visibility of a cell, we will add the height of the view of the neighboring cell to the covered distance. In fact, it reduces the scope when we look at these cells, and if they are skipped, it will not allow us to reach the cells behind them.
 3r37249.
 3r37249.
    int distance = current.Distance + 1; 3r37249. if (distance + neighbor.ViewElevation> range) {
continue; 3r37249.}

 3r37249. 3r3727226. 3r? 35297.
 3r37249. High cells block the view.
 3r37249.
 3r37249. 3r3r6464. 3r36465. Shouldn't we see tall cells in the distance? [/b]
As in the case of the mountain range, we can see their slopes adjacent to the cells that we see. But we cannot see the mountains from above, therefore we cannot see the cells themselves.

 3r37249. 3r337373. Do not look behind the corners
 3r37249. Now it seems that high cells block visibility to low ones, however, sometimes the field of view penetrates through them, although it seems that this should not be the case. This happens because the search algorithm still finds the path to these cells, bypassing the blocking cells. As a result, it looks as if our scope can go around obstacles. To avoid this, we need to ensure that only the shortest paths are taken into account when determining the visibility of a cell. This can be done by dropping paths that are longer than necessary.
 3r37249.
 3r37249.
    HexCoordinates fromCoordinates = fromCell.coordinates; 3r37249. while (searchFrontier.Count> 0) {

3r37249. for (HexDirection d = HexDirection.NE; d <= HexDirection.NW; d++) {
int distance = current.Distance + 1;
if (distance + neighbor.ViewElevation> range ||
distance> fromCoordinates.DistanceTo (x)) distance
 3r37249. We use only the shortest paths.
 3r37249.
 3r37249. So we corrected most obviously erroneous cases. For nearby cells, this works well, because there are only shortest paths to them. Farther cells have more options for paths, so over long distances there may still be a bending around the visibility. This will not be a problem if the areas of visibility remain small, and the differences in neighboring heights are not too large.
 3r37249.
 3r37249. And finally, instead of replacing nThe rendered area of ​​the review, we add to it the height of the review. The detachment's own field of view denotes its height, height of flight, or reconnaissance capabilities.
 3r37249.
 3r37249.
    range + = fromCell.ViewElevation;    

 3r37249. 3r3727226. 3r? 35371.
 3r37249. A full-field view at a low point of view.
 3r37249.
 3r37249. That is, the final rules of visibility refer to the vision when moving along the shortest path to the field of view, taking into account the difference in the height of the cells relative to the point of view. When a cell is out of scope, it blocks all paths through it. As a result, high observational points, the view from which does not interfere with anything, become strategically valuable.
 3r37249.
 3r37249. 3r3r6464. 3r36465. What about obstructing the visibility of objects? [/b]
I decided that the objects of relief will not affect the visibility, but it is possible. for example, make thick forests or walls add height to a cell. In this case, the player will be more difficult to evaluate the rules for determining the scope.

 3r37249. 3r? 35393. unitypackage
 3r37249.
 3r37249. 3r370767. Cells that cannot be examined
 3r37249. The last problem with visibility concerns the edges of the map. The relief ends abruptly and without transitions, because the cells on the edge have no neighbors.
 3r37249.
 3r37249. 3r3727226. 3r340408.
 3r37249. Marked edge of the map.
 3r37249.
 3r37249. Ideally, the visual display of unexplored areas and edges of the map should be the same. We can achieve this by adding special cases in the triangulation of edges, when they have no neighbors, but this will require additional logic, and we will have to work with missing cells. Therefore, this solution is not trivial. An alternative approach is to force the boundary cells of the map to be unexamined, even if they are within the scope of the detachment. This approach is much simpler, so let's use it. It also allows you to mark as unexamined and other cells, making it easier to achieve the creation of uneven edges of the map. In addition, the hidden cells on the edges allow you to create roads and rivers entering and outgoing from the map of rivers and roads, because their end points will be out of scope. Also using this solution you can add incoming and outgoing units from the map.
 3r37249.
 3r37249. 3r337373. Mark cells as investigated
 3r37249. To indicate that a cell can be examined, add HexCell property 3r319378. Explorable
.
 3r37249.
 3r37249.
    public bool Explorable {get; set;}    

 3r37249. Now the cell can be visible if it is examined, so we change the property IsVisible to accommodate this.
 3r37249.
 3r37249.
    public bool IsVisible {
get {
return visibility> 0 && Explorable; 3r37249.}
}

 3r37249. The same applies to IsExplored . However, for this we investigated the standard property. We need to convert it to an explicit property in order to be able to change the logic of its getter.
 3r37249.
 3r37249.
    public bool IsExplored {
get {
return explored && Explorable; 3r37249.}
private set {
explored = value; 3r37249.}
}
3r37249. …
3r37249. bool explored;

 3r37249. 3r337373. Hide the edge of the map
 3r37249. You can hide the edge of a rectangular map in method HexGrid. CreateCell . Cells that are not on the edge are examined, all others are unexplored.
 3r37249.
 3r37249.
    void CreateCell (int x, int z, int i) {

3r37249. HexCell cell = cells= Instantiate (cellPrefab); 3r37249. cell.transform.localPosition = position; 3r37249. cell.coordinates = HexCoordinates.FromOffsetCoordinates (x, z); 3r37249. cell.Index = i; 3r37249. cell.ShaderData = cellShaderData; 3r37249. 3r37249. cell.Explorable =
x> 0 && z> 0 && x 3r3-35502. 3r37249. …
}

 3r37249. Now the cards are darkened at the edges, hiding behind them huge unexplored spaces. As a result, the size of the studied map area decreases in each dimension by two.
 3r37249.
 3r37249. 3r3727226. 3r335514.
 3r37249. [i] Uncharted card edge.

 3r37249.
 3r37249. 3r3r6464. 3r36465. Is it possible to make the exploratory state editable? [/b]
Yes, it is possible, and will give us maximum versatility. You will also have to add this information when saving data.

 3r37249. 3r337373. Uninvestigated cells hinder visibility
 3r37249. Finally, if a cell cannot be explored, then it should interfere with visibility. Change HexGrid.GetVisibleCells to accommodate this.
 3r37249.
 3r37249.
    if (
neighbor == null ||
neighbor.SearchPhase> searchFrontierPhase ||
! neighbor.Explorable
) {
continue; 3r37249.}

 3r37249. 3r? 35555. unitypackage
 3r37249.
 3r37249. 3r335561. Part 23: we generate land 3r335562.
 3r37249. 3r335565.  3r37249. 3r335579. We fill in new maps with generated landscapes. 3r? 35580.  3r37249. 3r335579. Raise areas of land over water, some are flooded. 3r? 35580.  3r37249. 3r335579. We control the amount of land created, its height and irregularity. 3r? 35580.  3r37249. 3r335579. Add support for various configuration options for creating variable maps. 3r? 35580.  3r37249. 3r335579. Make it so that you can generate the same map again. 3r? 35580.  3r37249. 3r335582.
 3r37249. This part of the tutorial will be the beginning of a series on procedural map generation.
 3r37249.
 3r37249. This part is created in Unity ???.
 3r37249.
 3r37249. 3r3727226. 3r? 35594.
 3r37249. One of the many generated maps.
 3r37249.
 3r37249. 3r370767. Generation of cards
 3r37249. Although we can create any map, it takes a lot of time. It would be convenient if the application could help the designer by generating maps for him, which he can then change to his taste. You can take one more step and completely get rid of creating the design manually, completely transferring the responsibility of generating the finished card to the application. Thanks to this, it will be possible to play the game every time with a new card and each game session will be different. To make all this possible, we must create an algorithm for generating maps.
 3r37249.
 3r37249. The type of generation algorithm required depends on the type of cards required. There is no one right approach, you always have to find a compromise between plausibility and playability.
 3r37249.
 3r37249. For a card to be believable, it must appear to the player to be quite possible and real. This does not mean that the map should look like a part of our planet. It may be another planet or a completely different reality. But if it should denote the relief of the Earth, it must at least partially remind her.
 3r37249.
 3r37249. Playability is related to how the cards match the gameplay. Sometimes it comes into conflict with the plausibility. For example, although the mountain ranges may look beautiful, they at the same time severely restrict movement and survey of units. If this is undesirable, you will have to do without mountains, which will reduce the likelihood and limit the expressiveness of the game. Or we can save the mountains, but reduce their impact on the gameplay, which also can reduce the likelihood.
 3r37249.
 3r37249. In addition, you need to consider feasibility. For example, you can create a very realistic earth-like planet by simulating tectonic plates, erosion, rain, volcanic eruptions, the impact of meteorites and the moon, and so on. But the development of such a system will take a long time. In addition, the generation of such a planet may take a long time, and players will not want to wait a few minutes before starting a new game. That is, simulation is a powerful tool, but it comes at a price.
 3r37249.
 3r37249. In games, tradeoffs between plausibility, playability and feasibility are often used. Sometimes these compromises are invisible and seem completely normal, and sometimes they look random, inconstant or chaotic, depending on the decisions made during the development process. This applies not only to the generation of cards, but when developing a procedural generator of cards you need to treat this with particular attention. You can spend a lot of time creating an algorithm that generates beautiful cards that are useless for the game you are creating.
 3r37249.
 3r37249. In this series of tutorials, we will create an earth-like relief. It should look interesting, with large variability and the absence of large homogeneous areas. The scale of the relief will be large, the maps will cover one or more continents, areas of the oceans, or even the whole planet. We need control over geography, including the land massifs, climate, the number of regions and uneven terrain. In this part we will lay the foundation for the creation of sushi.
 3r37249.
 3r37249. 3r337373. Starting in editing mode.
 3r37249. We will focus on the map, and not on the gameplay, so it will be more convenient to launch the application in edit mode. Thanks to this, we can immediately see the cards. Therefore, we change HexMapEditor.Awake by setting the editing mode to true and turning on the shader keyword of this mode.
 3r37249.
 3r37249.
    void Awake () {
terrainMaterial.DisableKeyword ("GRID_ON"); 3r37249. Shader.EnableKeyword ("HEX_MAP_EDIT_MODE"); 3r37249. SetEditMode (true); 3r37249.}

 3r37249. 3r337373. Card Generator
 3r37249. Since quite a lot of code is needed to generate procedural maps, we will not add it directly to 3r319378. HexGrid
. Instead, we will create a new component HexMapGenerator , and 3r337378. HexGrid about him will not know. This will simplify the transition to another algorithm, if we need it.
 3r37249.
 3r37249. The generator needs a reference to the grid, so we will add a common field for it. In addition, add the general method GenerateMap Who will work on the algorithm. Give it the dimensions of the map as parameters, and then force it to use them to create a new empty map.
 3r37249.
 3r37249.
    using System.Collections.Generic; 3r37249. using UnityEngine; 3r37249. 3r37249. public class HexMapGenerator: MonoBehavior {
3r37249. public HexGrid grid; 3r37249. 3r37249. public void GenerateMap (int x, int z) {
grid.CreateMap (x, z); 3r37249.}
}

 3r37249. Add an object with the component to the scene. HexMapGenerator and connect it to the grid.
 3r37249.
 3r37249. 3r3727226. 3r?65699.
 3r37249. Map Generator Object.
 3r37249.
 3r37249. 3r337373. Change the menu of the new card
 3r37249. We will change NewMapMenu so that it can generate maps, not just create empty ones. We will manage its functionality through the boolean field generateMaps 3r3721313. which defaults to true . Let's create a common method for defining this field, as we did for switching options 3r -37198. HexMapEditor . Add a corresponding switch to the menu and connect it with the method.
 3r37249.
 3r37249.
    bool generateMaps = true; 3r37249. 3r37249. public void ToggleMapGeneration (bool toggle) {
generateMaps = toggle; 3r37249.}

 3r37249. 3r3727226. 3r? 35736.
 3r37249. Menu new card with a switch.
 3r37249.
 3r37249. Give the menu a link to the map generator. Then we force it, if necessary, to call the method GenerateMap generator, and not just perform CreateMap mesh.
 3r37249.
 3r37249.
    public HexMapGenerator mapGenerator; 3r37249. 3r37249. …
3r37249. void CreateMap (int x, int z) {
if (generateMaps) {
mapGenerator.GenerateMap (x, z); 3r37249.}
else {
hexGrid.CreateMap (x, z); 3r37249.}
HexMapCamera.ValidatePosition (); 3r37249. Close (); 3r37249.}

 3r37249. 3r3727226. 3r? 35774.
 3r37249. Connect to the generator.
 3r37249.
 3r37249. 3r337373. Access to cells
 3r37249. In order for the generator to work, it needs access to the cells. Do 3r337378. HexGrid There are already common methods Getcell which require either a position vector or a hexagon coordinate. The generator does not need to work with either one or the other, therefore we will add two convenient methods HexGrid.GetCell which will work with offset coordinates or cell index.
 3r37249.
 3r37249.
    public HexCell GetCell (int xOffset, int zOffset) {
return cells[xOffset + zOffset * cellCountX]; 3r37249.}
3r37249. public HexCell GetCell (int cellIndex) {
return cells[cellIndex]; 3r37249.}

 3r37249. Now HexMapGenerator can receive cells directly. For example, after creating a new map, using the coordinates of displacements, it can specify grass as the relief of the middle column of cells.
 3r37249.
 3r37249.
    public void GenerateMap (int x, int z) {
grid.CreateMap (x, z); 3r37249. for (int i = 0; i < z; i++) {
grid.GetCell (x /? i) .TerrainTypeIndex = 1;
}
} 3r37213. 3r3727214. 3r3r727235.  3r37249. 3r3727226. 3r? 35828.
 3r37249. A column of grass on a small map.
 3r37249.
 3r37249. 3r? 35838. unitypackage
 3r37249.
 3r37249. 3r370767. Making sushi
 3r37249. When generating a map, we start completely without sushi. One can imagine that the whole world is flooded with one huge ocean. Land is created when part of the ocean floor is pushed upward so that it rises above the water. We need to decide how much sushi should be created in such a way, where it will appear and what form it will have.
 3r37249.
 3r37249. 3r337373. Raise the terrain
 3r37249. Let's start small - we will raise one piece of land above the water. Create a method for this, RaiseTerrain with parameter to control the size of the plot. Call this method in GenerateMap by replacing the previous test code. Let's start with a small plot of land consisting of seven cells.
 3r37249.
 3r37249.
    public void GenerateMap (int x, int z) {
grid.CreateMap (x, z); 3r37249. //for (int i = 0; i < z; i++) {
//grid.GetCell (x /? i) .TerrainTypeIndex = 1;
//}
RaiseTerrain (7);
}
199. 199. 199. ???. (int chunkSize) {}

 3r37249. For the time being, we simply use the type of relief “grass” to denote raised land, while the initial relief “sand” refers to the ocean. Let's make RaiseTerrain take a random cell and change the type of its relief, until we get the right amount of land.
 3r37249.
 3r37249. To get a random cell, add the method GetRandomCell which determines the random cell index and gets the corresponding cell from the grid.
 3r37249.
 3r37249.
    void RaiseTerrain (int chunkSize) {
for (int i = 0; i < chunkSize; i++) {
GetRandomCell (). TerrainTypeIndex = 1;
}
}
HexCell GetRandomCell () {
by using this account for this file and time period by using this account for this file and time code by using the default settings that apply for your account; grid.cellCountZ));
} 3r3727213. 3r3727214. 3r37235.  3r37249. 3r3727226. 3r???.
 3r37249. Seven random sushi cells.
 3r37249.
 3r37249. Since in the end we may need a lot of random cells or cycle around all the cells several times, let's keep track of the number of cells in 3r319378 itself. HexMapGenerator
.
 3r37249.
 3r37249.
    int cellCount; 3r37249. 3r37249. public void GenerateMap (int x, int z) {
cellCount = x * z; 3r37249. …
}
3r37249. …
3r37249. HexCell GetRandomCell () {
return grid.GetCell (Random.Range (? cellCount)); 3r37249.}

 3r37249. 3r337373. Creating one site
 3r37249. So far, we turn seven random cells into land, and they can be anywhere. Most likely they do not form a single piece of land. In addition, we can select the same cells several times, so we will have a smaller volume of land. To solve both problems, without limitation, we will select only the first cell. After that, we need to select only those cells that are next to the ones selected earlier. These restrictions are similar to the limitations of the search path, so here we use the same approach.
 3r37249.
 3r37249. Add HexMapGenerator own property and phase counter of the search border, as it was in HexGrid .
 3r37249.
 3r37249.
    HexCellPriorityQueue searchFrontier; 3r37249. 3r37249. int searchFrontierPhase;    

 3r37249. We check that the priority queue exists before we need it.
 3r37249.
 3r37249.
    public void GenerateMap (int x, int z) {
cellCount = x * z; 3r37249. grid.CreateMap (x, z); 3r37249. if (searchFrontier == null) {
searchFrontier = new HexCellPriorityQueue (); 3r37249.}
RaiseTerrain (7); 3r37249.}

 3r37249. After creating a new map, the search boundary for all cells is zero. But if we are going to look for cells in the process of generating a map, then we will increase their search boundary in this process. If we perform many search operations, they may be in front of the search boundary phase recorded 3r-37198. HexGrid
. This may disrupt the search for a unit path. To avoid this, at the end of the card generation process, we will reset the search phase of all cells to zero.
 3r37249.
 3r37249.
    RaiseTerrain (7); 3r37249. for (int i = 0; i  < cellCount; i++) {
grid.GetCell (i) .SearchPhase = 0;
} 3r3727213.
3r37235.  3r37249. Now RaiseTerrain must search for the corresponding cells, and not select them randomly. This process is very similar to the search method in HexGrid . However, we will not visit cells more than once, so it will be enough for us to increase the phase of the search boundary by 1 instead of 2. Then we initialize the border with the first cell, which is chosen randomly. As usual, in addition to setting its search phase, we assign a zero value to its distance and heuristics.
 3r37249.
 3r37249.
    void RaiseTerrain (int chunkSize) {
//for (int i = 0; i < chunkSize; i++) {
//GetRandomCell (). TerrainType your search for ancestry (a)) search for ancestry (3r32437.) //}
searchFrontierPhase + = 1;
HexCell firstCell = GetRandomCell = 3R3r37249. HexCell firstCell = GetRandomCell = 3; ;
FirstCell.Distance = 0;
FirstCell.SearchHeuristic = 0;
SearchFrontier.Enqueue (firstCell);
} 3rr???r37214.
} 3r3722713.  3r37249. After this, the search cycle will be mostly familiar to us. In addition, in order to continue the search until the border is empty, we need to stop when the fragment reaches the desired size, so we’ll track it. At each iteration, we will retrieve the next cell from the queue, set its relief type, increase its size, and then bypass the neighbors of this cell. All neighbors are simply added to the border if they have not been added yet. We do not need to make any changes or comparisons. After completion, you need to clear the border.
 3r37249.
 3r37249.
    searchFrontier.Enqueue (firstCell); 3r37249. 3r37249. int size = 0; 3r37249. while (size  < chunkSize && searchFrontier.Count >  0) {
HexCell current = searchFrontier.Dequeue (); 3r37249. current.TerrainTypeIndex = 1; 3r37249. size + = 1; 3r37249. 3r37249. for (HexDirection d = HexDirection.NE; d <= HexDirection.NW; d++) {
HexCell neighbor = current.GetNeighbor (d);
if (neighbor & r neighbor && neighbor.SearchPhase < searchFrontierPhase) {
neighbor.SearchPhase = searchFrontierPhase; rr5a3r36084. neighbor r3r36084. search.rearchPhase = search for rr36084. neighbor.SearchPhase = search) .SearchHeuristic = 0;
SearchFrontier.Enqueue (neighbor);
}
}
}
SearchFrontier.Clear ();
3r372314.  3r37249. 3r3727226. 3r36048.
 3r37249. Line of cells.
 3r37249.
 3r37249. We got a single plot of the right size. It will be smaller only if there are not enough cells. Due to the way the border is filled, the plot always consists of a line going to the north-west. It changes direction only when it reaches the edge of the map.
 3r37249.
 3r37249. 3r337373. We connect cells
 3r37249. Land plots rarely resemble lines, and if they do, they are not always oriented in the same way. To change the shape of the plot, we need to change the priorities of the cells. The first random cell can be used as the center of the plot. Then the distance to all other cells will be relative to this point. So we will give a higher priority to cells that are closer to the center, thanks to which the plot will grow not as a line, but around the center.
 3r37249.
 3r37249.
    searchFrontier.Enqueue (firstCell); 3r37249. HexCoordinates center = firstCell.coordinates; 3r37249. 3r37249. int size = 0; 3r37249. while (size  < chunkSize && searchFrontier.Count >  0) {
HexCell current = searchFrontier.Dequeue (); 3r37249. current.TerrainTypeIndex = 1; 3r37249. size + = 1; 3r37249. 3r37249. for (HexDirection d = HexDirection.NE; d <= HexDirection.NW; d++) {
HexCell neighbor = current.GetNeighbor (d);
if (neighbor && neighbor.SearchPhase < searchFrontierPhase) {
neighbor.SearchPhase = searchFrontierPhase; rr-rmr36084. rrr606084. rr36084. neighbor.SearchPhase = search) (center);
neighbor.SearchHeuristic = 0;
searchFrontier.Enqueue (neighbor);
} 3r32437.}
} 3r37213. 3r37214.  3r37249. 3r3727226. 3r36096.
 3r37249. The accumulation of cells.
 3r37249.
 3r37249. And in fact, now our seven cells are beautifully packed in a compact hexagonal area, if the central cell is not on the edge of the map. Let's try now to use the size of the plot 30.
 3r37249.
 3r37249.
    RaiseTerrain (30);    

 3r37249. 3r3727226. 3r36117.
 3r37249. Sushi in 30 cells.
 3r37249.
 3r37249. We again obtained the same shape, although it was not enough to obtain the correct hexagon of the cells. Since the radius of the section is larger, it is more likely to be close to the edge of the map, which will force it to take another shape.
 3r37249.
 3r37249. 3r337373. Randomization of landform
 3r37249. We do not want all areas to look the same, so we will slightly change the priorities of the cells. Every time we add a neighboring cell to the border, if the next number is 3r-37198. Random.value
less than a certain threshold value, then the heuristics of this cell becomes not ? but 1. Let's use the value 0.5 as the threshold, that is, with the greatest probability it will affect half of the cells.
 3r37249.
 3r37249.
    neighbor.Distance = neighbor.coordinates.DistanceTo (center); 3r37249. neighbor.SearchHeuristic = Random.value  < 0.5f ? 1: 0;
searchFrontier.Enqueue (neighbor);

 3r37249. 3r3727226. 3r36150.
 3r37249. Distorted plot.
 3r37249.
 3r37249. Increasing the cell search heuristics, we made it visit later than expected. At the same time, other cells that are one step further from the center will be visited earlier, unless they have also increased the heuristics. This means that if we increase the heuristics of all the cells by one magnitude, this will not affect the map in any way. That is, threshold 1 will have no influence, as well as threshold 0. And threshold 0.8 will be equivalent to 0.2. That is, the probability of 0.5 makes the search process the most “trembling”.
 3r37249.
 3r37249. The appropriate amount of vibration depends on the type of relief you need, so let's make it customizable. Add to the generator the general field float jitterProbability with the attribute Range limited in the range of 0–0.5. We give it a default value equal to the average of this interval, that is, ???. This will allow us to customize the generator in the Unity inspector window.
 3r37249.
 3r37249.
   [Range(0f, 0.5f)]
public float jitterProbability = ???f;

 3r37249. 3r3727226. 3r36180.
 3r37249. Probability of fluctuations
 3r37249.
 3r37249. 3r3r6464. 3r36465. Can you make it customizable in the game UI? [/b]
This is possible, and in most games this is done. In the tutorial, I will not add this parameter to the game UI, but nothing prevents you from doing this. However, as a result, the generator will have quite a few configuration options, so consider this when creating the UI. You can wait until you know all the options. At this stage, you can even choose other restrictions, different terminology and limit the options available to players.

 3r37249. Now, to decide when the heuristics should be equal to ? we use probability instead of a constant value.
 3r37249.
 3r37249.
    neighbor.SearchHeuristic =
Random.value < jitterProbability ? 1: 0;

 3r37249. We use heuristic values ​​of 0 and 1. Although larger values ​​can be used, this will greatly worsen the deformation of the sections, most likely turning them into a bunch of bands.
 3r37249.
 3r37249. 3r337373. Raise several land sites
 3r37249. We will not be limited to the generation of one land plot. For example, place the call RaiseTerrain inward cycle to get five sections.
 3r37249.
 3r37249.
    for (int i = 0; i  < 5; i++) {
RaiseTerrain (30);
}

 3r37249. 3r3727226. 3r36230.
 3r37249. Five plots of land.
 3r37249.
 3r37249. Although now we are generating five plots of 30 cells each, but we do not necessarily get exactly 150 land cells. Since each section is created separately, they do not know about each other, so they can intersect. This is normal because it can create more interesting landscapes than just a set of isolated sites.
 3r37249.
 3r37249. To increase the variability of land, we can also change the size of each section. Add two integer fields to control the minimum and maximum sizes of sections. Assign them a sufficiently large interval, for example, 20–200. I will make the standard minimum equal to 3? and the standard maximum - 100.
 3r37249.
 3r37249.
   [Range(20, 200)]3r37249. public int chunkSizeMin = 30; 3r37249. 3r37249.[Range(20, 200)]3r37249. public int chunkSizeMax = 100;    

 3r37249. 3r3727226. 3r36259.
 3r37249. The interval sizes of the plots.
 3r37249.
 3r37249. Use these fields to randomly determine the size of the lot when calling 3r37198. RaiseTerrain
.
 3r37249.
 3r37249.
    RaiseTerrain (Random.Range (chunkSizeMin, chunkSizeMax + 1));    

 3r37249. 3r3727226.
 3r37249. Five sites with random sizes on the middle map.
 3r37249.
 3r37249. 3r337373. Create enough sushi
 3r37249. While we can not particularly control the amount of sushi generated. Although we can add the option of configuring the number of sections, the sizes of the sections themselves are random and may slightly or strongly intersect. Therefore, the number of sites does not guarantee the receipt of the required amount of land on the map. Let's add an option to directly control the percentage of land, expressed as an integer. Since 100% of land or water is not very interesting, we limit it to an interval of 5–9? with a value of 50 by default.
 3r37249.
 3r37249.
   [Range(5, 95)]3r37249. public int landPercentage = 50;    

 3r37249. 3r3727226. 3r36308.
 3r37249. The percentage of land.
 3r37249.
 3r37249. To ensure the creation of the required amount of land, we just need to continue to raise the terrain until a sufficient amount is obtained. To do this, we need to control the process, which will complicate the generation of land. Therefore, let's replace the existing cycle of raising plots with a call to the new method 3r37198. CreateLand . The first thing this method does is to calculate the number of cells that should become dry. This amount will be our total sushi cell.
 3r37249.
 3r37249.
    public void GenerateMap (int x, int z) {

//for (int i = 0; i < 5; i++) {
//RaiseTerrain (Random.Range (chunkSizeMin, chunkSizeMax + 1));
//}
CreateLand ();
for (int i = 0; < cellCount; i++) {
.grang.getCell (i) .SearchPhase = 0;
}
}
void CreateLand () {
int landBudget = Mathf. .3r37235.  3r37249. 3r337378. CreateLand
will call 3r37198. RaiseTerrain until we have spent the whole amount of cells. To not exceed the amount, change RaiseTerrain so that it gets the amount as an optional parameter. After completing the work, he must return the remaining amount.
 3r37249.
 3r37249.
    //void RaiseTerrain (int chunkSize) {
int RaiseTerrain (int chunkSize, int budget) {

return budget; 3r37249.}

 3r37249. The amount should decrease each time the cell is removed from the border and turns into land. If after this the entire amount is spent, we must stop searching and complete the area. In addition, it should be done only when the current cell is not land yet.
 3r37249.
 3r37249.
    while (size  < chunkSize && searchFrontier.Count >  0) {
HexCell current = searchFrontier.Dequeue (); 3r37249. if (current.TerrainTypeIndex == 0) {
current.TerrainTypeIndex = 1; 3r37249. if (--budget == 0) {
break; 3r37249.}
}
size + = 1; 3r37249. 3r37249. …
}

 3r37249. Now CreateLand can raise land until it spends the full amount of cells.
 3r37249.
 3r37249.
    void CreateLand () {
int landBudget = Mathf.RoundToInt (cellCount * landPercentage * ???f); 3r37249. while (landBudget> 0) {
landBudget = RaiseTerrain (
Random.Range (chunkSizeMin, chunkSizeMax + 1), landBudget
); 3r37249.}
}

 3r37249. 3r3727226. 3r36405.
 3r37249. Exactly half of the map was dry.
 3r37249.
 3r37249. 3r36415. unitypackage
 3r37249.
 3r37249. 3r370767. Consider the height
 3r37249. Land is not just a flat plate bounded by the coastline. It has a varying height, containing hills, mountains, valleys, lakes, and so on. Large elevation differences exist due to the interaction of slowly moving tectonic plates. Although we will not pretend it, our land areas must somehow resemble such plates. The plots do not move, but can intersect. And we can use it.
 3r37249.
 3r37249. 3r337373. We push the land up
 3r37249. Each parcel denotes a portion of land pushed out from the bottom of the ocean. Therefore, let's constantly increase the height of the current cell in 3r319378. RaiseTerrain and see what happens.
 3r37249.
 3r37249.
    HexCell current = searchFrontier.Dequeue (); 3r37249. current.Elevation + = 1; 3r37249. if (current.TerrainTypeIndex == 0) {

}

 3r37249. 3r3727226.
 3r37249. Drying with heights.
 3r37249.
 3r37249. We got heights, but they are difficult to see. You can make them more legible if you use your own type of relief for each level of height, like geographic layering. We will do this only to make the heights more noticeable, so you can simply use the height level as an index of the relief.
 3r37249.
 3r37249. 3r3r6464. 3r36465. What happens if the height exceeds the number of terrain types? [/b]
The shader will use the latest texture from the array of textures. In our case, the last type of terrain is snow, so we get a line of snow.

 3r37249. Instead of updating the cell elevation type each time you change the height, let's create a separate method SetTerrainType , to specify all types of relief only once.
 3r37249.
 3r37249.
    void SetTerrainType () {
for (int i = 0; i < cellCount; i++) {
HexCell cell = grid.GetCell (i);
cell.TerrainTypeIndex = cell.Elevation; 3r324379.  3r37249. We will call this method after creating sushi.
 3r37249.
 3r37249.
    public void GenerateMap (int x, int z) {

CreateLand (); 3r37249. SetTerrainType (); 3r37249. …
}

 3r37249. Now RaiseTerrain may not be engaged in the type of relief, and focus on the heights. To do this, change its logic. If the new height of the current cell is ? then it has just become dry, so the sum of the cells has decreased, which may lead to the completion of the growth of the site.
 3r37249.
 3r37249.
    HexCell current = searchFrontier.Dequeue (); 3r37249. current.Elevation + = 1; 3r37249. if (current.Elevation == 1 && --budget == 0) {
break; 3r37249.}
3r37249. //if (current.TerrainTypeIndex == 0) {
//current.TerrainTypeIndex = 1; 3r37249. //if (--budget == 0) {
//break; 3r37249. //} 3r324249. //}

 3r37249. 3r3727226. 3r36528.
 3r37249. Stratification layers.
 3r37249.
 3r37249. 3r337373. Add water
 3r37249. Let's explicitly indicate which cells are water or land by setting the water level for all cells to be 1. Make this in 3r37198. GenerateMap
before making sushi.
 3r37249.
 3r37249.
    public void GenerateMap (int x, int z) {
cellCount = x * z; 3r37249. grid.CreateMap (x, z); 3r37249. if (searchFrontier == null) {
searchFrontier = new HexCellPriorityQueue (); 3r37249.}
for (int i = 0; i < cellCount; i++) {
grid.GetCell (i) .WaterLevel = 1;
}
CreateLand ();
} 3rr72723. 3r37214.  3r37249. Now, to denote the layers of land, we can use all types of relief. All underwater cells will remain sand, as well as the lowest land cells. This can be done by subtracting the water level from the height and using the value as an index of the type of relief.
 3r37249.
 3r37249.
    void SetTerrainType () {
for (int i = 0; i < cellCount; i++) {
HexCell cell = grid.GetCell (i);
if
}

 3r37249. 3r3727226. 3r36583.
 3r37249. Drying and water.
 3r37249.
 3r37249. 3r337373. Increase the water level
 3r37249. We are not limited to one water level. Let's make it customizable using a common field with an interval of 1–5 and a value of 3 by default. Use this level when initializing cells.
 3r37249.
 3r37249.
   [Range(1, 5)]3r37249. public int waterLevel = 3; 3r37249. 3r37249. …
3r37249. public void GenerateMap (int x, int z) {

for (int i = 0; i 3r36610. grid.GetCell (i) .WaterLevel = waterLevel;
}
} 3r3727213. 3r37214. 3r3r7?235.  3r37249. 3r3727226. 3r366619.
 3r37249. 3r3727226. 3r36624.
 3r37249. The water level is 3. 3r37232.
 3r37249.
 3r37249. When the water level is ? we get less land than we expected. This happens because RaiseTerrain still thinks the water level is 1. DawaThis will be fixed.
 3r37249.
 3r37249.
    HexCell current = searchFrontier.Dequeue (); 3r37249. current.Elevation + = 1; 3r37249. if (current.Elevation == waterLevel && --budget == 0) {
break; 3r37249.}

 3r37249. Using higher water levels leads to. that cells do not become dry at once. When the water level is ? the first section will still remain under water. The ocean floor has risen, but still remains under water. Dry land is formed only when crossing at least two sections. The higher the water level, the more sections must intersect to create sushi. Therefore, as the water level rises, the land becomes more chaotic. In addition, when more sites are needed, there is a higher probability that they will intersect on already existing land, which is why mountains will occur more often and flat land less often, as in the case of using smaller sites.
 3r37249.
 3r37249. 3r3727226. 3r36655.
 3r37249. 3r3727226. 3r36660.
 3r37249. 3r3727226. 3r36665.
 3r37249. 3r3727226. 3r36670.
 3r37249. [i] Water levels are 2–? land is always 50%.

 3r37249.
 3r37249. 3r36680. unitypackage
 3r37249.
 3r37249. 3r370767. Vertical movement
 3r37249. While we were raising the sites up one height at a time, we are not obliged to limit ourselves to this.
 3r37249.
 3r37249. 3r337373. Raised high stretches
 3r37249. Although each section increases the height of its cells by one level, there may be breaks. This happens when they touch the edges of two sections. This may create isolated cliffs, but long cliff lines will be rare. We can increase the frequency of their appearance by increasing the height of the section by more than one step. But this should be done only for a certain proportion of sites. If all areas go high, it will be very difficult to move across the terrain. So let's make this parameter customizable using a probability field with a default value of ???.
 3r37249.
 3r37249.
   [Range(0f, 1f)]3r37249. public float highRiseProbability = ???f;    

 3r37249. 3r3727226. 3r367310.
 3r37249. The probability of a strong rise of the cells.
 3r37249.
 3r37249. Although we can use any increase in height for elevated areas, this quickly goes out of control. The height difference 2 already creates cliffs, so this is enough. Since you can skip a height equal to the level of the water, we need to change the way that the cell is turned into land. If it was below the water level, and now it is at the same level or higher, then we created a new land cell.
 3r37249.
 3r37249.
    int rise = Random.value  < highRiseProbability ? 2 : 1;
int size = 0; 3r37249. while (size < chunkSize && searchFrontier.Count > 0) {
HexCell current = searchFrontier.Dequeue (); 3r37249. int originalElevation = current.Elevation; 3r37249. current.Elevation = originalElevation + rise; 3r37249. if (
originalElevation < waterLevel &&
current.Elevation> = waterLevel && --budget == 0
) {
break; 3r37249.}
size + = 1; 3r37249. 3r37249. …
}

 3r37249. 3r3727226. 3r36747.
 3r37249. 3r3727226. 3r36752.
 3r37249. 3r3727226. 3r36757.
 3r37249. 3r3727226. 3r36762.
 3r37249. The probabilities of a strong increase in height are 0.2? 0.5? ??? and 1.
 3r37249.
 3r37249. 3r337373. Drop the land
 3r37249. Land does not always rise, sometimes it falls. When the land falls low enough, it is filled with water and it is lost. While we are not doing this. Since we only push the areas up, the land usually looks like a set of fairly round areas mixed up with each other. If we sometimes lower the plot down, we get more varying forms.
 3r37249.
 3r37249. 3r3727226. 3r367381.
 3r37249. Large map without sunken sushi.
 3r37249.
 3r37249. We can control the frequency of descending land using another probability field. Since lowering can destroy land, the probability of lowering should always be lower than the probability of elevation. Otherwise, it may take a very long time to get the desired land percentage. So let's use the maximum probability of dropping 0.4 with a default value of 0.2.
 3r37249.
 3r37249.
   [Range(0f, 0.4f)]3r37249. public float sinkProbability = 0.2f;    

 3r37249. 3r3727226. 3r380803.
 3r37249. The probability of lowering.
 3r37249.
 3r37249. Lowering the plot is like lifting, with some differences. Therefore, we duplicate the method. RaiseTerrain and change its name to SinkTerrain . Instead of determining the magnitude of the lift, we need a magnitude of the lowering, which can use the same logic. At the same time, comparisons to check whether we passed through the surface of the water, you need to turn. In addition, when lowering the relief, we are not limited to the sum of the cells. Instead, each lost land cell returns the amount spent on it, so we increase it and continue to work.
 3r37249.
 3r37249.
    int SinkTerrain (int chunkSize, int budget) {

3r37249. int sink = Random.value < highRiseProbability ? 2 : 1;
int size = 0; 3r37249. while (size < chunkSize && searchFrontier.Count > 0) {
HexCell current = searchFrontier.Dequeue (); 3r37249. int originalElevation = current.Elevation; 3r37249. current.Elevation = originalElevation - sink; 3r37249. if (
originalElevation> = waterLevel &&
current.Elevation < waterLevel
//&& --budget == 0
) {
//break; 3r37249. budget + = 1; 3r37249.}
size + = 1; 3r37249. 3r37249. …
}
searchFrontier.Clear (); 3r37249. return budget; 3r37249.}

 3r37249. Now at each iteration inside CreateLand we must either lower or raise land depending on the probability of lowering.
 3r37249.
 3r37249.
    void CreateLand () {
int landBudget = Mathf.RoundToInt (cellCount * landPercentage * ???f); 3r37249. while (landBudget> 0) {
int chunkSize = Random.Range (chunkSizeMin, chunkSizeMax - 1); 3r37249. if (Random) 3 3;;  3r37249. 3r3727226. 3r36875.
 3r37249. 3r3727226.
0.
 3r37249. 3r3727226. 3r36885.
 3r37249. 3r3727226.
0.
 3r37249. The probability of lowering 0.? 0.? 0.3 and 0.4.
 3r37249.
 3r37249. 3r337373. We limit the height
 3r37249. At the current stage, we can potentially overlap many sections of each other, sometimes with a few increases in height, some of which can descend and then rise again. At the same time, we can create very high and sometimes very low heights, especially when a high percentage of land is needed.
 3r37249.
 3r37249. 3r3727226. 3r36909.
 3r37249. Huge heights at 90% land.
 3r37249.
 3r37249. To limit height, let's add a custom minimum and maximum. A reasonable minimum will be somewhere between −4 and ? and an acceptable maximum may be in the range of 6–10. Let the default values ​​be −2 and 8. When manually editing the map, they will be outside the allowed limit, so you can change the UI slider of the editor, or leave it as it is.
 3r37249.
 3r37249.
   [Range(-4, 0)]3r37249. public int elevationMinimum = -2; 3r37249. 3r37249.[Range(6, 10)]3r37249. public int elevationMaximum = 8;    

 3r37249. 3r3727226.
 3r37249. Minimum and maximum height.
 3r37249.
 3r37249. Now in 3r337378. RaiseTerrain
we must ensure that the height does not exceed the permissible maximum. This can be done by checking whether the current cells will turn out to be too high. If so, then we skip them without changing the height or adding their neighbors. This will lead to the fact that land areas will avoid areas that have reached a maximum height, and grow around them.
 3r37249.
 3r37249.
    HexCell current = searchFrontier.Dequeue (); 3r37249. int originalElevation = current.Elevation; 3r37249. int newElevation = originalElevation + rise; 3r37249. if (newElevation> elevationMaximum) {
continue; 3r37249.}
current.Elevation = newElevation; 3r37249. if (
originalElevation < waterLevel &&
newElevation> = waterLevel && --budget == 0
) {
break; 3r37249.}
size + = 1;

 3r37249. Do the same in SinkTerrain but for minimum height.
 3r37249.
 3r37249.
    HexCell current = searchFrontier.Dequeue (); 3r37249. int originalElevation = current.Elevation; 3r37249. int newElevation = current.Elevation - sink; 3r37249. if (newElevation  < elevationMinimum) {
continue;
}
current .elevation = newElevation; 3r3737249. if (
originalelev> = waterLevel &&
newelevan 3r36986. = 1;

 3r37249. 3r3727226. 3r36995.
 3r37249. Limited height with 90% sushi.
 3r37249.
 3r37249. 3r337373. Maintain a negative height
 3r37249. At this stage, the save and load code cannot handle negative heights, because we store the height as byte. A negative number is converted when saved to a large positive. Therefore, when saving and loading the generated map, very high ones may appear in place of the original underwater cells.
 3r37249.
 3r37249. We can add support for negative height, saving it as an integer, not byte. However, we still do not need to provide support for multiple levels of height. In addition, we can shift the stored value by adding 127. This will allow you to correctly maintain heights in the range −127–128 within one byte. Change HexCell.Save accordingly.
 3r37249.
 3r37249.
    public void Save (BinaryWriter writer) {
writer.Write ((byte) terrainTypeIndex); 3r37249. writer.Write ((byte) (elevation + 127)); 3r37249. …
}

 3r37249. Since we have changed the way we save the map data, we will increase SaveLoadMenu.mapFileVersion to 4. 3r37235.  3r37249.
 3r37249.
    const int mapFileVersion = 4;    

 3r37249. Finally, change r3r37198. HexCell.Load
so that he subtracted 127 from the heights downloaded from version 4 files.
 3r37249.
 3r37249.
    public void Load (BinaryReader reader, int header) {
terrainTypeIndex = reader.ReadByte (); 3r37249. ShaderData.RefreshTerrain (this); 3r37249. elevation = reader.ReadByte (); 3r37249. if (header> = 4) {
elevation - = 127; 3r37249.}

}

 3r37249. unitypackage
 3r37249.
 3r37249. 3r370767. Recreating the same card 3r3r70768.
 3r37249. Now we can create a variety of maps. When generating each new result will be random. Using configuration options, we can control only the characteristics of the map, but not the most accurate form. But sometimes we need to recreate the exact same map again. For example, to share with a friend a beautiful card, or startagain after editing it manually. In addition, it is useful in the development of the game, so let's add this feature.
 3r37249.
 3r37249. 3r337373. Using the Seed
 3r37249. To make the map generation process unpredictable, we use 3r319378. Random.Range
and 3r31937. Random.value . To get the same pseudo-random sequence of numbers again, you need to use the same seed value. We have already applied a similar approach before, in HexMetrics.InitializeHashGrid . It first saves the current state of the number generator initialized with a certain seed value, and then restores its original state. We can use the same approach for HexMapGenerator.GenerateMap . Once again, we can memorize the old state and restore it upon completion, so as not to interfere with anything else using 3r31937. Random .
 3r37249.
 3r37249.
    public void GenerateMap (int x, int z) {
Random.State originalRandomState = Random.state; 3r37249. …
Random.state = originalRandomState; 3r37249.}

 3r37249. Next we need to make available the seed used to generate the last map. This is done using a common integer field.
 3r37249.
 3r37249.
    public int seed;    

 3r37249. 3r3727226. 3r33737.
 3r37249. Display seed.
 3r37249.
 3r37249. Now we need the seed value to initialize Random . To create random cards, you need to use a random seed. The simplest approach is to use an arbitrary value for seed, Random.Range . So that it does not affect the initial random state, we need to do this after saving it.
 3r37249.
 3r37249.
    public void GenerateMap (int x, int z) {
Random.State originalRandomState = Random.state; 3r37249. seed = Random.Range (? int.MaxValue); 3r37249. Random.InitState (seed); 3r37249. 3r37249. …
}

 3r37249. Since after the completion we restore the random state, then if we immediately generate another card, as a result we will get the same seed value. In addition, we do not know how the initial random state was initialized. Therefore, although it can serve as an arbitrary starting point, we need something more to randomize it with each call.
 3r37249.
 3r37249. There are various ways to initialize random number generators. In this case, you can simply combine several arbitrary values ​​that vary in a wide range, that is, the probability of re-generating the same card will be low. For example, we use the bottom 32 bits of the system time, expressed in cycles, plus the current execution time of the application. We combine these values ​​using the bitwise exclusive OR operation so that the result is not very large.
 3r37249.
 3r37249.
    seed = Random.Range (? int.MaxValue); 3r37249. seed ^ = (int) System.DateTime.Now.Ticks; 3r37249. seed ^ = (int) Time.unscaledTime; 3r37249. Random.InitState (seed);    

 3r37249. The resulting number can be negative, which for the generally available value of the seed does not look very nice. We can make it strictly positive by using bitwise masking with a maximum integer value that zeroes the sign bit.
 3r37249.
 3r37249.
    seed ^ = (int) Time.unscaledTime; 3r37249. seed & = int.MaxValue; 3r37249. Random.InitState (seed);    

 3r37249. 3r337373. Repeated use of Seed
 3r37249. We still generate random maps, but now we can see which seed value was used for each of them. To recreate the same card again, we must order the generator to use the same seed value again, and not create a new one. We do this by adding a switch using a boolean field.
 3r37249.
 3r37249.
    public bool useFixedSeed;    

 3r37249. 3r3727226. 3r37188.
 3r37249. The option of using a constant seed.
 3r37249.
 3r37249. If a permanent seed is selected, then we simply skip the generation of the new seed in GenerateMap . If we do not change the seed field manually, then the same map will be generated again.
 3r37249.
 3r37249.
    Random.State originalRandomState = Random.state; 3r37249. if (! useFixedSeed) {
seed = Random.Range (? int.MaxValue); 3r37249. seed ^ = (int) System.DateTime.Now.Ticks; 3r37249. seed ^ = (int) Time.time; 3r37249. seed & = int.MaxValue; 3r37249.}
Random.InitState (seed);

 3r37249. Now we can copy the seed value of the map we like and save it somewhere to generate it in the future. Do not forget that we will get the same card only if we use exactly the same generator parameters, that is, the same card size, as well as all other configuration options. Even a small change in these probabilities can create a completely different map. Therefore, besides seed, we need to remember all the settings.
 3r37249.
 3r37249. 3r3727226.
 3r37249. 3r3727226.
 3r37249. Large cards with values ​​of seed 0 and 92939678? standard parameters.
 3r37249.
 3r37249. unitypackage
3r37249. 3r37249. 3r37242. ! function (e) {function t (t, n) {if (! (n in e)) {for (var r, a = e.document, i = a.scripts, o = i.length; o-- ;) if (-1! == i[o].src.indexOf (t)) {r = i[o]; break} if (! r) {r = a.createElement ("script"), r.type = "text /jаvascript", r.async =! ? r.defer =! ? r.src = t, r.charset = "UTF-8"; var d = function () {var e = a.getElementsByTagName ("script")[0]; e.parentNode.insertBefore (r, e)}; "[object Opera]" == e.opera? a.addEventListener? a.addEventListener ("DOMContentLoaded", d,! 1): e.attachEvent ("onload", d ): d ()}}} t ("//mediator.mail.ru/script/2820404/"""_mediator") () (); 3r37243. 3r37249.
3r37249. 3r37249. 3r37249. 3r37249.
+ 0 -

Add comment