Sources.RU Magazine Поиск по журналу
 

TopList

Создание игр на JavaScript

Переводчик: vk

От Zanathel
«Передвижение белого квадрата по темному полю»

Создайте Вашу первую игру на JavaScript!

Введение.

JavaScript – очень мощный язык, гораздо более мощный, чем можно предположить.
Я понял, на что способны скриптовые языки после того, как поэкспериментировал с обработчиками событий от клавиатуры и полной перерисовкой экрана в сжатые сроки. В IE 5.0 я поставил перерисовку экрана каждые 10 милисекунд, и это не вызвало ни задержек, ни увеличения потребляемого объема памяти. Когда я это увидел, я понял, что просто обязан углубиться в изучение JavaScript. В результате получилась в чем-то простая, а в чем-то сложная игра, которая пользовалась успехом среди моих друзей. Сейчас я собираюсь попытаться создать универсальную относительно браузера игру с дружественным интерфейсом. Пусть семейство браузеров с открытым кодом представляет FireFox, как наиболее стандартный из них.


Тест.

Взгляните на нижеследующий код. Он корректно работает и в IE, и в FireFox. Что меня поразило, так это то, что FireFox по сравнению с IE предоставляет расширенную поддержку обработки событий:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0
Transitional//EN""http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" /> <title>Browser hooking capabilities</title> <script language="javascript" type="text/javascript"> <!-- var isIE = (String(typeof(document.all)) != "undefined"); function keyhookDown(ev) { var key = new Number(); if (isIE) key = event.keyCode; else key = ev.which; alert("Down: " + key.toString()); } function keyhookUp(ev) { var key = new Number(); if (isIE) key = event.keyCode; else key = ev.which; alert("Up: " + key.toString()); } function prepareKeyHook() { document.onkeyup = keyhookUp; document.onkeydown = keyhookDown; } --> </script> </head> <body id="gSurface" onload="prepareKeyHook()" name="gSurface"> </body> </html>

Это отличная новость.


Как все собрать.

    Начинать следует с передвижения объекта (квадрата) по экрану. Для этого потребуются обработчики событий, передвигаемый объект, перерисовка экрана и система координат. Перед тем как создать систему координат, нужно получить реальные размеры клиентской области окна. В IE они хранятся в document.body.offsetWidth и document.body.offsetHeight. А как в Firefox? Воспользовавшись Google, я нашел свойства window.innerWidth и window.innerHeight. И все прекрасно работает.
    Итак, высота и ширина системы координат определена. Теперь надо расположить наш объект точно в центре экрана. Как? Я попробовал разные способы, но наиболее подходящей мне показалась система позиционирования CSS. Перерисовывая конкретный участок экрана (ограниченный ячейкой таблицы), можно добиться высокой эффективности перерисовки.


Создание ядра

В обычных языках программирования Вы не перерисовываете постоянно весь экран. Вы перерисовываете конкретные участки (блиттинг). Эмуляция блиттинга в JavaScript заключается в том, что движущийся элемент располагается внутри отдельного слоя, который в общем случае может заполнять весь экран. Это позволяет не перерисовывать статические объекты, такие как дома, фон, статистика игры, сообщения и т.д. и спасает от мерцания картинки.

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0
Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" /> <title>Простой движущийся слой!</title> <script language="javascript" type="text/javascript"> <!--<![CDATA[ var surface = null; var SCREEN_X = 0, SCREEN_Y = 1; var bIE = (String(typeof(document.all)) != "undefined"); var iMs = 25; //////////////////////////// Загрузка игры ////////////////////////// function loadGame() { surface = document.getElementById("gSurface"); if (surface == null) { alert("Не могу найти нужную поверхность!\nОтмена загрузки..."); return; } } //]]>--> </script> <style type="text/css" media="all"> <!--<![CDATA[ html, body { width: 100%; height: 100%; padding: 0; margin: 0; overflow: hidden; } body { background-color: black; } #gSurface { position: relative; width: 100%; height: 100%; } #gBox { position: absolute; z-index: 2; width: 100px; height: 100px; background-color: white; } /*]]>*/--> </style> </head> <body id="gContainer" onload="loadGame()"> <div id="gSurface"> </div> </body> </html>

