Дизайн цілої карти проти дизайну масиву плиток


11

Я працюю над 2D RPG, в якому будуть представлені звичайні карти підземелля / міста (попередньо створені).

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

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

Що є найбільш поширеним методом, і які переваги / недоліки кожного?


1
Основне питання - пам’ять. Текстура, яка заповнює екран 1080p один раз, становить приблизно 60 Мб. Отже, для зображення використовується int per pixel, плитка використовує int per (pixel / (tileize * tileize)). Отже, якщо плитки 32,32, ви можете зобразити карту в 1024 рази більше, використовуючи плитки проти пікселя. Крім того, час обміну текстурами буде боляче підштовхувати стільки пам’яті навколо тощо ...
ClassicThunder

Відповіді:


19

По-перше, дозвольте мені сказати, що 2D RPG близькі і дорогі моєму серцю, і робота зі старими двигунами DX7 VB6 MORPG (не смійтесь, це було 8 років тому, зараз :-)) - це те, що вперше зацікавило мене розвитком гри. . Зовсім недавно я почав перетворювати гру, над якою працював в одному з цих двигунів для використання XNA.

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

Що стосується впровадження, я детально розповів про те, як я вирішував це в кількох питаннях тут, на GameDev.SE та в своєму блозі (обидва зв'язані нижче), і це не зовсім те, що ви запитуєте, тому я просто перейдіть сюди до основ. Я також зазначу особливості плиток, які роблять їх корисними для завантаження декількох великих попередньо зображених зображень. Якщо щось не зрозуміло, дайте мені знати.

  1. Перше, що вам потрібно зробити - це створити плитковий аркуш. Це просто велике зображення, яке містить усі ваші плитки, вирівняні в сітці. Це (а може бути і додаткове, залежно від кількості плиток) буде єдиним, що потрібно завантажити. Всього 1 зображення! Ви можете завантажити 1 на карту або одну з кожною плиткою в грі; що б організація не працювала на вас.
  2. Далі потрібно зрозуміти, як можна взяти цей "аркуш" і перекласти кожну плитку в число. Це досить просто з деякою простою математикою. Зауважте, що поділ тут є цілим поділом , тому десяткові знаки випадають (або округляються вниз, якщо вам більше зручно). Як конвертувати комірки в координати і назад.
  3. Гаразд, тепер, коли ви розбили таблицю плиток на ряд комірок (цифр), ви можете взяти ці цифри та підключити їх до будь-якого контейнера. Для простоти можна просто використовувати 2D масив.

    int[,] mapTiles = new int[100,100]; //Map is 100x100 tiles without much overhead
  4. Далі ви хочете їх намалювати. Один із способів зробити це ЛОТИ більш ефективним (залежно від розміру карти) - це обчислити лише комірки, які камера переглядає, і провести цикли через них. Це можна зробити, отримавши координати масиву плитки для карти верхнього лівого ( tl) та нижнього правого ( br) кутів камери . Потім петлю з, tl.X to br.Xі, вкладеного циклу, з, tl.Y to br.Yщоб намалювати їх. Приклад нижче:

    for (int x = tl.X; x <= br.X;x++) {
        for (int y = tl.Y; y <= br.Y;y++) {
            //Assuming tileset setup from image
            Vector2 tilesetCoordinate = new Vector2((mapTiles[x,y] % 8) * 32,(mapTiles[x,y] / 8) * 32);
            //Draw 32x32 tile using tilesetCoordinate as the source x,y
        }
    }
  5. Джекпот! Ось основи плиточного двигуна. Ви можете бачити, що легко мати навіть карту 1000x1000 з не надто великими накладними витратами. Крім того, якщо у вас менше 255 плиток, ви можете використовувати байтовий масив, скорочуючи пам'ять на 3 байти на комірку. Якщо байт занадто малий, ушорт, ймовірно, вистачить для ваших потреб.

Примітка. Я покинув концепцію світових координат (на чому базуватиметься положення вашої камери), оскільки це, на мою думку, виходить за рамки цієї відповіді. Про це можна прочитати тут, на GameDev.SE.

Мої ресурси з плиткових двигунів
Примітка: Все це орієнтоване на XNA, але це в значній мірі стосується будь-чого - вам просто потрібно змінити розіграші дзвінків.

  • У моїй відповіді на це запитання вказується, як я обробляю комірки карти та шари в своїй грі. (Див. Третє посилання.)
  • Моя відповідь на це питання пояснює, як я зберігаю дані у двійковому форматі.
  • Це перший (ну, технічно другий, але перший «технічний») пост в блозі про розвиток гри, над якою я працював. Вся серія містить інформацію про такі речі, як піксельні шейдери, освітлення, поведінка плиток, рух та все таке цікаве. Я фактично оновив публікацію, щоб включити зміст моєї відповіді до першого посилання, яке я розмістив вище, тому ви, можливо, захочете прочитати її замість (я, можливо, додав речі). Якщо у вас є якісь питання, ви можете залишити коментар там чи тут, і я з радістю допоможу.

Інші ресурси плиточного двигуна

  • Навчальний посібник з роботи з плитками з цього сайту дав мені основу, яку я використав для створення своїх карт.
  • Я ще не переглядав ці відеоуроки, тому що не мав часу, але вони, мабуть, корисні. :) Вони можуть бути застарілими, якщо ви використовуєте XNA.
  • На цьому сайті є ще кілька навчальних посібників, які (я думаю) засновані на вищезазначених відео. Можливо, варто перевірити.

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

