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

:

Изометрия — вещь, стара как компьютерные игры.

Сейчас пришло время, когда интернет и игры стали совмещаться в браузере (flash не в счет).

Примеров браузерных игр много, большая часть из них казуалки, но для гиков более интересны жанры action, RTS и RPG, а для разработчиков — их реализация.

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

Далее я расскажу:

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

Введение

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

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

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


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

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

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

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

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

    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);
            }
        }
    }

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

Отрисовка 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);
    ...

Как видно с этого примера, теперь мы рисуем ромб так, чтоб сверху небыло дырки.
Но теперь возникла проблема №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);
                ...

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

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

Источник

Если вы ведёте свой блог, микроблог, либо участвуете в какой-то популярной социальной сети, то вы можете быстро поделиться данной заметкой со своими друзьями и посетителями. Для этого воспользуйтесь предлагаемыми ниже кнопками:


Блог: http://romanlovetext.blogspot.com/