مقدمة
إذا كنت
كسولًا جدًا
بحيث لا تعتني بوقتك ، وتحقق مستوى للعبتك ، فقد وصلت إلى المكان الصحيح.
ستخبرك هذه المقالة بالتفصيل كيف يمكنك استخدام إحدى طرق الجيل
الأخرى باستخدام مثال المرتفعات والكهوف. سننظر في خوارزمية
Aldous-Broder وكيفية جعل الكهف الناتج أكثر جمالا.
في نهاية قراءة المقال ، يجب أن تحصل على شيء مثل هذا:
النظرية
الجبل
لنكون صادقين ، يمكن توليد الكهف من الصفر ، ولكن هل سيكون قبيحًا بطريقة ما؟ في دور
"المنصة" لوضع الألغام ، اخترت سلسلة جبال.
يتم إنشاء هذا الجبل بكل بساطة: دعونا نحصل على
مصفوفة ثنائية الأبعاد وارتفاع متغير ، يساوي مبدئيًا نصف طول
المصفوفة في البعد الثاني ؛ نذهب فقط عبر أعمدته ونملأ شيئًا بكل الخطوط في العمود إلى قيمة
ارتفاع متغيرة ، نغيره بفرصة عشوائية لأعلى أو لأسفل.
الكهف
ولإنشاء الأبراج المحصنة ، اخترت -
كما بدا لي - خوارزمية ممتازة. بعبارات بسيطة ، يمكن تفسيره على النحو التالي: فلنمتلك متغيرين (ربما عشرة) متغيرين
X و
Y ، وصفيف ثنائي الأبعاد 50
× 50 ، نعطي هذه المتغيرات قيمًا عشوائية داخل الصفيف ، على سبيل المثال ،
X = 26 ، و
Y = 28 . بعد ذلك ، نقوم بنفس الإجراءات عدة مرات: نحصل على رقم عشوائي من صفر إلى
، في حالتنا ، ما يصل إلى
أربعة ؛ وبعد ذلك ، اعتمادًا على العدد الذي تم إسقاطه ، نتغير
متغيراتنا:
switch (Random.Range(0, 4)) { case 0: X += 1; break; case 1: X -= 1; break; case 2: Y += 1; break; case 3: Y -= 1; break; }
ثم ، بالطبع ، نتحقق لنرى ما إذا كان أي متغير قد سقط خارج حدود مجالنا:
X = X < 0 ? 0 : (X >= 50 ? 49 : X); Y = Y < 0 ? 0 : (Y >= 50 ? 49 : Y);
بعد كل هذه الاختبارات ، نقوم بشيء في قيم
X و
Y الجديدة لصفيفنا
(على سبيل المثال: أضف واحدًا إلى العنصر) .
array[X, Y] += 1;
تحضير
من أجل بساطة التنفيذ والتصور لأساليبنا ، هل سنرسم الأشياء الناتجة؟ أنا سعيد لأنك لا تمانع! سنفعل ذلك مع
Texture2D .
للعمل ، نحتاج إلى نصين فقط:
ground_libray هو ما يدور حوله المقال. هنا نولد وننظف ونرسم
مولد الأرض هو ما ستستخدمه طبقة الأرض
فليكن الأول
ثابتًا ولن يرث من أي شيء:
public static class ground_libray
والثاني طبيعي ، فقط لن نحتاج إلى طريقة
التحديث .
أيضًا ، لنقم بإنشاء كائن لعبة على المسرح ، باستخدام مكون
SpriteRendererجزء عملي
مما تتكون؟
للعمل مع البيانات ، سنستخدم صفيفًا ثنائي الأبعاد. يمكنك أن تأخذ مصفوفة من أنواع مختلفة ، من
البايت أو
int ، إلى
Color ، ولكن أعتقد أن ذلك سيكون أفضل ما يمكن القيام به:
نوع جديدنكتب هذا الشيء في
ground_libray .
[System.Serializable] public class block { public float[] color = new float[3]; public block(Color col) { color = new float[3] { col.r, col.g, col.b }; } }
سأشرح ذلك بحقيقة أنه سيسمح لنا
بحفظ مجموعتنا
وتعديلها إذا لزم الأمر.
ماسيف
دعونا ، قبل أن نبدأ بتوليد الجبل ، نعين المكان الذي
سنخزنه فيه .
في نص
الأرض الأرضي ، كتبت ما يلي:
public int ground_size = 128; ground_libray.block[,] ground; Texture2D myT;
ground_size - حجم مجالنا (أي أن المصفوفة ستتكون من 16384 عنصر).
ground_libray.block [،] ground - هذا هو مجالنا للجيل.
Texture2D myT هو ما
سنعتمد عليه.
كيف ستعمل؟سيكون مبدأ العمل معنا على النحو التالي - سوف نطلق على بعض أساليب ground_libray من مولد الأرض ، مع إعطاء الحقل الأرضي الأول.
لنقم بإنشاء الطريقة الأولى في البرنامج النصي ground_libray:
صنع الجبل public static float mount_noise = 0.02f; public static void generate_mount(ref block[,] b) { int h_now = b.GetLength(1) / 2; for (int x = 0; x < b.GetLength(0); x++) for (int y = 0; y < h_now; y++) { b[x, y] = new block(new Color(0.7f, 0.4f, 0)); h_now += Random.value > (1.0f - mount_noise) ? (Random.value > 0.5 ? 1 : -1) : 0; } }
وسنحاول على الفور فهم ما يحدث هنا: كما قلت ،
نراجع فقط أعمدة المصفوفة
b الخاصة بنا ، وفي
نفس الوقت نغير متغير الارتفاع
h_now ، الذي كان في الأصل يساوي نصف
128 (64) . ولكن لا يزال هناك شيء جديد -
mount_noise . هذا المتغير مسؤول عن فرصة تغيير
h_now ، لأنه إذا قمت بتغيير الارتفاع في كثير من الأحيان ، سيبدو الجبل مثل
المشط .
اللونقمت على الفور بتعيين لون بني قليلاً ، دعه يكون على الأقل بعضًا - في المستقبل لن نحتاج إليه.
الآن دعنا نذهب إلى
ground_generator ونكتب هذا في طريقة
البدء :
ground = new ground_libray.block [ground_size, ground_size]; ground_libray.generate_mount(ref ground);
نقوم بتهيئة
الأرضية المتغيرة
بمجرد الحاجة إليها .
بعد ذلك ، دون تفسير ، أرسله إلى
ground_libray .
لذلك أنشأنا الجبل.
لماذا لا أستطيع رؤية الجبل الخاص بي؟
لنرسم الآن ما حصلنا عليه!
للرسم ،
سنكتب الطريقة التالية في
ground_libray :
رسم public static void paint(block[,] b, ref Texture2D t) { t = new Texture2D(b.GetLength(0), b.GetLength(1)); t.filterMode = FilterMode.Point; for (int x = 0; x < b.GetLength(0); x++) for (int y = 0; y < b.GetLength(1); y++) { if (b[x, y] == null) { t.SetPixel(x, y, new Color(0, 0, 0, 0)); continue; } t.SetPixel(x, y, new Color( b[x, y].color[0], b[x, y].color[1], b[x, y].color[2] ) ); } t.Apply(); }
هنا لن نعطي شخصًا ما مجالنا ، سنعطي نسخة منه فقط
(على الرغم من ذلك
، بسبب فئة الكلمة ، قدمنا أكثر من مجرد نسخة) . سنقدم أيضًا
Texture2D الخاص بنا لهذه الطريقة.
أول سطرين: نقوم بإنشاء نسيجنا
بحجم الحقل وإزالة التصفية .
بعد ذلك ، ننتقل إلى حقل الصفيف بأكمله وحيث لم ننشئ أي شيء
(يحتاج الفصل إلى التهيئة) - نرسم مربعًا فارغًا ، وإلا ، إذا لم يكن فارغًا ، فإننا نرسم ما حفظناه في العنصر.
وبالطبع ، عند الانتهاء ، ننتقل إلى
ground_generator ونضيف هذا:
ground = new ground_libray.block [ground_size, ground_size]; ground_libray.generate_mount(ref ground);
ولكن بغض النظر عن مقدار ما نرسمه على نسيجنا ، في اللعبة يمكننا رؤيته فقط من خلال وضع هذه اللوحة على شيء ما:
لا يقبل
SpriteRenderer Texture2D في أي مكان ، ولكن لا شيء يمنعنا من إنشاء
نقش متحرك من هذا النسيج -
Sprite.Create (
نسيج ،
مستطيل بإحداثيات الركن الأيسر السفلي وأعلى اليمين ،
إحداثيات المحور ).
سيتم تسمية هذه الخطوط بالأحدث ، وسنضيف الباقي فوق طريقة
الطلاء !
لي
الآن نحن بحاجة لملء حقولنا بكهوف عشوائية. لمثل هذه الإجراءات ، سنقوم أيضًا بإنشاء طريقة منفصلة في
ground_libray . أود أن أشرح على الفور معلمات الطريقة:
ref block[,] b - . int thick - int size - Color outLine -
الكهف public static void make_cave(ref block[,] b, int thick, int size, Color outLine) { int xNow = Random.Range(0, b.GetLength(0)); int yNow = Random.Range(0, b.GetLength(1) / 2); for (int i = 0; i < size; i++) { b[xNow, yNow] = null; make_thick(ref b, thick, new int[2] { xNow, yNow }, outLine); switch (Random.Range(0, 4)) { case 0: xNow += 1; break; case 1: xNow -= 1; break; case 2: yNow += 1; break; case 3: yNow -= 1; break; } xNow = xNow < 0 ? 0 : (xNow >= b.GetLength(0) ? b.GetLength(0) - 1 : xNow); yNow = yNow < 0 ? 0 : (yNow >= b.GetLength(1) ? b.GetLength(1) - 1 : yNow); } }
بادئ ذي بدء ، أعلنا عن المتغيرين
X و
Y ، ولكننا
أطلقنا عليهما اسم
xNow و
yNow على التوالي.
الأول ، وهو
xNow ، يحصل على قيمة عشوائية من صفر إلى حجم الحقل في البعد الأول.
والثاني -
yNow - يحصل أيضًا على قيمة عشوائية: من الصفر إلى منتصف الحقل في البعد الثاني.
لماذا؟ نحن نولد جبلنا من الوسط ، وفرصة أن ينمو إلى "السقف"
ليست كبيرة . وبناءً على ذلك ، لا أعتبره مناسبًا لتوليد كهوف في الهواء.
بعد ذلك ، تنتقل الحلقة على الفور ، ويعتمد عدد علاماتها على معلمة
الحجم . في كل مرة نقوم بتحديث الحقل في
موقعي xNow و
yNow ، وعندها فقط نقوم بتحديثها بأنفسنا
(يمكن وضع التحديثات الميدانية في النهاية - لن تشعر بالفرق)هناك أيضًا طريقة
make_thick ، في المعلمات التي نمر فيها مجالنا ،
وعرض شوط الكهف ،
وموضع التحديث الحالي للكهف ولون السكتة الدماغية :
السكتة الدماغية static void make_thick (ref block[,] b, int t, int[] start, Color o) { for (int x = (start[0] - t); x < (start[0] + t); x++) { if (x < 0 || x >= b.GetLength(0)) continue; for (int y = (start[1] - t); y < (start[1] + t); y++) { if (y < 0 || y >= b.GetLength(1)) continue; if (b[x, y] == null) continue; b[x, y] = new block(o); } } }
تأخذ الطريقة إحداثيات
البداية التي تم تمريرها إليها ، وحولها على مسافة
t يعيد طلاء جميع الكتل بالألوان
س - كل شيء بسيط للغاية!
الآن دعنا
نضيف هذا الخط إلى
مولد الأرض :
ground_libray.make_cave(ref ground, 2, 10000, new Color(0.3f, 0.3f, 0.3f));
يمكنك تثبيت البرنامج النصي
ground_generator كمكون على كائن لدينا والتحقق من كيفية عمله!

