Bagian 1-3: jala, warna, dan tinggi selBagian 4-7: gundukan, sungai, dan jalanBagian 8-11: air, bentang alam, dan bentengBagian 12-15: menyimpan dan memuat, tekstur, jarakBagian 16-19: menemukan jalan, regu pemain, animasiBagian 20-23: Kabut Perang, Penelitian Peta, Generasi ProseduralBagian 24-27: siklus air, erosi, bioma, peta silindrisBagian 8: air
- Tambahkan air ke sel.
- Triangulasi permukaan air.
- Buat selancar dengan busa.
- Campurkan air dan sungai.
Kami telah menambahkan dukungan sungai, dan di bagian ini kami akan sepenuhnya merendam sel-sel di dalam air.
Air datang.Tingkat air
Cara termudah adalah menerapkan dukungan air dengan menetapkannya pada level yang sama. Semua sel yang tingginya di bawah level ini direndam dalam air. Tetapi cara yang lebih fleksibel adalah menjaga air pada ketinggian yang berbeda, jadi mari kita buat ketinggian air berubah. Untuk ini,
HexCell
perlu memantau ketinggian airnya.
public int WaterLevel { get { return waterLevel; } set { if (waterLevel == value) { return; } waterLevel = value; Refresh(); } } int waterLevel;
Jika diinginkan, Anda dapat memastikan bahwa fitur bantuan tertentu tidak ada di bawah air. Tetapi untuk saat ini saya tidak akan melakukan ini. Hal-hal seperti jalan bawah air cocok untuk saya. Mereka dapat dianggap sebagai daerah yang baru saja terkena banjir.
Sel yang membanjiri
Sekarang kita memiliki level air, pertanyaan yang paling penting adalah apakah sel-sel itu di bawah air. Sebuah sel berada di bawah air jika permukaan airnya di atas ketinggiannya. Untuk mendapatkan informasi ini, kami akan menambahkan properti.
public bool IsUnderwater { get { return waterLevel > elevation; } }
Ini berarti bahwa ketika ketinggian dan ketinggian air sama, sel naik di atas air. Artinya, permukaan air yang sebenarnya ada di bawah ketinggian ini. Seperti halnya permukaan sungai, mari tambahkan offset yang sama -
HexMetrics.riverSurfaceElevationOffset
. Ubah namanya menjadi yang lebih umum.
Ubah
HexCell.RiverSurfaceY
sehingga menggunakan nama baru. Kemudian kami menambahkan properti serupa ke permukaan air sel yang banjir.
public float RiverSurfaceY { get { return (elevation + HexMetrics.waterElevationOffset) * HexMetrics.elevationStep; } } public float WaterSurfaceY { get { return (waterLevel + HexMetrics.waterElevationOffset) * HexMetrics.elevationStep; } }
Pengeditan air
Mengedit ketinggian air sama dengan mengubah ketinggian. Oleh karena itu,
HexMapEditor
harus memantau level air aktif dan apakah itu harus diterapkan ke sel.
int activeElevation; int activeWaterLevel; … bool applyElevation = true; bool applyWaterLevel = true;
Tambahkan metode untuk menghubungkan parameter ini dengan UI.
public void SetApplyWaterLevel (bool toggle) { applyWaterLevel = toggle; } public void SetWaterLevel (float level) { activeWaterLevel = (int)level; }
Dan tambahkan level air ke
EditCell
.
void EditCell (HexCell cell) { if (cell) { if (applyColor) { cell.Color = activeColor; } if (applyElevation) { cell.Elevation = activeElevation; } if (applyWaterLevel) { cell.WaterLevel = activeWaterLevel; } … } }
Untuk menambahkan ketinggian air ke UI, duplikat label dan penggeser ketinggian, lalu ubah. Ingatlah untuk melampirkan acara mereka ke metode yang tepat.
Slider level air.paket unityTriangulasi air
Untuk melakukan triangulasi air, kita membutuhkan jaring baru dengan material baru. Pertama, buat shader
Air , duplikat
River shader. Ubah sehingga menggunakan properti warna.
Shader "Custom/Water" { Properties { _Color ("Color", Color) = (1,1,1,1) _MainTex ("Albedo (RGB)", 2D) = "white" {} _Glossiness ("Smoothness", Range(0,1)) = 0.5 _Metallic ("Metallic", Range(0,1)) = 0.0 } SubShader { Tags { "RenderType"="Transparent" "Queue"="Transparent" } LOD 200 CGPROGRAM #pragma surface surf Standard alpha #pragma target 3.0 sampler2D _MainTex; struct Input { float2 uv_MainTex; }; half _Glossiness; half _Metallic; fixed4 _Color; void surf (Input IN, inout SurfaceOutputStandard o) { fixed4 c = _Color; o.Albedo = c.rgb; o.Metallic = _Metallic; o.Smoothness = _Glossiness; o.Alpha = ca; } ENDCG } FallBack "Diffuse" }
Buat material baru dengan shader ini, duplikat material
Water dan gantikan dengan shader. Biarkan tekstur suara, karena kita akan menggunakannya nanti.
Bahan air.Tambahkan anak baru ke prefab dengan menduplikasi anak
Rivers . Dia tidak membutuhkan koordinat UV, dan dia harus menggunakan
Air . Seperti biasa, kami akan melakukan ini dengan membuat turunan cetakan, mengubahnya, dan kemudian menerapkan perubahan ke cetakan. Setelah itu, singkirkan instance.
Air Benda Anak.Selanjutnya, tambahkan dukungan water mesh ke
HexGridChunk
.
public HexMesh terrain, rivers, roads, water; public void Triangulate () { terrain.Clear(); rivers.Clear(); roads.Clear(); water.Clear(); for (int i = 0; i < cells.Length; i++) { Triangulate(cells[i]); } terrain.Apply(); rivers.Apply(); roads.Apply(); water.Apply(); }
Dan hubungkan ke anak cetakan.
Objek Air terhubung.Segi enam air
Karena air membentuk lapisan kedua, mari berikan metode triangulasi kita sendiri untuk setiap arah. Kita perlu menyebutnya hanya ketika sel direndam dalam air.
void Triangulate (HexDirection direction, HexCell cell) { … if (cell.IsUnderwater) { TriangulateWater(direction, cell, center); } } void TriangulateWater ( HexDirection direction, HexCell cell, Vector3 center ) { }
Seperti halnya sungai, ketinggian permukaan air tidak banyak berbeda dalam sel dengan tingkat air yang sama. Karena itu, kita sepertinya tidak memerlukan tulang rusuk yang rumit. Segitiga sederhana sudah cukup.
void TriangulateWater ( HexDirection direction, HexCell cell, Vector3 center ) { center.y = cell.WaterSurfaceY; Vector3 c1 = center + HexMetrics.GetFirstSolidCorner(direction); Vector3 c2 = center + HexMetrics.GetSecondSolidCorner(direction); water.AddTriangle(center, c1, c2); }
Segi enam air.Senyawa air
Kita dapat menghubungkan sel tetangga dengan air dengan satu quadrangle.
water.AddTriangle(center, c1, c2); if (direction <= HexDirection.SE) { HexCell neighbor = cell.GetNeighbor(direction); if (neighbor == null || !neighbor.IsUnderwater) { return; } Vector3 bridge = HexMetrics.GetBridge(direction); Vector3 e1 = c1 + bridge; Vector3 e2 = c2 + bridge; water.AddQuad(c1, c2, e1, e2); }
Koneksi ujung-ujung air.Dan isi sudutnya dengan satu segitiga.
if (direction <= HexDirection.SE) { … water.AddQuad(c1, c2, e1, e2); if (direction <= HexDirection.E) { HexCell nextNeighbor = cell.GetNeighbor(direction.Next()); if (nextNeighbor == null || !nextNeighbor.IsUnderwater) { return; } water.AddTriangle( c2, e2, c2 + HexMetrics.GetBridge(direction.Next()) ); } }
Sambungan sudut air.Sekarang kita memiliki sel air yang terhubung ketika mereka berada di dekatnya. Mereka meninggalkan celah di antara mereka dan sel-sel kering dengan ketinggian yang lebih tinggi, tetapi kita akan meninggalkan ini untuk nanti.
Tingkat Air yang Diharmonisasikan
Kami berhipotesis bahwa sel bawah laut yang berdekatan memiliki level air yang sama. Jika demikian, maka semuanya terlihat baik, tetapi jika asumsi ini dilanggar, kesalahan terjadi.
Level air yang tidak konsisten.Kita bisa membuat air tetap di level yang sama. Misalnya, ketika level air sel yang banjir berubah, kita dapat menyebarkan perubahan ke sel tetangga untuk menjaga agar levelnya tersinkronisasi. Namun, proses ini harus berlanjut sampai bertemu sel-sel yang tidak direndam dalam air. Sel-sel ini menentukan batas-batas massa air.
Bahaya dari pendekatan ini adalah bahwa ia dapat dengan cepat lepas kendali. Jika pengeditan tidak berhasil, air dapat menutupi seluruh peta. Kemudian semua fragmen harus di-triangulasi secara bersamaan, yang akan menyebabkan lompatan besar dalam penundaan.
Jadi jangan lakukan itu dulu. Fitur ini dapat ditambahkan dalam editor yang lebih kompleks. Sementara konsistensi level air, kami meninggalkan hati nurani pengguna.
paket unityAnimasi air
Alih-alih warna seragam, kami akan menciptakan sesuatu yang menyerupai gelombang. Seperti pada shader lainnya, untuk saat ini kami tidak akan mengusahakan grafik yang indah, kami hanya perlu menunjuk ombak.
Airnya rata sempurna.Mari kita lakukan apa yang kita lakukan dengan sungai. Kami mencicipi kebisingan dengan posisi dunia dan menambahkannya ke warna yang seragam. Untuk menghidupkan permukaan, tambahkan waktu ke koordinat V.
struct Input { float2 uv_MainTex; float3 worldPos; }; … void surf (Input IN, inout SurfaceOutputStandard o) { float2 uv = IN.worldPos.xz; uv.y += _Time.y; float4 noise = tex2D(_MainTex, uv * 0.025); float waves = noise.z; fixed4 c = saturate(_Color + waves); o.Albedo = c.rgb; o.Metallic = _Metallic; o.Smoothness = _Glossiness; o.Alpha = ca; }
Air gulir, waktu × 10.Dua arah
Sejauh ini sama sekali tidak seperti gelombang. Mari memperumit gambar dengan menambahkan sampel derau kedua
dan kali ini menambahkan koordinat U. Kami menggunakan saluran noise yang berbeda untuk mendapatkan dua pola yang berbeda sebagai hasilnya. Gelombang jadi akan menjadi dua sampel ini ditumpuk bersama.
float2 uv1 = IN.worldPos.xz; uv1.y += _Time.y; float4 noise1 = tex2D(_MainTex, uv1 * 0.025); float2 uv2 = IN.worldPos.xz; uv2.x += _Time.y; float4 noise2 = tex2D(_MainTex, uv2 * 0.025); float waves = noise1.z + noise2.x;
Saat menjumlahkan kedua sampel, kami mendapatkan hasil dalam interval 0–2, jadi kami perlu memperkecilnya menjadi 0–1. Alih-alih hanya membagi gelombang menjadi dua, kita dapat menggunakan fungsi
smoothstep
untuk menciptakan hasil yang lebih menarik. Kami menempatkan ¾ - 2 pada 0-1 sehingga tidak ada gelombang yang terlihat di permukaan air.
float waves = noise1.z + noise2.x; waves = smoothstep(0.75, 2, waves);
Dua arah, waktu × 10.Gelombang pencampuran
Masih terlihat bahwa kita memiliki dua pola kebisingan bergerak yang tidak benar-benar berubah. Akan lebih masuk akal jika polanya berubah. Kita dapat menyadari hal ini dengan menginterpolasi berbagai saluran sampel kebisingan. Tapi ini tidak bisa dilakukan dengan cara yang sama, jika tidak seluruh permukaan air akan berubah secara bersamaan, dan ini sangat terlihat. Sebagai gantinya, kami akan membuat gelombang kebingungan.
Kami akan membuat gelombang pencampuran dengan bantuan sinusoid, yang bergerak secara diagonal di sepanjang permukaan air. Kami akan melakukan ini dengan menambahkan koordinat dunia X dan Z dan menggunakan jumlah sebagai input ke fungsi
sin
. Perkecil untuk mendapatkan band yang cukup besar. Dan tentu saja, mari kita tambahkan nilai yang sama untuk menghidupkannya.
float blendWave = sin((IN.worldPos.x + IN.worldPos.z) * 0.1 + _Time.y);
Gelombang sinus berkisar antara -1 dan 1, dan kita membutuhkan interval 0-1. Anda bisa mendapatkannya dengan mengkuadratkan gelombang. Untuk melihat hasil yang terisolasi, gunakan itu bukan warna yang berubah sebagai nilai output.
sin((IN.worldPos.x + IN.worldPos.z) * 0.1 + _Time.y); blendWave *= blendWave; float waves = noise1.z + noise2.x; waves = smoothstep(0.75, 2, waves); fixed4 c = blendWave; //saturate(_Color + waves);
Gelombang pencampuran.Untuk membuat gelombang pencampuran kurang terlihat, tambahkan beberapa noise dari kedua sampel.
float blendWave = sin( (IN.worldPos.x + IN.worldPos.z) * 0.1 + (noise1.y + noise2.z) + _Time.y ); blendWave *= blendWave;
Gelombang pencampuran yang terdistorsi.Akhirnya, kami menggunakan gelombang pencampuran untuk menginterpolasi antara dua saluran dari kedua sampel kebisingan. Untuk variasi maksimum, ambil empat saluran berbeda.
float waves = lerp(noise1.z, noise1.w, blendWave) + lerp(noise2.x, noise2.y, blendWave)
Gelombang pencampuran, waktu × 2.paket unityPantai
Kita sudah selesai dengan air terbuka, tapi sekarang kita perlu mengisi celah di air di sepanjang pantai. Karena kita harus menyesuaikan dengan kontur tanah, air pantai memerlukan pendekatan yang berbeda. Mari kita bagikan
TriangulateWater
menjadi dua metode - satu untuk perairan terbuka dan satu untuk pantai. Untuk memahami ketika kita bekerja dengan pantai, kita perlu melihat sel tetangga. Artinya, di
TriangulateWater
kita akan mendapatkan tetangga. Jika ada tetangga dan dia tidak di bawah air, maka kita berurusan dengan pantai.
void TriangulateWater ( HexDirection direction, HexCell cell, Vector3 center ) { center.y = cell.WaterSurfaceY; HexCell neighbor = cell.GetNeighbor(direction); if (neighbor != null && !neighbor.IsUnderwater) { TriangulateWaterShore(direction, cell, neighbor, center); } else { TriangulateOpenWater(direction, cell, neighbor, center); } } void TriangulateOpenWater ( HexDirection direction, HexCell cell, HexCell neighbor, Vector3 center ) { Vector3 c1 = center + HexMetrics.GetFirstSolidCorner(direction); Vector3 c2 = center + HexMetrics.GetSecondSolidCorner(direction); water.AddTriangle(center, c1, c2); if (direction <= HexDirection.SE && neighbor != null) {
Tidak ada triangulasi di sepanjang pantai.Karena pantai terdistorsi, kita harus mengubah segitiga air di sepanjang pantai. Oleh karena itu, kita memerlukan bagian atas tepi dan kipas segitiga.
void TriangulateWaterShore ( HexDirection direction, HexCell cell, HexCell neighbor, Vector3 center ) { EdgeVertices e1 = new EdgeVertices( center + HexMetrics.GetFirstSolidCorner(direction), center + HexMetrics.GetSecondSolidCorner(direction) ); water.AddTriangle(center, e1.v1, e1.v2); water.AddTriangle(center, e1.v2, e1.v3); water.AddTriangle(center, e1.v3, e1.v4); water.AddTriangle(center, e1.v4, e1.v5); }
Penggemar segitiga di sepanjang pantai.Berikutnya adalah potongan tulang rusuk, seperti pada relief normal. Namun, kami tidak diwajibkan untuk membatasi diri hanya untuk area tertentu, karena kami hanya memanggil
TriangulateWaterShore
ketika kami bertemu pantai, yang membutuhkan strip.
water.AddTriangle(center, e1.v4, e1.v5); Vector3 bridge = HexMetrics.GetBridge(direction); EdgeVertices e2 = new EdgeVertices( e1.v1 + bridge, e1.v5 + bridge ); water.AddQuad(e1.v1, e1.v2, e2.v1, e2.v2); water.AddQuad(e1.v2, e1.v3, e2.v2, e2.v3); water.AddQuad(e1.v3, e1.v4, e2.v3, e2.v4); water.AddQuad(e1.v4, e1.v5, e2.v4, e2.v5);
Garis-garis tulang rusuk di sepanjang pantai.Demikian pula, kita juga harus menambahkan segitiga sudut setiap kali.
water.AddQuad(e1.v4, e1.v5, e2.v4, e2.v5); HexCell nextNeighbor = cell.GetNeighbor(direction.Next()); if (nextNeighbor != null) { water.AddTriangle( e1.v5, e2.v5, e1.v5 + HexMetrics.GetBridge(direction.Next()) ); }
Sudut iga di sepanjang pantai.Sekarang kami memiliki air siap untuk pantai. Sebagian darinya selalu berada di bawah lubang bantuan, sehingga tidak ada lubang.
Pantai UV
Kita bisa meninggalkan semuanya apa adanya, tetapi akan menarik jika air pantai punya jadwal sendiri. Misalnya, efek busa, yang menjadi lebih besar saat mendekati pantai. Untuk mengimplementasikannya, shader harus tahu seberapa dekat fragmen dengan pantai. Kami dapat mengirimkan informasi ini melalui koordinat UV.
Air terbuka tidak memiliki koordinat UV, dan tidak perlu busa. Itu hanya dibutuhkan untuk air di dekat pantai. Oleh karena itu, persyaratan untuk kedua jenis air ini sangat berbeda. Adalah logis untuk membuat mesh Anda sendiri untuk setiap jenis. Oleh karena itu, kami menambahkan dukungan untuk objek mesh lain ke HexGridChunk.
public HexMesh terrain, rivers, roads, water, waterShore; public void Triangulate () { terrain.Clear(); rivers.Clear(); roads.Clear(); water.Clear(); waterShore.Clear(); for (int i = 0; i < cells.Length; i++) { Triangulate(cells[i]); } terrain.Apply(); rivers.Apply(); roads.Apply(); water.Apply(); waterShore.Apply(); }
Mesh baru ini akan menggunakan
TriangulateWaterShore
.
void TriangulateWaterShore ( HexDirection direction, HexCell cell, HexCell neighbor, Vector3 center ) { … waterShore.AddQuad(e1.v1, e1.v2, e2.v1, e2.v2); waterShore.AddQuad(e1.v2, e1.v3, e2.v2, e2.v3); waterShore.AddQuad(e1.v3, e1.v4, e2.v3, e2.v4); waterShore.AddQuad(e1.v4, e1.v5, e2.v4, e2.v5); HexCell nextNeighbor = cell.GetNeighbor(direction.Next()); if (nextNeighbor != null) { waterShore.AddTriangle( e1.v5, e2.v5, e1.v5 + HexMetrics.GetBridge(direction.Next()) ); } }
Gandakan objek air, hubungkan ke cetakan dan mengaturnya sehingga menggunakan koordinat UV. Kami juga membuat shader dan material untuk air pantai, menduplikasi shader dan material air yang ada.
Fasilitas tepi air dan bahan UV.Ubah shader
Water Shore sehingga alih-alih air akan menampilkan koordinat UV.
fixed4 c = fixed4(IN.uv_MainTex, 1, 1)
Karena belum ada koordinat yang ditetapkan, itu akan menampilkan warna solid. Berkat ini, mudah untuk melihat bahwa pantai benar-benar menggunakan jala terpisah dengan material.
Pisahkan jaring untuk pantai.Mari kita letakkan informasi pantai dalam koordinat V. Di sisi air, berikan nilai 0, di sisi darat - nilai 1. Karena kita tidak perlu mengirimkan apa pun, semua koordinat U hanya akan menjadi 0.
waterShore.AddQuad(e1.v1, e1.v2, e2.v1, e2.v2); waterShore.AddQuad(e1.v2, e1.v3, e2.v2, e2.v3); waterShore.AddQuad(e1.v3, e1.v4, e2.v3, e2.v4); waterShore.AddQuad(e1.v4, e1.v5, e2.v4, e2.v5); waterShore.AddQuadUV(0f, 0f, 0f, 1f); waterShore.AddQuadUV(0f, 0f, 0f, 1f); waterShore.AddQuadUV(0f, 0f, 0f, 1f); waterShore.AddQuadUV(0f, 0f, 0f, 1f); HexCell nextNeighbor = cell.GetNeighbor(direction.Next()); if (nextNeighbor != null) { waterShore.AddTriangle( e1.v5, e2.v5, e1.v5 + HexMetrics.GetBridge(direction.Next()) ); waterShore.AddTriangleUV( new Vector2(0f, 0f), new Vector2(0f, 1f), new Vector2(0f, 0f) ); }
Transisi ke pantai salah.Kode di atas berfungsi untuk edge, tetapi salah di beberapa sudut. Jika tetangga berikutnya ada di bawah air, maka pendekatan ini akan benar. Tetapi ketika tetangga berikutnya tidak di bawah air, puncak ketiga segitiga itu akan berada di bawah tanah.
waterShore.AddTriangleUV( new Vector2(0f, 0f), new Vector2(0f, 1f), new Vector2(0f, nextNeighbor.IsUnderwater ? 0f : 1f) );
Transisi ke pantai sudah benar.Busa di pantai
Sekarang transisi ke pantai diimplementasikan dengan benar, Anda dapat menggunakannya untuk membuat efek busa. Cara termudah adalah menambahkan nilai pantai ke warna yang seragam.
void surf (Input IN, inout SurfaceOutputStandard o) { float shore = IN.uv_MainTex.y; float foam = shore; fixed4 c = saturate(_Color + foam); o.Albedo = c.rgb; o.Metallic = _Metallic; o.Smoothness = _Glossiness; o.Alpha = ca; }
Busa linier.Untuk membuat busa lebih menarik, kalikan dengan persegi sinusoid.
float foam = sin(shore * 10); foam *= foam * shore;
Busa Sinusoid Persegi Memudar.Mari kita buat busa depan lebih besar saat mendekati pantai. Ini dapat dilakukan dengan mengambil akar kuadratnya sebelum menggunakan nilai pantai.
float shore = IN.uv_MainTex.y; shore = sqrt(shore);
Busa menjadi lebih tebal di dekat pantai.Tambahkan distorsi agar terlihat lebih alami. Mari kita buat distorsi lebih lemah saat mendekati pantai. Jadi akan lebih baik untuk melapisi pantai.
float2 noiseUV = IN.worldPos.xz; float4 noise = tex2D(_MainTex, noiseUV * 0.015); float distortion = noise.x * (1 - shore); float foam = sin((shore + distortion) * 10); foam *= foam * shore;
Busa dengan distorsi.Dan, tentu saja, kita menjiwai semua ini: baik sinusoid dan distorsi.
float2 noiseUV = IN.worldPos.xz + _Time.y * 0.25; float4 noise = tex2D(_MainTex, noiseUV * 0.015); float distortion = noise.x * (1 - shore); float foam = sin((shore + distortion) * 10 - _Time.y); foam *= foam * shore;
Busa Animasi.Selain busa yang masuk, ada juga yang mundur. Mari kita tambahkan sinusoid kedua, yang bergerak ke arah yang berlawanan, untuk mensimulasikannya. Buat itu lebih lemah dan tambahkan shift waktu. Busa jadi akan menjadi maksimum dari dua sinusoid ini.
float distortion1 = noise.x * (1 - shore); float foam1 = sin((shore + distortion1) * 10 - _Time.y); foam1 *= foam1; float distortion2 = noise.y * (1 - shore); float foam2 = sin((shore + distortion2) * 10 + _Time.y + 2); foam2 *= foam2 * 0.7; float foam = max(foam1, foam2) * shore;
Busa masuk dan surut.Perpaduan ombak dan busa
Ada transisi yang tajam antara air terbuka dan air pantai karena gelombang air terbuka tidak termasuk dalam air pantai. Untuk memperbaikinya, kita harus memasukkan gelombang ini ke dalam shader
Water Shore .
Alih-alih menyalin kode wave, mari kita tempelkan ke file yang menyertakan
Water.cginc . Bahkan, kami memasukkan kode ke dalamnya untuk kedua busa dan gelombang, masing-masing sebagai fungsi terpisah.
Bagaimana cara shader menyertakan file berfungsi?Membuat file shader include Anda sendiri tercakup dalam tutorial
Rendering 5, Multiple Lights .
float Foam (float shore, float2 worldXZ, sampler2D noiseTex) { // float shore = IN.uv_MainTex.y; shore = sqrt(shore); float2 noiseUV = worldXZ + _Time.y * 0.25; float4 noise = tex2D(noiseTex, noiseUV * 0.015); float distortion1 = noise.x * (1 - shore); float foam1 = sin((shore + distortion1) * 10 - _Time.y); foam1 *= foam1; float distortion2 = noise.y * (1 - shore); float foam2 = sin((shore + distortion2) * 10 + _Time.y + 2); foam2 *= foam2 * 0.7; return max(foam1, foam2) * shore; } float Waves (float2 worldXZ, sampler2D noiseTex) { float2 uv1 = worldXZ; uv1.y += _Time.y; float4 noise1 = tex2D(noiseTex, uv1 * 0.025); float2 uv2 = worldXZ; uv2.x += _Time.y; float4 noise2 = tex2D(noiseTex, uv2 * 0.025); float blendWave = sin( (worldXZ.x + worldXZ.y) * 0.1 + (noise1.y + noise2.z) + _Time.y ); blendWave *= blendWave; float waves = lerp(noise1.z, noise1.w, blendWave) + lerp(noise2.x, noise2.y, blendWave); return smoothstep(0.75, 2, waves); }
Ubah
Water shader sehingga ia menggunakan file include yang baru.
#include "Water.cginc" sampler2D _MainTex; … void surf (Input IN, inout SurfaceOutputStandard o) { float waves = Waves(IN.worldPos.xz, _MainTex); fixed4 c = saturate(_Color + waves); o.Albedo = c.rgb; o.Metallic = _Metallic; o.Smoothness = _Glossiness; o.Alpha = ca; }
Di
Water Shore shader, nilai dihitung untuk busa dan gelombang. Lalu kami meredam ombak saat mendekati pantai. Hasil akhir akan maksimal dari busa dan gelombang.
#include "Water.cginc" sampler2D _MainTex; … void surf (Input IN, inout SurfaceOutputStandard o) { float shore = IN.uv_MainTex.y; float foam = Foam(shore, IN.worldPos.xz, _MainTex); float waves = Waves(IN.worldPos.xz, _MainTex); waves *= 1 - shore; fixed4 c = saturate(_Color + max(foam, waves)); o.Albedo = c.rgb; o.Metallic = _Metallic; o.Smoothness = _Glossiness; o.Alpha = ca; }
Campuran busa dan gelombang.paket unityLagi tentang air pantai
Bagian dari jaring pantai disembunyikan di bawah jaring bantuan. Ini normal, tetapi hanya sebagian kecil yang disembunyikan. Sayangnya, tebing curam menyembunyikan sebagian besar air pantai, dan karenanya berbusa.
Air pantai nyaris tersembunyi.Kita dapat menangani ini dengan meningkatkan ukuran strip pantai. Ini bisa dilakukan dengan mengurangi jari-jari heksagon air. Untuk ini, selain koefisien integritas, kita memerlukan koefisien air
HexMetrics
, serta metode untuk mendapatkan sudut air.
Koefisien integritas adalah 0,8. Untuk menggandakan ukuran senyawa air, kita perlu mengatur koefisien air menjadi 0,6.
public const float waterFactor = 0.6f; public static Vector3 GetFirstWaterCorner (HexDirection direction) { return corners[(int)direction] * waterFactor; } public static Vector3 GetSecondWaterCorner (HexDirection direction) { return corners[(int)direction + 1] * waterFactor; }
Kami akan menggunakan metode baru ini HexGridChunk
untuk menemukan sudut air. void TriangulateOpenWater ( HexDirection direction, HexCell cell, HexCell neighbor, Vector3 center ) { Vector3 c1 = center + HexMetrics.GetFirstWaterCorner(direction); Vector3 c2 = center + HexMetrics.GetSecondWaterCorner(direction); … } void TriangulateWaterShore ( HexDirection direction, HexCell cell, HexCell neighbor, Vector3 center ) { EdgeVertices e1 = new EdgeVertices( center + HexMetrics.GetFirstWaterCorner(direction), center + HexMetrics.GetSecondWaterCorner(direction) ); … }
Menggunakan sudut air.Jarak antara segi enam air sebenarnya berlipat ganda. Sekarang HexMetrics
itu juga harus memiliki metode membuat jembatan di dalam air. public const float waterBlendFactor = 1f - waterFactor; public static Vector3 GetWaterBridge (HexDirection direction) { return (corners[(int)direction] + corners[(int)direction + 1]) * waterBlendFactor; }
Ubah HexGridChunk
sehingga dia menggunakan metode baru. void TriangulateOpenWater ( HexDirection direction, HexCell cell, HexCell neighbor, Vector3 center ) { … if (direction <= HexDirection.SE && neighbor != null) { Vector3 bridge = HexMetrics.GetWaterBridge(direction); … if (direction <= HexDirection.E) { … water.AddTriangle( c2, e2, c2 + HexMetrics.GetWaterBridge(direction.Next()) ); } } } void TriangulateWaterShore ( HexDirection direction, HexCell cell, HexCell neighbor, Vector3 center ) { … Vector3 bridge = HexMetrics.GetWaterBridge(direction); … HexCell nextNeighbor = cell.GetNeighbor(direction.Next()); if (nextNeighbor != null) { waterShore.AddTriangle( e1.v5, e2.v5, e1.v5 + HexMetrics.GetWaterBridge(direction.Next()) ); … } }
Jembatan panjang di air.Antara tulang rusuk air dan daratan
Meskipun ini memberi kita lebih banyak ruang untuk busa, sekarang lebih banyak lagi yang tersembunyi di bawah bantuan. Idealnya, kita akan dapat menggunakan iga air di sisi air, dan iga darat di sisi darat.Kita tidak dapat menggunakan jembatan sederhana untuk menemukan tepi tanah yang berlawanan, jika kita mulai dari sudut-sudut air. Sebaliknya, kita bisa pergi ke arah yang berlawanan, dari pusat tetangga. Ubah TriangulateWaterShore
untuk menggunakan pendekatan baru ini.
Sudut tepi salah.Ini berhasil, hanya sekarang kita kembali perlu mempertimbangkan dua kasus untuk segitiga sudut. HexCell nextNeighbor = cell.GetNeighbor(direction.Next()); if (nextNeighbor != null) {
Sudut tepi yang benar.Ini bekerja dengan baik, tetapi sekarang karena sebagian besar busa terlihat, itu menjadi sangat jelas. Untuk mengimbangi ini, kami akan membuat efek sedikit lebih lemah dengan mengurangi skala nilai pantai di shader. shore = sqrt(shore) * 0.9
Busa siap.paket unitySungai bawah laut
Kami berakhir dengan air, setidaknya di tempat-tempat di mana tidak ada sungai yang mengalir ke sana. Karena air dan sungai belum saling memperhatikan, sungai akan mengalir melalui dan di bawah air.Sungai mengalir di dalam air.Urutan di mana objek-objek tembus pandang diberikan tergantung pada jaraknya dari kamera. Objek terdekat diberikan terakhir, sehingga mereka berada di atas. Saat menggerakkan kamera, ini berarti kadang-kadang sungai dan air terkadang muncul saling berhadapan. Mari kita mulai dengan membuat urutan rendering konstan. Sungai harus ditarik di atas air sehingga air terjun ditampilkan dengan benar. Kita bisa menerapkan ini dengan mengubah antrian shader Sungai . Tags { "RenderType"="Transparent" "Queue"="Transparent+1" }
Kami menggambar sungai terakhir.Menyembunyikan sungai bawah laut
Meskipun dasar sungai mungkin berada di bawah air, dan air sebenarnya dapat mengalir melaluinya, kita seharusnya tidak melihat air ini. Dan terlebih lagi, itu tidak harus diberikan di atas permukaan air nyata. Kita dapat membuang air sungai bawah laut dengan menambahkan segmen sungai hanya ketika sel saat ini tidak di bawah air. void TriangulateWithRiverBeginOrEnd ( HexDirection direction, HexCell cell, Vector3 center, EdgeVertices e ) { … if (!cell.IsUnderwater) { bool reversed = cell.HasIncomingRiver; … } } void TriangulateWithRiver ( HexDirection direction, HexCell cell, Vector3 center, EdgeVertices e ) { … if (!cell.IsUnderwater) { bool reversed = cell.IncomingRiver == direction; … } }
Untuk TriangulateConnection
mulai dengan, kami akan menambahkan segmen sungai ketika baik arus maupun sel tetangga tidak terendam air. if (cell.HasRiverThroughEdge(direction)) { e2.v3.y = neighbor.StreamBedY; if (!cell.IsUnderwater && !neighbor.IsUnderwater) { TriangulateRiverQuad( e1.v2, e1.v4, e2.v2, e2.v4, cell.RiverSurfaceY, neighbor.RiverSurfaceY, 0.8f, cell.HasIncomingRiver && cell.IncomingRiver == direction ); } }
Tidak ada lagi sungai bawah laut.Air terjun
Tidak ada lagi sungai bawah laut, tetapi sekarang kami memiliki lubang di tempat-tempat sungai di mana mereka bertemu dengan permukaan air. Sungai pada tingkat yang sama dengan air menciptakan lubang kecil atau overlay. Tetapi yang paling mencolok adalah air terjun yang hilang untuk sungai yang mengalir dari ketinggian yang lebih tinggi. Mari kita urus mereka terlebih dahulu.Segmen sungai dengan air terjun yang digunakan untuk melewati permukaan air. Akibatnya, ia mendapati dirinya sebagian di atas, dan sebagian lagi di bawah air. Kita harus menjaga bagian di atas permukaan air, membuang yang lainnya. Anda harus bekerja keras untuk ini, jadi buat metode terpisah.Metode baru ini membutuhkan empat puncak, dua ketinggian sungai, dan ketinggian air. Kami akan mengaturnya sehingga kami melihat ke arah arus, ke bawah air terjun. Oleh karena itu, dua puncak pertama dan sisi kiri dan kanan akan berada di atas, dan yang lebih rendah akan mengikuti. void TriangulateWaterfallInWater ( Vector3 v1, Vector3 v2, Vector3 v3, Vector3 v4, float y1, float y2, float waterY ) { v1.y = v2.y = y1; v3.y = v4.y = y2; rivers.AddQuad(v1, v2, v3, v4); rivers.AddQuadUV(0f, 1f, 0.8f, 1f); }
Kami akan memanggil metode ini TriangulateConnection
ketika tetangga di bawah air dan kami membuat air terjun. if (!cell.IsUnderwater) { if (!neighbor.IsUnderwater) { TriangulateRiverQuad( e1.v2, e1.v4, e2.v2, e2.v4, cell.RiverSurfaceY, neighbor.RiverSurfaceY, 0.8f, cell.HasIncomingRiver && cell.IncomingRiver == direction ); } else if (cell.Elevation > neighbor.WaterLevel) { TriangulateWaterfallInWater( e1.v2, e1.v4, e2.v2, e2.v4, cell.RiverSurfaceY, neighbor.RiverSurfaceY, neighbor.WaterSurfaceY ); } }
Kita juga perlu memproses air terjun ke arah yang berlawanan, ketika sel saat ini di bawah air, dan yang berikutnya tidak. if (!cell.IsUnderwater) { … } else if ( !neighbor.IsUnderwater && neighbor.Elevation > cell.WaterLevel ) { TriangulateWaterfallInWater( e2.v4, e2.v2, e1.v4, e1.v2, neighbor.RiverSurfaceY, cell.RiverSurfaceY, cell.WaterSurfaceY ); }
Jadi sekali lagi kita mendapatkan kuadrat dari sungai asli. Selanjutnya kita perlu mengubah TriangulateWaterfallInWater
sehingga menaikkan puncak yang lebih rendah ke permukaan air. Sayangnya, mengubah hanya koordinat Y tidak akan cukup. Ini bisa mendorong air terjun dari tebing, yang bisa membentuk lubang. Sebagai gantinya, Anda harus memindahkan simpul bawah ke atas menggunakan interpolasi.Interpolasi.Untuk memindahkan puncak yang lebih rendah ke atas, bagilah jarak mereka di bawah permukaan air dengan ketinggian air terjun. Ini akan memberi kita nilai interpolator. v1.y = v2.y = y1; v3.y = v4.y = y2; float t = (waterY - y2) / (y1 - y2); v3 = Vector3.Lerp(v3, v1, t); v4 = Vector3.Lerp(v4, v2, t); rivers.AddQuad(v1, v2, v3, v4); rivers.AddQuadUV(0f, 1f, 0.8f, 1f);
Alhasil, kita mendapatkan air terjun pendek yang memiliki orientasi yang sama. Namun, karena posisi simpul bawah telah berubah, mereka tidak akan terdistorsi seperti simpul asli. Ini berarti bahwa hasil akhir masih tidak akan bertepatan dengan air terjun asli. Untuk mengatasi masalah ini, kita perlu secara manual mendistorsi simpul sebelum interpolasi, dan kemudian menambahkan quad tidak terdistorsi. v1.y = v2.y = y1; v3.y = v4.y = y2; v1 = HexMetrics.Perturb(v1); v2 = HexMetrics.Perturb(v2); v3 = HexMetrics.Perturb(v3); v4 = HexMetrics.Perturb(v4); float t = (waterY - y2) / (y1 - y2); v3 = Vector3.Lerp(v3, v1, t); v4 = Vector3.Lerp(v4, v2, t); rivers.AddQuadUnperturbed(v1, v2, v3, v4); rivers.AddQuadUV(0f, 1f, 0.8f, 1f);
Karena kita sudah memiliki metode untuk menambahkan segitiga tidak terdistorsi, kita benar-benar tidak perlu membuat satu untuk paha depan. Oleh karena itu, kami menambahkan metode yang diperlukan HexMesh.AddQuadUnperturbed
. public void AddQuadUnperturbed ( Vector3 v1, Vector3 v2, Vector3 v3, Vector3 v4 ) { int vertexIndex = vertices.Count; vertices.Add(v1); vertices.Add(v2); vertices.Add(v3); vertices.Add(v4); triangles.Add(vertexIndex); triangles.Add(vertexIndex + 2); triangles.Add(vertexIndex + 1); triangles.Add(vertexIndex + 1); triangles.Add(vertexIndex + 2); triangles.Add(vertexIndex + 3); }
Air terjun berakhir di permukaan air.paket unityMuara
Ketika sungai mengalir pada ketinggian yang sama dengan permukaan air, jaring sungai menyentuh jaring pantai. Jika itu adalah sungai yang mengalir ke laut atau ke laut, maka akan ada aliran sungai yang bertemu dengan ombak. Oleh karena itu, kami akan memanggil muara daerah tersebut.Sungai memenuhi pantai tanpa merusak puncak.Sekarang kami memiliki dua masalah dengan mulut. Pertama, sungai quad menghubungkan puncak kedua dan keempat dari tulang rusuk, melewatkan yang ketiga. Karena pantai air tidak menggunakan puncak ketiga, itu dapat membuat lubang atau tumpang tindih. Kita bisa menyelesaikan masalah ini dengan mengubah geometri mulut.Masalah kedua adalah bahwa ada transisi yang tajam antara busa dan material sungai. Untuk mengatasinya, kita membutuhkan bahan lain yang melakukan pencampuran efek sungai dan air.Ini berarti bahwa mulut memerlukan pendekatan khusus, jadi mari kita buat metode terpisah untuk mereka. Itu harus dipanggil TriangulateWaterShore
ketika ada sungai bergerak ke arah saat ini. void TriangulateWaterShore ( HexDirection direction, HexCell cell, HexCell neighbor, Vector3 center ) { … if (cell.HasRiverThroughEdge(direction)) { TriangulateEstuary(e1, e2); } else { waterShore.AddQuad(e1.v1, e1.v2, e2.v1, e2.v2); waterShore.AddQuad(e1.v2, e1.v3, e2.v2, e2.v3); waterShore.AddQuad(e1.v3, e1.v4, e2.v3, e2.v4); waterShore.AddQuad(e1.v4, e1.v5, e2.v4, e2.v5); waterShore.AddQuadUV(0f, 0f, 0f, 1f); waterShore.AddQuadUV(0f, 0f, 0f, 1f); waterShore.AddQuadUV(0f, 0f, 0f, 1f); waterShore.AddQuadUV(0f, 0f, 0f, 1f); } … } void TriangulateEstuary (EdgeVertices e1, EdgeVertices e2) { }
Daerah tidak perlu mencampur kedua efek untuk mengisi seluruh strip. Bentuk trapesium akan cukup bagi kita. Karena itu, kita bisa menggunakan dua segitiga pantai di sisinya. void TriangulateEstuary (EdgeVertices e1, EdgeVertices e2) { waterShore.AddTriangle(e2.v1, e1.v2, e1.v1); waterShore.AddTriangle(e2.v5, e1.v5, e1.v4); waterShore.AddTriangleUV( new Vector2(0f, 1f), new Vector2(0f, 0f), new Vector2(0f, 0f) ); waterShore.AddTriangleUV( new Vector2(0f, 1f), new Vector2(0f, 0f), new Vector2(0f, 0f) ); }
Lubang trapesium untuk area pencampuran.Koordinat UV2
Untuk membuat efek sungai, kita membutuhkan koordinat UV. Tetapi untuk menciptakan efek busa, Anda juga membutuhkan koordinat UV. Artinya, saat mencampurnya, kita membutuhkan dua set koordinat UV. Untungnya, jerat engine Unity dapat mendukung hingga empat set UV. Kami hanya perlu menambahkan untuk HexMesh
mendukung set kedua. public bool useCollider, useColors, useUVCoordinates, useUV2Coordinates; [NonSerialized] List<Vector2> uvs, uv2s; public void Clear () { … if (useUVCoordinates) { uvs = ListPool<Vector2>.Get(); } if (useUV2Coordinates) { uv2s = ListPool<Vector2>.Get(); } triangles = ListPool<int>.Get(); } public void Apply () { … if (useUVCoordinates) { hexMesh.SetUVs(0, uvs); ListPool<Vector2>.Add(uvs); } if (useUV2Coordinates) { hexMesh.SetUVs(1, uv2s); ListPool<Vector2>.Add(uv2s); } … }
Untuk menambahkan set kedua UV, kami menduplikasi metode bekerja dengan UV dan mengubah cara yang kita butuhkan. public void AddTriangleUV2 (Vector2 uv1, Vector2 uv2, Vector3 uv3) { uv2s.Add(uv1); uv2s.Add(uv2); uv2s.Add(uv3); } public void AddQuadUV2 (Vector2 uv1, Vector2 uv2, Vector3 uv3, Vector3 uv4) { uv2s.Add(uv1); uv2s.Add(uv2); uv2s.Add(uv3); uv2s.Add(uv4); } public void AddQuadUV2 (float uMin, float uMax, float vMin, float vMax) { uv2s.Add(new Vector2(uMin, vMin)); uv2s.Add(new Vector2(uMax, vMin)); uv2s.Add(new Vector2(uMin, vMax)); uv2s.Add(new Vector2(uMax, vMax)); }
Fungsi River Shader
Karena kita akan menggunakan efek sungai dalam dua shader, kita akan memindahkan kode dari River shader ke fungsi file Water include yang baru . float River (float2 riverUV, sampler2D noiseTex) { float2 uv = riverUV; uv.x = uv.x * 0.0625 + _Time.y * 0.005; uv.y -= _Time.y * 0.25; float4 noise = tex2D(noiseTex, uv); float2 uv2 = riverUV; uv2.x = uv2.x * 0.0625 - _Time.y * 0.0052; uv2.y -= _Time.y * 0.23; float4 noise2 = tex2D(noiseTex, uv2); return noise.x * noise2.w; }
Ubah shader Sungai untuk menggunakan fitur baru ini. #include "Water.cginc" sampler2D _MainTex; … void surf (Input IN, inout SurfaceOutputStandard o) { float river = River(IN.uv_MainTex, _MainTex); fixed4 c = saturate(_Color + river); … }
Benda mulut
Tambahkan HexGridChunk
mulut untuk mendukung objek jala. public HexMesh terrain, rivers, roads, water, waterShore, estuaries; public void Triangulate () { terrain.Clear(); rivers.Clear(); roads.Clear(); water.Clear(); waterShore.Clear(); estuaries.Clear(); for (int i = 0; i < cells.Length; i++) { Triangulate(cells[i]); } terrain.Apply(); rivers.Apply(); roads.Apply(); water.Apply(); waterShore.Apply(); estuaries.Apply(); }
Buat shader, material dan objek mulut, menduplikasi pantai dan mengubahnya. Hubungkan ke fragmen, dan gunakan koordinat UV dan UV2.Obyek Estuarties.Triangulasi mulut
Kita dapat memecahkan masalah lubang atau tumpang tindih dengan menempatkan segitiga antara ujung sungai dan tengah tepi air. Karena shader mulut kami adalah duplikat dari shader pantai, kami mengatur koordinat UV agar sesuai dengan efek busa. void TriangulateEstuary (EdgeVertices e1, EdgeVertices e2) { … estuaries.AddTriangle(e1.v3, e2.v2, e2.v4); estuaries.AddTriangleUV( new Vector2(0f, 0f), new Vector2(0f, 1f), new Vector2(0f, 1f) ); }
Segitiga tengah.Kita dapat mengisi seluruh trapesium dengan menambahkan quad di kedua sisi segitiga tengah. estuaries.AddQuad(e1.v2, e1.v3, e2.v1, e2.v2); estuaries.AddTriangle(e1.v3, e2.v2, e2.v4); estuaries.AddQuad(e1.v3, e1.v4, e2.v4, e2.v5); estuaries.AddQuadUV(0f, 0f, 0f, 1f); estuaries.AddTriangleUV( new Vector2(0f, 0f), new Vector2(0f, 1f), new Vector2(0f, 1f) ); estuaries.AddQuadUV(0f, 0f, 0f, 1f);
Trapesium siap.Mari kita putar orientasi quad ke kiri sehingga memiliki koneksi diagonal pendek, dan sebagai hasilnya kita mendapatkan geometri simetris. estuaries.AddQuad(e2.v1, e1.v2, e2.v2, e1.v3); estuaries.AddTriangle(e1.v3, e2.v2, e2.v4); estuaries.AddQuad(e1.v3, e1.v4, e2.v4, e2.v5); estuaries.AddQuadUV( new Vector2(0f, 1f), new Vector2(0f, 0f), new Vector2(0f, 1f), new Vector2(0f, 0f) );
Rotated quad, geometri simetrisAliran sungai
Untuk mendukung efek sungai, kita perlu menambahkan koordinat UV2. Bagian bawah segitiga tengah berada di tengah sungai, jadi koordinatnya U harus sama dengan 0,5. Karena sungai mengalir ke arah air, titik kiri menerima koordinat U sama dengan 1, dan yang kanan menerima koordinat U dengan nilai 0. Kami mengatur koordinat Y ke 0 dan 1, sesuai dengan arah arus. estuaries.AddTriangleUV2( new Vector2(0.5f, 1f), new Vector2(1f, 0f), new Vector2(0f, 0f) );
Segi empat di kedua sisi segitiga harus bertepatan dengan orientasi ini. Kami menjaga koordinat U yang sama untuk titik yang melebihi lebar sungai. estuaries.AddQuadUV2( new Vector2(1f, 0f), new Vector2(1f, 1f), new Vector2(1f, 0f), new Vector2(0.5f, 1f) ); estuaries.AddTriangleUV2( new Vector2(0.5f, 1f), new Vector2(1f, 0f), new Vector2(0f, 0f) ); estuaries.AddQuadUV2( new Vector2(0.5f, 1f), new Vector2(0f, 1f), new Vector2(0f, 0f), new Vector2(0f, 0f) );
UV2 trapesium.Untuk memastikan bahwa kami mengatur koordinat UV2 dengan benar, buat Estuary shader merendernya. Kami dapat mengakses koordinat ini dengan menambahkan ke struktur input float2 uv2_MainTex
. struct Input { float2 uv_MainTex; float2 uv2_MainTex; float3 worldPos; }; … void surf (Input IN, inout SurfaceOutputStandard o) { float shore = IN.uv_MainTex.y; float foam = Foam(shore, IN.worldPos.xz, _MainTex); float waves = Waves(IN.worldPos.xz, _MainTex); waves *= 1 - shore; fixed4 c = fixed4(IN.uv2_MainTex, 1, 1); … }
Koordinat UV2.Semuanya terlihat bagus, Anda dapat menggunakan shader untuk membuat efek sungai. void surf (Input IN, inout SurfaceOutputStandard o) { … float river = River(IN.uv2_MainTex, _MainTex); fixed4 c = saturate(_Color + river); … }
Gunakan UV2 untuk membuat efek sungai.Kami menciptakan sungai sedemikian rupa sehingga ketika melakukan triangulasi koneksi antar sel, koordinat sungai V berubah dari 0,8 menjadi 1. Oleh karena itu, di sini kita juga harus menggunakan interval ini, dan bukan dari 0 hingga 1. Namun, koneksi pantai 50% lebih dari koneksi sel biasa . Oleh karena itu, untuk yang paling cocok dengan aliran sungai, kita harus mengubah nilai dari 0,8 menjadi 1,1. estuaries.AddQuadUV2( new Vector2(1f, 0.8f), new Vector2(1f, 1.1f), new Vector2(1f, 0.8f), new Vector2(0.5f, 1.1f) ); estuaries.AddTriangleUV2( new Vector2(0.5f, 1.1f), new Vector2(1f, 0.8f), new Vector2(0f, 0.8f) ); estuaries.AddQuadUV2( new Vector2(0.5f, 1.1f), new Vector2(0f, 1.1f), new Vector2(0f, 0.8f), new Vector2(0f, 0.8f) );
Aliran sungai dan muara yang tersinkronisasi.Pengaturan aliran
Sementara sungai bergerak dalam garis lurus. Tetapi ketika air mengalir ke area yang lebih luas, itu mengembang. Arus akan melengkung. Kita dapat mensimulasikan ini dengan melipat koordinat UV2.Alih-alih menjaga koordinat U atas konstan di luar lebar sungai, pindahkan mereka dengan 0,5. Titik paling kiri adalah 1.5, paling kanan adalah .50.5.Pada saat yang sama, kami memperluas aliran dengan menggerakkan koordinat U dari titik bawah kiri dan kanan. Ubah yang kiri dari 1 menjadi 0,7, dan yang kanan dari 0 menjadi 0,3. estuaries.AddQuadUV2( new Vector2(1.5f, 0.8f), new Vector2(0.7f, 1.1f), new Vector2(1f, 0.8f), new Vector2(0.5f, 1.1f) ); … estuaries.AddQuadUV2( new Vector2(0.5f, 1.1f), new Vector2(0.3f, 1.1f), new Vector2(0f, 0.8f), new Vector2(-0.5f, 0.8f) );
Perluasan sungai.Untuk menyelesaikan efek lengkungan, ubah koordinat V dari empat titik yang sama. Karena air mengalir dari ujung sungai, kita akan meningkatkan koordinat V dari titik atas ke 1. Dan untuk membuat kurva yang lebih baik, kita akan meningkatkan koordinat V dari dua titik bawah menjadi 1.15. estuaries.AddQuadUV2( new Vector2(1.5f, 1f), new Vector2(0.7f, 1.15f), new Vector2(1f, 0.8f), new Vector2(0.5f, 1.1f) ); estuaries.AddTriangleUV2( new Vector2(0.5f, 1.1f), new Vector2(1f, 0.8f), new Vector2(0f, 0.8f) ); estuaries.AddQuadUV2( new Vector2(0.5f, 1.1f), new Vector2(0.3f, 1.15f), new Vector2(0f, 0.8f), new Vector2(-0.5f, 1f) );
Jalur sungai yang melengkung.Campur Sungai dan Pantai
Yang tersisa bagi kita adalah mencampur efek dari pantai dan sungai. Untuk melakukan ini, kami menggunakan interpolasi linier, dengan mengambil nilai pantai sebagai interpolator. float shoreWater = max(foam, waves); float river = River(IN.uv2_MainTex, _MainTex); float water = lerp(shoreWater, river, IN.uv_MainTex.x); fixed4 c = saturate(_Color + water);
Meskipun ini berhasil, Anda mungkin mendapatkan kesalahan kompilasi. Kompiler mengeluh tentang definisi ulang _MainTex_ST
. Alasannya adalah kesalahan di dalam kompiler shader permukaan Unity yang disebabkan oleh penggunaan simultan dari uv_MainTex
dan uv2_MainTex
. Kita perlu mencari solusinya.Alih-alih menggunakannya uv2_MainTex
, kita harus mentransfer koordinat UV sekunder secara manual. Untuk melakukan ini, ganti nama uv2_MainTex
menjadi riverUV
. Kemudian tambahkan fungsi titik ke shader, yang menetapkan koordinat untuk itu. #pragma surface surf Standard alpha vertex:vert … struct Input { float2 uv_MainTex; float2 riverUV; float3 worldPos; }; … void vert (inout appdata_full v, out Input o) { UNITY_INITIALIZE_OUTPUT(Input, o); o.riverUV = v.texcoord1.xy; } void surf (Input IN, inout SurfaceOutputStandard o) { … float river = River(IN.riverUV, _MainTex); … }
Interpolasi berdasarkan nilai pantai.Interpolasi bekerja, dengan pengecualian dari simpul kiri dan kanan di atas. Pada titik-titik ini sungai harus menghilang. Karena itu, kami tidak dapat menggunakan nilai pantai. Kita harus menggunakan nilai yang berbeda, yang pada kedua simpul ini adalah 0. Untungnya, kita masih memiliki koordinat U dari set UV pertama, sehingga kita dapat menyimpan nilai ini di sana. estuaries.AddQuadUV( new Vector2(0f, 1f), new Vector2(0f, 0f), new Vector2(1f, 1f), new Vector2(0f, 0f) ); estuaries.AddTriangleUV( new Vector2(0f, 0f), new Vector2(1f, 1f), new Vector2(1f, 1f) ); estuaries.AddQuadUV( new Vector2(0f, 0f), new Vector2(0f, 0f), new Vector2(1f, 1f), new Vector2(0f, 1f) );
Campuran yang tepat.Sekarang mulut memiliki campuran yang baik antara sungai yang berkembang, air pantai dan busa. Meskipun ini tidak cocok dengan air terjun, efek ini juga terlihat bagus dengan air terjun.Muara aksiunitypackageSungai mengalir dari badan air
Kami sudah memiliki sungai yang mengalir ke badan air, tetapi tidak ada dukungan untuk sungai yang mengalir ke arah yang berbeda. Ada danau dari mana sungai mengalir, jadi kita perlu menambahkannya juga.Ketika sebuah sungai mengalir keluar dari badan air, sebenarnya mengalir menuju ketinggian yang lebih tinggi. Ini saat ini tidak memungkinkan. Kita perlu membuat pengecualian dan membiarkan situasi ini jika ketinggian air sesuai dengan ketinggian titik target. Mari kita tambahkan ke HexCell
metode pribadi yang memeriksa sesuai dengan kriteria baru kami apakah tetangga adalah titik target yang tepat untuk sungai keluar. bool IsValidRiverDestination (HexCell neighbor) { return neighbor && ( elevation >= neighbor.elevation || waterLevel == neighbor.elevation ); }
Kami akan menggunakan metode baru kami untuk menentukan apakah mungkin untuk membuat sungai keluar. public void SetOutgoingRiver (HexDirection direction) { if (hasOutgoingRiver && outgoingRiver == direction) { return; } HexCell neighbor = GetNeighbor(direction);
Juga, di sana Anda perlu memeriksa sungai ketika mengubah ketinggian sel atau ketinggian air. Mari kita buat metode pribadi yang akan melakukan tugas ini. void ValidateRivers () { if ( hasOutgoingRiver && !IsValidRiverDestination(GetNeighbor(outgoingRiver)) ) { RemoveOutgoingRiver(); } if ( hasIncomingRiver && !GetNeighbor(incomingRiver).IsValidRiverDestination(this) ) { RemoveIncomingRiver(); } }
Kami akan menggunakan metode baru ini di properti Elevation
dan WaterLevel
. public int Elevation { … set { …
Keluar dan memasuki danau sungai.Putar ombak
Kami menciptakan HexGridChunk.TriangulateEstuary
, menyarankan bahwa sungai hanya bisa mengalir ke badan air. Karena itu, sebagai akibatnya, aliran sungai selalu bergerak dalam satu arah. Kita perlu membalikkan aliran ketika berhadapan dengan sungai yang mengalir keluar dari badan air. Untuk melakukan ini, Anda perlu TriangulateEstuary
tahu tentang arah aliran. Oleh karena itu, kami memberinya parameter Boolean yang menentukan apakah kami berhadapan dengan sungai yang masuk. void TriangulateEstuary ( EdgeVertices e1, EdgeVertices e2, bool incomingRiver ) { … }
Kami akan meneruskan informasi ini saat memanggil metode ini dari TriangulateWaterShore
. if (cell.HasRiverThroughEdge(direction)) { TriangulateEstuary(e1, e2, cell.IncomingRiver == direction); }
Sekarang kita perlu memperluas aliran sungai dengan mengubah koordinat UV2. Koordinat U untuk sungai keluar perlu dicerminkan: −0.5 menjadi 1.5, 0 menjadi 1, 1 menjadi 0, dan 1.5 menjadi −0.5.Dengan koordinat V, segalanya menjadi sedikit lebih rumit. Jika Anda melihat bagaimana kami bekerja dengan koneksi sungai terbalik, maka 0,8 harus menjadi 0, dan 1 harus −0,2. Ini berarti bahwa 1,1 menjadi −0,3, dan 1,15 menjadi −0,35.Karena dalam setiap kasus koordinat UV2 sangat berbeda, mari kita menulis kode terpisah untuk mereka. void TriangulateEstuary ( EdgeVertices e1, EdgeVertices e2, bool incomingRiver ) { … if (incomingRiver) { estuaries.AddQuadUV2( new Vector2(1.5f, 1f), new Vector2(0.7f, 1.15f), new Vector2(1f, 0.8f), new Vector2(0.5f, 1.1f) ); estuaries.AddTriangleUV2( new Vector2(0.5f, 1.1f), new Vector2(1f, 0.8f), new Vector2(0f, 0.8f) ); estuaries.AddQuadUV2( new Vector2(0.5f, 1.1f), new Vector2(0.3f, 1.15f), new Vector2(0f, 0.8f), new Vector2(-0.5f, 1f) ); } else { estuaries.AddQuadUV2( new Vector2(-0.5f, -0.2f), new Vector2(0.3f, -0.35f), new Vector2(0f, 0f), new Vector2(0.5f, -0.3f) ); estuaries.AddTriangleUV2( new Vector2(0.5f, -0.3f), new Vector2(0f, 0f), new Vector2(1f, 0f) ); estuaries.AddQuadUV2( new Vector2(0.5f, -0.3f), new Vector2(0.7f, -0.35f), new Vector2(1f, 0f), new Vector2(1.5f, -0.2f) ); } }
Rute sungai yang benar.paket unityBagian 9: fitur bantuan
- Tambahkan objek ke relief.
- Kami membuat dukungan untuk tingkat kepadatan objek.
- Kami menggunakan berbagai objek di tingkat.
- Campurkan tiga jenis objek.
Pada bagian ini kita akan berbicara tentang menambahkan objek ke medan. Kami akan membuat objek seperti bangunan dan pohon.Konflik antara hutan, lahan pertanian dan urbanisasi.Tambahkan dukungan untuk objek
Meskipun bentuk reliefnya bervariasi, sejauh ini tidak ada yang terjadi. Ini adalah negeri yang tak bernyawa. Untuk menghembuskan kehidupan ke dalamnya, Anda perlu menambahkan benda-benda tersebut. seperti pohon dan rumah. Objek-objek ini bukan bagian dari relief mesh, tetapi akan menjadi objek yang terpisah. Tapi ini tidak menghentikan kita untuk menambahkannya saat melakukan triangulasi medan.HexGridChunk
Tidak peduli bagaimana mesh bekerja. Dia hanya memerintahkan salah satu anaknya untuk HexMesh
menambahkan segitiga atau quad. Demikian pula, ia dapat memiliki elemen anak yang berhubungan dengan penempatan objek pada mereka.Manajer Obyek
Mari kita membuat komponen HexFeatureManager
yang menangani objek dalam satu fragmen. Kami menggunakan skema yang sama dengan HexMesh
metode yang diberikan padanya Clear
, Apply
dan AddFeature
. Karena objek perlu ditempatkan di suatu tempat, metode AddFeature
menerima parameter posisi.Kami akan mulai dengan implementasi kosong yang tidak akan melakukan apa pun untuk saat ini. using UnityEngine; public class HexFeatureManager : MonoBehaviour { public void Clear () {} public void Apply () {} public void AddFeature (Vector3 position) {} }
Sekarang kita dapat menambahkan tautan ke komponen semacam itu di HexGridChunk
. Kemudian Anda bisa memasukkannya dalam proses triangulasi, seperti semua elemen anak HexMesh
. public HexFeatureManager features; public void Triangulate () { terrain.Clear(); rivers.Clear(); roads.Clear(); water.Clear(); waterShore.Clear(); estuaries.Clear(); features.Clear(); for (int i = 0; i < cells.Length; i++) { Triangulate(cells[i]); } terrain.Apply(); rivers.Apply(); roads.Apply(); water.Apply(); waterShore.Apply(); estuaries.Apply(); features.Apply(); }
Mari kita mulai dengan menempatkan satu objek di tengah setiap sel void Triangulate (HexCell cell) { for (HexDirection d = HexDirection.NE; d <= HexDirection.NW; d++) { Triangulate(d, cell); } features.AddFeature(cell.Position); }
Sekarang kita membutuhkan manajer objek nyata. Tambahkan anak lain ke prefab Hex Grid Chunk dan berikan komponen HexFeatureManager
. Kemudian Anda dapat menghubungkan sebuah fragmen ke sana.Manajer objek ditambahkan ke prefab fragmen.Objek cetakan
Objek medan apa yang akan kita buat? Untuk tes pertama, sebuah kubus sangat cocok. Mari kita buat kubus yang cukup besar, misalnya, dengan skala (3, 3, 3), dan mengubahnya menjadi cetakan. Juga buat materi untuknya. Saya menggunakan bahan standar dengan warna merah. Mari kita hapus collider-nya, karena kita tidak membutuhkannya.Cetakan rumah pabrikanManajer objek akan memerlukan tautan ke cetakan ini, jadi tambahkan ke HexFeatureManager
lalu sambungkan. Karena akses ke komponen transformasi diperlukan untuk menempatkan objek, kami menggunakannya sebagai jenis tautan. public Transform featurePrefab;
Manajer objek dengan cetakan.Membuat instance objek
Strukturnya sudah siap, dan kita dapat mulai menambahkan fitur terain! Cukup buat instance prefab di HexFeatureManager.AddFeature
dan atur posisinya. public void AddFeature (Vector3 position) { Transform instance = Instantiate(featurePrefab); instance.localPosition = position; }
Contoh fitur medan.Mulai sekarang, medan akan diisi dengan kubus. Setidaknya bagian atas kubus, karena asal lokal untuk kubus kubus di Unity adalah di tengah kubus, dan bagian bawah berada di bawah permukaan relief. Untuk menempatkan kubus pada topografi, kita perlu memindahkannya setinggi setengahnya. public void AddFeature (Vector3 position) { Transform instance = Instantiate(featurePrefab); position.y += instance.localScale.y * 0.5f; instance.localPosition = position; }
Kubus di permukaan relief.Bagaimana jika kita menggunakan mesh lain?. , , . .
Tentu saja, sel kita terdistorsi, jadi kita perlu mengubah posisi objek. Jadi kami menyingkirkan pengulangan mesh sempurna. instance.localPosition = HexMetrics.Perturb(position);
Posisi objek terdistorsi.Penghancuran benda-benda bantuan
Setiap kali sebuah fragmen diperbarui, kami membuat objek bantuan baru. Ini berarti bahwa saat kita membuat lebih banyak objek di posisi yang sama. Untuk menghindari duplikat, kita perlu menyingkirkan benda-benda tua saat membersihkan sebuah fragmen.Cara tercepat untuk melakukan ini adalah dengan membuat objek wadah permainan dan mengubah semua benda bantuan menjadi anak-anaknya. Kemudian, ketika dipanggil, Clear
kami akan menghancurkan wadah ini dan membuat yang baru. Wadah itu sendiri akan menjadi anak dari manajernya. Transform container; public void Clear () { if (container) { Destroy(container.gameObject); } container = new GameObject("Features Container").transform; container.SetParent(transform, false); } … public void AddFeature (Vector3 position) { Transform instance = Instantiate(featurePrefab); position.y += instance.localScale.y * 0.5f; instance.localPosition = HexMetrics.Perturb(position); instance.SetParent(container, false); }
Mungkin, tidak efisien untuk membuat dan menghancurkan objek bantuan setiap saat., , . . . , , , . HexFeatureManager.Apply
. . , , .
paket unityPenempatan benda bantuan
Sementara kita menempatkan benda di tengah setiap sel. Untuk sel kosong, ini terlihat normal, tetapi pada sel yang berisi sungai dan jalan, serta dibanjiri air, tampaknya aneh.Objek ada di mana-mana.Karena itu, mari kita periksa sebelum menempatkan objek HexGridChunk.Triangulate
apakah selnya kosong. if (!cell.IsUnderwater && !cell.HasRiver && !cell.HasRoads) { features.AddFeature(cell.Position); }
Akomodasi terbatas.Satu objek per arah
Hanya satu objek per sel yang tidak terlalu banyak. Masih ada banyak ruang untuk banyak objek. Oleh karena itu, kami menambahkan objek tambahan ke pusat masing-masing dari enam segitiga sel, yaitu, satu per arah.Kami akan melakukan ini dengan metode lain Triangulate
, ketika kami tahu bahwa tidak ada sungai di dalam sel. Kita masih perlu memeriksa apakah kita berada di bawah air dan apakah ada jalan di dalam sel. Namun dalam hal ini, kami hanya tertarik pada jalan yang menuju ke arah saat ini. void Triangulate (HexDirection direction, HexCell cell) { … if (cell.HasRiver) { … } else { TriangulateWithoutRiver(direction, cell, center, e); if (!cell.IsUnderwater && !cell.HasRoadThroughEdge(direction)) { features.AddFeature((center + e.v1 + e.v5) * (1f / 3f)); } } … }
Banyak fasilitas, tetapi tidak di sekitar sungai.Ini menciptakan lebih banyak objek! Mereka muncul di dekat jalan, tetapi masih menghindari sungai. Untuk menempatkan objek di sepanjang sungai, kita juga bisa menambahkannya di dalam TriangulateAdjacentToRiver
. Tetapi sekali lagi hanya ketika segitiga tidak di bawah air dan tidak ada jalan di atasnya. void TriangulateAdjacentToRiver ( HexDirection direction, HexCell cell, Vector3 center, EdgeVertices e ) { … if (!cell.IsUnderwater && !cell.HasRoadThroughEdge(direction)) { features.AddFeature((center + e.v1 + e.v5) * (1f / 3f)); } }
Objek muncul di sebelah sungai.Apakah mungkin membuat begitu banyak objek?, dynamic batching Unity. , . batch. « », . instancing, dynamic batching.
paket unityBerbagai benda
Semua benda bantuan kita memiliki orientasi yang sama, yang terlihat sangat tidak wajar. Mari kita berikan masing-masing sentuhan acak. public void AddFeature (Vector3 position) { Transform instance = Instantiate(featurePrefab); position.y += instance.localScale.y * 0.5f; instance.localPosition = HexMetrics.Perturb(position); instance.localRotation = Quaternion.Euler(0f, 360f * Random.value, 0f); instance.SetParent(container, false); }
Belokan acak.Jadi hasilnya menjadi jauh lebih beragam. Sayangnya, setiap kali sebuah fragmen diperbarui, objek menerima rotasi acak baru. Mengedit sel seharusnya tidak mengubah objek di lingkungan, jadi kita perlu pendekatan yang berbeda.Kami memiliki tekstur suara yang selalu sama. Namun, tekstur ini mengandung noise gradien Perlin, dan konsisten secara lokal. Inilah yang kita butuhkan saat mendistorsi posisi simpul dalam sel. Namun belokan tidak harus konsisten. Semua belokan harus sama kemungkinan dan campurannya. Oleh karena itu, kita memerlukan tekstur dengan nilai acak non-gradien, yang dapat disampel tanpa penyaringan bilinear. Pada dasarnya, ini adalah kotak hash yang membentuk dasar dari kebisingan gradien.Membuat tabel hash
Kita bisa membuat tabel hash dari array nilai float dan mengisinya sekali dengan nilai acak. Berkat ini, kami tidak perlu tekstur sama sekali. Mari kita tambahkan HexMetrics
. Ukuran 256 oleh 256 sudah cukup untuk variasi yang cukup. public const int hashGridSize = 256; static float[] hashGrid; public static void InitializeHashGrid () { hashGrid = new float[hashGridSize * hashGridSize]; for (int i = 0; i < hashGrid.Length; i++) { hashGrid[i] = Random.value; } }
Nilai acak dihasilkan oleh rumus matematika yang selalu memberikan hasil yang sama. Urutan yang dihasilkan tergantung pada jumlah benih, yang secara default sama dengan nilai waktu saat ini. Itulah sebabnya di setiap sesi permainan kami akan mendapatkan hasil yang berbeda.Untuk memastikan bahwa objek yang identik selalu diciptakan kembali, kita perlu menambahkan parameter seed ke metode inisialisasi. public static void InitializeHashGrid (int seed) { hashGrid = new float[hashGridSize * hashGridSize]; Random.InitState(seed); for (int i = 0; i < hashGrid.Length; i++) { hashGrid[i] = Random.value; } }
Sekarang kita telah menginisialisasi aliran angka acak, kita akan selalu mendapatkan urutan yang sama dari itu. Oleh karena itu, kejadian yang tampaknya acak terjadi setelah generasi peta juga akan selalu sama. Kita dapat menghindari ini dengan menyimpan keadaan generator angka acak sebelum menginisialisasi itu. Setelah menyelesaikan pekerjaan, kita bisa menanyakan status lama. Random.State currentState = Random.state; Random.InitState(seed); for (int i = 0; i < hashGrid.Length; i++) { hashGrid[i] = Random.value; } Random.state = currentState;
Tabel hash diinisialisasi HexGrid
pada saat yang sama yang memberikan tekstur noise. Artinya, dalam metode HexGrid.Start
dan HexGrid.Awake
. Kami membuatnya sehingga nilai-nilai tidak dihasilkan lebih sering dari yang diperlukan. public int seed; void Awake () { HexMetrics.noiseSource = noiseSource; HexMetrics.InitializeHashGrid(seed); … } void OnEnable () { if (!HexMetrics.noiseSource) { HexMetrics.noiseSource = noiseSource; HexMetrics.InitializeHashGrid(seed); } }
Variabel seed generik memungkinkan kita untuk memilih nilai seed untuk peta. Nilai apa pun akan berlaku. Saya memilih 1234.Pilihan benih.Menggunakan tabel hash
Untuk menggunakan tabel hash, tambahkan ke HexMetrics
metode pengambilan sampel. Seperti SampleNoise
, ia menggunakan koordinat posisi XZ untuk mendapatkan nilai. Indeks hash ditemukan dengan membatasi koordinat ke nilai integer, dan kemudian mendapatkan sisa dari pembagian integer dengan ukuran tabel. public static float SampleHashGrid (Vector3 position) { int x = (int)position.x % hashGridSize; int z = (int)position.z % hashGridSize; return hashGrid[x + z * hashGridSize]; }
Apa yang dilakukan%?, , — . , −4, −3, −2, −1, 0, 1, 2, 3, 4 modulo 3 −1, 0, −2, −1, 0, 1, 2, 0, 1.
Ini berfungsi untuk koordinat positif, tetapi tidak untuk yang negatif, karena untuk angka seperti itu sisanya akan negatif. Kita dapat memperbaikinya dengan menambahkan ukuran tabel ke hasil negatif. int x = (int)position.x % hashGridSize; if (x < 0) { x += hashGridSize; } int z = (int)position.z % hashGridSize; if (z < 0) { z += hashGridSize; }
Sekarang untuk setiap unit persegi kita menciptakan nilai kita sendiri. Namun, pada kenyataannya, kita tidak perlu kepadatan meja seperti itu. Objek ditempatkan terpisah satu sama lain. Kami dapat meregangkan tabel dengan mengurangi skala posisi sebelum menghitung indeks. Satu nilai unik untuk kotak 4 x 4 akan cukup bagi kami. public const float hashGridScale = 0.25f; public static float SampleHashGrid (Vector3 position) { int x = (int)(position.x * hashGridScale) % hashGridSize; if (x < 0) { x += hashGridSize; } int z = (int)(position.z * hashGridScale) % hashGridSize; if (z < 0) { z += hashGridSize; } return hashGrid[x + z * hashGridSize]; }
Mari kita kembali ke HexFeatureManager.AddFeature
dan menggunakan tabel hash baru kami untuk mendapatkan nilai. Setelah kami menerapkannya untuk menentukan rotasi, objek akan tetap diam saat mengedit medan. public void AddFeature (Vector3 position) { float hash = HexMetrics.SampleHashGrid(position); Transform instance = Instantiate(featurePrefab); position.y += instance.localScale.y * 0.5f; instance.localPosition = HexMetrics.Perturb(position); instance.localRotation = Quaternion.Euler(0f, 360f * hash, 0f); instance.SetParent(container, false); }
Ambang penempatan
Meskipun objek memiliki rotasi yang berbeda, suatu pola masih terlihat dalam penempatannya. Setiap sel memiliki tujuh objek. Kita dapat menambahkan kekacauan pada skema ini, secara acak melewatkan beberapa objek. Bagaimana kita memutuskan apakah akan menambahkan objek atau tidak? Tentu saja, memeriksa nilai acak lain!Artinya, sekarang, alih-alih satu nilai hash, kita perlu dua. Dukungan mereka dapat ditambahkan dengan menggunakan hash alih-alih float
variabel sebagai tipe array tabel Vector2
. Tetapi operasi vektor tidak masuk akal untuk nilai hash, jadi mari kita buat struktur khusus untuk tujuan ini. Dia hanya membutuhkan dua nilai float. Dan mari kita tambahkan metode statis untuk membuat sepasang nilai acak. using UnityEngine; public struct HexHash { public float a, b; public static HexHash Create () { HexHash hash; hash.a = Random.value; hash.b = Random.value; return hash; } }
Tidakkah harus diserialkan?, , Unity. , .
Ubahlah HexMetrics
sehingga menggunakan struktur baru. static HexHash[] hashGrid; public static void InitializeHashGrid (int seed) { hashGrid = new HexHash[hashGridSize * hashGridSize]; Random.State currentState = Random.state; Random.InitState(seed); for (int i = 0; i < hashGrid.Length; i++) { hashGrid[i] = HexHash.Create(); } Random.state = currentState; } public static HexHash SampleHashGrid (Vector3 position) { … }
Sekarang HexFeatureManager.AddFeature
memiliki akses ke dua nilai hash. Mari kita gunakan yang pertama untuk memutuskan apakah akan menambahkan objek, atau melewatkannya. Jika nilainya sama dengan atau lebih besar dari 0,5, maka lewati. Dengan melakukan itu, kita akan menyingkirkan sekitar setengah dari objek. Nilai kedua akan digunakan seperti biasa untuk menentukan rotasi. public void AddFeature (Vector3 position) { HexHash hash = HexMetrics.SampleHashGrid(position); if (hash.a >= 0.5f) { return; } Transform instance = Instantiate(featurePrefab); position.y += instance.localScale.y * 0.5f; instance.localPosition = HexMetrics.Perturb(position); instance.localRotation = Quaternion.Euler(0f, 360f * hash.b, 0f); instance.SetParent(container, false); }
Kepadatan benda berkurang hingga 50%.paket unityMenggambar objek
Alih-alih menempatkan objek di mana-mana, mari kita membuatnya dapat diedit. Tapi kami tidak akan menggambar objek yang terpisah, tetapi menambahkan level objek ke setiap sel. Level ini akan mengendalikan kemungkinan benda muncul di dalam sel. Secara default, nilainya nol, yaitu objek tidak akan ada.Karena batu merah di medan kita tidak terlihat seperti benda alami, sebut saja itu bangunan. Mereka akan mewakili urbanisasi. Mari tambahkan ke HexCell
tingkat urbanisasi. public int UrbanLevel { get { return urbanLevel; } set { if (urbanLevel != value) { urbanLevel = value; RefreshSelfOnly(); } } } int urbanLevel;
Kita dapat membuat tingkat urbanisasi untuk sel bawah air sama dengan nol, tetapi ini tidak perlu, kita melewatkan pembuatan objek bawah air. Dan mungkin di beberapa titik kita akan menambahkan badan air urbanisasi, seperti dermaga dan struktur bawah laut.Slider kepadatan
Untuk mengubah tingkat urbanisasi, kami menambahkan HexMapEditor
satu lagi slider untuk mendukung. int activeUrbanLevel; … bool applyUrbanLevel; … public void SetApplyUrbanLevel (bool toggle) { applyUrbanLevel = toggle; } public void SetUrbanLevel (float level) { activeUrbanLevel = (int)level; } void EditCell (HexCell cell) { if (cell) { … if (applyWaterLevel) { cell.WaterLevel = activeWaterLevel; } if (applyUrbanLevel) { cell.UrbanLevel = activeUrbanLevel; } if (riverMode == OptionalToggle.No) { cell.RemoveRiver(); } … } }
Tambahkan slider lain ke UI dan gabungkan dengan metode yang sesuai. Saya akan menempatkan panel baru di sisi kanan layar untuk menghindari meluapnya panel kiri.Berapa level yang kita butuhkan? Mari kita bahas empat, yang menunjukkan kepadatan nol, rendah, sedang, dan tinggi.Penggeser urbanisasi.Perubahan ambang batas
Sekarang kita memiliki tingkat urbanisasi, kita perlu menggunakannya untuk menentukan apakah akan menempatkan objek. Untuk melakukan ini, kita perlu menambahkan tingkat urbanisasi sebagai parameter tambahan HexFeatureManager.AddFeature
. Mari kita selangkah lagi dan transfer saja sel itu sendiri. Di masa depan, itu akan lebih nyaman bagi kita.Cara tercepat untuk menggunakan tingkat urbanisasi adalah dengan mengalikannya dengan 0,25 dan menggunakan nilai sebagai ambang baru untuk melewatkan objek. Karena ini, probabilitas penampilan objek akan meningkat dengan setiap level sebesar 25%. public void AddFeature (HexCell cell, Vector3 position) { HexHash hash = HexMetrics.SampleHashGrid(position); if (hash.a >= cell.UrbanLevel * 0.25f) { return; } … }
Agar ini bekerja, mari kita sel untuk HexGridChunk
. void Triangulate (HexCell cell) { … if (!cell.IsUnderwater && !cell.HasRiver && !cell.HasRoads) { features.AddFeature(cell, cell.Position); } } void Triangulate (HexDirection direction, HexCell cell) { … if (!cell.IsUnderwater && !cell.HasRoadThroughEdge(direction)) { features.AddFeature(cell, (center + e.v1 + e.v5) * (1f / 3f)); } … } … void TriangulateAdjacentToRiver ( HexDirection direction, HexCell cell, Vector3 center, EdgeVertices e ) { … if (!cell.IsUnderwater && !cell.HasRoadThroughEdge(direction)) { features.AddFeature(cell, (center + e.v1 + e.v5) * (1f / 3f)); } }
Menggambar tingkat kepadatan urbanisasi.paket unityBeberapa cetakan benda bantuan
Perbedaan dalam kemungkinan objek muncul tidak cukup untuk membuat pemisahan yang jelas antara urbanisasi tingkat rendah dan tinggi. Dalam beberapa sel, hanya akan ada lebih atau kurang dari jumlah bangunan yang diharapkan. Kita dapat membuat perbedaan lebih jelas dengan menggunakan prefab kita sendiri untuk setiap level.Kami menyingkirkan bidang featurePrefab
di HexFeatureManager
dan menggantinya dengan array Rak itan urbanisasi. Untuk mendapatkan prefab yang sesuai, kami akan mengurangi satu dari tingkat urbanisasi dan menggunakan nilai sebagai indeks. <del>
Buat dua duplikat cetakan dari objek, ganti nama dan ubahlah sehingga menunjukkan tiga tingkat urbanisasi yang berbeda. Level 1 adalah kepadatan rendah, jadi kami menggunakan kubus dengan panjang satuan tepi, yang menunjukkan gubuk. Saya akan skala prefab level 2 ke (1,5, 2, 1,5) sehingga terlihat seperti bangunan dua lantai. Untuk bangunan level 3 tinggi, saya menggunakan skala (2, 5, 2).Menggunakan cetakan yang berbeda untuk setiap tingkat urbanisasi.Prefab Mix
Kami tidak diharuskan membatasi diri pada pemisahan jenis-jenis bangunan secara ketat. Anda dapat mencampurnya sedikit, seperti yang terjadi di dunia nyata. Alih-alih satu ambang per level, mari kita gunakan tiga, satu untuk setiap jenis bangunan.Pada level 1 kami menggunakan penempatan gubuk di 40% kasus. Tidak akan ada bangunan lain di sini sama sekali. Untuk level kami menggunakan tiga nilai (0,4, 0, 0).Di level 2, ganti gubuk dengan bangunan yang lebih besar, dan tambahkan peluang 20% untuk gubuk tambahan. Kami tidak akan melakukan gedung tinggi. Artinya, kami menggunakan ambang tiga nilai (0,2, 0,4, 0).Pada level 3, kami mengganti bangunan sedang dengan gedung tinggi, mengganti gubuk lagi, dan menambahkan 20% kemungkinan gubuk lainnya. Nilai ambang akan sama dengan (0,2, 0,2, 0,4).Artinya, idenya adalah bahwa dengan meningkatnya tingkat urbanisasi, kami akan meningkatkan bangunan yang ada dan menambahkan yang baru ke tempat-tempat kosong. Untuk menghapus bangunan yang ada, kita perlu menggunakan interval nilai hash yang sama. Jika hash antara 0 dan 0,4 di level 1 adalah gubuk, maka pada level 3 interval yang sama akan membuat bangunan tinggi. Pada level 3, bangunan tinggi harus dibuat dengan hash di kisaran 0-0,4, bangunan dua lantai di kisaran 0,4-0,6, dan gubuk di kisaran 0,6-0,8. Jika Anda memeriksanya dari yang terbesar ke yang terkecil, maka ini dapat dilakukan dengan menggunakan triple of threshold (0,4, 0,6, 0,8). Ambang level 2 akan menjadi (0, 0.4, 0.6), dan ambang level 1 akan menjadi (0, 0, 0.4).Mari kita simpan ambang ini diHexMetrics
sebagai kumpulan array dengan metode yang memungkinkan Anda untuk mendapatkan ambang batas untuk tingkat tertentu. Karena kita hanya tertarik pada level dengan objek, kita mengabaikan level 0. static float[][] featureThresholds = { new float[] {0.0f, 0.0f, 0.4f}, new float[] {0.0f, 0.4f, 0.6f}, new float[] {0.4f, 0.6f, 0.8f} }; public static float[] GetFeatureThresholds (int level) { return featureThresholds[level]; }
Selanjutnya, tambahkan ke HexFeatureManager
metode yang menggunakan tingkat dan nilai hash untuk memilih cetakan. Jika level lebih besar dari nol, maka kita mendapatkan ambang menggunakan level yang dikurangi satu. Lalu kami menggilir melalui ambang batas hingga salah satu dari mereka melebihi nilai hash. Ini berarti bahwa kami telah menemukan cetakan. Jika kami tidak menemukan, maka kembalikan nol. Transform PickPrefab (int level, float hash) { if (level > 0) { float[] thresholds = HexMetrics.GetFeatureThresholds(level - 1); for (int i = 0; i < thresholds.Length; i++) { if (hash < thresholds[i]) { return urbanPrefabs[i]; } } } return null; }
Pendekatan ini membutuhkan penataan ulang tautan ke prefab sehingga mereka beralih dari kepadatan tinggi ke rendah.Pesanan cetakan awal terbalik.Kami akan menggunakan metode baru kami AddFeature
untuk memilih cetakan. Jika kita tidak menerimanya, maka kita melewatkan objek. Kalau tidak, buat instance dan lanjutkan seperti sebelumnya. public void AddFeature (HexCell cell, Vector3 position) { HexHash hash = HexMetrics.SampleHashGrid(position);
Campur cetakan.Variasi level
Sekarang kami memiliki bangunan campuran, tetapi sejauh ini hanya ada tiga. Kami selanjutnya dapat meningkatkan variabilitas dengan menghubungkan koleksi prefab ke setiap tingkat kepadatan urbanisasi. Setelah itu, dimungkinkan untuk memilih salah satunya secara acak. Ini akan membutuhkan nilai acak baru, jadi tambahkan ketiga c HexHash
. public float a, b, c; public static HexHash Create () { HexHash hash; hash.a = Random.value; hash.b = Random.value; hash.c = Random.value; return hash; }
Mari mengubahnya HexFeatureManager.urbanPrefabs
menjadi array array, dan menambahkan PickPrefab
parameter ke metode choice
. Kami menggunakannya untuk memilih indeks array bawaan, mengalikannya dengan panjang array ini dan mengubahnya menjadi integer. public Transform[][] urbanPrefabs; … Transform PickPrefab (int level, float hash, float choice) { if (level > 0) { float[] thresholds = HexMetrics.GetFeatureThresholds(level - 1); for (int i = 0; i < thresholds.Length; i++) { if (hash < thresholds[i]) { return urbanPrefabs[i][(int)(choice * urbanPrefabs[i].Length)]; } } } return null; }
Mari kita benarkan pilihan kita pada nilai hash kedua (B). Maka Anda perlu beralih dari B ke C. public void AddFeature (HexCell cell, Vector3 position) { HexHash hash = HexMetrics.SampleHashGrid(position); Transform prefab = PickPrefab(cell.UrbanLevel, hash.a, hash.b); if (!prefab) { return; } Transform instance = Instantiate(prefab); position.y += instance.localScale.y * 0.5f; instance.localPosition = HexMetrics.Perturb(position); instance.localRotation = Quaternion.Euler(0f, 360f * hash.c, 0f); instance.SetParent(container, false); }
Sebelum melanjutkan, kita perlu mempertimbangkan apa yang Random.value
mungkin mengembalikan nilai 1. Karena ini, indeks array mungkin melampaui. Untuk mencegah hal ini terjadi, mari kita skalakan nilai hash. Kami cukup mengatur semuanya agar tidak khawatir tentang spesifik yang kami gunakan. public static HexHash Create () { HexHash hash; hash.a = Random.value * 0.999f; hash.b = Random.value * 0.999f; hash.c = Random.value * 0.999f; return hash; }
Sayangnya, inspektur tidak menampilkan array array. Karena itu, kami tidak dapat mengonfigurasinya. Untuk menyiasati batasan ini, buat struktur berseri yang dapat digunakan untuk mengenkapsulasi array bawaan. Mari kita beri dia metode yang mengubah dari pilihan ke indeks array dan mengembalikan cetakan. using UnityEngine; [System.Serializable] public struct HexFeatureCollection { public Transform[] prefabs; public Transform Pick (float choice) { return prefabs[(int)(choice * prefabs.Length)]; } }
Kami menggunakan di HexFeatureManager
tempat array built-in array koleksi seperti itu.
Sekarang kita dapat menetapkan beberapa bangunan untuk setiap tingkat kepadatan. Karena mereka independen, kita tidak harus menggunakan jumlah yang sama per level. Saya hanya menggunakan dua opsi per level, menambahkan opsi yang lebih rendah lagi untuk masing-masing. Saya memilih timbangan (3,5, 3, 2), (2,75, 1,5, 1,5) dan (1,75, 1, 1) untuk mereka.Dua jenis bangunan per tingkat kepadatan.paket unityBeberapa jenis benda
Dalam skema yang ada, kita dapat membuat struktur perkotaan yang cukup layak. Namun, relief itu mungkin tidak hanya berisi bangunan. Bagaimana dengan pertanian atau tanaman? Mari kita tambahkan ke HexCell
level dan untuk mereka. Mereka tidak saling eksklusif dan dapat bercampur. public int FarmLevel { get { return farmLevel; } set { if (farmLevel != value) { farmLevel = value; RefreshSelfOnly(); } } } public int PlantLevel { get { return plantLevel; } set { if (plantLevel != value) { plantLevel = value; RefreshSelfOnly(); } } } int urbanLevel, farmLevel, plantLevel;
Tentu saja, ini membutuhkan dukungan dalam HexMapEditor
dua slider tambahan. int activeUrbanLevel, activeFarmLevel, activePlantLevel; bool applyUrbanLevel, applyFarmLevel, applyPlantLevel; … public void SetApplyFarmLevel (bool toggle) { applyFarmLevel = toggle; } public void SetFarmLevel (float level) { activeFarmLevel = (int)level; } public void SetApplyPlantLevel (bool toggle) { applyPlantLevel = toggle; } public void SetPlantLevel (float level) { activePlantLevel = (int)level; } … void EditCell (HexCell cell) { if (cell) { … if (applyUrbanLevel) { cell.UrbanLevel = activeUrbanLevel; } if (applyFarmLevel) { cell.FarmLevel = activeFarmLevel; } if (applyPlantLevel) { cell.PlantLevel = activePlantLevel; } … } }
Tambahkan mereka ke UI.Tiga slider.Juga, koleksi tambahan akan dibutuhkan HexFeatureManager
. public HexFeatureCollection[] urbanCollections, farmCollections, plantCollections;
Tiga koleksi benda bantuan.Saya membuat untuk kedua pertanian dan tanaman dua cetakan per tingkat kepadatan, serta untuk membangun koleksi. Untuk mereka semua, saya menggunakan kubus. Peternakan memiliki bahan hijau muda, tanaman memiliki bahan hijau tua.Saya membuat kubus pertanian dengan ketinggian 0,1 unit untuk menunjukkan peruntukan lahan pertanian persegi. Sebagai skala kepadatan tinggi, saya memilih (2,5, 0,1, 2,5) dan (3,5, 0,1, 2). Rata-rata, situs memiliki luas 1,75 dan ukuran 2,5 kali 1,25. Tingkat kepadatan yang rendah diperoleh dengan luas 1 dan ukuran 1,5 x 0,75.Tanaman cetakan menunjukkan pohon tinggi dan semak besar. Pabrikan dengan kepadatan tinggi adalah yang terbesar, (1,25, 4,5, 1,25) dan (1,5, 3, 1,5). Skala rata-rata adalah (0,75, 3, 0,75) dan (1, 1,5, 1). Tanaman terkecil memiliki ukuran (0,5, 1,5, 0,5) dan (0,75, 1, 0,75).Pilihan fitur bantuan
Setiap jenis objek harus menerima nilai hash sendiri sehingga mereka memiliki pola kreasi yang berbeda dan Anda dapat mencampurnya. Tambahkan HexHash
dua nilai tambahan. public float a, b, c, d, e; public static HexHash Create () { HexHash hash; hash.a = Random.value * 0.999f; hash.b = Random.value * 0.999f; hash.c = Random.value * 0.999f; hash.d = Random.value * 0.999f; hash.e = Random.value * 0.999f; return hash; }
Sekarang Anda harus HexFeatureManager.PickPrefab
bekerja dengan koleksi yang berbeda. Tambahkan parameter untuk menyederhanakan proses. Juga, ubah hash yang digunakan oleh varian prefab yang dipilih menjadi D, dan hash untuk rotasi menjadi E. Transform PickPrefab ( HexFeatureCollection[] collection, int level, float hash, float choice ) { if (level > 0) { float[] thresholds = HexMetrics.GetFeatureThresholds(level - 1); for (int i = 0; i < thresholds.Length; i++) { if (hash < thresholds[i]) { return collection[i].Pick(choice); } } } return null; } public void AddFeature (HexCell cell, Vector3 position) { HexHash hash = HexMetrics.SampleHashGrid(position); Transform prefab = PickPrefab( urbanCollections, cell.UrbanLevel, hash.a, hash.d ); … instance.localRotation = Quaternion.Euler(0f, 360f * hash.e, 0f); instance.SetParent(container, false); }
Saat ini AddFeature
memilih urbanisasi prefab. Ini normal, kami membutuhkan lebih banyak opsi. Oleh karena itu, kami menambahkan cetakan lain dari peternakan. Sebagai nilai hash, gunakan B. Pilihan opsi lagi akan menjadi D. Transform prefab = PickPrefab( urbanCollections, cell.UrbanLevel, hash.a, hash.d ); Transform otherPrefab = PickPrefab( farmCollections, cell.FarmLevel, hash.b, hash.d ); if (!prefab) { return; }
Contoh cetakan apa yang akan kita buat sebagai hasilnya? Jika salah satu dari mereka ternyata nol, maka pilihannya jelas. Namun, jika keduanya ada, maka kita perlu membuat keputusan. Mari kita tambahkan prefab dengan nilai hash terendah. Transform otherPrefab = PickPrefab( farmCollections, cell.FarmLevel, hash.b, hash.d ); if (prefab) { if (otherPrefab && hash.b < hash.a) { prefab = otherPrefab; } } else if (otherPrefab) { prefab = otherPrefab; } else { return; }
Campuran objek perkotaan dan pedesaan.Selanjutnya, lakukan hal yang sama dengan tanaman menggunakan nilai hash C. if (prefab) { if (otherPrefab && hash.b < hash.a) { prefab = otherPrefab; } } else if (otherPrefab) { prefab = otherPrefab; } otherPrefab = PickPrefab( plantCollections, cell.PlantLevel, hash.c, hash.d ); if (prefab) { if (otherPrefab && hash.c < hash.a) { prefab = otherPrefab; } } else if (otherPrefab) { prefab = otherPrefab; } else { return; }
Namun, kami tidak bisa hanya menyalin kode. Ketika kita memilih pedesaan daripada objek perkotaan, kita perlu membandingkan hash tanaman dengan hash pertanian, dan bukan dengan urban. Oleh karena itu, kita perlu melacak hash yang kita putuskan untuk memilih dan membandingkannya. float usedHash = hash.a; if (prefab) { if (otherPrefab && hash.b < hash.a) { prefab = otherPrefab; usedHash = hash.b; } } else if (otherPrefab) { prefab = otherPrefab; usedHash = hash.b; } otherPrefab = PickPrefab( plantCollections, cell.PlantLevel, hash.c, hash.d ); if (prefab) { if (otherPrefab && hash.c < usedHash) { prefab = otherPrefab; } } else if (otherPrefab) { prefab = otherPrefab; } else { return; }
Campuran objek perkotaan, pedesaan dan tanaman.paket unityBagian 10: dinding
- Kami melampirkan sel.
- Kami membangun dinding di sepanjang tepi sel.
- Mari kita melewati sungai dan jalan.
- Hindari air dan hubungkan dengan tebing.
Pada bagian ini kita akan menambahkan antara sel-sel dinding.Tidak ada yang lebih menarik daripada tembok tinggi.Editing Dinding
Untuk mendukung dinding, kita perlu tahu di mana menempatkannya. Kami akan menempatkan mereka di antara sel di sepanjang tepi yang menghubungkannya. Karena benda yang sudah ada terletak di bagian tengah sel, kita tidak perlu khawatir bahwa dinding akan melewatinya.Dinding di sepanjang tepi.Dinding adalah benda medan, meski besar. Seperti objek lain, kami tidak akan mengeditnya secara langsung. Sebagai gantinya, kami akan mengubah sel. Kami tidak akan memiliki segmen yang terpisah dari dinding, tetapi akan terlibat dalam melampirkan sel secara keseluruhan.Properti Bertembok
Untuk mendukung sel berpagar, tambahkan ke HexCell
properti Walled
. Ini adalah saklar sederhana. Karena dinding terletak di antara sel, kita perlu memperbarui sel yang diedit dan tetangganya. public bool Walled { get { return walled; } set { if (walled != value) { walled = value; Refresh(); } } } bool walled;
Switch editor
Untuk mengganti status "berpagar" sel, kita perlu menambahkan HexMapEditor
dukungan untuk sakelar. Oleh karena itu, kami menambahkan bidang lain OptionalToggle
dan metode untuk menyetelnya. OptionalToggle riverMode, roadMode, walledMode; … public void SetWalledMode (int mode) { walledMode = (OptionalToggle)mode; }
Tidak seperti sungai dan jalan, dinding tidak berpindah dari sel ke sel, tetapi berada di antara mereka. Karena itu, kita tidak perlu memikirkan drag and drop. Ketika sakelar dinding aktif, kita cukup mengatur keadaan berpagar sel saat ini berdasarkan keadaan sakelar ini. void EditCell (HexCell cell) { if (cell) { … if (roadMode == OptionalToggle.No) { cell.RemoveRoads(); } if (walledMode != OptionalToggle.Ignore) { cell.Walled = walledMode == OptionalToggle.Yes; } if (isDrag) { … } } }
Kami menduplikasi salah satu elemen sebelumnya dari sakelar UI dan mengubahnya sehingga mereka mengontrol keadaan "pagar". Saya akan menempatkan mereka di panel UI bersama dengan objek lain.Saklar "pagar".paket unityMenciptakan dinding
Karena dinding mengikuti kontur sel, mereka seharusnya tidak memiliki bentuk yang konstan. Karenanya, kami tidak dapat hanya menggunakan prefab untuknya, seperti yang kami lakukan dengan fitur terain lainnya. Alih-alih, kita perlu membangun jaring, seperti yang kita lakukan dengan bantuan. Ini berarti bahwa fragmen cetakan kami membutuhkan elemen anak lain HexMesh
. Gandakan salah satu jerat anak lainnya dan buat objek Walls yang baru memberi bayangan. Mereka tidak membutuhkan apa pun kecuali simpul dan segitiga, sehingga semua opsi HexMesh
harus dinonaktifkan.Dinding Rak Pabrikan Anak Perusahaan.Akan logis bahwa tembok itu adalah objek perkotaan, jadi bagi mereka saya menggunakan bahan merah bangunan.Manajemen dinding
Karena dinding adalah benda yang melegakan, mereka harus menghadapinya HexFeatureManager
. Oleh karena itu, kami akan memberikan pengelola objek bantuan tautan ke objek Walls , dan membuatnya memanggil metode Clear
dan Apply
. public HexMesh walls; … public void Clear () { … walls.Clear(); } public void Apply () { walls.Apply(); }
Dinding terhubung ke manajer topografi.Bukankah seharusnya Walls menjadi anak Fitur?, . , Walls Hex Grid Chunk .
Sekarang kita perlu menambahkan metode ke manajer yang memungkinkan kita menambahkan dinding ke dalamnya. Karena dinding berada di sepanjang tepi antara sel, ia perlu mengetahui simpul yang sesuai dari tepi dan sel. HexGridChunk
akan menyebabkannya melalui TriangulateConnection
, pada saat triangulasi sel dan salah satu tetangganya. Dari sudut pandang ini, sel saat ini berada di sisi dekat dinding, dan yang lain di sisi yang jauh. public void AddWall ( EdgeVertices near, HexCell nearCell, EdgeVertices far, HexCell farCell ) { }
Kami akan memanggil metode baru ini HexGridChunk.TriangulateConnection
setelah menyelesaikan semua pekerjaan penghubung lainnya dan segera sebelum transisi ke segitiga sudut. Kami akan membiarkan pengelola benda-benda bantuan memutuskan sendiri di mana tembok itu seharusnya berada. void TriangulateConnection ( HexDirection direction, HexCell cell, EdgeVertices e1 ) { … if (cell.GetEdgeType(direction) == HexEdgeType.Slope) { … } else { … } features.AddWall(e1, cell, e2, neighbor); HexCell nextNeighbor = cell.GetNeighbor(direction.Next()); if (direction <= HexDirection.E && nextNeighbor != null) { … } }
Bangun segmen dinding
Seluruh dinding akan berkelok-kelok melewati beberapa tepi sel. Setiap tepi hanya mengandung satu elemen dinding. Dari sudut pandang sel dekat, segmen dimulai di sisi kiri tulang rusuk dan berakhir di sisi kanan. Mari kita tambahkan ke HexFeatureManager
metode terpisah yang menggunakan empat simpul di sudut-sudut tepi. void AddWallSegment ( Vector3 nearLeft, Vector3 farLeft, Vector3 nearRight, Vector3 farRight ) { }
Sisi dekat dan jauh.AddWall
dapat memanggil metode ini dengan tepi pertama dan terakhir dari tepi. Tapi dinding hanya boleh ditambahkan ketika kita memiliki koneksi antara sel berpagar dan sel non-berpagar. Tidak masalah sel mana yang ada di dalam dan mana yang di luar, hanya perbedaan statusnya yang diperhitungkan. public void AddWall ( EdgeVertices near, HexCell nearCell, EdgeVertices far, HexCell farCell ) { if (nearCell.Walled != farCell.Walled) { AddWallSegment(near.v1, far.v1, near.v5, far.v5); } }
Segmen dinding yang paling sederhana adalah satu quad, berdiri di tengah tulang rusuk. Kami akan menemukan puncaknya yang lebih rendah, menyisipkan ke tengah dari yang terdekat ke puncak terjauh. void AddWallSegment ( Vector3 nearLeft, Vector3 farLeft, Vector3 nearRight, Vector3 farRight ) { Vector3 left = Vector3.Lerp(nearLeft, farLeft, 0.5f); Vector3 right = Vector3.Lerp(nearRight, farRight, 0.5f); }
Seberapa tinggi seharusnya tembok itu? Mari atur ketinggiannya HexMetrics
. Saya membuat mereka seukuran level sel satu. public const float wallHeight = 3f;
HexFeatureManager.AddWallSegment
dapat menggunakan ketinggian ini untuk memposisikan simpul ketiga dan keempat dari quad, dan juga menambahkannya ke jala walls
. Vector3 left = Vector3.Lerp(nearLeft, farLeft, 0.5f); Vector3 right = Vector3.Lerp(nearRight, farRight, 0.5f); Vector3 v1, v2, v3, v4; v1 = v3 = left; v2 = v4 = right; v3.y = v4.y = left.y + HexMetrics.wallHeight; walls.AddQuad(v1, v2, v3, v4);
Sekarang kita dapat mengedit dinding dan mereka akan ditampilkan sebagai garis quad. Namun, kita tidak akan melihat dinding yang berkelanjutan. Setiap quad hanya terlihat di satu sisi. Wajahnya diarahkan ke sel tempat ia ditambahkan.Dinding quad satu sisi.Kita dapat dengan cepat menyelesaikan masalah ini dengan menambahkan quad kedua yang menghadap ke arah lain. walls.AddQuad(v1, v2, v3, v4); walls.AddQuad(v2, v1, v4, v3);
Dinding bilateral.Sekarang semua dinding terlihat secara keseluruhan, tetapi masih ada lubang di sudut-sudut sel tempat ketiga sel bertemu. Kami akan mengisinya nanti.Dinding tebal
Meskipun dinding sudah terlihat di kedua sisi, mereka tidak memiliki ketebalan. Bahkan, dindingnya tipis, seperti kertas, dan nyaris tak terlihat pada sudut tertentu. Jadi mari kita membuatnya utuh dengan menambahkan ketebalan. Atur ketebalannya HexMetrics
. Saya memilih nilai 0,75 unit, menurut saya cocok. public const float wallThickness = 0.75f;
Untuk membuat dua dinding tebal, Anda harus membelah dua paha depan. Mereka harus bergerak ke arah yang berlawanan. Satu sisi harus bergerak ke arah tepi dekat, yang lain ke tepi jauh. Vektor offset untuk ini sama far - near
, tetapi untuk meninggalkan bagian atas dinding datar, kita perlu mengatur komponen Y menjadi 0.Karena ini perlu dilakukan untuk sisi kiri dan kanan segmen dinding, mari kita tambahkan HexMetrics
vektor offset ke metode untuk menghitung ini. public static Vector3 WallThicknessOffset (Vector3 near, Vector3 far) { Vector3 offset; offset.x = far.x - near.x; offset.y = 0f; offset.z = far.z - near.z; return offset; }
Agar dinding tetap berada di tengah tulang rusuk, jarak gerakan sebenarnya sepanjang vektor ini harus sama dengan setengah ketebalan untuk setiap sisi. Dan untuk memastikan bahwa kami benar-benar memindahkan jarak yang tepat, kami menormalkan vektor perpindahan sebelum menskalanya. return offset.normalized * (wallThickness * 0.5f);
Kami menggunakan metode ini HexFeatureManager.AddWallSegment
untuk mengubah posisi paha depan. Karena vektor perpindahan bergerak dari yang terdekat ke sel jauh, kurangi dari quad dekat dan tambahkan ke yang jauh. Vector3 left = Vector3.Lerp(nearLeft, farLeft, 0.5f); Vector3 right = Vector3.Lerp(nearRight, farRight, 0.5f); Vector3 leftThicknessOffset = HexMetrics.WallThicknessOffset(nearLeft, farLeft); Vector3 rightThicknessOffset = HexMetrics.WallThicknessOffset(nearRight, farRight); Vector3 v1, v2, v3, v4; v1 = v3 = left - leftThicknessOffset; v2 = v4 = right - rightThicknessOffset; v3.y = v4.y = left.y + HexMetrics.wallHeight; walls.AddQuad(v1, v2, v3, v4); v1 = v3 = left + leftThicknessOffset; v2 = v4 = right + rightThicknessOffset; v3.y = v4.y = left.y + HexMetrics.wallHeight; walls.AddQuad(v2, v1, v4, v3);
Dinding dengan offset.Paha depan sekarang bias, meskipun ini tidak sepenuhnya terlihat.Apakah ketebalan dindingnya sama?, «-» . , . . , . , . , - , . .
Bagian atas dinding
Untuk membuat ketebalan dinding terlihat dari atas, kita perlu menambahkan quad ke bagian atas dinding. Cara termudah untuk melakukan ini adalah dengan mengingat dua simpul atas dari quad pertama dan menghubungkannya dengan dua simpul atas dari quad kedua. Vector3 v1, v2, v3, v4; v1 = v3 = left - leftThicknessOffset; v2 = v4 = right - rightThicknessOffset; v3.y = v4.y = left.y + HexMetrics.wallHeight; walls.AddQuad(v1, v2, v3, v4); Vector3 t1 = v3, t2 = v4; v1 = v3 = left + leftThicknessOffset; v2 = v4 = right + rightThicknessOffset; v3.y = v4.y = left.y + HexMetrics.wallHeight; walls.AddQuad(v2, v1, v4, v3); walls.AddQuad(t1, t2, v3, v4);
Dinding dengan atasan.Menikung
Kami masih memiliki lubang di sudut sel. Untuk mengisinya, kita perlu menambahkan segmen ke area segitiga di antara sel. Setiap sudut menghubungkan tiga sel. Setiap sel mungkin memiliki atau tidak memiliki dinding. Artinya, delapan konfigurasi dimungkinkan.Konfigurasi sudut.Kami menempatkan dinding hanya di antara sel dengan negara berpagar yang berbeda. Ini mengurangi jumlah konfigurasi menjadi enam. Di masing-masing, salah satu sel berada di dalam kurva dinding. Mari kita pertimbangkan sel ini sebagai titik referensi di mana dinding melengkung. Dari sudut pandang sel ini, dinding dimulai dengan tepi yang sama dengan sel kiri dan berakhir dengan tepi yang sama dengan sel kanan.Peran sel.Artinya, kita perlu membuat metode AddWallSegment
yang parameternya adalah tiga simpul sudut. Meskipun kita dapat menulis kode untuk melakukan triangulasi pada segmen ini, sebenarnya ini adalah kasus khusus dari metode ini AddWallSegment
. Titik jangkar memainkan peran kedua simpul dekat. void AddWallSegment ( Vector3 pivot, HexCell pivotCell, Vector3 left, HexCell leftCell, Vector3 right, HexCell rightCell ) { AddWallSegment(pivot, left, pivot, right); }
Selanjutnya, buat varian metode AddWall
untuk tiga simpul sudut dan sel-selnya. Tujuan dari metode ini adalah untuk menentukan sudut, yang merupakan titik referensi, jika ada. Karena itu, ia harus mempertimbangkan kedelapan konfigurasi yang mungkin dan meminta AddWallSegment
enam dari mereka. public void AddWall ( Vector3 c1, HexCell cell1, Vector3 c2, HexCell cell2, Vector3 c3, HexCell cell3 ) { if (cell1.Walled) { if (cell2.Walled) { if (!cell3.Walled) { AddWallSegment(c3, cell3, c1, cell1, c2, cell2); } } else if (cell3.Walled) { AddWallSegment(c2, cell2, c3, cell3, c1, cell1); } else { AddWallSegment(c1, cell1, c2, cell2, c3, cell3); } } else if (cell2.Walled) { if (cell3.Walled) { AddWallSegment(c1, cell1, c2, cell2, c3, cell3); } else { AddWallSegment(c2, cell2, c3, cell3, c1, cell1); } } else if (cell3.Walled) { AddWallSegment(c3, cell3, c1, cell1, c2, cell2); } }
Untuk menambahkan segmen sudut, panggil metode ini di akhir HexGridChunk.TriangulateCorner
. void TriangulateCorner ( Vector3 bottom, HexCell bottomCell, Vector3 left, HexCell leftCell, Vector3 right, HexCell rightCell ) { … features.AddWall(bottom, bottomCell, left, leftCell, right, rightCell); }
Dinding dengan sudut, tetapi masih ada lubang.Tutup lubangnya
Masih ada lubang di dinding karena ketinggian segmen dinding bervariasi. Sementara segmen di sepanjang tepi memiliki ketinggian konstan, segmen sudut berada di antara dua tepi yang berbeda. Karena setiap tepi dapat memiliki tinggi sendiri, lubang muncul di sudut-sudut.Untuk memperbaikinya, ubahlah AddWallSegment
sehingga ia menyimpan secara terpisah koordinat Y dari simpul atas kiri dan kanan. float leftTop = left.y + HexMetrics.wallHeight; float rightTop = right.y + HexMetrics.wallHeight; Vector3 v1, v2, v3, v4; v1 = v3 = left - leftThicknessOffset; v2 = v4 = right - rightThicknessOffset; v3.y = leftTop; v4.y = rightTop; walls.AddQuad(v1, v2, v3, v4); Vector3 t1 = v3, t2 = v4; v1 = v3 = left + leftThicknessOffset; v2 = v4 = right + rightThicknessOffset; v3.y = leftTop; v4.y = rightTop; walls.AddQuad(v2, v1, v4, v3);
Dinding tertutup.Dinding sekarang ditutup, tetapi Anda mungkin masih melihat lubang di bayang-bayang dinding. Ini disebabkan oleh parameter Bias Normal dari pengaturan bayangan arah. Ketika lebih besar dari nol, segitiga benda yang melemparkan bayangan bergerak sepanjang normal ke permukaan. Ini menghindari bayangan diri, tetapi pada saat yang sama menciptakan lubang dalam kasus di mana segitiga terlihat ke arah yang berbeda. Dalam hal ini, lubang dapat dibuat dalam bayangan geometri halus, misalnya, seperti dinding kita.Anda dapat menyingkirkan artefak bayangan ini dengan menurunkan bias normal menjadi nol. Atau ubah mode dinding renderer Cast Shadows mesh ke Two Sided . Ini akan membuat bayangan membuat objek membuat kedua sisi setiap segitiga dinding untuk rendering, yang akan menutup semua lubang.Tidak ada lagi lubang di bayangan.paket unityDinding langkan
Sejauh ini, dinding kami cukup lurus. Untuk medan datar ini tidak buruk sama sekali, tetapi terlihat aneh ketika dinding bertepatan dengan tepian. Ini terjadi ketika ada perbedaan satu tingkat ketinggian antara sel-sel di sisi yang berlawanan dari dinding.Dinding lurus di tepian.Ikuti tepinya
Alih-alih membuat satu segmen untuk seluruh tepi, kami akan membuat satu untuk setiap bagian dari strip tepi. Kita dapat melakukan ini dengan memanggil empat kali AddWallSegment
di versi AddWall
tepi. public void AddWall ( EdgeVertices near, HexCell nearCell, EdgeVertices far, HexCell farCell ) { if (nearCell.Walled != farCell.Walled) { AddWallSegment(near.v1, far.v1, near.v2, far.v2); AddWallSegment(near.v2, far.v2, near.v3, far.v3); AddWallSegment(near.v3, far.v3, near.v4, far.v4); AddWallSegment(near.v4, far.v4, near.v5, far.v5); } }
Dinding melengkung.Dinding sekarang mengulangi bentuk tepi yang terdistorsi. Dalam kombinasi dengan tepian, terlihat jauh lebih baik. Selain itu, ini menciptakan dinding yang lebih menarik pada relief datar.Menempatkan dinding di tanah
Melihat dinding di tepian, Anda dapat menemukan masalah. Dindingnya tergantung di tanah! Ini berlaku untuk tepi rata yang cenderung, tetapi biasanya tidak begitu terlihat.Dinding tergantung di udara.Untuk mengatasi masalah tersebut, kita perlu menurunkan dinding. Cara termudah adalah menurunkan seluruh dinding sehingga bagian atasnya tetap rata. Pada saat yang sama, sebagian dinding di sisi atas akan sedikit turun ke relief, tetapi ini cocok untuk kita.Untuk menurunkan dinding, kita perlu menentukan sisi mana yang lebih rendah - dekat atau jauh. Kami hanya bisa menggunakan ketinggian sisi terendah, tetapi kami tidak perlu terlalu rendah. Anda dapat menginterpolasi koordinat Y dari rendah ke tinggi dengan offset di bawah 0,5. Karena dinding hanya sesekali menjadi lebih tinggi dari langkah bawah langkan, kita dapat menggunakan langkah vertikal langkan sebagai penyeimbang. Ketebalan dinding yang berbeda dari konfigurasi langkan mungkin memerlukan offset yang berbeda.Dinding yang diturunkan.Mari kita tambahkan ke HexMetrics
metode WallLerp
yang berhubungan dengan interpolasi ini, selain rata-rata koordinat X dan Z dari simpul dekat dan jauh. Itu didasarkan pada suatu metode TerraceLerp
. public const float wallElevationOffset = verticalTerraceStepSize; … public static Vector3 WallLerp (Vector3 near, Vector3 far) { near.x += (far.x - near.x) * 0.5f; near.z += (far.z - near.z) * 0.5f; float v = near.y < far.y ? wallElevationOffset : (1f - wallElevationOffset); near.y += (far.y - near.y) * v; return near; }
Paksa HexFeatureManager
metode ini untuk menentukan simpul kiri dan kanan. void AddWallSegment ( Vector3 nearLeft, Vector3 farLeft, Vector3 nearRight, Vector3 farRight ) { Vector3 left = HexMetrics.WallLerp(nearLeft, farLeft); Vector3 right = HexMetrics.WallLerp(nearRight, farRight); … }
Dinding berdiri di tanah.Perubahan distorsi dinding
Sekarang tembok kami dalam perjanjian yang baik dengan perbedaan ketinggian. Tetapi mereka masih tidak sepenuhnya sesuai dengan tepi yang terdistorsi, meskipun mereka dekat dengan mereka. Ini terjadi karena pertama-tama kita menentukan bagian atas dinding, dan kemudian mendistorsi mereka. Karena simpul-simpul ini berada di suatu tempat antara simpul-simpul tepi dekat dan jauh, distorsi mereka akan sedikit berbeda.Fakta bahwa dinding mengikuti tulang rusuk secara tidak akurat bukanlah masalah. Namun, distorsi bagian atas dinding berubah ketebalan yang relatif seragam. Jika kita mengatur dinding berdasarkan simpul terdistorsi, dan kemudian menambahkan paha depan tidak terdistorsi, ketebalannya tidak akan jauh berbeda. void AddWallSegment ( Vector3 nearLeft, Vector3 farLeft, Vector3 nearRight, Vector3 farRight ) { nearLeft = HexMetrics.Perturb(nearLeft); farLeft = HexMetrics.Perturb(farLeft); nearRight = HexMetrics.Perturb(nearRight); farRight = HexMetrics.Perturb(farRight); … walls.AddQuadUnperturbed(v1, v2, v3, v4); … walls.AddQuadUnperturbed(v2, v1, v4, v3); walls.AddQuadUnperturbed(t1, t2, v3, v4); }
Bagian atas dinding yang tidak terdistorsi.Berkat pendekatan ini, dinding tidak akan lagi mengikuti tepian setepat sebelumnya. Tetapi sebagai imbalannya mereka akan menjadi kurang rusak dan akan memiliki ketebalan yang lebih konstan.Ketebalan dinding lebih konsisten.paket unityLubang-lubang di dinding
Sejauh ini, kami telah mengabaikan kemungkinan sungai atau jalan melintasi tembok. Ketika ini terjadi, kita harus membuat lubang di dinding yang dilewati oleh sungai atau jalan.Untuk melakukan ini, tambahkan AddWall
dua parameter Boolean untuk menunjukkan apakah sungai atau jalan melewati tepi. Meskipun kita dapat menanganinya secara berbeda, mari kita hapus dua segmen menengah dalam kedua kasus. public void AddWall ( EdgeVertices near, HexCell nearCell, EdgeVertices far, HexCell farCell, bool hasRiver, bool hasRoad ) { if (nearCell.Walled != farCell.Walled) { AddWallSegment(near.v1, far.v1, near.v2, far.v2); if (hasRiver || hasRoad) {
Sekarang HexGridChunk.TriangulateConnection
harus menyediakan data yang diperlukan. Karena dia sudah membutuhkan informasi yang sama, mari kita cache dalam variabel Boolean dan merekam panggilan ke metode yang sesuai hanya sekali. void TriangulateConnection ( HexDirection direction, HexCell cell, EdgeVertices e1 ) { … bool hasRiver = cell.HasRiverThroughEdge(direction); bool hasRoad = cell.HasRoadThroughEdge(direction); if (hasRiver) { … } if (cell.GetEdgeType(direction) == HexEdgeType.Slope) { TriangulateEdgeTerraces(e1, cell, e2, neighbor, hasRoad); } else { TriangulateEdgeStrip(e1, cell.Color, e2, neighbor.Color, hasRoad); } features.AddWall(e1, cell, e2, neighbor, hasRiver, hasRoad); … }
Lubang-lubang di dinding untuk melewati sungai dan jalan.Kami menutupi dinding
Bukaan baru ini menciptakan tempat untuk melengkapi tembok. Kita perlu menutup titik-titik akhir ini dengan paha depan agar kita tidak bisa melihat melalui sisi-sisi tembok. Mari kita buat HexFeatureManager
metode untuk tujuan ini AddWallCap
. Ini berfungsi seperti itu AddWallSegment
, tetapi hanya membutuhkan sepasang puncak jarak dekat. Buat dia menambahkan quad, pergi dari dekat ke sisi jauh dinding. void AddWallCap (Vector3 near, Vector3 far) { near = HexMetrics.Perturb(near); far = HexMetrics.Perturb(far); Vector3 center = HexMetrics.WallLerp(near, far); Vector3 thickness = HexMetrics.WallThicknessOffset(near, far); Vector3 v1, v2, v3, v4; v1 = v3 = center - thickness; v2 = v4 = center + thickness; v3.y = v4.y = center.y + HexMetrics.wallHeight; walls.AddQuadUnperturbed(v1, v2, v3, v4); }
Ketika AddWall
menemukan bahwa kami membutuhkan lubang, kami menambahkan penutup antara pasangan tepi kedua dan keempat. Untuk pasangan keempat simpul, Anda perlu mengubah orientasi, jika tidak, wajah quad akan melihat ke dalam. public void AddWall ( EdgeVertices near, HexCell nearCell, EdgeVertices far, HexCell farCell, bool hasRiver, bool hasRoad ) { if (nearCell.Walled != farCell.Walled) { AddWallSegment(near.v1, far.v1, near.v2, far.v2); if (hasRiver || hasRoad) { AddWallCap(near.v2, far.v2); AddWallCap(far.v4, near.v4); } … } }
Lubang tertutup di dinding.Bagaimana dengan lubang di sekitar tepi peta?, . . , .
paket unityMenghindari tebing dan air
Akhirnya, mari kita lihat tepi yang berisi tebing atau air. Karena tebing pada dasarnya adalah dinding besar, maka tidak masuk akal untuk menempatkan dinding tambahan di atasnya. Selain itu, akan terlihat buruk. Dinding bawah laut juga sama sekali tidak masuk akal, seperti halnya pembatasan oleh dinding pantai.Dinding di tebing dan di dalam air.Kami dapat menghapus dinding dari tepi yang tidak perlu ini dengan tambahan cek masuk AddWall
. Dinding tidak bisa berada di bawah air, dan tulang rusuk yang umum dengannya tidak bisa menjadi tebing. public void AddWall ( EdgeVertices near, HexCell nearCell, EdgeVertices far, HexCell farCell, bool hasRiver, bool hasRoad ) { if ( nearCell.Walled != farCell.Walled && !nearCell.IsUnderwater && !farCell.IsUnderwater && nearCell.GetEdgeType(farCell) != HexEdgeType.Cliff ) { … } }
Dinding yang menghalangi sepanjang tulang rusuk telah dihapus, tetapi sudut tetap di tempatnya.Menghapus sudut dinding
Menghapus segmen sudut yang tidak perlu akan membutuhkan sedikit usaha. Kasus paling sederhana adalah ketika sel pendukung berada di bawah air. Ini memastikan bahwa tidak ada segmen dinding di dekatnya yang dapat dihubungkan. void AddWallSegment ( Vector3 pivot, HexCell pivotCell, Vector3 left, HexCell leftCell, Vector3 right, HexCell rightCell ) { if (pivotCell.IsUnderwater) { return; } AddWallSegment(pivot, left, pivot, right); }
Tidak ada lagi sel pendukung bawah air.Sekarang kita perlu melihat dua sel lainnya. Jika salah satunya ada di bawah air atau terhubung ke sel pendukung oleh istirahat, maka tidak ada dinding di sepanjang tulang rusuk ini. Jika ini benar untuk setidaknya satu sisi, maka tidak boleh ada segmen dinding di sudut ini.Kami secara individual menentukan apakah ada dinding kiri atau kanan. Kami menempatkan hasilnya dalam variabel Boolean untuk membuatnya lebih mudah untuk dikerjakan. if (pivotCell.IsUnderwater) { return; } bool hasLeftWall = !leftCell.IsUnderwater && pivotCell.GetEdgeType(leftCell) != HexEdgeType.Cliff; bool hasRighWall = !rightCell.IsUnderwater && pivotCell.GetEdgeType(rightCell) != HexEdgeType.Cliff; if (hasLeftWall && hasRighWall) { AddWallSegment(pivot, left, pivot, right); }
Menghapus semua sudut yang mengganggu.Tutup sudutnya
Ketika tidak ada dinding di tepi kiri atau kanan, pekerjaan selesai. Tetapi jika dinding itu hanya dalam satu arah, itu berarti ada lubang lain di dinding itu. Karena itu, Anda perlu menutupnya. if (hasLeftWall) { if (hasRighWall) { AddWallSegment(pivot, left, pivot, right); } else { AddWallCap(pivot, left); } } else if (hasRighWall) { AddWallCap(right, pivot); }
Kami menutup dinding.Koneksi dinding dengan tebing
Dalam satu situasi, dinding terlihat tidak sempurna. Ketika dinding mencapai bagian bawah tebing, itu berakhir. Tetapi karena tebing tidak sepenuhnya vertikal, lubang sempit dibuat antara dinding dan tepi tebing. Di puncak tebing, masalah seperti itu tidak muncul.Lubang di antara dinding dan wajah tebing.Akan jauh lebih baik jika dinding terus ke ujung tebing. Kita dapat melakukan ini dengan menambahkan segmen dinding lain antara ujung dinding saat ini dan ujung atas tebing. Karena sebagian besar segmen ini akan disembunyikan di dalam tebing, kita dapat melakukannya tanpa mengurangi ketebalan dinding di dalam tebing menjadi nol. Dengan demikian, cukup bagi kita untuk membuat irisan: dua paha depan ke titik dan segitiga di atas mereka. Mari kita buat metode untuk tujuan ini AddWallWedge
. Ini dapat dilakukan dengan menyalin AddWallCap
dan menambahkan titik irisan. void AddWallWedge (Vector3 near, Vector3 far, Vector3 point) { near = HexMetrics.Perturb(near); far = HexMetrics.Perturb(far); point = HexMetrics.Perturb(point); Vector3 center = HexMetrics.WallLerp(near, far); Vector3 thickness = HexMetrics.WallThicknessOffset(near, far); Vector3 v1, v2, v3, v4; Vector3 pointTop = point; point.y = center.y; v1 = v3 = center - thickness; v2 = v4 = center + thickness; v3.y = v4.y = pointTop.y = center.y + HexMetrics.wallHeight;
Untuk AddWallSegment
sudut kita akan memanggil metode ini ketika dinding hanya berjalan satu arah dan dinding ini berada pada ketinggian yang lebih rendah dari sisi lainnya. Dalam kondisi seperti inilah kita berhadapan dengan tepi jurang. if (hasLeftWall) { if (hasRighWall) { AddWallSegment(pivot, left, pivot, right); } else if (leftCell.Elevation < rightCell.Elevation) { AddWallWedge(pivot, left, right); } else { AddWallCap(pivot, left); } } else if (hasRighWall) { if (rightCell.Elevation < leftCell.Elevation) { AddWallWedge(right, pivot, left); } else { AddWallCap(right, pivot); } }
, .unitypackage11:
.Pada bagian sebelumnya, kami menambahkan dukungan dinding. Ini adalah segmen dinding lurus sederhana tanpa perbedaan nyata. Sekarang kita akan membuat dinding lebih menarik dengan menambahkan menara.Segmen dinding harus dibuat secara prosedural agar sesuai dengan relief. Ini tidak diperlukan untuk menara, kita bisa menggunakan prefab biasa.Kita bisa membuat menara sederhana dua kubus dengan bahan merah. Pangkalan menara memiliki ukuran 2 kali 2 unit dan tinggi 4 unit, yaitu lebih tebal dan lebih tinggi dari dinding. Di atas kubus ini kita akan menempatkan unit kubus yang menunjukkan bagian atas menara. Seperti semua cetakan lainnya, kubus ini tidak memerlukan colliders.Karena model menara terdiri dari beberapa objek, kami menjadikannya anak-anak dari objek root. Tempatkan mereka sehingga asal lokal akar berada di dasar menara. Berkat ini, kita dapat menempatkan menara tanpa khawatir tentang tingginya.Menara cetakan.Tambahkan tautan ke cetakan ini HexFeatureManager
dan sambungkan. public Transform wallTower;
Tautan ke menara cetakan.Menara bangunan
Mari kita mulai dengan menempatkan menara di tengah setiap segmen dinding. Untuk melakukan ini, kita akan membuat menara di akhir metode AddWallSegment
. Posisinya akan menjadi rata-rata poin kiri dan kanan segmen. void AddWallSegment ( Vector3 nearLeft, Vector3 farLeft, Vector3 nearRight, Vector3 farRight ) { … Transform towerInstance = Instantiate(wallTower); towerInstance.transform.localPosition = (left + right) * 0.5f; towerInstance.SetParent(container, false); }
Satu menara per segmen dinding.Kami punya banyak menara di sepanjang dinding, tetapi orientasinya tidak berubah. Kita perlu mengubah rotasi mereka sehingga mereka sejajar dengan dinding. Karena kita memiliki titik dinding kanan dan kiri, kita tahu arah mana yang benar. Kita dapat menggunakan pengetahuan ini untuk menentukan orientasi segmen dinding, dan karenanya menara.Alih-alih menghitung sendiri rotasi, kami cukup menetapkan Transform.right
vektor ke properti . Kode Unity akan mengubah rotasi objek sehingga arah lokalnya sesuai dengan vektor yang ditransmisikan. Transform towerInstance = Instantiate(wallTower); towerInstance.transform.localPosition = (left + right) * 0.5f; Vector3 rightDirection = right - left; rightDirection.y = 0f; towerInstance.transform.right = rightDirection; towerInstance.SetParent(container, false);
Menara selaras dengan dinding.Bagaimana cara kerja tugas Transform.right?Quaternion.FromToRotation
. .
public Vector3 right { get { return rotation * Vector3.right; } set { rotation = Quaternion.FromToRotation(Vector3.right, value); } }
Kurangi jumlah menara
Satu menara per segmen terlalu banyak. Mari kita membuat penambahan menara opsional dengan menambahkan AddWallSegment
parameter ke boolean. Setel ke nilai default false
. Dalam hal ini, semua menara akan hilang. void AddWallSegment ( Vector3 nearLeft, Vector3 farLeft, Vector3 nearRight, Vector3 farRight, bool addTower = false ) { … if (addTower) { Transform towerInstance = Instantiate(wallTower); towerInstance.transform.localPosition = (left + right) * 0.5f; Vector3 rightDirection = right - left; rightDirection.y = 0f; towerInstance.transform.right = rightDirection; towerInstance.SetParent(container, false); } }
Mari kita tempatkan menara hanya di sudut sel. Hasilnya, kami mendapatkan lebih sedikit menara dengan jarak yang cukup konstan di antara mereka. void AddWallSegment ( Vector3 pivot, HexCell pivotCell, Vector3 left, HexCell leftCell, Vector3 right, HexCell rightCell ) { … AddWallSegment(pivot, left, pivot, right, true); … }
Menara hanya di sudut-sudut.Kelihatannya cukup bagus, tetapi kita mungkin membutuhkan penempatan menara yang lebih sedikit. Seperti fitur medan lainnya, kita dapat menggunakan tabel hash untuk memutuskan apakah akan meletakkan menara di sudut. Untuk melakukan ini, kami menggunakan pusat sudut untuk sampel tabel, dan kemudian kami akan membandingkan salah satu nilai hash dengan nilai ambang batas menara. HexHash hash = HexMetrics.SampleHashGrid( (pivot + left + right) * (1f / 3f) ); bool hasTower = hash.e < HexMetrics.wallTowerThreshold; AddWallSegment(pivot, left, pivot, right, hasTower);
Nilai ambang mengacu pada HexMetrics
. Dengan nilai 0,5, menara akan dibuat dalam setengah kasus, tetapi kita dapat membuat dinding dengan banyak menara atau tanpa menara sama sekali. public const float wallTowerThreshold = 0.5f;
Menara acak.Kami menghapus menara dari lereng
Sekarang kami menempatkan menara terlepas dari bentuk medan. Namun, di lereng menara terlihat tidak masuk akal. Di sini dindingnya miring dan dapat memotong bagian atas menara.Menara di lereng.Untuk menghindari kemiringan, kami akan memeriksa apakah sel sudut kanan dan kiri sama tingginya. Hanya dalam hal ini adalah mungkin untuk menempatkan menara. bool hasTower = false; if (leftCell.Elevation == rightCell.Elevation) { HexHash hash = HexMetrics.SampleHashGrid( (pivot + left + right) * (1f / 3f) ); hasTower = hash.e < HexMetrics.wallTowerThreshold; } AddWallSegment(pivot, left, pivot, right, hasTower);
Tidak ada lagi menara di dinding lereng.Kami menempatkan dinding dan menara di tanah
Meskipun kita menghindari dinding di lereng, relief di kedua sisi dinding masih bisa memiliki ketinggian yang berbeda. Dinding dapat berjalan di sepanjang tepian, dan sel dengan ketinggian yang sama dapat memiliki posisi vertikal yang berbeda. Karena itu, pangkalan menara mungkin ada di udara.Menara di udara.Bahkan, dinding di lereng juga bisa menggantung di udara, tetapi ini tidak begitu terlihat seperti untuk menara.Dinding ada di udara.Ini dapat diperbaiki dengan meregangkan pangkal dinding dan menara ke tanah. Untuk melakukan ini, tambahkan offset Y untuk dinding HexMetrics
. Satu unit ke bawah sudah cukup. Tingkatkan ketinggian menara dengan jumlah yang sama. public const float wallHeight = 4f; public const float wallYOffset = -1f;
Kami mengubahnya HexMetrics.WallLerp
sehingga ketika menentukan koordinat Y, itu memperhitungkan offset baru. public static Vector3 WallLerp (Vector3 near, Vector3 far) { near.x += (far.x - near.x) * 0.5f; near.z += (far.z - near.z) * 0.5f; float v = near.y < far.y ? wallElevationOffset : (1f - wallElevationOffset); near.y += (far.y - near.y) * v + wallYOffset; return near; }
Kami juga perlu mengubah cetakan menara, karena pangkalan sekarang akan menjadi satu unit di bawah tanah. Oleh karena itu, kami menambah ketinggian kubus dasar sebanyak satu unit dan karenanya mengubah posisi lokal kubus.Dinding dan menara di tanah.paket unityJembatan
Pada tahap ini, kita memiliki sungai dan jalan, tetapi jalan tidak dapat menyeberangi sungai dengan cara apa pun. Ini waktu yang tepat untuk menambahkan jembatan.Mari kita mulai dengan kubus skala sederhana yang akan memainkan peran jembatan cetakan. Lebar sungai bervariasi, tetapi ada sekitar tujuh unit jarak antara pusat jalan di kedua sisi. Oleh karena itu, kami memberikan skala perkiraan (3, 1, 7). Tambahkan material perkotaan merah prefab dan singkirkan collider-nya. Seperti halnya menara, letakkan kubus di dalam objek root dengan skala yang sama. Karena itu, geometri jembatan itu sendiri tidak akan menjadi penting.Tambahkan tautan ke prefab jembatan HexFeatureManager
dan tetapkan prefab. public Transform wallTower, bridge;
Pabrikan jembatan yang ditugaskan.Penempatan jembatan
Untuk menempatkan jembatan, kita perlu metode HexFeatureManager.AddBridge
. Jembatan harus terletak di antara pusat sungai dan salah satu sisi sungai. public void AddBridge (Vector3 roadCenter1, Vector3 roadCenter2) { Transform instance = Instantiate(bridge); instance.localPosition = (roadCenter1 + roadCenter2) * 0.5f; instance.SetParent(container, false); }
Kami akan mengirimkan pusat jalan yang tidak terdistorsi, jadi kami harus mengubah mereka sebelum menempatkan jembatan. roadCenter1 = HexMetrics.Perturb(roadCenter1); roadCenter2 = HexMetrics.Perturb(roadCenter2); Transform instance = Instantiate(bridge);
Untuk meluruskan jembatan dengan benar, kita dapat menggunakan pendekatan yang sama seperti ketika memutar menara. Dalam hal ini, pusat jalan menentukan vektor maju jembatan. Karena kita tetap berada di dalam sel yang sama, vektor ini pasti akan horisontal, jadi kita tidak perlu nol komponennya Y. Transform instance = Instantiate(bridge); instance.localPosition = (roadCenter1 + roadCenter2) * 0.5f; instance.forward = roadCenter2 - roadCenter1; instance.SetParent(container, false);
Kami membangun jembatan melintasi sungai lurus
Satu-satunya konfigurasi sungai yang membutuhkan jembatan lurus dan melengkung. Jalan bisa melewati titik akhir, dan di zigzag jalan hanya bisa dekat.Untuk memulai, mari cari tahu sungai lurus. Di dalam, HexGridChunk.TriangulateRoadAdjacentToRiver
operator pertama else if
mengatur jalan di sekitar sungai tersebut. Karena itu, di sini kita akan menambahkan jembatan.Kami berada di satu sisi sungai. Pusat jalan bergerak dari sungai, dan kemudian pusat sel juga bergeser. Untuk menemukan pusat jalan di sisi yang berlawanan, kita perlu memindahkan arah yang berlawanan dengan jumlah yang sama. Ini harus dilakukan sebelum mengubah pusat itu sendiri. void TriangulateRoadAdjacentToRiver ( HexDirection direction, HexCell cell, Vector3 center, EdgeVertices e ) { … else if (cell.IncomingRiver == cell.OutgoingRiver.Opposite()) { … roadCenter += corner * 0.5f; features.AddBridge(roadCenter, center - corner * 0.5f); center += corner * 0.25f; } … }
Jembatan di atas sungai lurus.Jembatan telah muncul! Tetapi sekarang kita memiliki satu contoh jembatan untuk setiap arah di mana sungai tidak mengalir. Kita perlu memastikan bahwa hanya satu instance dari jembatan dihasilkan dalam sel. Ini dapat dilakukan dengan memilih satu arah relatif ke sungai dan atas dasar untuk menghasilkan jembatan. Anda dapat memilih arah mana pun. roadCenter += corner * 0.5f; if (cell.IncomingRiver == direction.Next()) { features.AddBridge(roadCenter, center - corner * 0.5f); } center += corner * 0.25f;
Selain itu, kita perlu menambahkan jembatan hanya ketika ada jalan di kedua sisi sungai. Saat ini, kami sudah yakin bahwa ada jalan di sisi saat ini. Karena itu, Anda perlu memeriksa apakah ada jalan di seberang sungai. if (cell.IncomingRiver == direction.Next() && ( cell.HasRoadThroughEdge(direction.Next2()) || cell.HasRoadThroughEdge(direction.Opposite()) )) { features.AddBridge(roadCenter, center - corner * 0.5f); }
Jembatan antara jalan di kedua sisi.Jembatan di atas sungai yang melengkung
Jembatan di atas sungai yang melengkung bekerja serupa, tetapi topologinya sedikit berbeda. Kami akan menambahkan jembatan ketika kami berada di luar kurva. Ini terjadi di blok terakhir else
. Ia menggunakan arah tengah untuk mengimbangi tengah jalan. Kita perlu menggunakan offset ini dua kali dengan skala yang berbeda, jadi simpanlah ke variabel. void TriangulateRoadAdjacentToRiver ( HexDirection direction, HexCell cell, Vector3 center, EdgeVertices e ) { … else { HexDirection middle; if (previousHasRiver) { middle = direction.Next(); } else if (nextHasRiver) { middle = direction.Previous(); } else { middle = direction; } if ( !cell.HasRoadThroughEdge(middle) && !cell.HasRoadThroughEdge(middle.Previous()) && !cell.HasRoadThroughEdge(middle.Next()) ) { return; } Vector3 offset = HexMetrics.GetSolidEdgeMiddle(middle); roadCenter += offset * 0.25f; } … }
Skala perpindahan di bagian luar kurva adalah 0,25, dan di dalam HexMetrics.innerToOuter * 0.7f
. Kami menggunakannya untuk menempatkan jembatan. Vector3 offset = HexMetrics.GetSolidEdgeMiddle(middle); roadCenter += offset * 0.25f; features.AddBridge( roadCenter, center - offset * (HexMetrics.innerToOuter * 0.7f) );
Jembatan di atas sungai yang melengkung.Di sini kita perlu menghindari duplikat jembatan. Kita bisa melakukan ini dengan menambahkan jembatan hanya dari arah tengah. Vector3 offset = HexMetrics.GetSolidEdgeMiddle(middle); roadCenter += offset * 0.25f; if (direction == middle) { features.AddBridge( roadCenter, center - offset * (HexMetrics.innerToOuter * 0.7f) ); }
Dan lagi, Anda perlu memastikan bahwa jalan ada di sisi yang berlawanan. if ( direction == middle && cell.HasRoadThroughEdge(direction.Opposite()) ) { features.AddBridge( roadCenter, center - offset * (HexMetrics.innerToOuter * 0.7f) ); }
Jembatan antara jalan di kedua sisi.Penskalaan jembatan
Karena kita mendistorsi medan, jarak antara pusat jalan dan sisi berlawanan dari sungai bervariasi. Terkadang jembatan terlalu pendek, kadang terlalu panjang.Memvariasikan jarak tetapi panjang jembatan konstan.Meskipun kami telah membuat jembatan dengan panjang tujuh unit, Anda dapat menskalakannya agar sesuai dengan jarak sebenarnya antara pusat jalan. Ini berarti bahwa model jembatan cacat. Karena jaraknya tidak terlalu bervariasi, deformasi mungkin lebih dapat diterima daripada jembatan yang tidak cocok untuk panjangnya.Untuk melakukan penskalaan yang tepat kita perlu mengetahui panjang awal prefab jembatan. Kami akan menyimpan panjang ini dalam HexMetrics
. public const float bridgeDesignLength = 7f;
Sekarang kita dapat menetapkan skala di sepanjang instance Z jembatan ke jarak antara pusat jalan, dibagi dengan panjang aslinya. Karena akar prefab jembatan memiliki skala yang sama, jembatan akan meregang dengan benar. public void AddBridge (Vector3 roadCenter1, Vector3 roadCenter2) { roadCenter1 = HexMetrics.Perturb(roadCenter1); roadCenter2 = HexMetrics.Perturb(roadCenter2); Transform instance = Instantiate(bridge); instance.localPosition = (roadCenter1 + roadCenter2) * 0.5f; instance.forward = roadCenter2 - roadCenter1; float length = Vector3.Distance(roadCenter1, roadCenter2); instance.localScale = new Vector3( 1f, 1f, length * (1f / HexMetrics.bridgeDesignLength) ); instance.SetParent(container, false); }
Panjang jembatan yang berubah.Konstruksi jembatan
Alih-alih kubus sederhana, kita bisa menggunakan model jembatan yang lebih menarik. Misalnya, Anda dapat membuat jembatan melengkung kasar dengan tiga kubus bersisik dan diputar. Tentu saja, Anda dapat membuat model 3D yang jauh lebih kompleks, termasuk bagian jalan. Tetapi perhatikan bahwa seluruh objek akan sedikit dikompresi dan diregangkan.Jembatan melengkung dengan panjang berbeda.paket unityBenda khusus
Sejauh ini, sel-sel kita dapat mengandung benda-benda perkotaan, pedesaan dan tanaman. Meskipun masing-masing dari mereka memiliki tiga level, semua benda cukup kecil dibandingkan dengan ukuran sel. Bagaimana jika kita membutuhkan bangunan besar, seperti kastil?Mari kita tambahkan jenis objek khusus ke medan. Benda-benda seperti itu sangat besar sehingga menempati seluruh sel. Masing-masing benda ini unik dan membutuhkan cetakan sendiri. Misalnya, kastil sederhana dapat dibuat dari satu kubus pusat ditambah empat menara sudut. Skala (6, 4, 6) untuk kubus pusat akan membuat kunci yang cukup besar, yang cocok bahkan dalam sel yang sangat cacat.Cetakan di puri.Objek khusus lainnya dapat berupa ziggurat, misalnya, dibangun dari tiga kubus yang diletakkan di atas satu sama lain. Untuk kubus bawah, skala (8, 2.5, 8) cocok.Prefab ziggurat.Objek khusus bisa berupa apa saja, belum tentu arsitektur. Sebagai contoh, sekelompok pohon besar hingga sepuluh unit dapat menunjukkan sel diisi dengan megaflora.Cetakan awal megaflora.Tambahkan ke HexFeatureManager
array untuk melacak cetakan ini. public Transform[] special;
Pertama, tambahkan kastil ke array, lalu ziggurat, dan kemudian megaflora.Kustomisasi objek khusus.Membuat sel khusus
Sekarang HexCell
diperlukan indeks objek khusus, yang menentukan jenis objek khusus, jika ada. int specialIndex;
Seperti objek bantuan lainnya, mari kita berikan kemampuan untuk menerima dan menetapkan nilai ini. public int SpecialIndex { get { return specialIndex; } set { if (specialIndex != value) { specialIndex = value; RefreshSelfOnly(); } } }
Secara default, sel tidak mengandung objek khusus. Kami menyatakan ini dengan indeks 0. Tambahkan properti yang menggunakan pendekatan ini untuk menentukan apakah sel khusus. public bool IsSpecial { get { return specialIndex > 0; } }
Untuk mengedit sel, tambahkan dukungan untuk indeks objek khusus di HexMapEditor
. Ini bekerja sama dengan tingkat fasilitas perkotaan, pedesaan dan pabrik. int activeUrbanLevel, activeFarmLevel, activePlantLevel, activeSpecialIndex; … bool applyUrbanLevel, applyFarmLevel, applyPlantLevel, applySpecialIndex; … public void SetApplySpecialIndex (bool toggle) { applySpecialIndex = toggle; } public void SetSpecialIndex (float index) { activeSpecialIndex = (int)index; } … void EditCell (HexCell cell) { if (cell) { if (applyColor) { cell.Color = activeColor; } if (applyElevation) { cell.Elevation = activeElevation; } if (applyWaterLevel) { cell.WaterLevel = activeWaterLevel; } if (applySpecialIndex) { cell.SpecialIndex = activeSpecialIndex; } if (applyUrbanLevel) { cell.UrbanLevel = activeUrbanLevel; } … } }
Tambahkan slider ke UI untuk mengontrol objek khusus. Karena kami memiliki tiga objek, kami menggunakan interval 0–3 dalam slider. Nol berarti tidak adanya objek, satu - kastil, dua - ziggurat, tiga - megaflora.Slider untuk objek khusus.Menambahkan Objek Khusus
Sekarang kita dapat menetapkan objek khusus ke sel. Agar mereka muncul, kita perlu menambahkan ke HexFeatureManager
metode lain. Ini hanya membuat sebuah instance dari objek khusus yang diinginkan dan menempatkannya di posisi yang diinginkan. Karena nol menunjukkan tidak adanya objek, kita harus mengurangi unit dari indeks objek khusus sel sebelum mendapatkan akses ke array prefab. public void AddSpecialFeature (HexCell cell, Vector3 position) { Transform instance = Instantiate(special[cell.SpecialIndex - 1]); instance.localPosition = HexMetrics.Perturb(position); instance.SetParent(container, false); }
Mari kita beri objek rotasi acak menggunakan tabel hash. public void AddSpecialFeature (HexCell cell, Vector3 position) { Transform instance = Instantiate(special[cell.SpecialIndex - 1]); instance.localPosition = HexMetrics.Perturb(position); HexHash hash = HexMetrics.SampleHashGrid(position); instance.localRotation = Quaternion.Euler(0f, 360f * hash.e, 0f); instance.SetParent(container, false); }
Saat melakukan triangulasi sel, kami HexGridChunk.Triangulate
akan memeriksa apakah sel tersebut berisi objek khusus. Jika demikian, maka kita memanggil metode baru kita, sama seperti AddFeature
. void Triangulate (HexCell cell) { for (HexDirection d = HexDirection.NE; d <= HexDirection.NW; d++) { Triangulate(d, cell); } if (!cell.IsUnderwater && !cell.HasRiver && !cell.HasRoads) { features.AddFeature(cell, cell.Position); } if (cell.IsSpecial) { features.AddSpecialFeature(cell, cell.Position); } }
Benda khusus. Mereka jauh lebih besar dari biasanya.Hindari sungai
Karena objek khusus terletak di tengah sel, mereka tidak bergabung dengan sungai, karena mereka akan menggantung di atasnya.Benda di sungai.Untuk mencegah objek khusus dibuat di atas sungai, kami mengubah properti HexCell.SpecialIndex
. Kami akan mengubah indeks hanya ketika tidak ada sungai di dalam sel. public int SpecialIndex { … set { if (specialIndex != value && !HasRiver) { specialIndex = value; RefreshSelfOnly(); } } }
Selain itu, saat menambahkan sungai, kita harus menyingkirkan semua objek khusus. Sungai harus membasuh mereka. Ini dapat dilakukan dengan HexCell.SetOutgoingRiver
mengatur indeks objek khusus ke 0 dalam metode . public void SetOutgoingRiver (HexDirection direction) { … hasOutgoingRiver = true; outgoingRiver = direction; specialIndex = 0; neighbor.RemoveIncomingRiver(); neighbor.hasIncomingRiver = true; neighbor.incomingRiver = direction.Opposite(); neighbor.specialIndex = 0; SetRoad((int)direction, false); }
Kami menghindari jalan
Seperti sungai, jalan juga buruk dengan benda-benda khusus, tetapi tidak semuanya begitu mengerikan. Anda bahkan dapat meninggalkan jalan apa adanya. Beberapa fasilitas mungkin kompatibel dengan jalan, sementara yang lain mungkin tidak. Oleh karena itu, Anda dapat membuatnya bergantung pada objek. Tapi kami akan membuatnya lebih mudah.Benda di jalan.Dalam hal ini, biarkan benda-benda khusus mengalahkan jalan. Karena itu, ketika mengubah indeks objek khusus, kami juga akan menghapus semua jalan dari sel. public int SpecialIndex { … set { if (specialIndex != value && !HasRiver) { specialIndex = value; RemoveRoads(); RefreshSelfOnly(); } } }
Bagaimana jika kita menghapus objek tertentu?0, , . .
Selain itu, ini berarti bahwa saat menambahkan jalan, kami harus melakukan pemeriksaan tambahan. Kami akan menambahkan jalan hanya jika tidak ada sel yang merupakan sel dengan objek khusus. public void AddRoad (HexDirection direction) { if ( !roads[(int)direction] && !HasRiverThroughEdge(direction) && !IsSpecial && !GetNeighbor(direction).IsSpecial && GetElevationDifference(direction) <= 1 ) { SetRoad((int)direction, true); } }
Hindari benda lain
Objek khusus tidak dapat dicampur dengan jenis objek lain. Jika mereka tumpang tindih, maka itu akan terlihat berantakan. Mungkin juga tergantung pada objek tertentu, tetapi kami akan menggunakan pendekatan yang sama.Objek yang bersinggungan dengan objek lain.Dalam hal ini, kita akan menekan benda yang lebih kecil, seolah-olah benda itu berada di bawah air. Kali ini kita akan check-in HexFeatureManager.AddFeature
. public void AddFeature (HexCell cell, Vector3 position) { if (cell.IsSpecial) { return; } … }
Hindari air
Kami juga memiliki masalah dengan air. Akankah fitur khusus bertahan selama banjir? Karena kita menghancurkan objek kecil di sel yang terendam, mari kita lakukan hal yang sama dengan objek khusus.Benda di dalam air.Di HexGridChunk.Triangulate
kami akan melakukan pemeriksaan banjir yang sama untuk objek khusus dan biasa. void Triangulate (HexCell cell) { for (HexDirection d = HexDirection.NE; d <= HexDirection.NW; d++) { Triangulate(d, cell); } if (!cell.IsUnderwater && !cell.HasRiver && !cell.HasRoads) { features.AddFeature(cell, cell.Position); } if (!cell.IsUnderwater && cell.IsSpecial) { features.AddSpecialFeature(cell, cell.Position); } }
Karena kedua operator if
sekarang memeriksa apakah sel di bawah air, kami dapat mentransfer tes dan melakukan hanya sekali. void Triangulate (HexCell cell) { for (HexDirection d = HexDirection.NE; d <= HexDirection.NW; d++) { Triangulate(d, cell); } if (!cell.IsUnderwater) { if (!cell.HasRiver && !cell.HasRoads) { features.AddFeature(cell, cell.Position); } if (cell.IsSpecial) { features.AddSpecialFeature(cell, cell.Position); } } }
Untuk percobaan, sejumlah objek seperti itu akan cukup bagi kita.paket unity