ID тела документа – gContainer, а ID перерисовываемой поверхности – gSurface. Я также создал функцию, выполняющуюся всякий раз при загрузке страницы (не обновлении, а именно загрузке). В ней инициализируется передвигаемый слой (gBox).


Инициализация сцены

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

(ширина поверхности – ширина объекта)/2
(высота поверхности – высота объекта)/2

Перед тем как записать эту формулу на JavaScript, нужно определить класс движущегося объекта. Я назвал его «_box» (я ставлю символ подчеркивания в начале имен всех моих классов JavaScript). Он содержит x и y-координаты слоя, пропорции, скорость (в пикселях за такт) и параметры движения.

//////////////////////////// Классы ////////////////////////////
function _box(x, y, w, h, speed)
    {
        this.x = x;
        this.y = y;
        this.w = w;
        this.h = h;
        this.speed = speed;

        this.ix = false;
        this.dx = false;
        this.iy = false;
        this.dy = false;
}

Ix, dx, iy и dy – флаги увеличения (increase) и уменьшения (decrease) координат x и y. Так как сначала объект должен покоиться, установим все четыре флага в false. Объект не движется ни вниз, ни вверх, ни влево, ни вправо.
Теперь давайте напишем функцию, создающую движущийся объект. Для этого понадобится объявить глобальную переменную box.

var surface = null, box = null;

Объявив её, можно перейти к написанию функции:

//////////////////////////// Загрузка игры //////////////////////////
function prepareBox()
    {
        box = new _box(0, 0, 100, 100, 8);
        box.x = (getScreenSize(SCREEN_X) - box.w) / 2;
        box.y = (getScreenSize(SCREEN_Y) - box.h) / 2;
    }

Если Вы новичок в объектно-ориентированном программировании на JavaScript, поясняю, что я определил экземпляр класса _box как переменную box. Параметрами конструктора являются начальные значения свойств. Обратиться к свойству созданного экземпляра можно посредством точки и имени нужного свойства.
Как Вы могли заметить, я использовал ещё не определенную функцию getScreenSize. Нам нужно быстро получать размеры экрана, а так как соответствующие команды для разных браузеров различны, я решил выделить определение размеров в самостоятельную функцию:

//////////////////////////// Экран /////////////////////////////
function getScreenSize(s)
    {
        if (s == SCREEN_X)
        {
            if (bIE)
                return document.body.offsetWidth;

            return window.innerWidth;
        }
        else
        {
            if (bIE)
                return document.body.offsetHeight;

            return window.innerHeight;
        }
    }

У нас есть класс движущегося объекта, функция для его создания и функция определения размеров экрана. Что осталось? Нужна функция, которая будет вызываться всеми нашими обрабтчиками событий, такими как OnKeyUp и OnKeyDown. Нужна также функция, которая будет вызываться каждые 25 милисекунд (это значение хранится в переменной iMs, см. второй листинг). Ну и, разумеется, нужна функция для вывода объекта на экран и передвижения его в целевую точку в соответствии с заданными параметрами движения.
Начнем с методов-обработчиков событий. Расположим их перед функцией PrepareBox().

function attachEvents()
    {
        document.onkeydown = keyDown;
        document.onkeyup = keyUp;
    }

Просто? Да, но больше ничего и не требуется. Теперь надо обработать события клавиатуры:

//////////////////////////// Обработка событий клавиатуры ///////////////////////////
function keyDown(e)
    {
        switch ((bIE) ? event.keyCode : e.which)
        {
            case 37: // left
                box.ix = false;
                box.dx = true;
                break;
            case 38: // up
                box.iy = true;
                box.dy = false;
                break;
            case 39: // right
                box.ix = true;
                box.dx = false;
                break;
            case 40: // down
                box.iy = false;
                box.dy = true;
                break;
        }
    }