المزيد عن الكهوف ...- لإنشاء المزيد من الكهوف ، يمكنك استدعاء طريقة make_cave عدة مرات (استخدم حلقة)
- لا يؤدي تغيير معلمة الحجم دائمًا إلى زيادة حجم الكهف ، ولكنه غالبًا ما يصبح أكبر
- بتغيير المعلمة السميكة ، يمكنك زيادة عدد العمليات بشكل ملحوظ:
إذا كانت المعلمة 3 ، فسيكون عدد المربعات في دائرة نصف قطرها 36 ، لذا مع حجم المعلمة = 40،000 ، سيكون عدد العمليات 36 * 40،000 = 1440000
تصحيح الكهف

هل لاحظت أن الكهف في هذا المنظر لا يبدو أفضل؟ الكثير من التفاصيل الإضافية
(ربما تفكر بشكل مختلف) .
للتخلص من شوائب بعض
# 4d4d4d سنكتب هذه الطريقة في
ground_libray :
منظف public static void clear_caves(ref block[,] b) { for (int x = 0; x < b.GetLength(0); x++) for (int y = 0; y < b.GetLength(1); y++) { if (b[x, y] == null) continue; if (solo(b, 2, 13, new int[2] { x, y })) b[x, y] = null; } }
ولكن سيكون من الصعب فهم ما يحدث هنا إذا كنت لا تعرف وظيفة الوظيفة
الفردية :
static bool solo (block[,] b, int rad, int min, int[] start) { int cnt = 0; for (int x = (start[0] - rad); x <= (start[0] + rad); x++) { if (x < 0 || x >= b.GetLength(0)) continue; for (int y = (start[1] - rad); y <= (start[1] + rad); y++) { if (y < 0 || y >= b.GetLength(1)) continue; if (b[x, y] == null) cnt += 1; else continue; if (cnt >= min) return true; } } return false; }
في معلمات هذه الوظيفة ، يجب أن يكون مجالنا ،
نصف قطر التحقق من النقطة ،
"عتبة التدمير" وإحداثيات النقطة المطلوب فحصها .
فيما يلي شرح مفصل لما تقوم به هذه الوظيفة:
int cnt هو عداد "العتبة" الحالية
فيما يلي دورتان تتحققان من جميع النقاط حول النقطة التي تم تمرير إحداثياتها للبدء . إذا كانت هناك نقطة فارغة ، فإننا نضيف واحدة إلى cnt ، عند الوصول إلى "عتبة الدمار" ، نعيد الحقيقة - النقطة غير ضرورية . وإلا فإننا لا نلمسها.
قمت بتعيين حد التدمير إلى 13 نقطة فارغة ، ونصف قطر التحقق هو 2 (أي أنه سيتحقق من 24 نقطة ، لا يشمل النقطة المركزية)
مثالسيبقى هذا واحد دون أذى ، حيث لا يوجد سوى
9 نقاط فارغة.

