Власний Tilemap движок

В даній статті я навчу Вас створювати рівні практично для ігор будь-якого жанру і зробити їх розробку значно простіше. Ми створимо tile map движок, який Ви зможете використати в своїх проектах. Ми буде використовувати Haxe і OpenFL, але процес створення схожий для багатьох мов.
 
Трохи про те, що буде описано в статті
 
     
  • Що таке tile-based гра?
  •  
  • Створення або пошук власних «плиток»
  •  
  • Написання коду для відображення рівня
  •  
  • Редагування рівнів
  •  
 
 

Що таке tile-based гра?

Звичайно, Ви можете знайти визначення тайловой графіки в вікіпедії, але, щоб зрозуміти, що це, достатньо засвоїти кілька речей
 
     
  • Tile — плитка — маленької зображення, зазвичай прямокутної форми, яке виступає в ролі шматочка пазла при побудові великих зображень
  •  
  • Карта — група плиток, об'єднаних разом
  •  
  • Tile-based відсилає нас до методу створінь рівнів в іграх. Код розміщує плитки в заздалегідь визначених місцях
  •  
У нашій статті плитки будуть мати прямокутну форму. Існує кілька крутих фішок, які Ви отримуєте, використовуючи тайловую графіком. Найкрутіше — немає необхідності малювати величезні зображення для кожного рівня. П'ятдесят зображень з роздільною здатністю 1280x768px для 50-ти рівневої гри проти одного зображення з сотнею плиток мають величезні відмінності. Інша перевага полягає в тому, що розміщувати предмети, використовуючи тайловую графіком, стає помітно простіше.
 
 

Створення або пошук плиток

Перше, що потрібно для побудови движка — набір плиток. У Вас є два варіанти: використовувати вже готові tiles або зробити свої власні. Якщо Ви вирішите використовувати готові зображення, то їх можна легко знайти по всьому інтернетом. Мінусом є те, що ця графіка не була зроблена спеціально для вашої гри. З іншого боку, якщо Ви просто експериментуєте, то даний варіант цілком підійде.
 
Думаю, що з пошуком плиток виникнути проблем не повинно, тому я не буду детально на цьому зупинятися.
 
 
Створення плиток
Існує безліч чудових інструментів, що дозволяє легко створювати свої зображення. Багато розробники для своєї роботи використовують дані інструменти
 Це — найпопулярніші інструменти для створення піксель-арту. Якщо Вам потрібно щось більш потужне, то GIMP ідеально підійде.
 
Як тільки Ви обрали програму, Ви можете почати експериментувати зі своїми власними тайлами. Оскільки дана стаття покаже, як створити власний движок тайловой графіком, то я не буду детально зупинятися на самих плитках.
 
 

Написання коду

Коли у нас є все необхідне, ми може зануритися безпосередньо в саме програмування.
 
 
Відображення однієї плитки на екрані
Давайте почнемо з найпростішої задачі, відображення однієї плитки на екрані. Переконайтеся, що всі Ваші зображення мають один розмір і збережені в різних файлах (про зберігання спрайтів в одному фалі ми поговоримо пізніше).
 
Як тільки всі Ваші плитки знаходяться в папці з ресурсами проекту, Ви можете написати простий
Tile
клас. Ось приклад на Haxe
 
import flash.display.Sprite;
import flash.display.Bitmap;
import openfl.Assets;
 
class Tile extends Sprite {
 
    private var image:Bitmap;
 
    public function new() {
        super();
 
        image = new Bitmap(Assets.getBitmapData("assets/grassLeftBlock.png"));
        addChild(image);
    }
}

Зараз, все, що ми робимо — поміщаємо плитку на екран. Єдина річ, яку робить клас — імпортування зображення з папки з ресурсами і додавання як дочірнього об'єкта. Те, як виглядатиме даний клас дуже сильно залежить від мови програмування, який Ви використовуєте.
 
Тепер, коли у нас є клас
Tile
, нам потрібно створити екземпляр
Tile
і додати його в нашому головному класі.
 
import flash.display.Sprite;
import flash.events.Event;
import flash.Lib;
 
class Main extends Sprite {
 
    public function new() {
        super();
 
        var tile = new Tile();
        addChild(tile);
    }
 
    public static function main() {
        Lib.current.addChild(new Main());
    }
}

Клас
Main
створює новий об'єкт
Tile
при виклику конструктора і додає його в список відображення.
 
