Создание игр на 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»)
H | X | X | X | X | X | X |
X | X | X | X | X | X | X |
X | X | X | X | X | X | X |
X | X | X | X | X | X | X |
X | X | X | X | X | X | X |
O | X | X | X | X | X | W |
Рис. 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!
|