لكن هذه لم تكن محظوظة - حوالي
14 نقطة فارغة

وصف موجز للخوارزمية:
نمر عبر الحقل بأكمله ونتحقق من جميع النقاط لمعرفة ما إذا كانت هناك حاجة إليها. بعد ذلك ، نضيف ببساطة السطر التالي إلى
مولد الأرض :
ground_libray.clear_caves(ref ground);
كما نرى ، فإن معظم الجسيمات غير الضرورية اختفت ببساطة.أضف بعض الألوان
يبدو جبلنا رتيباً للغاية ، أجده مملاً.
دعنا نضيف بعض اللون. أضف طريقة
level_paint إلى
ground_libray :
رسم الجبال public static void level_paint(ref block[,] b, Color[] all_c) { for (int x = 0; x < b.GetLength(0); x++) { int lvl_div = -1; int counter = 0; int lvl_now = 0; for (int y = b.GetLength(1) - 1; y > 0; y--) { if (b[x, y] != null && lvl_div == -1) lvl_div = y / all_c.Length; else if (b[x, y] == null) continue; b[x, y] = new block(all_c[lvl_now]); lvl_now += counter >= lvl_div ? 1 : 0; lvl_now = (lvl_now >= all_c.Length) ? (all_c.Length - 1) : lvl_now; counter = counter >= lvl_div ? 0 : (counter += 1); } } } </ <cut />source> . , , . , . <b>Y </b> , . </spoiler> <b>ground_generator </b> : <source lang="cs"> ground_libray.level_paint(ref ground, new Color[3] { new Color(0.2f, 0.8f, 0), new Color(0.6f, 0.2f, 0.05f), new Color(0.2f, 0.2f, 0.2f), });
اخترت 3 ألوان فقط:
الأخضر والأحمر الداكن والرمادي الداكن .
بالطبع ، يمكنك تغيير كل من عدد الألوان وقيم كل منها. اتضح مثل هذا:
ولكن لا يزال يبدو صارمًا جدًا لإضافة القليل من العشوائية إلى الألوان ،
سنكتب هذه الخاصية في
ground_libray :
ألوان عشوائية public static float color_randomize = 0.1f; static float crnd { get { return Random.Range(1.0f - color_randomize, 1.0f + color_randomize); } }
والآن في
طرق level_paint و
make_thick ، في السطور التي نعين فيها الألوان ، على سبيل المثال في
make_thick :
b[x, y] = new block(o);
سنكتب هذا:
b[x, y] = new block(o * crnd);
وفي
level_paint b[x, y] = new block(all_c[lvl_now] * crnd);
في النهاية ، يجب أن يبدو كل شيء على النحو التالي:
المساوئ
لنفترض أن لدينا حقلًا بحجم 1024 × 1024 ، نحتاج إلى توليد 24 كهفًا ، سمك حوافها سيكون 4 ، والحجم 80،000.
1024 * 1024 + 24 * 64 * 80،000 =
5،368،832،000،000 عملية.
هذه الطريقة مناسبة فقط لتوليد وحدات صغيرة لعالم اللعبة ، فمن
المستحيل إنشاء شيء كبير جدًا
في كل مرة .