Коли запускається гра, буде викликана функція
main()
і новий об'єкт типу
Main
буде додано на сцену. Зверніть увагу, що Ваша нова плитка з'явиться в лівому верхньому кутку екрану.
 
 
Використання масивів для відображення всіх плиток
Наступний крок — придумати метод відображення всіх плиток. Найпростіший метод — заповнити масив числами, кожне з яких відповідає якійсь плитці. Потім, Ви просто перебираєте всі елементи масиву і відображаєте їх.
 
У нас є вибір: використовувати звичайний масив або матрицю. Якщо Ви не знайомі з матрицями, то просто знайте, що це масив, який містить в собі ще масиви. Більшість мов програмування представляють це, як nameOfArray [x] [y].
 
X і Y ми використовуємо, як координати на екрані. Може бути нам вдасться використовуватися ці X і Y для відображення наших плиток? Отже, давайте поглянемо на матрицю
 
private var exampleArr = [ [0, 0, 0, 0, 0],
                           [0, 0, 0, 0, 0],
                           [0, 0, 0, 0, 0],
                           [0, 0, 0, 0, 0],


Зауважимо, що нульовий елемент в даному масиві — масив з п'яти чисел. Це значить, що спочатку ви отримуєте елемент y, а потім x. Якщо Ви спробуєте взяти елемент [1] [0], то Ви отримаєте доступ до шостої плитці.
 
Якщо Ви не розумієте, як працюють матриці, не турбуйтесь. В даній статті я буду використовувати звичайний масив, щоб спростити нашу задачу.
 
private var exampleArr = [ 0, 0, 0, 0, 0,
                           0, 0, 0, 0, 0,
                           0, 0, 0, 0, 0,
                           0, 0, 0, 0, 0,
                           0, 0, 0, 0, 0 ];

Приклад Вище показує, що використовувати звичайний масив — набагато простіше. Ми можемо відобразити конкретний елемент, обчисливши координати, використовуючи просту формулу.
 
Тепер, давайте напишемо код, який створює наш масив. Заповнимо його 1. Цифра один означатиме ID нашої першої плитки.
 
Для зберігання масиву нам потрібно створити змінну всередині класу
Main

 
private var map:Array<Int>;

Може виглядати трохи дивно, тому я поясню.
Ім'я змінної — map, а тип — Array (масив). означає, що масив містить числа.
Тепер, додамо трохи коду в наш конструктор нашого класу, щоб ініціалізувати нашу карту
 
map = new Array<Int>();

Цей фрагмент створює порожній масив, який скоро ми заповнимо. Але спочатку, давайте оголосимо кілька змінних, які допоможуть нам з математикою
 
public static var TILE_WIDTH = 60;
public static var TILE_HEIGHT = 60;
public static var SCREEN_WIDTH = 600;
public static var SCREEN_HEIGHT = 360;

Ці змінні є
public static
для того, щоб дати нам доступ до них з будь-якого місця нашої програми. Ви могли помітити, що виходячи з даних чисел, ми будемо вирішувати, скільки осередків зберігати в масиві.
 
var w = Std.int(SCREEN_WIDTH / TILE_WIDTH);
var h = Std.int(SCREEN_HEIGHT / TILE_HEIGHT);
 
for (i in 0...w * h) {
    map[i] = 1
}

Тут ми задаємо значення 10 для змінної w і 6 для h, як я вже сказав. Далі, ми повинні пройтися циклом по масиву, щоб умістити 10 * 6 чисел.
 image
Тепер у нас є простенька карта, але нам же потрібно розташувати плитки правильно, так? Для цього повернемося назад в клас
Tile
і створимо функцію, що дозволяє нам це зробити
 
public function setLoc(x:Int, y:Int) {
    image.x = x * Main.TILE_WIDTH;
    image.y = y * Main.TILE_HEIGHT;
}

Коли ми викликаємо функцію
setLoc()
, ми передаємо координати x і y. Функція бере ці значення і переводить їх в координати в пікселях, множачи на
TILE_WIDTH 
і
TILE_HEIGHT

 
Єдина річ, яку залишилося зробити — описати в класі
Main
процес створення і позиціонування плиток на карті
 
for (i in 0...map.length) {
    var tile = new Tile();
    var x = i % w;
    var y = Math.floor(i / w);
    tile.setLoc(x, y);
    addChild(tile);
}

Так! Все правильно. Екран заповнений плитками. Давайте розберемося, що відбувається вище
 
 
Формула
Давайте обговоримо формулу, про яку я згадував вище.
 
Ми обчислюємо
x
, привласнюючи йому залишок від ділення
i
на
w
. Це робиться для того, щоб повернути
x
значення 0 на початку кожної лінії.
 image
Для
y
ми беремо
floor()
від i / w.
 
Нарешті, я б хотів сказати трохи про прокрутку рівня. Зазвичай у Вас не вийде створити рівень, який повністю буде поміщатися на екрані. Ваші карти будуть помітно більше, ніж екран, тому немає необхідності малювати ту частину карти, яку користувач не побачить. Це можна поправити за допомогою все тієї ж математики. Ви повинні вважати, які плитки будуть видні на екрані, а які — ні.
 
Наприклад: Екран розміром 500х500, а плитки — 100х100, а Ваш світ — 1000х1000. Вам потрібно робити просту перевірку перед отрисовкой плитки.
 
Все, що нам залишилося — створити різні типи плиток і відобразити що-небудь гарне.
 
 
Різні типи плиток
Окей, тепер у нас є карта, повна однакових елементи. Не погано було б мати більше одного типу елементів, а це значить, що нам доведеться змінити наш конструктор в класі
Tile

 
public function new(id:Int) {
        super();
 
        switch(id) {
            case 1:
                image = new Bitmap(Assets.getBitmapData("assets/grassLeftBlock.png"));
            case 2:
                image = new Bitmap(Assets.getBitmapData("assets/grassCenterBlock.png"));
            case 3:
                image = new Bitmap(Assets.getBitmapData("assets/grassRightBlock.png"));
            case 4:
                image = new Bitmap(Assets.getBitmapData("assets/goldBlock.png"));
            case 5:
                image = new Bitmap(Assets.getBitmapData("assets/globe.png"));
            case 6:
                image = new Bitmap(Assets.getBitmapData("assets/mushroom.png"));
        }
        addChild(image);
    }

Тепер у нас є шість різних типів плиток. Мені потрібна конструкція
switch
, щоб вибирати, яке зображення потрібно відобразити. Ви могли помітити, що тепер конструктор приймає як параметр число, що означає тип плитки.
 
Повернемося в конструктор класу
Main
і відредагуємо наш цикл
 
for (i in 0...map.length) {
    var tile = new Tile(map[i]);
    var x = i % w;
    var y = Math.floor(i / w);
    tile.setLoc(x, y);
    addChild(tile);
}

Єдина зміна полягає в тому, що тепер ми передаємо конструктору тип блоку. Якби Ви спробували запустити програму без даної правки, до виконання закінчилося б помилкою.
 
Зараз все стало на свої місця, але потрібно придумати дизайн карти замість того, щоб заповнювати її випадковими блоками. Перше, видаліть цикл в конструкторі
Main
, яким заповнюють масив одиницями. А потім створіть Вашу карту вручну
 
map = [ 0, 4, 0, 0, 0, 0, 0, 5, 0, 0,
        0, 0, 0, 0, 0, 1, 2, 2, 3, 0,
        0, 0, 0, 6, 0, 0, 0, 0, 0, 0,
        1, 2, 2, 3, 0, 0, 0, 0, 0, 0,
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
        0, 0, 0, 0, 0, 0, 1, 2, 2, 3 ];

Якщо відформатувати масив, як зроблено у мене, то можна легко побачити приблизний вигляд рівня. Але ніхто не забороняє Вам забити масив просто в один рядок.
 
При запуску програми Ви побачите декілька помилок. Проблема полягає в тому, що наша карта містить нульовий тип блоку, який не має зображення і наш клас просто не знає, що з цим робити. Виправимо це непорозуміння
 
if (image != null) addChild(image);

Ця коротка перевірка позбавить нас від докучливої ​​помилки з нульовим покажчиком. Остання зміна торкнеться функцію
setLoc()
. Ми намагаємося використовувати змінні
x
і
y
, що не були ініціалізовані
 
public function setLoc(x:Int, y:Int) {
    if (image != null) {
        image.x = x * Main.TILE_WIDTH;
        image.y = y * Main.TILE_HEIGHT;
    }
}

Завдяки цим двом простим умовам Ви тепер можете запустити гру і побачити простий рівень. Тайли, які вміють id 0 ми сприймаємо, як пусте місце. Додайте який-небудь фон. щоб це виглядало більш привабливо.
 image
 
 

Висновок

Ви тільки що створили tale-based движок. Тепер Ви знаєте, що таке тайловая графіка і як їй користуватися. Ви навчилися створювати і редагувати рівні. Але не зупиняйтеся на цьому, адже стільки всього ще можна поліпшити в нашому движку.

Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.