Покоління підземелля без коридорів і кімнатних залежностей


15

Я створюю гру з процедурно створеним світом, створеним на початку гри, що складається з декількох областей, представлених сітками (скажімо, 8x8, 9x6, розміри в ідеалі були б довільними). Передбачається, що ці області будуть пов'язані між собою через список залежностей.

З'єднання існує, коли щонайменше 3 простору цієї сітки оголюються між цими двома областями. У середній комірці тієї області простору 3 є двері між областями:

Я намагався знайти спосіб їх з'єднання, але він стає все складнішим, тим більше областей, які потрібно враховувати одночасно.

Я спробував декілька паперових прототипів, і хоча це дуже простий процес, коли це робиться візуально, я не знайшов гарного набору математичних виразів, що дозволяє мені розміщувати номери з однаковою ефективністю за кодом.

Ось "простий" приклад, з яким я зараз борюся:

  • Область 'a' потрібно підключити до 'b' і 'c'
  • Область 'b' потрібно підключити до 'a' і 'd'
  • Область 'c' потрібно підключити до 'a' і 'd'
  • Область 'd' потрібно підключити до 'b' і 'c'

Враховуйте, що для простоти ми розміщуємо кімнати за порядком їх появи у списку (я спробував інших). Тому я підходжу до цього як до вашого стандартного процедурного алгоритму покоління Dungeon Generation.

Ми розміщуємо "а" де-небудь на дошці, оскільки це перша область. Далі ми вибираємо стіну навмання, і оскільки ніщо не пов’язане з цією стіною, ми можемо розмістити туди "b":

Тепер нам потрібно розмістити "c", але "a" вже є на дошці і має зайняту стіну, тому ми вирішимо поставити її на іншу стіну. Але не кожне розташування зробить це, оскільки надходить "d", і його також потрібно підключити до "b" і "c":

Я спробував можливе обмеження, що 2 кімнати, що мають однаковий набір залежностей, не можуть знаходитися на протилежних стінах, але навіть це не гарантує успіху:

І в інших випадках, коли ділянки мають різні розміри, знаходячись на протилежних стінах:

Крім того, не враховувати використану стіну є хибним припущенням, оскільки вона виключає дійсні рішення:

Я намагався шукати дослідження інших алгоритмів процедурного покоління або подібних, таких як алгоритми оптимального прямокутного упаковки та графічного макета, але зазвичай ці алгоритми не враховують усіх обмежень цієї проблеми і їх важко поєднувати.

Я подумав про купу підходів, включаючи розміщення області та зворотній шлях до тих пір, поки не знайдеться відповідне місце розташування, але вони здаються дуже залежними від спроб та помилок та дорогими з точки зору обчислення. Але, враховуючи обширне дослідження останніх двох проблем, про які я згадав, це може бути єдиним / найкращим рішенням?

Я просто хотів побачити, чи були у когось подібні проблеми в минулому чи готовий допомогти мені розібратися в цьому і дати мені кілька вказівників щодо того, з чого я повинен почати алгоритм. Або, якщо цього не зробити, мені доведеться розглянути обмеження, які я встановив.


Кімнати повинні бути повністю квадратними?
AturSams

Якщо ви маєте на увазі, якщо вони повинні мати 4 стіни і не більше, то так, але я зробив це для спрощення світового простору. Мені потрібно легко обчислити простір, який займає кожна ділянка, щоб я знав, чи зможу вкласти все, що хочу.
Йоана Альмейда

Відповіді:


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>

Там, де "сусідні", як ви описали (спільне використання принаймні трьох сусідів)

Constraint називається недійсним всякий раз , коли дві кімнати НЕ суміжні, і існують обидві кімнати.

Визначте стан, що є дійсним, коли:

// 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;

Тепер якість породженої підземелля буде залежати від порядку, в якому розглядаються кімнати та дії. Ви можете отримати цікаві та різні результати, ймовірно, лише випадковим чином переслідуючи дії, які ви здійснюєте на кожному етапі, тим самим виконуючи випадкову прогулянку по графіку стану дії. Ефективність пошуку значно залежатиме від того, наскільки швидко ви можете відхилити недійсні стани. Це може допомогти генерувати дійсні стани із обмежень, коли ви хочете знайти дійсні дії.


