- PVSM.RU - https://www.pvsm.ru -

Работа с изометрическими матрицами. Часть 1

Изометрия — вещь, стара как компьютерные игры.
Сейчас пришло время, когда интернет и игры стали совмещаться в браузере (flash не в счет).
Примеров браузерных игр много, большая часть из них казуалки, но для гиков
более интересны жанры action, RTS и RPG, а для разработчиков — их реализация.

Для жанра RTS, RPG и пошаговых стратегий матрица является основным механизмом для движения
юнитов, рисования текстур и многое другое. Но когда Вы попробуете объеденить
матрицу и изометрические текстуры, Вы попадете в ад, с которого вы не вылезите,
пока не напишете прослойку для управления и воздействия на эту матрицу.

Под катом я расскажу:

  1. Как рисовать изометрическую матрицу
  2. Как нарисовать fullscreen изометрическую матрицу

Введение

Нарисовать обычную квадратную матрицу весьма просто, она симметрична, поиск координат вообще задача не из сложных, но изометрические матрицы вносят 2 коррективы в работу:

  1. Рисование матрицы должно происходить из центра холста.
  2. Визуальный обман отрисовки.

Работа с изометрическими матрицами. Часть 1

Все эти проблемы должны быть учтены во время реализации.

Отрисовка матрицы

Как говорилось в первом пункте особенностей изометрической матрицы, отрисовка должна происходить из центра холста. Значит, нам нужна середина холста.

Во втором пункте стоит обратить внимание на визуальный обман. Мозг [1], смотря на изображение, хочет его рисовать по горизонтали, но делать это нужно по диагонали, как показано на следующем изображении.
Работа с изометрическими матрицами. Часть 1

Самые основные проблемы мы уже рассмотрели, давайте посмотрим на реализацию.

Все откомментировано, есть рабочий пример, поэтому Вы все можете увидеть сами.

Реализация отрисовки

    var element = document.getElementById('canvas');
    element.width = 1024;
    element.height = 512;
    
    var ctx = element.getContext('2d');

    var image = new Image();
    image.src = 'http://habrastorage.org/storage2/e61/42d/5b2/e6142d5b26d171611d60d5941e390398.png';

    // Высота и ширина изображения
    var width = 128;
    var height = 64;
    
    image.onload = function() {

        function draw(x, y) {
            ctx.drawImage(image, x - width / 2, y - height / 2, width, height);
        }

        // Это будет координата X и Y для отрисовки ромба.
        // Мы не будем искать все углы для отрисовки прямоугольника, а только его центр
        // чтоб потом мы уже высчитали, как именно будем рисовать этот ромб.
        var Xo = 0;
        var Yo = 0;

        // Получение центра холста
        var C = Math.floor(element.width / 2);

        // Это сдвиг оси X, который вычисляется в зависимости
        // от текущей координаты Y
        var Xc = 0;
        
        var matrixHeight = 8;
        var matrixWidth = 8;

        for(var y = 0; y < matrixHeight; y++) {

            // Здесь высчитывается, на какой высоте должна начинаться отрисовка 
            Yo = (height / 2) * y;

            // Про эту переменную я уже рассказал чуть ранее.
            Xc = C - (width / 2 * y);

            
            for(var x = 0; x < matrixWidth; x++) {

                // Мы берем сдвиг координаты X и добавляем половину ширины изображения.
                // Мы никогда не сдвигаемся на всю ширину, только на половину.
                // Такая фишка изометрической матрицы
                Xo = Xc + (x * (width / 2));

                // Дополнительный сдвиг по оси Y.
                // Когда мы вычисляем координату X, то она мало того 
                // что сдвигается по оси X, но еще и по оси Y.
                Yo += height / 2;

                draw(Xo, Yo);
            }
        }
    }

Вот готовый пример [2]

Отрисовка fullscreen матрицы

Ну вот мы и смогли отрисовать изометрическую матрицу, теперь нужно отрисовать ее в fullscreen.
С ней немножко сложнее, а точнее:

  1. Вычислить общую высоту сдвига матрицы
  2. Нужно вычислить дополнительную высоту и ширину матрицы, с помощью которой будет заполнена пустая область.
  3. Поставить ограничения на отрисовку невидимых областей

Теория и практика вычисления общей высоты сдвига матрицы

На практике при отрисовке стандартной матрицы мы получаем пустую область в виде треугольника,
а значит мы можем вычислить неизвестную сторону и по ее высоте сделать сдвиг, чтоб не осталось дырок.

Нам известна длина одного катета и мы можем легко узнать длину гипотинузы.

сторона A — известная длина катета (ширина всей области отрисовки, деленная на 2). Т.е. 1024px / 2 = 512px, длина катета A
сторона B — этот катет мы не знаем, его нужно будет вычислить.
сторона C — Гипотенуза, чтоб узнать ее длину, нам нужно вычислить длину одной из сторон нашего маленького ромбика.
Для этого берем половину ширины и половину высоты ромба, приводим их в квадрат и суммируем, а у результата вычисляем квадратный корень.

    ...
    var width = 128;
    var height = 64;
    
    var grainLength = Math.sqrt((width / 2) * (width / 2) + (height / 2) * (height / 2));
    // Получается примерно 71.554
    // Если учесть, что высота изображения 64px и ширина 128px
    
    image.onload = function() {
    ...

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

    ...
    var grainLength = Math.sqrt((width / 2) * (width / 2) + (height / 2) * (height / 2));
    
    var center = Math.floor(element.width / 2);
    var hypotenuse = center / (width / 2) * grainLength;
    
    image.onload = function() {
    ...

Далее мы приводим известный катет и гипотенузу в квадрат. Отнимаем от гипотенузы катет, а у результата высчитываем квадратный корень.

    ...
    var hypotenuse = center / (width / 2) * grainLength;

    var catheusA = center * center;
    hypotenuse *= hypotenuse; // У меня закончилась фантазия на именования перменных, поэтому я сделаю так.

    var catheusB = Math.sqrt(hypotenyse - catheusA);
    
    image.onload = function() {
    ...

Все, уровнение решено, теперь осталось применить результат в вычислениях отрисовки.

    ...
        for(var y = 0; y < matrixHeight; y++) {
            Yo = (height / 2) * y;
            Xc = C - (64 * y);
    ...

Как видно с этого примера [3], теперь мы рисуем ромб так, чтоб сверху небыло дырки.
Но теперь возникла проблема №2. Нужно вычислить дополнительную высоту и ширину области.

Сделать это не сложно, но теперь мы отказываемся от статично заданной ширины или высоты матрицы.
Если ширина, больше чем высота, то ширина, должна делиться на 32, а иначе высота

        ...
        var matrixHeight = (element.width > element.height ? element.width : element.height) / 32;
        var matrixWidth = (element.width > element.height ? element.width : element.height) / 32;
        ...

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

                ...
                Xo = Xc + (x * 64);
                Yo += 32;
                
                if(Xo < (width / -1) || Xo > element.width + width || Yo < (height / -1) || Yo > element.height + height)
                    continue;

                draw(Xo, Yo);
                ...

Вот и все. Вот полностью готовый пример [4]. Теперь Вы научились рисовать полноценные изометрические матрицы, но это только половина дела.

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

Автор: Zerstoren


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/javascript/8843

Ссылки в тексте:

[1] Мозг: http://www.braintools.ru

[2] пример: http://forum-game.org/examples/habr/canvas/0.html

[3] примера: http://forum-game.org/examples/habr/canvas/1.html

[4] пример: http://forum-game.org/examples/habr/canvas/2.html