Практический пример: запутывание в HTML5 Canvas

Введение

Прошлой весной (2010 г.) я заинтересовался быстро растущей поддержкой HTML5 и связанных с ним технологий. В то время мы с другом соревновались друг с другом в двухнедельных соревнованиях по разработке игр, чтобы отточить свои навыки программирования и разработки, а также воплотить в жизнь игровые идеи, которые мы постоянно подбрасывали друг другу. Итак, я, естественно, начал включать элементы HTML5 в свои конкурсные работы, чтобы лучше понять, как они работают, и иметь возможность делать то, что было почти невозможно с использованием более ранних спецификаций HTML.

Из многих новых функций HTML5 растущая поддержка тега Canvas дала мне прекрасную возможность реализовать интерактивное искусство с помощью JavaScript, что побудило меня попробовать реализовать игру-головоломку, которая теперь называется Entanglement . Я уже создал прототип, используя обратную сторону плиток Settlers of Catan, поэтому, используя его в качестве своего рода чертежа, есть три основные части создания шестиугольной плитки на холсте HTML5 для веб-игры: рисование шестиугольника, рисование путей, и вращая плитку. Ниже подробно описывается, как я выполнил каждую из этих задач в их нынешней форме.

Рисование шестиугольника

В исходной версии Entanglement я использовал несколько методов рисования холста для рисования шестиугольника, но текущая форма игры использует drawImage() для рисования текстур, вырезанных из листа спрайтов.

Лист спрайтов плитки
Лист спрайтов плитки

Я объединил изображения в один файл, так что это всего лишь один запрос к серверу вместо десяти, как в данном случае. Чтобы нарисовать выбранный шестиугольник на холсте, мы сначала должны собрать вместе наши инструменты: холст, контекст и изображение.

Чтобы создать холст, все, что нам нужно, это тег холста в нашем html-документе, например:

<canvas id="myCanvas"></canvas>

Я даю ему идентификатор, чтобы мы могли вставить его в наш скрипт:

var cvs = document.getElementById('myCanvas');

Во-вторых, нам нужно получить 2D-контекст холста, чтобы мы могли начать рисовать:

var ctx = cvs.getContext('2d');

Наконец, нам нужно изображение. Если он называется «tiles.png» в той же папке, что и наша веб-страница, мы можем получить его следующим образом:

var img = new Image();
img.src = 'tiles.png';

Теперь, когда у нас есть три компонента, мы можем использовать ctx.drawImage(), чтобы нарисовать нужный нам шестиугольник из листа спрайтов на холст:

ctx.drawImage(img, sourceX, sourceY, sourceWidth, sourceHeight,
            destinationX, destinationY, destinationWidth, destinationHeight);

В данном случае мы используем четвертый шестиугольник слева в верхнем ряду. Кроме того, мы нарисуем его на холсте в верхнем левом углу, сохранив тот же размер, что и оригинал. Если предположить, что шестиугольники имеют ширину 400 пикселей и высоту 346 пикселей, в целом это будет выглядеть примерно так:

var cvs = document.getElementById('myCanvas');
var ctx = cvs.getContext('2d');
var img = new Image();
img.src = 'tiles.png';
var sourceX = 1200;
var sourceY = 0;
var sourceWidth = 400;
var sourceHeight = 346;
var destinationX = 0;
var destinationY = 0;
var destinationWidth = 400;
var destinationHeight = 346;
ctx.drawImage(img, sourceX, sourceY, sourceWidth, sourceHeight,
            destinationX, destinationY, destinationWidth, destinationHeight);

Мы успешно скопировали часть изображения на холст, в результате чего:

Шестиугольная плитка
Шестиугольная плитка

Рисование путей

Теперь, когда наш шестиугольник нарисован на холсте, мы хотим нарисовать на нем несколько линий. Сначала мы рассмотрим геометрию шестиугольной плитки. Нам нужны два конца линии на каждой стороне, каждый из которых заканчивается на расстоянии 1/4 от концов вдоль каждого края и на расстоянии 1/2 края друг от друга, например:

Конечные точки линий на шестиугольной плитке
Конечные точки линий на шестиугольной плитке

Нам также нужна красивая кривая, поэтому методом проб и ошибок я обнаружил, что, если я проведу перпендикулярную линию от края в каждой конечной точке, пересечение каждой пары конечных точек вокруг заданного угла шестиугольника дает хороший результат. контрольная точка Безье для заданных конечных точек:

Контрольные точки на шестиугольной плитке
Контрольные точки на шестиугольной плитке

Теперь мы сопоставляем конечные и контрольные точки с декартовой плоскостью, соответствующей нашему изображению холста, и готовы вернуться к коду. Для простоты начнем с одной строки. Мы начнем с рисования пути от верхней левой конечной точки до нижней правой конечной точки. Поскольку наше предыдущее шестиугольное изображение имело размер 400x346, это сделает нашу верхнюю конечную точку шириной 150 пикселей и 0 пикселей вниз, сокращенно (150, 0). Это контрольная точка (150, 86). Конечная точка нижнего края — (250, 346) с контрольной точкой (250, 260):

Координаты первой кривой Безье
Координаты первой кривой Безье

Имея координаты на руках, мы готовы начать рисовать. Мы начнем с ctx.beginPath(), а затем перейдем к первой конечной точке, используя:

ctx.moveTo(pointX1,pointY1);

Затем мы можем нарисовать саму линию с помощью ctx.bezierCurveTo() следующим образом:

ctx.bezierCurveTo(controlX1, controlY1, controlX2, controlY2, pointX2, pointY2);

Поскольку мы хотим, чтобы линия имела красивую границу, мы обведем этот путь дважды, каждый раз используя разную ширину и цвет. Цвет будет установлен с помощью свойства ctx.strokeStyle, а ширина будет установлена ​​с помощью ctx.lineWidth. В целом рисование первой линии будет выглядеть так:

var pointX1 = 150;
var pointY1 = 0;
var controlX1 = 150;
var controlY1 = 86;
var controlX2 = 250;
var controlY2 = 260;
var pointX2 = 250;
var pointY2 = 346;
ctx.beginPath();
ctx.moveTo(pointX1, pointY1);
ctx.bezierCurveTo(controlX1, controlY1, controlX2, controlY2, pointX2, pointY2);
ctx.lineWidth = 15;
ctx.strokeStyle = '#ffffff';
ctx.stroke();
ctx.lineWidth = 10;
ctx.strokeStyle = '#786c44';
ctx.stroke();

Теперь у нас есть шестиугольная плитка, первая линия которой проходит поперек:

Одиночная линия на шестиугольной плитке
Одиночная линия на шестиугольной плитке

Введя координаты остальных 10 конечных точек, а также соответствующих контрольных точек кривой Безье, мы можем повторить шаги, описанные выше, и создать плитку примерно так:

Готовая шестиугольная плитка.
Готовая шестиугольная плитка

Вращение холста

Когда у нас есть плитка, мы хотим иметь возможность поворачивать ее, чтобы в игре можно было выбирать разные пути. Чтобы сделать это с помощью холста, мы используем ctx.translate() и ctx.rotate() . Мы хотим, чтобы плитка вращалась вокруг своего центра, поэтому наш первый шаг — переместить опорную точку холста в центр шестиугольной плитки. Для этого мы используем:

ctx.translate(originX, originY);

Где originX будет составлять половину ширины шестиугольной плитки, а originY — половину высоты, что дает нам:

var originX = 200;
var originY = 173;
ctx.translate(originX, originY);

Теперь мы можем вращать плитку с новой центральной точкой. Поскольку у шестиугольника шесть сторон, нам нужно повернуть его на величину, кратную значению Math.PI, делённому на 3. Мы сохраним простоту и сделаем один поворот по часовой стрелке, используя:

ctx.rotate(Math.PI / 3);

Однако, поскольку наш шестиугольник и линии используют старые координаты (0,0) в качестве начала координат, как только мы закончим вращение, нам нужно будет перевести их обратно перед рисованием. Итак, в целом мы теперь имеем:

var originX = 200;
var originY = 173;
ctx.translate(originX, originY);
ctx.rotate(Math.PI / 3);
ctx.translate(-originX, -originY);

Если добавить приведенный выше перевод и поворот перед нашим кодом рендеринга, он теперь будет отображать повернутую плитку:

Повернутая шестиугольная плитка
Повернутая шестиугольная плитка

Краткое содержание

Выше я выделил некоторые возможности, которые HTML5 может предложить с помощью тега Canvas, включая рендеринг изображений, рисование кривых Безье и вращение холста. Использование тега Canvas HTML5 и его инструментов рисования JavaScript для Entanglement оказалось приятным занятием, и я с нетерпением жду появления множества новых приложений и игр, которые другие создадут с помощью этой открытой и развивающейся технологии.

