Dungeon Generation ที่ไม่มีทางเดินและการขึ้นต่อกันของห้อง


15

ฉันกำลังสร้างเกมด้วยโลกที่สร้างขึ้นตามขั้นตอนที่สร้างขึ้นในตอนต้นของเกมซึ่งประกอบด้วยหลายพื้นที่ที่แสดงโดยกริด (พูด, 8x8, 9x6 ขนาดจะเป็นการดีตามอำเภอใจ) พื้นที่เหล่านี้ควรจะเชื่อมต่อซึ่งกันและกันผ่านรายการการพึ่งพา

มีการเชื่อมต่อเมื่อมีช่องว่างอย่างน้อย 3 ช่องของกริดนั้นระหว่างสองพื้นที่ ในเซลล์ตรงกลางของพื้นที่เชื่อมต่อ 3 พื้นที่นั้นเป็นประตูทางเข้าระหว่างพื้นที่:

ฉันพยายามคิดหาวิธีเชื่อมโยงพวกมันเข้าด้วยกัน แต่มันก็ซับซ้อนมากขึ้นเรื่อย ๆ ในด้านที่คุณต้องพิจารณาในเวลาเดียวกัน

ฉันได้ลองทำต้นแบบกระดาษและในขณะที่มันเป็นกระบวนการที่ง่ายมากเมื่อทำการมองเห็นฉันยังไม่พบชุดนิพจน์ทางคณิตศาสตร์ที่ดีที่ทำให้ฉันสามารถวางห้องที่มีประสิทธิภาพเดียวกันด้วยรหัส

นี่เป็นตัวอย่าง "ง่าย ๆ " ที่ฉันกำลังดิ้นรนอยู่ตอนนี้:

  • พื้นที่ 'a' จำเป็นต้องเชื่อมต่อกับ 'b' และ 'c'
  • พื้นที่ 'b' จำเป็นต้องเชื่อมต่อกับ 'a' และ 'd'
  • พื้นที่ 'c' จำเป็นต้องเชื่อมต่อกับ 'a' และ 'd'
  • พื้นที่ 'd' จำเป็นต้องเชื่อมต่อกับ 'b' และ 'c'

พิจารณาเพื่อความง่ายเราวางห้องตามลำดับที่ปรากฏในรายการ (ฉันลองคนอื่นแล้ว) ดังนั้นฉันจึงเข้าใกล้สิ่งนี้เป็นอัลกอริทึม Dungeon Generation แบบมาตรฐานของคุณ

เราวาง 'a' ไว้ที่ใดก็ได้บนกระดานเนื่องจากเป็นพื้นที่แรก ต่อไปเราจะสุ่มเลือกกำแพงและเนื่องจากไม่มีอะไรเชื่อมต่อกับผนังนั้นเราจึงสามารถวาง 'b' ไว้ที่นั่น:

ตอนนี้เราต้องวาง 'c' แต่ 'a' อยู่บนกระดานอยู่แล้วและมีกำแพงที่ว่างอยู่ดังนั้นเราจึงตัดสินใจวางมันไว้บนอีกกำแพงหนึ่ง แต่ไม่ใช่ทุกตำแหน่งที่จะทำเพราะ 'd' กำลังจะมาถึงและจะต้องเชื่อมต่อกับ 'b' และ 'c' ด้วยเช่นกัน:

ฉันลองข้อ จำกัด ที่เป็นไปได้ที่ห้อง 2 ห้องที่มีชุดของการอ้างอิงที่เหมือนกันไม่สามารถอยู่บนผนังฝั่งตรงข้ามได้ แต่ก็ไม่ได้รับประกันความสำเร็จ:

และในกรณีอื่น ๆ ที่พื้นที่มีขนาดแตกต่างกันการอยู่บนกำแพงฝั่งตรงข้ามสามารถทำงานได้:

นอกจากนี้การไม่พิจารณาผนังที่ใช้เป็นข้อสันนิษฐานที่มีข้อบกพร่อง

ฉันได้ลองค้นหางานวิจัยเกี่ยวกับอัลกอริธึมการสร้างโพรซีเดอร์อื่นหรือที่คล้ายกันเช่นอัลกอริธึมการบรรจุที่เหมาะสมและอัลกอฮอร์เลย์เอาต์กราฟ แต่โดยปกติอัลกอริทึมเหล่านั้นไม่ได้คำนึงถึง