@Mikalichov - Радий, що можу допомогти! :)
Річард Марскелл - Дракір

Як правило, за допомогою 2D масиву ви бажаєте використовувати перший вимір як ваш Y, а наступний як ваш X. У пам'яті масив буде послідовно 0,0-i, а потім 1,0-i. Якщо ви робите вкладені петлі з першою куркою X, ви насправді стрибаєте вперед і назад в пам’яті, а не послідуючи послідовний шлях читання. Завжди для (y), тоді для (x).
Brett W

@BrettW - Дійсний пункт. Дякую. Мене здивувало, як 2D-масиви зберігаються в .Net (рядовий мажор. Порядок сьогодні дізнався щось нове! :-)). Однак після оновлення мого коду я зрозумів, що він точно такий, як ви описуєте, лише із зміненими змінними. Різниця полягає в тому, в якому порядку малюють плитку. Мій поточний приклад коду малює зверху вниз, зліва направо, тоді як те, що ви описуєте, малює зліва направо, зверху вниз. Тож, для простоти, я вирішив повернути його до оригіналу. :-)
Річард Марскелл - Дракір

6

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

При цьому обидві системи можна використовувати окремо або також разом.

Стандартна система на основі плитки має такі сильні сторони:

  • Простий 2D масив плиток
  • Кожна плитка має один матеріал
    • Цей матеріал може мати одну текстуру, множину або змішану з навколишніх плиток
    • Можна повторно використовувати текстури для всіх плиток цього типу, зменшуючи створення та переробку активів
  • Кожна плитка може бути прохідною чи ні (виявлення зіткнення)
  • Легко відтворювати та ідентифікувати частини карти
    • Положення символів та положення карти є абсолютними координатами
    • Простий в циклі через відповідні плитки для області екрану

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

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

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

Вам потрібно буде надати більше деталей щодо вашого середовища / двигуна / мови, щоб надати приклади коду.


Дякую! Я працюю під C # і збираюся подібну (але менш талановиту) версію Final Fantasy 6 (або в основному більшість квадратних SNES RPG), тому плитка для підлоги І структурна плитка (тобто будинки). Моя найбільша стурбованість полягає в тому, що я не бачу, як створити 2D масив, не витрачаючи годин на перевірку: "там трав'яний кут, потім три прямих горизонталі, потім кут будинку, потім ..", з безліччю можливих помилок уздовж шлях.
Cristol.GdM

Схоже, Річард вас охопив з точки зору специфіки коду. Я особисто використовував би перелік туалетів і використовував битфлаги, щоб визначити значення плитки. Це дозволяє зберігати 32+ значення у середньому цілому. Ви також зможете зберігати кілька прапорів на плитку. Тож ви можете сказати Grass = true, Wall = true, Collision = true тощо без окремих змінних. Тоді ви просто призначаєте "теми", які визначають графік плитки для графіки для конкретного регіону карти.
Brett W

3

Малювання карти: Плитка легко в двигуні, тому що тоді карта може бути представлена ​​як гігантський масив індексів плитки. Код малювання - це лише вкладений цикл:

for i from min_x to max_x:
    for j from min_y to max_y:
        Draw(Tiles[i][j], getPosition(i,j))

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

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

let x,y = character.position
let i,j = getTileIndex(x,y) // <- getTileIndex is the opposite function of getPosition
let tile = Tiles[i][j]

Припустимо, вам потрібно перевірити наявність зіткнень з скелями / деревами тощо. Ви можете отримати плитку в положенні (положення символу + розмір спрайта символу + поточний напрямок) і перевірити, чи позначено він як прохідний або неприступний.

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