Смішно ви повинні згадати це рішення. Я спілкувався з другом раніше, і він зазначив, що я, мабуть, повинен вивчити алгоритми пошуку на основі дерев, але не знав, як їх використовувати в цьому контексті. Ваше повідомлення було відкриттям для очей! Це, звичайно, здається можливим рішенням, якщо ви керуєте поколінням філій і зробите кілька оптимізацій, щоб скоротити погані гілки якнайшвидше.
Joana Almeida

7

Пріоритети вашого покоління конфліктують. При генеруванні рівнів вашою першою ціллю повинна бути мережа планарних (неперекриваються), з'єднаних точок , незалежно від масштабу. Потім приступайте до створення кімнат з точок всередині цієї павутини. Створення перших форм кімнати - це помилка, взагалі кажучи. Спершу створіть підключення, а потім подивіться, які форми приміщень можна розмістити в ньому.

Загальний алгоритм

  1. Створіть кількісну сітку для підлоги достатнього розміру для підтримки рівня, використовуючи 2D масив або зображення.

  2. Розкидання навмання вказують навпроти цього порожнього простору. Ви можете використовувати звичайну випадкову перевірку на кожній плитці, щоб побачити, чи отримує вона точку, або використовувати стандартний / гауссовий розподіл для розсіювання точок. Призначте для кожного пункту унікальне кольорове / числове значення. Це посвідчення особи. (PS Якщо після цього кроку ви відчуваєте, що вам потрібно збільшити масштаб простору, будь ласка, зробіть це.)

  3. Для кожної такої генерованої точки послідовно наростають коло меж або прямокутник, що виходить за один крок (як правило, швидкість 0,5-1,0 комірок / пікселів на крок) у xта y. Ви можете вирощувати всі межі паралельно, починаючи з нуля розміру на одному кроці, або ви можете почати вирощувати їх у різний час та з різними темпами, надаючи упередження розміру тих, що починаються раніше (уявіть, що розсада росте, де деякі починати пізно). Під "ростом" я маю на увазі заповнення щойно збільшених меж кольором / ідентифікатором, унікальним для початкової точки для цих меж. Метафорою для цього було б тримання фломастерів на звороті аркуша паперу та спостереження за розростанням чорнильних блоків, поки вони не зустрінуться.

  4. У якийсь момент межі деякої точки та іншої точки збираються зіткнутися під час кроку зростання. Це момент, коли ви повинні припинити зростати межі для цих двох точок - принаймні в рівномірному сенсі, описаному на кроці 3.

  5. Після того, як ви зросли всі межі очок наскільки це можливо і зупинили всі процеси зростання, у вас з’явиться карта, яка повинна бути значною мірою, але не повністю заповнена. Ви, можливо, захочете запакувати той порожній простір, який я вважаю білим, ніби забарвленням у аркуші паперу.

Післяпроцесорне заповнення простору

Для заповнення порожніх / білих пробілів, що залишилися, за крок 5 можна використовувати різноманітні методи:

  • Нехай одна сусідня, вже забарвлена ​​область вимагає простору, затопивши її таким кольором, щоб все це з'єднувалося.
  • Заливаються новими, поки що невикористаними кольорами / номерами / ідентифікаторами, таким чином, що вони утворюють абсолютно нові області.
  • Круглий робін підходить таким чином, що кожна вже заповнена сусідня область трохи «переростає» в порожній простір. Подумайте, як тварини п’ють навколо отвору для поливу: всі вони отримують частину води.
  • Не заповнюйте повністю порожній простір, просто перекресліть його, щоб з'єднати існуючі ділянки, використовуючи прямі проходи.

Порушення

В якості останнього кроку для того, щоб все виглядало більш органічно, ви можете зробити збурення краю в різній мірі на клітинах країв областей. Просто не забудьте перекрити важливі маршрути руху.

Теорія, заради інтересів

Це схоже на підхід, застосований у діаграмах Вороного / Триангуляція Делоне , за винятком того, що у вищенаведеному ви не явно створюєте краї - натомість, коли стикаються ділянки, зростання припиняється. Ви помітите, що діаграми Вороного заповнюють простір; це тому, що вони не перестають зростати лише на дотик, а на деякий номінальний ступінь перекриття. Ви можете спробувати подібне.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.