Ссылка на код

Все приведенные выше примеры кода объединены ниже в качестве ссылки:

var cvs = document.getElementById('myCanvas');
var ctx = cvs.getContext('2d');
var img = new Image();
img.src = 'tiles.png';

var originX = 200;
var originY = 173;
ctx.translate(originX, originY);
ctx.rotate(Math.PI / 3);
ctx.translate(-originX, -originY);

var sourceX = 1200;
var sourceY = 0;
var sourceWidth = 400;
var sourceHeight = 346;
var destinationX = 0;
var destinationY = 0;
var destinationWidth = 400;
var destinationHeight = 346;
ctx.drawImage(img, sourceX, sourceY, sourceWidth, sourceHeight,
            destinationX, destinationY, destinationWidth, destinationHeight);

ctx.beginPath();
var pointX1 = 150;
var pointY1 = 0;
var controlX1 = 150;
var controlY1 = 86;
var controlX2 = 250;
var controlY2 = 260;
var pointX2 = 250;
var pointY2 = 346;
ctx.moveTo(pointX1, pointY1);
ctx.bezierCurveTo(controlX1, controlY1, controlX2, controlY2, pointX2, pointY2);
ctx.lineWidth = 15;
ctx.strokeStyle = '#ffffff';
ctx.stroke();
ctx.lineWidth = 10;
ctx.strokeStyle = '#786c44';
ctx.stroke();

ctx.beginPath();
pointX1 = 250;
pointY1 = 0;
controlX1 = 250;
controlY1 = 86;
controlX2 = 150;
controlY2 = 86;
pointX2 = 75;
pointY2 = 43;
ctx.moveTo(pointX1, pointY1);
ctx.bezierCurveTo(controlX1, controlY1, controlX2, controlY2, pointX2, pointY2);
ctx.lineWidth = 15;
ctx.strokeStyle = '#ffffff';
ctx.stroke();
ctx.lineWidth = 10;
ctx.strokeStyle = '#786c44';
ctx.stroke();

ctx.beginPath();
pointX1 = 150;
pointY1 = 346;
controlX1 = 150;
controlY1 = 260;
controlX2 = 300;
controlY2 = 173;
pointX2 = 375;
pointY2 = 213;
ctx.moveTo(pointX1, pointY1);
ctx.bezierCurveTo(controlX1, controlY1, controlX2, controlY2, pointX2, pointY2);
ctx.lineWidth = 15;
ctx.strokeStyle = '#ffffff';
ctx.stroke();
ctx.lineWidth = 10;
ctx.strokeStyle = '#786c44';
ctx.stroke();

ctx.beginPath();
pointX1 = 325;
pointY1 = 43;
controlX1 = 250;
controlY1 = 86;
controlX2 = 300;
controlY2 = 173;
pointX2 = 375;
pointY2 = 130;
ctx.moveTo(pointX1, pointY1);
ctx.bezierCurveTo(controlX1, controlY1, controlX2, controlY2, pointX2, pointY2);
ctx.lineWidth = 15;
ctx.strokeStyle = '#ffffff';
ctx.stroke();
ctx.lineWidth = 10;
ctx.strokeStyle = '#786c44';
ctx.stroke();

ctx.beginPath();
pointX1 = 25;
pointY1 = 130;
controlX1 = 100;
controlY1 = 173;
controlX2 = 100;
controlY2 = 173;
pointX2 = 25;
pointY2 = 213;
ctx.moveTo(pointX1, pointY1);
ctx.bezierCurveTo(controlX1, controlY1, controlX2, controlY2, pointX2, pointY2);
ctx.lineWidth = 15;
ctx.strokeStyle = '#ffffff';
ctx.stroke();
ctx.lineWidth = 10;
ctx.strokeStyle = '#786c44';
ctx.stroke();

ctx.beginPath();
pointX1 = 325;
pointY1 = 303;
controlX1 = 250;
controlY1 = 260;
controlX2 = 150;
controlY2 = 260;
pointX2 = 75;
pointY2 = 303;
ctx.moveTo(pointX1, pointY1);
ctx.bezierCurveTo(controlX1, controlY1, controlX2, controlY2, pointX2, pointY2);
ctx.lineWidth = 15;
ctx.strokeStyle = '#ffffff';
ctx.stroke();
ctx.lineWidth = 10;
ctx.strokeStyle = '#786c44';
ctx.stroke();