ฉันคิดถึงวิธีการหลายอย่างรวมถึงการวางพื้นที่และย้อนกลับจนกระทั่งพบตำแหน่งที่เหมาะสม แต่พวกเขาดูเหมือนจะขึ้นอยู่กับการลองผิดลองถูกและมีค่าใช้จ่ายสูงในแง่ของการคำนวณ แต่จากการวิจัยอย่างละเอียดเกี่ยวกับสองปัญหาสุดท้ายที่ฉันพูดถึงมันอาจเป็นทางออกเดียวที่ดีที่สุด?

ฉันแค่อยากจะดูว่าใครบางคนมีปัญหาที่คล้ายกันในอดีตหรือไม่ก็ยินดีที่จะช่วยฉันคิดออกและให้ฉันชี้ไม่กี่ที่ที่ฉันควรเริ่มต้นด้วยอัลกอริทึม หรือถ้าไม่เช่นนั้นฉันจะต้องคลายข้อ จำกัด ที่ฉันตั้งไว้


ห้องพักต้องเป็นสี่เหลี่ยมจัตุรัสโดยสมบูรณ์หรือไม่
wolfdawn

ถ้าคุณหมายถึงว่าพวกเขาจะต้องมี 4 กำแพงและไม่มากกว่านั้นใช่ แต่ฉันทำอย่างนั้นเพื่อทำให้พื้นที่โลกง่ายขึ้น ฉันต้องคำนวณพื้นที่แต่ละพื้นที่เพื่อให้ฉันรู้ว่าฉันจะสามารถใส่ทุกอย่างที่ฉันต้องการได้หรือไม่
Joana Almeida

คำตอบ:


6

นี่เป็นปัญหาที่เยี่ยมยอด ฉันเชื่อว่าสามารถแก้ไขได้โดยใช้การวางแผนปฏิบัติการในพื้นที่ของตำแหน่งห้อง

กำหนดสถานะของโลกดังนี้

//State:
//    A list of room states.
//    Room state:
//      - Does room exist?
//      - Where is room's location?
//      - What is the room's size?

กำหนดข้อ จำกัดเป็น:

 // Constraint(<1>, <2>):
 //  - If room <1> and <2> exist, Room <1> is adjacent to Room <2>

โดยที่ "ติดกัน" ตรงที่คุณอธิบาย (แบ่งปันเพื่อนบ้านอย่างน้อย 3 คน)

จำกัดกล่าวจะถูกยกเลิกเมื่อใดก็ตามที่ทั้งสองห้องพักที่ไม่ได้อยู่ติดกันและทั้งสองห้องอยู่

กำหนดรัฐที่จะถูกต้องเมื่อ:

// foreach Constraint:
//        The Constraint is "not invalidated".
// foreach Room:
//       The room does not intersect another room.

กำหนดการกระทำเป็นตำแหน่งของห้องพักให้รัฐปัจจุบัน การกระทำนั้นถูกต้องทุกครั้งที่สถานะผลลัพธ์จากการกระทำนั้นถูกต้อง ดังนั้นเราสามารถสร้างรายการการกระทำสำหรับแต่ละรัฐ:

// Returns a list of valid actions from the current state
function List<Action> GetValidActions(State current, List<Constraint> constraints):
    List<Action> actions = new List<Action>();
    // For each non-existent room..
    foreach Room room in current.Rooms:
        if(!room.Exists)
            // Try to put it at every possible location
            foreach Position position in Dungeon:
                 State next = current.PlaceRoom(room, position)
                 // If the next state is valid, we add that action to the list.
                 if(next.IsValid(constraints))
                     actions.Add(new Action(room, position));

ทีนี้สิ่งที่คุณเหลือไว้คือกราฟโดยที่รัฐคือโหนดและการกระทำเป็นลิงค์ เป้าหมายคือการหารัฐที่ใช้ได้ทั้งห้องและทุกห้อง เราสามารถค้นหาตำแหน่งที่ถูกต้องได้โดยการค้นหาผ่านกราฟด้วยวิธีการโดยพลการหรืออาจใช้การค้นหาเชิงลึกก่อน การค้นหาจะมีลักษณะดังนี้:

// Given a start state (with all rooms set to *not exist*), and a set of
// constraints, finds a valid end state where all the constraints are met,
// using a depth-first search.
// Notice that this gives you the power to pre-define the starting conditions
// of the search, to for instance define some key areas of your dungeon by hand.
function State GetFinalState(State start, List<Constraint> constraints)
    Stack<State> stateStack = new Stack<State>();
    State current = start;
    stateStack.push(start);
    while not stateStack.IsEmpty():
        current = stateStack.pop();
        // Consider a new state to expand from.
        if not current.checked:
            current.checked = true;
            // If the state meets all the constraints, we found a solution!
            if(current.IsValid(constraints) and current.AllRoomsExist()):
                return current;

            // Otherwise, get all the valid actions
            List<Action> actions = GetValidActions(current, constraints);

            // Add the resulting state to the stack.
            foreach Action action in actions:
                State next = current.PlaceRoom(action.room, action.position);
                stateStack.push(next);

    // If the stack is completely empty, there is no solution!
    return NO_SOLUTION;

ตอนนี้คุณภาพของดันเจี้ยนที่สร้างขึ้นจะขึ้นอยู่กับลำดับการพิจารณาห้องและการกระทำ คุณสามารถได้รับผลลัพธ์ที่น่าสนใจและแตกต่างกันโดยการสุ่มอนุญาตการกระทำที่คุณทำในแต่ละขั้นตอนโดยการสุ่มเดินผ่านกราฟการกระทำของรัฐ ประสิทธิภาพการค้นหาจะขึ้นอยู่กับว่าคุณสามารถปฏิเสธสถานะที่ไม่ถูกต้องได้เร็วเพียงใด มันอาจช่วยในการสร้างสถานะที่ถูกต้องจากข้อ จำกัด เมื่อใดก็ตามที่คุณต้องการค้นหาการกระทำที่ถูกต้อง


ตลกคุณควรพูดถึงวิธีนี้ ก่อนหน้านี้ฉันได้พูดคุยกับเพื่อนและเขาบอกว่าฉันควรจะมองเข้าไปในอัลกอริธึมการค้นหาแบบอิง Tree แต่ฉันไม่แน่ใจว่าจะใช้มันอย่างไรในบริบทนี้ โพสต์ของคุณเปิดหูเปิดตา! ดูเหมือนว่าจะเป็นทางออกที่เป็นไปได้แน่นอนถ้าคุณจัดการการสร้างสาขาและทำการเพิ่มประสิทธิภาพเล็กน้อยเพื่อตัดกิ่งที่ไม่ดีโดยเร็วที่สุด
Joana Almeida

7

ลำดับความสำคัญของรุ่นของคุณขัดแย้งกัน เมื่อสร้างระดับเป้าหมายแรกของคุณควรเป็นเว็บของระนาบ (ไม่ทับซ้อนกัน) จุดเชื่อมต่อโดยไม่คำนึงถึงระดับ จากนั้นดำเนินการสร้างห้องจากจุดภายในเว็บนั้น การสร้างรูปร่างของห้องก่อนเป็นความผิดพลาดโดยทั่วไปแล้วการพูด สร้างการเชื่อมต่อก่อนจากนั้นดูว่าสามารถใช้แบบฟอร์มใดในห้องพัก

