Статьи

Игры: Битва в открытом море, часть 2

На прошлой неделе я представил игру HTML5, известную как SeaBattle , в качестве демонстрации того, чего вы можете достичь с помощью API-интерфейсов HTML5 для аудио, холста и веб-хранилища. Затем я показал вам, как встроить эту игру в веб-страницу, и рассмотрел ее архитектуру. Эта статья начинает углубляться в эту архитектуру, фокусируясь на инициализации. Он исследует SeaBattle объекта SeaBattle init(width, height) и связанные с ним функции.

Инициализация SeaBattle

В листинге 1 представлена ​​реализация функции init(width, height) .

 init: function(width, height) { var canvas = $("<canvas width='"+width+"' height='"+height+"'></canvas>"); canvas.appendTo("body"); SeaBattle.ctx = canvas.get(0).getContext("2d"); SeaBattle.ctx.font = "30px Arial"; SeaBattle.ctx.textAlign = "center"; var seed = 5*height/6; SeaBattle.hillTops = new Array(); for (var i = 0; i < width; i++) { SeaBattle.hillTops.push(seed); var x = SeaBattle.rnd(seed); if (x < seed/4) { if (--seed < 2*height/3) seed = 2*height/3; } else if (x > 3*seed/4) { if (++seed > height-1) seed = height-1; } } SeaBattle.width = width; SeaBattle.height = height; SeaBattle.dc = new Array(SeaBattle.MAX_DC); SeaBattle.torp = new Array(SeaBattle.MAX_TORP); SeaBattle.explosion = null; SeaBattle.msg = ""; SeaBattle.score = 0; SeaBattle.hiScore = 0; if (SeaBattle.supports_html5_storage()) { var temp = localStorage.getItem("hiScore"); if (temp != undefined) SeaBattle.hiScore = temp; } SeaBattle.lives = 4; window.keydown = {}; function keyName(event) { return jQuery.hotkeys.specialKeys[event.which] || String.fromCharCode(event.which).toLowerCase(); } $(document).bind("keydown", function(event) { keydown[keyName(event)] = true; }); $(document).bind("keyup", function(event) { keydown[keyName(event)] = false; }); SeaBattle.imgTitle = new Image(); SeaBattle.imgTitle.src = "images/title.png"; SeaBattle.imgSky = new Image(); SeaBattle.imgSky.src = "images/sky.png"; SeaBattle.imgMoon = new Image(); SeaBattle.imgMoon.src = "images/moon.png"; SeaBattle.imgShipLeft = new Image(); SeaBattle.imgShipLeft.src = "images/shipLeft.png"; SeaBattle.imgShipRight = new Image(); SeaBattle.imgShipRight.src = "images/shipRight.png"; SeaBattle.imgSubLeft = new Image(); SeaBattle.imgSubLeft.src = "images/subLeft.png"; SeaBattle.imgSubRight = new Image(); SeaBattle.imgSubRight.src = "images/subRight.png"; SeaBattle.imgExplosion = new Array(); for (var i = 0; i < 17; i++) { var image = new Image(); image.src = "images/ex"+i+".png"; SeaBattle.imgExplosion.push(image); } SeaBattle.imgTorpedo = new Image(); SeaBattle.imgTorpedo.src = "images/torpedo.png"; SeaBattle.imgDC = new Image(); SeaBattle.imgDC.src = "images/dc.png"; SeaBattle.audBombLoaded = false; SeaBattle.audBomb = document.createElement("audio"); SeaBattle.audBomb.onloadeddata = new function() { SeaBattle.audBombLoaded = true; }; SeaBattle.audBomb.src = (navigator.userAgent.indexOf("MSIE") == -1) ? "audio/bomb.wav" : "audio/bomb.mp3"; SeaBattle.state = SeaBattle.STATE_INIT; } 

Листинг 1. Инициализация игры включает создание / инициализацию холста и подводного ландшафта, привязку горячих клавиш, загрузку игрового ресурса и многое другое.

В листинге 1 сначала используется jQuery для создания узла элемента <canvas> , а затем устанавливается его в дереве обозревателя Document Object Model (DOM). Это решает эту задачу следующим образом:

  1. Вызовите конструктор jQuery( html ) чтобы проанализировать строку html , создать узлы DOM из проанализированного HTML и создать / вернуть объект jQuery который ссылается на эти узлы. В листинге 1 создается один узел <canvas> DOM.
  2. appendTo("body") для этого нового объекта jQuery чтобы присоединить проанализированные узлы DOM HTML к узлу элемента <body> веб-страницы. В листинге 1 присоединяется узел <canvas> узлу <body> страницы.

Контекст холста получается через canvas.get(0).getContext("2d") и присваивается SeaBattle ctx SeaBattle . Затем свойства font и textAlign контекста 2D-рисования инициализируются, чтобы указать, что текст должен быть нарисован шрифтом Arial с высотой 30 пикселей, и чтобы упростить горизонтальное центрирование текста.

В листинге 1 продолжается создание подводного ландшафта путем случайного выбора вершин холмов. Крайняя левая вершина холма находится в средней точке нижней трети холста. Каждая вершина холма справа относительно предыдущей вершины холма.