function keyUp(e)
    {
        switch ((bIE) ? event.keyCode : e.which)
        {
            case 37: // left
                box.dx = false;
                break;
            case 38: // up
                box.iy = false;
                break;
            case 39: // right
                box.ix = false;
                break;
            case 40: // down
                box.dy = false;
                break;
        }
    }

А теперь позвольте мне пояснить приведеннй код, так как он может оказаться непрозрачным. Здесь я меняю параметры движения объекта. Когда пользователь нажимает на правую стрелку, х-координата объекта должна увеличиваться, а сам объект – двигаться от левой границы экрана к правой. Поэтому я отслеживаю нажатие 39-ой кнопки (это и есть правая стрелка), и как только она нажата, сбрасываю флаг уменьшения х-координаты (нам не нужно двигаться влево) и устанавливаю флаг её увеличения (двигаемся вправо). Прямо противоположные действия нужно осуществить по нажатию левой стрелки (37-ой кнопки).
У-координата равна нулю в самом низу экрана. Если я хочу поднять объект вверх, я должен увеличивать у-координату. Для этого я устанавливаю флаг увеличения у-координаты (true) и сбрасываю флаг уменьшения (false).
Так как я хочу, чтобы объект двигался только пока нажаты соответствующие кнопки, нужно сбрасывать все флаги, когда кнопку отпускают. Например, если я отпускаю верхнюю стрелку, то это означает, что я не хочу, чтобы объект продолжал двигаться вверх. Поэтому я сбрасываю флаг увеличения у-координаты, что немедленно остановит дальнейшее движение объекта вверх. Если в это время я продолжаю нажимать, например, левую стрелку, то движение влево не остановится, пока я и её не отпущу.
А сейчас напишем функцию вывода объекта на экран! При изменении координат следует учитывать, что объект не должен выходить за пределы экрана.

//////////////////////////// Перерисовка /////////////////////
function printBox()
{
    if (box.ix)
    {
        if (box.x + box.w + box.speed <= getScreenSize(SCREEN_X))
            box.x += box.speed;
    }

    if (box.dx)
    {
        if (box.x - box.speed >= 0)
            box.x -= box.speed;
    }

    if (box.iy)
    {
        if (box.y + box.h + box.speed <= getScreenSize(SCREEN_Y))
            box.y += box.speed;
    }

    if (box.dy)
    {
        if (box.y - box.speed >= 0)
            box.y -= box.speed;
    }

    surface.innerHTML = 
'<div id="gBox" style="left:' + box.x + 'px;bottom:' + box.y + 'px"></div>'; }

Система координат в HTML/CSS начинается в левом нижнем углу (см. рис. 1, «0»)


HXXXXXX
XXXXXXX
XXXXXXX
XXXXXXX
XXXXXXX
OXXXXXW

Рис. 1 – Система координат

С помощью выражений «X > ширина экрана» и «У > высота экрана» не-возможно определить, что объект вышел за пределы экрана. В этом случае объект будет останавливаться только тогда, когда границы экрана достигнет его точка «0» (см. рис. 1). Чтобы объект останавливался по достижении границ экрана точ-ками «H» и «W», нужно добавлять к координате Х в условиях остановки высоту и ширину объекта. То есть, вместо «Х > ширина экрана» используем «W > ширина экрана». Тогда никакая часть объекта не покинет область рисования. Передвинув координаты объекта, выведем его как слой. Заметьте, что я значительно сократил код, поместив всю статическую информацию об объекте в тег style.

Мы почти достигли финала! Остался последний шаг. Нужна функция, которая будет вызываться через определенный интервал (25 милисекунд). После этого нужно будет вызвать все написанные функции в теле функции loadGame(), которая пока пуста.

function updateScreen()
{
	surface.innerHTML = '';
	printBox();
    window.setTimeout('updateScreen()', iMs);
}

