في هذا المنشور سوف أصف خوارزمية التوليد الإجرائي لمستويات زنزانة ثنائية الأبعاد ذات بنية محددة مسبقًا. في الجزء الأول ، سيتم تقديم وصف عام ، وفي الجزء الثاني ، سيتم تنفيذ الخوارزمية.
مقدمة
تمت كتابة الخوارزمية كجزء
من عمل درجة البكالوريوس وتستند إلى مقالة كتبها
Ma et al (2014) . كان الهدف من العمل هو تسريع الخوارزمية وتكميلها بوظائف جديدة. أنا سعيد جدًا بالنتيجة ، لأننا جعلنا الخوارزمية سريعة بما يكفي لاستخدامها أثناء تنفيذ اللعبة. بعد الانتهاء من عمل البكالوريوس ، قررنا تحويله إلى
مقال وإرساله إلى مؤتمر Game-ON 2018.
خوارزمية
لإنشاء مستوى لعبة ، تتلقى الخوارزمية كمدخلات مجموعة من لبنات البناء المضلعة ورسم بياني لاتصال المستوى (طوبولوجيا المستوى). تشير العقد في الرسم البياني إلى الغرف ، وتحدد الحواف الاتصالات بينهما. الغرض من الخوارزمية هو تعيين شكل وموقع الغرفة لكل عقدة في الرسم البياني بحيث لا تتقاطع أشكال الغرفتين ، ويمكن توصيل كل زوج من الغرف المجاورة بالأبواب.
(أ)
(ب)
(ج)
(د)
يوضح الشكلان (ج) و (د) المخططات التي تم إنشاؤها من الرسم البياني للمدخلات (أ) وكتل البناء (ب).
باستخدام الرسم البياني للاتصال ، يمكن لمصمم اللعبة التحكم بسهولة في تدفق اللعب. هل تحتاج إلى مسار مشترك واحد إلى غرفة رئيسه مع العديد من المسارات الجانبية الاختيارية؟ ابدأ فقط برسم الرسم البياني للمسار ثم حدد بعض العقد التي يمكن للاعب من خلالها الاختيار: إما السير على طول المسار الرئيسي أو استكشاف المسار الجانبي مع الكنوز المحتملة و / أو الوحوش التي تنتظره. هل تحتاج إلى قطع الطريق؟ ما عليك سوى اختيار العقدتين في الرسم البياني وإضافة طريق قصير يربطهما. إمكانيات هذا المخطط لا حصر لها.
أمثلة من الرسوم البيانية المدخلات. يظهر المسار الرئيسي باللون الأحمر ، والمسارات المساعدة باللون الأزرق ، والمسار القصير باللون البرتقالي.لا تسمح الخوارزمية لمصممي الألعاب فقط بإدارة الهيكل العالي المستوى للخرائط التي تم إنشاؤها ، بل توفر أيضًا القدرة على التحكم في مظهر الغرف الفردية وكيفية توصيلها ببعضها البعض.
أشكال مختلفة للغرف المختلفة
ذكرت غرفة رئيسه في نهاية المستوى. لا نريد أن تبدو غرفة الرئيس كأي غرفة عادية أخرى ، أليس كذلك؟ تسمح لك الخوارزمية بضبط النماذج لكل غرفة. على سبيل المثال ، يمكننا إنشاء غرفة في بداية المستوى وغرفة رئيس ، والتي يجب أن تحتوي على مجموعات خاصة بها من أشكال الغرف ، ومجموعة واحدة مشتركة لجميع الغرف الأخرى.
تم إنشاء دائرتين من الرسم البياني للمدخلات ، حيث يرتبط شكل خاص من الغرف بالغرفة رقم 8.
يشار صراحة مواقف الباب
تخيل أن لديك نصًا عالي الجودة لاجتماع رئيسه ، وأننا بحاجة إلى دخول اللاعب إلى غرفة الرئيس من مجموعة محددة. أو قد يكون لدينا قالب غرفة يتم فيه حجز بعض البلاط للجدران والعقبات الأخرى. تسمح الخوارزمية للمصممين بتعيين مواقع الأبواب الممكنة بوضوح لأشكال الغرف الفردية.
لكن في بعض الأحيان قد يكون الهدف هو عكس ذلك. يمكننا إنشاء قوالب غرف بطريقة يمكن أن تكون بها الأبواب في أي مكان تقريبًا. بسبب هذا ، فإننا نفرض قيودًا أقل على الخوارزمية ؛ لذلك ، غالبًا ما يتم تشغيلها بشكل أسرع ، وقد تبدو الدوائر المولدة أقل رتابة وأكثر عضوية. في مثل هذه الحالات ، من الممكن الإشارة ببساطة إلى طول الأبواب وإلى أي مدى يجب أن تكون بعيدة عن الزوايا. المسافة من الزوايا هي نوع من التسوية بين الترتيب اليدوي لجميع الأبواب ووجود الأبواب في أي مكان.
(أ)
(ب)
يوضح الشكل (أ) أنواع المواضع المختلفة للباب - تحتوي الغرفة المربعة على 8 أوضاع للباب محددة بوضوح ، وتستخدم الغرفة المستطيلة الطول والمسافة من الزوايا. يوضح الشكل (ب) مخططًا بسيطًا تم إنشاؤه مع أشكال الغرف في الشكل (أ).
ممرات بين الغرف
عندما نتحدث عن مستويات الأبراج المحصنة ، كثيرا ما نتخيل غرف متصلة بواسطة ممرات ضيقة. أود أن أفترض أن الاتصالات على الرسم البياني للإدخال تشير إلى الممرات ، لكنها ليست كذلك. أنها تضمن ببساطة أن جميع العقد المجاورة ستكون مرتبطة مباشرة عن طريق الأبواب. إذا كنا نريد توصيل الغرف بواسطة ممرات ، فعلينا إدخال عقدة جديدة بين جميع أزواج الغرف المجاورة والتظاهر بأنها غرف ممرات (مع أشكال معينة من الغرف ومواقف أبواب معينة).
(أ)
(ب)
رسم توضيحي لكيفية تعديل الرسم البياني للمدخلات لإضافة ممرات بين الغرف. يوضح الشكل (أ) الرسم البياني للمدخلات قبل إضافة غرف الممر. يوضح الشكل (ب) الرسم البياني للمدخلات الذي تم إنشاؤه من (أ) بإضافة غرف جديدة بين جميع الغرف المجاورة للرسم البياني الأصلي.
لسوء الحظ ، يؤدي هذا إلى تعقيد مهمة الخوارزمية إلى حد كبير ، لأن عدد العقد يتضاعف كثيرًا. لذلك ، قمت بتطبيق إصدار من الخوارزمية التي تأخذ في الاعتبار الممرات ، مما يسمح بتقليل انخفاض الأداء عند ترتيب غرف الممر. في الوقت الحالي ، تدعم الخوارزمية إما الممرات بين جميع الغرف ، أو الغياب الكامل للممرات ، ولكن في المستقبل أخطط لجعلها أكثر مرونة.
أمثلة
تم إنشاء العديد من المخططات التي تم إنشاؤها من مجموعات مختلفة من لبنات البناء ومع وجود ممرات.العديد من المخططات التي تم إنشاؤها من مجموعات مختلفة من اللبنات مع ممرات وخارجها.في الجزء الثاني من المنشور ، سأتحدث عن التشغيل الداخلي للخوارزمية.
أنا أعمل أيضًا على مكون إضافي للوحدة لتوليد زنزانة إجرائية ، والتي ستشمل هذه الخوارزمية. أفعل ذلك لأنه على الرغم من إمكانية استخدام هذه الخوارزمية مباشرةً في الوحدة (تتم كتابتها في C #) ، إلا أن راحة العمل بها ليست مثالية. يستغرق إنشاء قوالب غرفة بدون واجهة المستخدم الرسومية كثيرًا من الوقت ، وهناك حاجة إلى الكثير من التعليمات البرمجية لتحويل إخراج الخوارزمية إلى التمثيل المستخدم داخل اللعبة.
بما أنني نفسي لست مطورًا للعبة ، فإن هدفي هو جعل المكوّن الإضافي جيدًا بما يكفي ليستخدمه الآخرون. إذا سارت الأمور على ما يرام ، فسأحاول نشر التحديثات عندما يكون لدي شيء مثير للاهتمام أقوله. لدي بالفعل بعض الأفكار حول المولد نفسه واختبار قدراته.
لقطات من ملحق الوحدة (المشروع قيد التطوير)الجزء 2. تنفيذ الخوارزمية
سأتحدث في هذا الجزء عن الأفكار الأساسية الموضوعة في أساس الخوارزمية الموضحة في الجزء الأول من المنشور. في البداية ، أردت أن أصف المفاهيم الأساسية جنبًا إلى جنب مع التحسينات الرئيسية اللازمة لتكون الخوارزمية سريعة بدرجة كافية. ومع ذلك ، كما اتضح ، حتى المفاهيم الأساسية أكثر من كافية لهذا المنصب. لذلك ، قررت الكشف عن تحسينات في الأداء في مقال مقبل.
الدافع
قبل الانتقال إلى التنفيذ ، أرغب في عرض نتيجة ما سنفعله. يعرض الفيديو أدناه 30 دائرة مختلفة تم إنشاؤها من رسم بياني واحد للإدخال ومجموعة واحدة من لبنات البناء. تتوقف الخوارزمية دائمًا عن 500 مللي ثانية بعد إنشاء الدائرة ، ثم تحاول إنشاء واحدة أخرى.
كيف يعمل؟
الغرض من الخوارزمية هو تعيين شكل وموضع الغرفة لكل عقدة في الرسم البياني بحيث لا تتقاطع غرفتان ، ويتم توصيل الغرف المجاورة بالأبواب.
إحدى الطرق لتحقيق ذلك هي تجربة كل المجموعات الممكنة من أشكال الغرف ومواقعها. ومع ذلك ، كما قد تعتقد ، سيكون هذا غير فعال للغاية ، وربما لن نتمكن من إنشاء دوائر ، حتى بناءً على الرسوم البيانية البسيطة للغاية.
بدلاً من البحث عن كل المجموعات الممكنة ، تقوم الخوارزمية بحساب كيفية توصيل جميع الغرف الفردية (مساحات التكوين المزعومة) بشكل صحيح وتستخدم هذه المعلومات لتوجيه البحث. لسوء الحظ ، حتى مع هذه المعلومات ، لا يزال من الصعب للغاية العثور على المخطط الصحيح. لذلك ، لدراسة فعالة لمساحة البحث ، نستخدم تقنية التحسين الاحتمالي (في هذه الحالة ، محاكاة الصلب). ولمزيد من تسريع عملية التحسين ، نقوم بتقسيم مهمة الإدخال إلى مهام فرعية أصغر وأسهل في حلها. يتم ذلك عن طريق تقسيم الرسم البياني إلى أجزاء أصغر (تسمى سلاسل) مع إنشاء مخططات لاحقة لكل منها بالترتيب.
مساحات التكوين
بالنسبة لزوج من المضلعات التي يكون أحدها ثابتًا في مكانه والآخر يمكن أن يتحرك بحرية ، فإن مساحة التكوين هي مجموعة من المواضع لمضلع حر لا يتقاطع فيه مضلعان ويمكن توصيلهما عن طريق الأبواب. عند العمل مع المضلعات ، يمكن تمثيل كل مساحة تكوين كمجموعة من الخطوط الفارغة ويمكن حسابها بواسطة أدوات هندسية بسيطة.
(أ)
(ب)
تكوينات الفضاء. يوضح الشكل (أ) مساحة التكوين (الخطوط الحمراء) لمستطيل حر بالنسبة إلى مضلع ثابت على شكل حرف L. وهي تحدد جميع مواقع مركز المربع الذي لا تتقاطع فيه الكتلتان بلمس كل منهما الآخر. يوضح الشكل (ب) تقاطع (النقاط الصفراء) لمساحات التكوين في مربع متحرك بالنسبة لمستطيلين ثابتين.
يتم استخدام الخوارزمية التالية لحساب مساحة التكوين لكتلة واحدة ثابتة ومجانية. نختار نقطة مرجعية في الكتلة المتحركة ونضع في الاعتبار جميع المواقع في
، عند تحريك المضلع بحيث تقع النقطة المرجعية في هذا الموقع ، تلمس الكتل المنقولة والثابتة بعضها البعض ، لكن لا تتقاطع. تشكل مجموعة كل هذه النقاط مساحة تكوين الكتل اثنين (الشكل (أ) أعلاه). من أجل الحصول على مساحة تكوينات الكتلة المتحركة بالنسبة إلى كتلتين ثابتتين أو أكثر ، يتم حساب تقاطع مسافات التكوين الفردية (الشكل (ب) أعلاه).
تستخدم الخوارزمية مسافات التكوين بطريقتين. أولاً ، بدلاً من تجربة المواضع العشوائية للغرف الفردية ، نستخدم مساحات التهيئة للبحث عن مواقع تؤدي إلى أكبر عدد ممكن من الغرف المجاورة المتصلة بالأبواب. للقيام بذلك ، نحتاج إلى الحصول على أقصى تقاطع غير فارغ لمسافات التكوين الخاصة بالجيران. ثانياً ، نستخدم مساحات التكوين للتحقق مما إذا كان يمكن ربط جميع أزواج الغرف المجاورة بالأبواب. يتم ذلك عن طريق التحقق مما إذا كان موضع الغرفة داخل مساحة التكوين لجميع جيرانها.
نظرًا لأن أشكال الغرف لا تتغير أثناء تنفيذ اللعبة ، يمكننا حساب مسافات التكوين مسبقًا لجميع أزواج أشكال الغرف قبل بدء الخوارزمية. بفضل هذا ، تسارعت الخوارزمية بأكملها بشكل كبير.
مخطط تدريجي
عند حل مشكلة معقدة ، تتمثل إحدى الطرق الممكنة في تقسيمها إلى مهام فرعية أصغر وأبسط ، وحلها بالفعل. هذا هو بالضبط ما سنفعله بمهمة وضع غرف فردية. بدلاً من ترتيب جميع الغرف في وقت واحد ، نقوم بتقسيم الرسم البياني للمدخلات إلى مخططات فرعية أصغر ونحاول إنشاء مخططات منها واحدة تلو الأخرى. يصف مؤلفو الخوارزمية الأصلية هذه السطور بالرسومات الفرعية "سلاسل" بسبب مبدأ هذه الرسوم البيانية ، حيث لا تحتوي كل عقدة على أكثر من جارتين ، وبالتالي من السهل جدًا إنشاء مخططها.
دارة الإخراج النهائية هي دائمًا مكون واحد متصل ، لذلك لا معنى لتوصيل المكونات الفردية بالدائرة ، ثم حاول الجمع بينها ، لأن عملية الدمج يمكن أن تكون معقدة للغاية. لذلك ، بعد وضع السلسلة ، ستكون السلسلة المتصلة التالية دائمًا هي السلسلة المتصلة بالرؤوس المصطفة بالفعل في الدائرة.
Layout GenerateLayout(inputGraph) { var emptyLayout = new Layout(); var stack = new Stack<Layout>(); stack.Push(emptyLayout); while(!stack.Empty()) { var layout = stack.Pop(); var nextChain = GetNextChain(layout, inputGraph); Layout[] partialLayouts = AddChain(layout, nextChain); if (!partialLayouts.Empty()) { if (IsFullLayout(partialLayouts[0])) { return partialLayouts[0]; } else { stack.Push(partialLayouts); } } } return null; }
الشفرة الزائفة التي توضح طريقة تدريجية لإيجاد المخطط الصحيح.يوضح الرمز الزائف الموضح أعلاه تنفيذ مخطط تدريجي. في كل تكرار للخوارزمية (الأسطر 6-18) ، نأخذ أولاً الدائرة الأخيرة من المكدس (السطر 7) ونحسب السلسلة التي يجب إضافتها بعد ذلك (السطر 8). يمكن القيام بذلك ببساطة عن طريق تخزين رقم الدائرة الأخيرة المضافة إلى كل دائرة جزئية. من خلال الخطوة التالية ، نضيف السلسلة التالية إلى الدائرة (السطر 9) ، وننشئ عدة دوائر مفصلة ، ونحفظها. إذا فشلت مرحلة التوسع ، ولكن لم تتم إضافة أنظمة جزئية جديدة إلى المكدس ، وستحتاج الخوارزمية إلى المتابعة مع آخر نظام جزئي محفوظ. ندعو هذا الموقف إلى البحث عن عائد لأن الخوارزمية لا يمكنها تمديد المخطط الجزئي الحالي ويجب أن تعود وتواصل مع مخطط آخر محفوظ. يكون ذلك ضروريًا في العادة عندما لا يكون هناك مساحة كافية لتوصيل دوائر إضافية بالرؤوس المكونة بالفعل في الدائرة. كذلك ، فإن البحث عن نتائج هو السبب في أننا نحاول دائمًا إنشاء العديد من المخططات المتقدمة (السطر 9). خلاف ذلك ، لن يكون لدينا شيء نعود إليه. تنتهي العملية عندما نقوم بإنشاء دائرة كاملة ، أو عندما تكون المكدس فارغة.
(أ) الرسم البياني الإدخال
(ب) مخطط جزئي
(ج) مخطط جزئي
(د) مخطط كامل
(هـ) مخطط كامل
مخطط تدريجي. يوضح الشكلان (ب) و (ج) مخططين جزئيين بعد تخطيط الدائرة الأولى. يوضح الشكل (د) الدائرة الكاملة بعد التمدد (ب) بواسطة دائرة ثانية. يوضح الشكل (هـ) الدائرة الكاملة بعد التمدد (ج) بواسطة دائرة ثانية.
لتقسيم الرسم البياني إلى أجزاء ، تحتاج إلى العثور على تضمين ثابت من الرسم البياني للإدخال ، أي رسم على المستوى الذي تتقاطع فيه الحواف فقط عند نقاط النهاية. ثم يتم استخدام هذا المرفق للحصول على جميع وجوه الرسم البياني. الفكرة الرئيسية للتحلل هي أنه من الصعب إنشاء دائرة من الدورات ، لأن العقد لديها قيود أكثر. لذلك ، نحاول ترتيب الدورات في بداية التحلل ، بحيث تتم معالجتها في أقرب وقت ممكن وتقليل احتمالية الحاجة للعودة في المراحل اللاحقة. يتم تشكيل سلسلة التحلل الأولى بواسطة أصغر وجه للمرفق ، ثم تتم إضافة الوجوه اللاحقة في ترتيب البحث المتسع. إذا كان هناك وجوه أخرى يمكن اختيارها ، فسيتم استخدام أصغرها. عند عدم وجود وجوه متبقية ، تتم إضافة المكونات غير الدورية المتبقية. يوضح الشكل 4 مثالًا للتحلل في السلسلة ، والذي تم الحصول عليه وفقًا لهذه الخطوات.
(أ)
(ب)
التحلل إلى سلاسل. يوضح الشكل (ب) مثالًا عن كيفية وضع الشكل (أ) على دارة. يمثل كل لون سلسلة منفصلة. تشير الأرقام إلى الترتيب الذي يتم به إنشاء السلاسل.
(ج) التصميم الجزئي الجيد
(د) مخطط كامل
(أ) الرسم البياني الإدخال
(ب) الرسم البياني الجزئي السيئ
بحث مع عودة. يوضح الشكل (ب) مثالًا على دائرة جزئية رديئة ، لأنه لا توجد مساحة كافية فيها لتوصيل العقدتين 0 و 9. لإنشاء الدائرة الكاملة (د) ، من الضروري العودة إلى دائرة جزئية أخرى (ج).
محاكاة الصلب
خوارزمية محاكاة التلدين هي تقنية تحسين احتمالية تهدف إلى دراسة مساحة الدوائر الممكنة. تم اختياره من قبل مؤلفي المقال الأصلي لأنه اتضح أنه مفيد في مواقف مماثلة في الماضي. عند تنفيذ الخوارزمية ، قررت استخدام نفس الطريقة ، لأنني أردت أن أبدأ بما أثبت فعاليته بالفعل في حل هذه المشكلة. ومع ذلك ، أعتقد أنه يمكن استبداله بطريقة أخرى وأن مكتبتي مكتوبة بطريقة تجعل عملية استبدال الطريقة بسيطة للغاية.
تقوم خوارزمية محاكاة التلدين بتقييم التغييرات الصغيرة في التهيئة الحالية أو الدائرة الحالية. هذا يعني أننا نقوم بإنشاء تكوين جديد عن طريق اختيار عقدة واحدة بشكل عشوائي وتغيير موضعها أو شكلها. إذا كان التكوين الجديد يحسن وظيفة الطاقة ، فسيتم قبولها دائمًا. خلاف ذلك ، هناك فرصة ضئيلة لقبول التكوين على أي حال. تقل احتمالية اتخاذ قرارات أسوأ بمرور الوقت. تم تصميم وظيفة الطاقة بطريقة تفرض غرامات كبيرة على العقد المتقاطعة والعقد المجاورة لا تلامس بعضها البعض.
A هي منطقة التقاطع الكلية بين جميع أزواج الكتل في المخطط. D هو مجموع مربعات المسافات بين مراكز أزواج الكتل في الرسم البياني للمدخلات المجاورة لكن لا تلمس بعضها البعض. القيمة
يؤثر على احتمال أن يسمح الصلب محاكاة للذهاب إلى أسوأ التكوين. يتم حساب هذه المعلمة من متوسط مساحة الكتل البرمجية الإنشائية.
بعد تحديد العقدة المراد تغييرها ، نقوم بتغيير شكلها أو موضعها. كيف نختار وظيفة جديدة؟ يمكنك اختياره بشكل عشوائي ، ولكن هذا غالباً ما يؤدي إلى انخفاض وظيفة الطاقة ، وسوف تتقارب الخوارزمية ببطء شديد. هل يمكننا اختيار موقع من المرجح أن يزيد من وظيفة الطاقة؟ ترى ما الذي أفضي إليه؟ نستخدم مساحات التهيئة لتحديد موضع يلبي حدود أكبر عدد من الغرف المجاورة.
بعد ذلك ، لتغيير الشكل ، ببساطة حدد أحد أشكال الغرف المتاحة. في حين أن الخوارزمية لا تحاول تحديد الشكل الذي من المرجح أن يقودنا إلى المخطط الصحيح. ومع ذلك ، سيكون من المثير للاهتمام تجربة هذه الميزة ومعرفة ما إذا كانت تسرع الخوارزمية.
List<Layout> AddChain(chain, layout) { var currentLayout = GetInitialLayout(layout, chain); var generatedLayouts = new List<Layout>(); for (int i = 1; i <= n, i++) { if () { break; } for (int j = 1; j <= m, j++) { var newLayout = PerturbLayout(currentLayout, chain); if (IsValid(newLayout)) { if (DifferentEnough(newLayout, generatedLayouts)) { generatedLayouts.Add(newLayout); } } if () { currentLayout = newLayout; } else if () { currentLayout = newLayout; } } } return generatedLayouts; }
هذا هو الرمز الزائف للطريقة المسؤولة عن إنشاء دائرة دوائر منفصلة عن طريق محاكاة الصلب.
لتسريع العملية برمتها ، سنحاول العثور على التكوين الأولي للطاقة المنخفضة. للقيام بذلك ، سوف نقوم بترتيب العقد الموجودة في السلسلة الحالية لإجراء بحث أولي اتساعًا ، بدءًا من المناطق المجاورة للعقد الموجودة بالفعل في المخطط. ثم يتم وضع العقد المطلوبة واحدة في كل مرة ويتم تحديد الموقع ذي الطاقة الأقل من مساحة التكوين. نحن هنا لا نفعل أي تراجع - هذه عملية بسيطة الجشع.
فيديو مكافأة
يعرض الفيديو أدناه الرسوم البيانية التي تم إنشاؤها من نفس الرسم البياني للإدخال كما في الفيديو الأول. ومع ذلك ، هذه المرة يتم عرض نهج تدريجي. قد تلاحظ أن الخوارزمية تضيف سلاسل العقد واحدة تلو الأخرى. يُرى أيضًا أنه في بعض الأحيان توجد دائرتان جزئيتان متتاليتان لهما نفس عدد العقد. يحدث هذا عندما تعود الخوارزمية مرة أخرى. إذا فشلت المحاولات الأولى لإضافة دائرة أخرى إلى الدائرة الجزئية الأولى ، فستحاول الخوارزمية دائرة جزئية أخرى.
المواد القابلة للتحميل
يمكن العثور على تطبيق الخوارزمية على .NET في
جيثب الخاص بي . يحتوي المستودع على .NET DLL وتطبيق WinForms GUI ، الذي تتحكم فيه ملفات تكوين YAML.