Продолжая, значения width и height передаваемые в init(width, height) , сохраняются в одноименных свойствах SeaBattle чтобы к ним можно было получить доступ из других функций. Кроме того, инициализируются следующие свойства SeaBattle :

  • dc инициализируется в массив, который будет хранить не более MAX_DC объектов глубинного заряда.
  • torp инициализируется в массив, который будет хранить не более MAX_TORP объектов торпеды.
  • explosion инициализируется null . Функция update() проверяет это свойство, чтобы выяснить, происходит ли взрыв. Когда происходит взрыв, explosion присваивается ссылка на объект взрыва.
  • msg инициализируется пустой строкой. Когда корабль или подводная лодка побеждает, этому свойству присваивается подходящее сообщение для последующего отображения в функции draw() .
  • score обнуляется и отражает текущий счет игрока. Эта оценка появляется в верхнем левом углу холста.
  • hiScore инициализируется нулем и отражает самый высокий предыдущий результат игрока. Если текущий браузер поддерживает локальный аспект веб-хранилища HTML5, и если этот счет ранее был сохранен, для hiScore установлено сохраненное значение. Высокая оценка отображается в скобках после текущей оценки.
  • lives инициализируется равным четырем и отражает общее количество жизней разрушителей, которые можно прожить до окончания игры. Это число уменьшается на единицу каждый раз, когда разрушитель уничтожается.

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

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

  1. Прикрепите слушателей событий вниз и вверх к холсту, как в canvas.onkeydown = keyDown; и canvas.onkeydown = keyUp; , keyDown и keyUp идентифицируют функции, которые отвечают на события нажатия клавиш вниз и вверх соответственно.
  2. Создайте изначально пустой ассоциативный массив и назначьте его объекту window , как в window.keydown = {} . Клавиша каждой записи будет именем клавиши, которая была нажата, и ее значение будет истинным, если клавиша нажата, или ложью, когда клавиша нажата.
  3. Для каждого из keyDown() и keyUp() вызовите функцию, которая возвращает имя ключа, которое является либо символьным ключом, либо не символьным (специальным) ключом. Затем используйте результат в качестве индекса в массиве keydown . Для keyDown() присвойте true этой записи массива. Для keyUp() вместо этого присвойте false.

Реализация этого решения может быть неприятной. Например, charCode всегда неопределен в Opera. Почему бы не позволить jQuery и плагину jQuery HotKeys выполнять большую часть этой работы за вас?

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

В листинге 1 теперь начинается загрузка ресурсов изображений, которые хранятся в каталоге images , путем создания экземпляра объекта Image и присвоения местоположения и имени изображения свойству src объекта. Также начинается загрузка аудио ресурса, который хранится в audio каталоге. В отличие от других браузеров, Safari не предоставляет объект Audio . Чтобы обеспечить согласованное поведение в разных браузерах, document.createElement("audio") используется для создания эквивалентного объекта.

Когда изображение завершает загрузку, объект Image присваивает true своему complete свойству. Чтобы определить, что аудиофайл завершил загрузку, onloadeddata « Audio » назначается функция обработчика onloadeddata которая присваивает true SeaBattle audBombLoaded .

За исключением Internet Explorer, все браузеры, упомянутые в первой части этой серии, поддерживают формат WAV. Вместо этого Internet Explorer поддерживает MP3. В листинге 1 определяется, является ли текущий браузер Internet Explorer, прежде чем выбрать подходящий аудиофайл для загрузки. Выражение navigator.userAgent.indexOf("MSIE") возвращает значение, отличное от -1, когда текущим браузером является Internet Explorer. Этот факт помогает листингу 1 выбирать между audio/bomb.wav и audio/bomb.mp3 , которые назначаются свойству src объекта « Audio ».

Последняя задача листинга 1 — добавить свойство state к объекту SeaBattle и присвоить этому свойству STATE_INIT . Это состояние приводит к тому, что холст представляет центрированное сообщение Initializing... пока все ресурсы игры не закончат загрузку.

Получение случайных целых

Функция init(width, height) полагается на SeaBattle rnd(limit) SeaBattle которая возвращает случайные целые числа, чтобы она могла генерировать ландшафт. В листинге 2 представлена ​​реализация rnd(limit) .

 rnd: function(limit) { return (Math.random()*limit)|0; } 

Листинг 2: Побитовые операторы заставляют JavaScript преобразовывать числа с плавающей точкой в ​​целые числа.

В листинге 2 возвращается случайно выбранное целое число от нуля до limit - 1 . Поскольку требуется целочисленный результат, а Math.random()*limit возвращает число с дробью, |0 используется для усечения результата до целого числа. Чтобы узнать больше о преобразовании JavaScript в целочисленные возможности, ознакомьтесь с разделом часто задаваемых вопросов по преобразованию типов Javascript . В частности, прочтите раздел ToInt32 FAQ, чтобы узнать о функции ToInt32 реализации JavaScript.

Обнаружение HTML5 локального хранилища

Функция init(width, height) также опирается на SeaBattle supports_html5_storage() SeaBattle для определения локального аспекта веб-хранилища. В листинге 3 представлена ​​реализация supports_html5_storage() .

 supports_html5_storage: function() { try { return 'localStorage' in window && window['localStorage'] !== null && window['localStorage'] !== undefined; } catch (e) { return false; } } 

Листинг 3: Старые версии Firefox вызывают исключение, когда куки отключены.

В листинге 3 обнаруживается поддержка локального аспекта веб-хранилища путем проверки объекта глобального window на наличие свойства localStorage . Когда это свойство существует и не является null или undefined , эта функция возвращает true; в противном случае возвращается false.

Вывод

Функция init(width, height) работает с функциями rnd(limit) и SeaBattle supports_html5_storage() для правильной инициализации объекта SeaBattle . Следующим шагом в понимании игрового процесса SeaBattle является изучение функции update() , которая является предметом третьей части этой серии. В следующую пятницу вы также узнаете, как реализован объект корабля.