Очистим поверхность и выведем новый объект. Повторяйте все время! А теперь – последний штрих!

function loadGame()
{
    surface = document.getElementById("gSurface");
    if (surface == null)
    {
    alert("Can't find the required surface!\nCancelling...");
    return;
    }
    attachEvents();
    prepareBox();
    updateScreen();
}

Мы отследили все события, подготовили объект к движению (поместили его в центр экрана и инициализировали глобальную переменную для дальнейшего использования) и вызвали бесконечно повторяющуюся функцию updateScreen().
Вот готовый код:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0
Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" /> <title>Простой движущийся слой!</title> <script language="javascript" type="text/javascript"> <!--<![CDATA[ var box = null, surface = null; var SCREEN_X = 0, SCREEN_Y = 1; var bIE = (String(typeof(document.all)) != "undefined"); var iMs = 25; //////////////////////////// Классы //////////////////////////// function _box(x, y, w, h, speed) { this.x = x; this.y = y; this.w = w; this.h = h; this.speed = speed; this.ix = false; this.dx = false; this.iy = false; this.dy = false; } //////////////////////////// Экран ///////////////////////////// function getScreenSize(s) { if (s == SCREEN_X) { if (bIE) return document.body.offsetWidth; return window.innerWidth; } else { if (bIE) return document.body.offsetHeight; return window.innerHeight; } } //////////////////////////// Перерисовка ///////////////////// function printBox() { if (box.ix) { if (box.x + box.w + box.speed <= getScreenSize(SCREEN_X)) box.x += box.speed; } if (box.dx) { if (box.x - box.speed >= 0) box.x -= box.speed; } if (box.iy) { if (box.y + box.h + box.speed <= getScreenSize(SCREEN_Y)) box.y += box.speed; } if (box.dy) { if (box.y - box.speed >= 0) box.y -= box.speed; } surface.innerHTML =
'<div id="gBox" style="left:' + box.x + 'px;bottom:' + box.y + 'px"></div>'; } function updateScreen() { surface.innerHTML = ''; printBox(); window.setTimeout('updateScreen()', iMs); } //////////////////////////// События /////////////////////////// function keyDown(e) { switch ((bIE) ? event.keyCode : e.which) { case 37: // влево box.ix = false; box.dx = true; break; case 38: // вверх box.iy = true; box.dy = false; break; case 39: // вправо box.ix = true; box.dx = false; break; case 40: // вниз box.iy = false; box.dy = true; break; } } function keyUp(e) { switch ((bIE) ? event.keyCode : e.which) { case 37: // влево box.dx = false; break; case 38: // вверх box.iy = false; break; case 39: // вправо box.ix = false; break; case 40: // вниз box.dy = false; break; } } //////////////////////////// Загрузка игры ////////////////////////// function prepareBox() { box = new _box(0, 0, 100, 100, 8); box.x = (getScreenSize(SCREEN_X) - box.w) / 2; box.y = (getScreenSize(SCREEN_Y) - box.h) / 2; } function attachEvents() { document.onkeydown = keyDown; document.onkeyup = keyUp; } function loadGame() { surface = document.getElementById("gSurface"); if (surface == null) { alert("Не могу найти нужную поверхность!\nОтмена загрузки..."); return; } attachEvents(); prepareBox(); updateScreen(); } //]]>--> </script> <style type="text/css" media="all"> <!--<![CDATA[ html, body { width: 100%; height: 100%; padding: 0; margin: 0; overflow: hidden; } body { background-color: black; } #gSurface { position: relative; width: 100%; height: 100%; } #gBox { position: absolute; z-index: 2; width: 100px; height: 100px; background-color: white; } /*]]>*/--> </style> </head> <body id="gContainer" onload="loadGame()"> <div id="gSurface"> </div> </body>

Наслаждайтесь!

С уважением, vk!



 Design by Шишкин Алексей (Лёха)  ©2004-2008 by sources.ru