В одном из писем, которые я получаю от своих читателей, меня спросили: "как создать карту для игры HTML5 canvas, используя набор плиток, упакованных в один спрайт. Вместо того чтобы просто ответить на этот вопрос письмом, я решил, что будет хорошей идеей написать краткое руководство о том, как создать свою карту для игры с помощью javascript.
Итак, давайте создадим потрясающий ландшафт, который вы можете использовать в своей игре. Первое, что мы должны создать набор плиток для фона. Вы можете создать свой собственный или откуда-нибудь спереть. Я решил воспользоваться этим игровым фоном:
Мне нравится визуально разделять элемент ландшафта на отдельные плитки, а затем маркировать каждую в определенном порядке. Это помогает мне легко увидеть то количество плиток, которое мне потребуется для создания моей карты.
После того, как у нас появится набор плиток, нам нужно создать структуру, которую будет использовать наша игра для формирования плиточного фона. Самый простой способ сделать это использовать двумерный массив, который содержит массив чисел, каждый номер которого представляюет собой плитку (а значит нам нужно промаркировать изображение для простоты использования). Мы можем организовать циклический обход массива и тем самым сгенерировать фон.
Количество строк и столбцов в 2D массиве зависит от вашей игры. Для этого урока мы создадим фон из 32 ячеек по горизонтали и 20 по вертикали. Получится солидная игровая карта размером 1024 × 640 из плиток размером 32 × 32.
Создание массива 2D, честно говоря, самая нудная и трудоемкая часть работы, поэтому есть смысл воспользоваться специальной программой или ее аналогами. Они используют визуальный редактор, так что вы можете выбрать нужную вам плитку, а затем поместить ее там, где вам нужно. Эти программы используют 2D массив, но они делают за вас всю черную работу. Но, так как у нас нет программы, которая могла бы создать его для нас, нам придется сделать это вручную. Это значит, что нам придется вручную вводить в 2D массив номер каждой плитки, соответствующий ее положению на фоне.
Чтобы сэкономить время, я приведу здесь массив с некоторыми заполнеными плитками.
var ground = [
[172, 172, 172, 79, 34, 34, 34, 34, 34, 34, 34, 34, 56, 57, 54, 55, 56, 147, 67, 67, 68, 79, 79, 171, 172, 172, 173, 79, 79, 55, 55, 55],
[172, 172, 172, 79, 34, 34, 34, 34, 34, 34, 146, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 155, 142, 172, 159, 189, 79, 79, 55, 55, 55],
[172, 172, 172, 79, 79, 34, 34, 34, 34, 34, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 171, 172, 159, 189, 79, 79, 79, 55, 55, 55],
[188, 188, 188, 79, 79, 79, 79, 34, 34, 34, 36, 172, 172, 143, 142, 157, 79, 79, 79, 79, 79, 79, 187, 159, 189, 79, 79, 79, 55, 55, 55, 55],
[79, 79, 79, 79, 79, 79, 79, 79, 34, 34, 36, 172, 159, 158, 172, 143, 157, 79, 79, 79, 79, 79, 79, 79, 79, 79, 39, 51, 51, 51, 55, 55],
[79, 79, 79, 79, 79, 79, 79, 79, 79, 34, 36, 172, 143, 142, 172, 172, 143, 157, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 55],
[79, 79, 79, 79, 79, 79, 79, 79, 79, 34, 52, 172, 172, 172, 172, 172, 172, 143, 156, 157, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79],
[79, 79, 79, 79, 79, 79, 79, 79, 79, 34, 52, 172, 172, 172, 172, 172, 172, 159, 188, 189, 79, 79, 79, 79, 79, 171, 172, 172, 173, 79, 79, 79],
[79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 188, 158, 172, 172, 172, 172, 173, 79, 79, 79, 79, 79, 79, 79, 187, 158, 159, 189, 79, 79, 79],
[79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 171, 172, 172, 159, 188, 189, 79, 79, 79, 79, 79, 79, 79, 79, 171, 173, 79, 79, 79, 79],
[79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 171, 172, 172, 173, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 171, 173, 79, 79, 79, 79],
[155, 142, 157, 79, 79, 79, 79, 79, 79, 79, 79, 79, 187, 188, 188, 189, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 171, 173, 79, 79, 79, 79],
[171, 172, 173, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 171, 173, 79, 79, 79, 79],
[171, 172, 143, 156, 157, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 187, 189, 79, 79, 79, 79],
[187, 188, 158, 172, 173, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79],
[79, 79, 79, 188, 189, 79, 79, 79, 79, 79, 79, 155, 156, 156, 157, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 155, 156],
[34, 34, 79, 79, 79, 79, 79, 79, 79, 79, 79, 171, 172, 172, 173, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 155, 142, 172],
[34, 34, 34, 79, 79, 79, 79, 79, 79, 79, 79, 171, 172, 172, 173, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 171, 172, 172],
[34, 34, 34, 34, 79, 79, 79, 79, 79, 79, 155, 172, 172, 159, 189, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 171, 172, 172],
[34, 34, 34, 34, 34, 34, 79, 79, 79, 79, 171, 172, 172, 173, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 79, 155, 142, 172, 172]
];
Теперь с помощью этого массива с данными мы должны сгенерировать фон. Для этого нам придется сделать сделать три вещи:
- Пройтись по каждому числу в массиве
- Перевести числа в строке и столбце в соответствующее им плиточное изображения
- Нарисовать плитку на холсте в позиции, соответствующей числам строке и столбце массива
Итак, сначала цикл:
var tilesetImage = new Image();
tilesetImage.src = 'path/to/image.png';
tilesetImage.onload = drawImage;
var canvas = document.getElementById('main');
var ctx = canvas.getContext('2d');
var tileSize = 32; // Размер плитки (32×32)
var rowTileCount = 20; // Количество плиток в каждом ряду фона
var colTileCount = 32; // Количество плиток в каждом столбце фона
var imageNumTiles = 16; // Количество плиток в каждом ряду тайлсета
function drawImage () {
for (var r = 0; r < rowTileCount; r++) {
for (var c = 0; c < colTileCount; c++) {
var tile = ground[ r ][ c ]; // Шаги 2 и 3
}
}
}
Далее, мы определяем ряд и столбец плитки. Это не так сложно, как кажется. Чтобы найти ряд плитки, мы делим число на количество плиток в ряду (16) и округляем. Для расчета столбца плитки, мы Mod (%
) число на количество плиток в ряду и округляем. Например, давайте определим нужные ряд и столбец плитки под номером 10. Это ряд 0 и столбец 10 (то есть, первый ряд и одиннадцатый столбец, но так как мы начали отчет с нуля, нумерация всех рядов и столбцов тоже должна начинаться с нуля, а не с единицы). Используем наш формулу, row = Math.floor(10 / 16) = Math.floor(0.625) = 0
и col = Math.floor(10 % 16) = Math.floor(10) = 10
.
var tileRow = (tile / imageNumTiles) | 0; // Операция "побитовое ИЛИ"
var tileCol = (tile % imageNumTiles) | 0;
Операция "побитовое ИЛИ" (| 0
) делает тоже самое, что и Math.floor
, но гораздо быстрее. Наконец, мы должны нарисовать плитки размером 32×32 на холсте. Удобно, что API canvas предоставляет возможность сделать это с помощью функции DrawImage (). Определяя область отсечения, мы можем отобразить только нужную часть изображения, а не всю картинку.
Чтобы перевести строку и столбец плитки в координаты изображения x
и y
, мы просто умножим их на размер плитки. Когда мы рисуем изображение на холсте, мы делаем то же самое для r
и c
, чтобы правильно нарисовать плитку на фотовом изображении.
ctx.drawImage(tilesetImage, (tileCol * tileSize), (tileRow * tileSize), tileSize, tileSize, (c * tileSize), (r * tileSize), tileSize, tileSize);
Собираем все вместе, получаем:
var tilesetImage = new Image();
tilesetImage.src = 'path/to/image.png';
tilesetImage.onload = drawImage;
var canvas = document.getElementById('main');
var ctx = canvas.getContext('2d');
var tileSize = 32; // Размер плитки (32×32)
var rowTileCount = 20; // Количество плиток в каждом ряду фона
var colTileCount = 32; // Количество плиток в каждом столбце фона
var imageNumTiles = 16; // Количество плиток в каждом ряду тайлсета
function drawImage () {
for (var r = 0; r < rowTileCount; r++) {
for (var c = 0; c < colTileCount; c++) {
var tile = ground[ r ][ c ];
var tileRow = (tile / imageNumTiles) | 0; // Операция "побитовое ИЛИ"
var tileCol = (tile % imageNumTiles) | 0;
ctx.drawImage(tilesetImage, (tileCol * tileSize), (tileRow * tileSize), tileSize, tileSize, (c * tileSize), (r * tileSize), tileSize, tileSize);
}
}
}
И, когда мы пробежимся по всему массиву, мы нарисуем наш ландшафт на холсте.
Расслоение плитки
Как видите, наш фон выглядит не очень интересно. Я бы даже сказал тоскливо. Как вы уже, наверное, догадались, нам потребуется еще один 2D массив, который мы нарисуем сверху первого.
var layer1 = [
[0, 0, 32, 33, 0, 236, 0, 0, 236, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 69, 0, 0, 0, 0, 0, 32, 33],
[0, 0, 48, 49, 0, 236, 220, 220, 236, 0, 0, 147, 72, 73, 70, 71, 72, 73, 83, 83, 84, 85, 0, 0, 0, 0, 0, 48, 49],
[0, 0, 64, 65, 54, 0, 236, 236, 0, 0, 162, 163, 84, 89, 86, 87, 88, 89, 99, 99, 100, 101, 0, 0, 0, 0, 7, 112, 113],
[0, 0, 80, 81, 70, 54, 55, 50, 0, 0, 0, 179, 100, 105, 102, 103, 104, 105, 0, 0, 0, 0, 0, 0, 16, 22, 23, 39],
[0, 0, 96, 97, 86, 70, 65, 144, 193, 0, 0, 37, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 48, 49],
[0, 0, 0, 0, 102, 86, 81, 160, 161, 0, 0, 37, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 64, 65, 174, 175, 67, 66, 54],
[0, 0, 0, 0, 0, 102, 97, 176, 177, 0, 0, 37, 0, 252, 0, 0, 0, 201, 202, 0, 0, 0, 0, 0, 80, 81, 190, 191, 83, 82, 70, 71],
[0, 0, 0, 0, 0, 0, 0, 48, 49, 0, 0, 53, 0, 0, 0, 0, 0, 217, 218, 0, 0, 0, 0, 0, 96, 97, 222, 223, 99, 98, 86, 87],
[201, 202, 0, 0, 0, 0, 0, 64, 65, 66, 68, 69, 0, 0, 0, 0, 0, 233, 234, 0, 0, 0, 0, 0, 238, 239, 0, 0, 238, 239, 102, 103],
[217, 218, 0, 0, 0, 0, 0, 80, 81, 82, 84, 85, 0, 0, 0, 0, 0, 249, 250, 0, 0, 0, 0, 0, 254, 255, 0, 0, 254, 255],
[233, 234, 0, 0, 0, 0, 0, 96, 97, 98, 100, 101, 0, 0, 0, 0, 0, 0, 0],
[249, 250, 0, 0, 201, 202, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 238, 239, 0, 0, 238, 239],
[0, 0, 0, 0, 217, 218, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 254, 255, 0, 0, 254, 255],
[0, 0, 0, 0, 233, 234, 196, 197, 198],
[2, 3, 4, 0, 249, 250, 228, 229, 230],
[18, 19, 20, 8, 0, 0, 244, 245, 246, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 201, 202],
[0, 35, 40, 24, 25, 8, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 0, 0, 0, 0, 0, 0, 0, 0, 217, 218],
[0, 0, 0, 40, 41, 20, 8, 9, 0, 0, 0, 0, 0, 0, 0, 16, 17, 18, 19, 20, 21, 0, 0, 0, 0, 0, 0, 0, 233, 234],
[0, 0, 0, 0, 40, 19, 24, 25, 8, 9, 0, 0, 0, 0, 0, 48, 49, 50, 51, 52, 115, 3, 4, 0, 0, 0, 0, 0, 249, 250],
[0, 0, 0, 0, 0, 0, 40, 41, 20, 21, 0, 0, 0, 0, 0, 64, 65, 66, 67, 52, 19, 19, 20, 21]
];
Добавляем новый слой в нашем цикле:
for (var r = 0; r < rowTileCount; r++) {
for (var c = 0; c < colTileCount; c++) {
var tile = ground[ r ][ c ];
var tileRow = (tile / imageNumTiles) | 0; // Операция "побитовое ИЛИ"
var tileCol = (tile % imageNumTiles) | 0;
ctx.drawImage(tilesetImage, (tileCol * tileSize), (tileRow * tileSize), tileSize, tileSize, (c * tileSize), (r * tileSize), tileSize, tileSize);
tile = layer1[ r ][ c ]; tileRow = (tile / imageNumTiles) | 0;
tileCol = (tile % imageNumTiles) | 0;
ctx.drawImage(tilesetImage, (tileCol * tileSize), (tileRow * tileSize), tileSize, tileSize, (c * tileSize), (r * tileSize), tileSize, tileSize);
}
}
Вы можете добавить столько слоев, сколько вам нужно, чтобы создать то изображение, которое вы хотите.
Вывод
Набор плиток является отличным способом, чтобы создать большие и стильные карты без необходимости в отдельных изображений для карт игрового мира. Повторяясь в нужных местах, плитки уменьшают число необходимых файлов для карт, а значит скорость, с которой игра загружается увеличивается. Этот простой движок может быть использованг в любой вашей игре для создания плиточного фона.