อัลกอริทึมทั่วไป

  1. สร้างตารางพื้นแบบ quantized ที่มีขนาดเพียงพอเพื่อรองรับระดับของคุณโดยใช้อาร์เรย์หรือภาพ 2D

  2. กระจายคะแนนแบบสุ่มทั่วพื้นที่ว่างเปล่านี้ คุณสามารถใช้การตรวจสอบแบบสุ่มธรรมดาในแต่ละไทล์เพื่อดูว่ามันได้รับจุดหรือใช้การกระจายแบบเกาส์ / มาตรฐานเพื่อกระจายคะแนน กำหนดค่าสี / ตัวเลขที่ไม่ซ้ำกันให้กับทุกจุด นี่คือรหัส (ป.ล. ถ้าหลังจากขั้นตอนนี้คุณรู้สึกว่าคุณจำเป็นต้องขยายพื้นที่ของคุณโดยวิธีการทั้งหมดทำ)

  3. สำหรับแต่ละจุดที่สร้างดังกล่าวในลำดับแบบค่อยเป็นค่อยไปเติบโตวงกลมขอบเขตหรือขอบเขตสี่เหลี่ยมผืนผ้าโดยขั้นตอนเดียว (โดยปกติอัตรา 0.5-1.0 เซลล์ / พิกเซลต่อขั้นตอน) ในและx yคุณสามารถขยายขอบเขตทั้งหมดในแบบขนานทั้งหมดเริ่มจากขนาดศูนย์ในขั้นตอนเดียวกันหรือคุณสามารถเริ่มเติบโตในเวลาต่าง ๆ และในอัตราต่าง ๆ ให้อคติกับขนาดของที่เริ่มต้นก่อน (จินตนาการต้นกล้าเติบโตที่บาง เริ่มช้า) โดย "เติบโต" ฉันหมายถึงเติมขอบเขตที่เพิ่งเพิ่มใหม่ด้วยสี / ID ที่ไม่ซ้ำกันไปยังจุดเริ่มต้นสำหรับขอบเขตเหล่านั้น คำอุปมานี้จะถือปากกามาร์กเกอร์ที่ด้านหลังของกระดาษและดูหมึกสีต่าง ๆ เติบโตจนกว่าพวกเขาจะพบกัน

  4. ในบางจุดขอบเขตของบางจุดและอีกจุดหนึ่งจะเกิดการชนกันในระหว่างการเติบโต นี่คือจุดที่คุณควรหยุดเพิ่มขอบเขตสำหรับจุดสองจุดเหล่านั้น - อย่างน้อยก็ในความหมายเดียวที่อธิบายไว้ในขั้นตอนที่ 3

  5. เมื่อคุณขยายขอบเขตของคะแนนทั้งหมดเท่าที่จะเป็นไปได้และหยุดกระบวนการเจริญเติบโตทั้งหมดคุณจะมีแผนที่ที่ควรจะเป็นส่วนใหญ่ แต่ไม่ได้เติมเต็มทั้งหมด ตอนนี้คุณอาจต้องการที่จะบรรจุพื้นที่ว่างเหล่านั้นซึ่งฉันจะถือว่าเป็นสีขาวราวกับว่าการระบายสีในแผ่นกระดาษ

โพสต์กระบวนการเติมพื้นที่

สามารถใช้เทคนิคที่หลากหลายเพื่อเติมลงในช่องว่างที่ว่าง / ขาวที่ยังคงอยู่ต่อขั้นตอนที่ 5:

  • มีเพื่อนบ้านที่มีสีเดียวอยู่แล้วพื้นที่สีอ้างสิทธิ์โดยน้ำท่วมเติมสีนั้นเพื่อให้ทุกคนเข้าร่วม
  • เต็มไปด้วยสี / หมายเลข / รหัสใหม่ที่ยังไม่ได้ใช้ซึ่งทำให้เกิดพื้นที่ใหม่ทั้งหมด
  • วิธีโรบินกลมเช่นนั้นแต่ละพื้นที่ใกล้เคียงเต็มแล้ว "เติบโต" เล็กน้อยในพื้นที่ว่างเปล่า นึกถึงสัตว์ที่กำลังดื่มน้ำอยู่รอบ ๆ หลุม: พวกมันทั้งหมดได้รับน้ำ
  • อย่าเติมเต็มพื้นที่ว่างเปล่าโดยสิ้นเชิงเพียงแค่ข้ามมันเพื่อเชื่อมโยงพื้นที่ที่มีอยู่โดยใช้ทางเดินตรง

การก่อกวน

เป็นขั้นตอนสุดท้ายที่ทำให้สิ่งต่าง ๆ ดูเป็นธรรมชาติมากขึ้นคุณสามารถทำการรบกวนขอบในองศาที่แตกต่างกันบนขอบเซลล์ของพื้นที่ อย่าลืมปิดกั้นเส้นทางการเคลื่อนไหวที่สำคัญ

ทฤษฎีเพื่อประโยชน์ของ

วิธีนี้คล้ายกับวิธีการที่ใช้ในVoronoi Diagrams / Delaunay Triangulationยกเว้นว่าในกรณีข้างต้นคุณจะไม่สร้างขอบอย่างชัดเจน - แทนเมื่อพื้นที่ที่ชนกันชนกันหยุดการเจริญเติบโต คุณจะสังเกตเห็นว่าแผนภาพ Voronoi นั้นเต็มไปด้วยอวกาศ นี่เป็นเพราะพวกเขาไม่หยุดการเจริญเติบโตเพียง แต่สัมผัส แต่มีระดับความเหลื่อมล้ำเล็กน้อย คุณสามารถลองคล้ายกัน

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.