Статьи

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

На прошлой неделе наша игровая серия углубилась в архитектуру SeaBattle, обсудив SeaBattle update() объекта SeaBattle вместе с конструктором makeShip(x, y, bound1, bound2) . Это четвертая статья в нашей серии из пяти частей, и она продолжает исследовать эту архитектуру, охватывая конструкторы для подводных лодок, глубинных зарядов, торпед и взрывов. Также обсуждаются intersects(r1, r2) и обнаружение столкновений.

Создание подводной лодки

Функция update() отвечает за создание подводной лодки и других игровых объектов. Он выполняет создание подводной лодки с помощью конструктора makeSub(x, y, bound1, bound2) . В листинге 1 представлена ​​реализация этого конструктора.

 makeSub: function(x, y, bound1, bound2) { this.x = x; this.y = y; this.bound1 = bound1; this.bound2 = bound2; this.bbox = { left: 0, top: 0, right: 0, bottom: 0 }; this.LEFT = 0; this.RIGHT = 1; this.dir = (x >= SeaBattle.width) ? this.LEFT : this.RIGHT; this.exploded = false; this.height = SeaBattle.imgSubLeft.height; this.vx = SeaBattle.rnd(5)+2; this.width = SeaBattle.imgSubLeft.width; this.draw = function() { SeaBattle.ctx.drawImage((this.dir == this.LEFT)? SeaBattle.imgSubLeft : SeaBattle.imgSubRight, this.x-this.width/2, this.y-this.height/2); } this.getBBox = function() { this.bbox.left = this.x-this.width/2; this.bbox.top = this.y-this.height/2; this.bbox.right = this.x+this.width/2; this.bbox.bottom = this.y+this.height/2; return this.bbox; } this.move = function() { if (this.dir == this.LEFT) { this.x -= this.vx; if (this.x-this.width/2 < this.bound1) { this.x += this.vx; this.vx = SeaBattle.rnd(3)+1; this.dir = this.RIGHT; } } else { this.x += this.vx; if (this.x+this.width/2 > this.bound2) { this.x -= this.vx; this.vx = SeaBattle.rnd(3)+1; this.dir = this.LEFT; } } } } 

Листинг 1: Функция move() автоматически переключает направление подводной лодки после прохождения левого или правого края.

В листинге 1 сначала сохраняются аргументы в свойствах объекта подводной лодки, а затем вводятся еще 11 свойств объекта:

  • bbox ссылается на прямоугольный объект, который служит ограничительной рамкой для обнаружения столкновений. Этот объект передается в качестве аргумента функции intersects(r1, r2) .
  • LEFT — псевдо-константа, используемая вместе со свойством dir .
  • RIGHT — это псевдо-константа, используемая вместе со свойством dir .
  • dir указывает текущее направление подводной лодки.
  • exploded указывает, exploded ли подводная лодка.
  • height указывает высоту подводного изображения в пикселях.
  • vx задает горизонтальную скорость подводной лодки с точки зрения количества пикселей, которые перемещает подводная лодка.
  • width определяет ширину подводного изображения в пикселях.
  • draw() рисует изображение подводной лодки, совпадающее со свойствами x и y подводной лодки.
  • getBBox() возвращает обновленный объект bbox . Этот объект обновляется с учетом изменения горизонтального положения подводной лодки.
  • move() перемещает подводную лодку влево или вправо.

Создание глубинного заряда

Когда нажимается пробел, update() пытается создать объект глубинного заряда (в каждый момент могут быть задействованы только два глубинных заряда). makeDepthCharge(bound) листинга 2 используется для создания заряда глубины.

 makeDepthCharge: function(bound) { this.bound = bound; this.bbox = { left: 0, top: 0, right: 0, bottom: 0 }; this.height = SeaBattle.imgDC.width; this.width = SeaBattle.imgDC.height; this.draw = function() { SeaBattle.ctx.drawImage(SeaBattle.imgDC, this.x-this.width/2, this.y-this.height/2); } this.getBBox = function() { this.bbox.left = this.x-this.width/2; this.bbox.top = this.y-this.height/2; this.bbox.right = this.x+this.width/2; this.bbox.bottom = this.y+this.height/2; return this.bbox; } this.move = function move() { this.y++; if (this.y+this.height/2 > this.bound) return false; return true; } this.setLocation = function(x, y) { this.x = x; this.y = y; } } 

Листинг 2: Текущее местоположение заряда глубины совпадает с центром его изображения.

В листинге 2 сначала сохраняется аргумент, переданный bound параметру, в свойстве объекта глубинного заряда, а затем вводятся еще семь свойств объекта:

  • bbox ссылается на прямоугольный объект, который служит ограничительной рамкой для обнаружения столкновений.
  • height указывает высоту изображения глубины заряда в пикселях.
  • width определяет ширину изображения глубины заряда в пикселях.
  • draw() рисует изображение глубинного заряда.
  • getBBox() возвращает обновленный объект bbox центром в текущих значениях x и y объекта.
  • move() продвигает глубинный заряд вниз на один пиксель, пока не будет пройдена нижняя граница.
  • setLocation(x, y) определяет местоположение глубинного заряда, которое совпадает с центром изображения глубинного заряда.

Изготовление торпеды

Когда центр подводной лодки виден, случайно сгенерированное целое число равно некоторому значению, и в игре находится менее 15 торпед, update() создает объект торпеды. Фактическая работа по созданию этого объекта выполняется конструктором makeTorpedo(bound) листинге 3.

 makeTorpedo: function(bound) { this.bound = bound; this.bbox = { left: 0, top: 0, right: 0, bottom: 0 }; this.height = SeaBattle.imgTorpedo.height; this.width = SeaBattle.imgTorpedo.width; this.draw = function() { SeaBattle.ctx.drawImage(SeaBattle.imgTorpedo, this.x-this.width/2, this.y); } this.getBBox = function() { this.bbox.left = this.x-this.width/2; this.bbox.top = this.y; this.bbox.right = this.x+this.width/2; this.bbox.bottom = this.y+this.height; return this.bbox; } this.move = function move() { this.y--; if (this.y < this.bound) return false; return true; } this.setLocation = function(x, y) { this.x = x; this.y = y; } } 

Листинг 3: Текущее местоположение торпеды совпадает с верхним центром ее изображения.

В листинге 3 сначала сохраняется аргумент, переданный его bound параметру, в одноименном свойстве торпедного объекта, а затем вводятся еще семь свойств объекта:

  • bbox ссылается на прямоугольный объект, который служит ограничительной рамкой для обнаружения столкновений.
  • height определяет высоту изображения торпеды в пикселях.
  • width определяет ширину изображения торпеды в пикселях.
  • draw() рисует изображение торпеды.
  • getBBox() возвращает обновленный объект bbox центрированный вокруг текущего значения x объекта.
  • move() продвигает торпеду вверх на один пиксель. Эта функция возвращает истину до тех пор, пока верх изображения торпеды не пройдет верхнюю границу, после чего вернет ложь.
  • setLocation(x, y) указывает местоположение торпеды, которое совпадает с верхним центром изображения торпеды. Его аргументы хранятся в свойствах x и y объекта торпеды.

Обнаружение столкновения

Функция update() части 3 опирается на функцию intersects(r1, r2) чтобы определить, произошло ли столкновение между торпедой и кораблем или между глубинным зарядом и подводной лодкой. В листинге 4 представлена ​​реализация этой функции.

 intersects: function(r1, r2) { return !(r2.left > r1.right || r2.right < r1.left || r2.top > r1.bottom || r2.bottom < r1.top); } 

Листинг 4: Два прямоугольника проверяются на пересечение.

В листинге 4 определяется, пересекаются ли два его прямоугольных аргумента (возвращаемых из getBBox() ), сначала определяя, находится ли второй прямоугольник ( r2 ) полностью справа или слева от, ниже или выше первого прямоугольника ( r1 ), а затем отрицая результат.

Если вы помните из части 3, ограничивающий прямоугольник корабля не полностью центрирован по вертикали вокруг текущего местоположения y объекта. Хотя верхняя часть расположена вертикально по центру, нижняя — не потому, что я назначаю this.y+2 вместо this.y+this.height/2 для this.bbox.bottom .

Рисунок 1: Изображение корабля обведено красной рамкой, чтобы четко показать размеры пустого вертикального пространства.

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

Если бы я указал this.y+this.height/2 в качестве нижней границы, пересекающаяся торпеда взорвалась бы слишком далеко от дна корабля, чтобы выглядеть правдоподобно. Эта проблема не существует с подводной лодкой, чьи изображения не имеют чрезмерного количества пустого вертикального пространства.

Взрывать

Функция update() реагирует на коллизию, вызывая конструктор makeExplosion(isShip) для создания объекта взрыва. Переданный логический аргумент имеет значение true, когда корабль взрывается, и false в противном случае. В листинге 5 показано, как реализован этот конструктор.

 makeExplosion: function(isShip) { this.isShip = isShip; this.counter = 0; this.height = SeaBattle.imgExplosion[0].height; this.imageIndex = 0; this.width = SeaBattle.imgExplosion[0].width; this.advance = function() { if (++this.counter < 4) return true; this.counter = 0; if (++this.imageIndex == 8) { if (this.isShip) SeaBattle.ship.exploded = true; else SeaBattle.sub.exploded = true; } else if (this.imageIndex > 16) { this.imageIndex = 0; return false; } return true; } this.draw = function() { SeaBattle.ctx.drawImage(SeaBattle.imgExplosion[this.imageIndex], this.x-this.width/2, this.y-this.height/2); } this.setLocation = function(x, y) { this.x = x; this.y = y; try { SeaBattle.audBomb.play(); } catch (e) { // Safari without QuickTime results in an exception } } } 

Листинг 5: Взрыв начинает воспроизводить звук, как только указывается его местоположение.

makeExplosion(isShip) листинге 5 сначала сохраняет аргумент, переданный параметру isShip в isShip объекта isShip , а затем вводит семь дополнительных свойств объекта:

  • counter используется для замедления продвижения взрыва, чтобы он не исчезал слишком быстро.
  • height определяет высоту каждого изображения взрыва в пикселях.
  • imageIndex указывает нулевой индекс следующего изображения взрыва для отображения.
  • width определяет ширину каждого изображения взрыва в пикселях.
  • advance() увеличивает взрыв каждый раз, когда counter равен четырем. Когда imageIndex равняется восьми, почти половина взрыва заканчивается, и взрывающийся корабль или подводная лодка удаляется.
  • draw() рисует следующее изображение взрыва.
  • setLocation(x, y) определяет местоположение взрыва, которое совпадает с центром каждого изображения взрыва. Его аргументы хранятся в свойствах x и y объекта взрыва.

После установки местоположения взрыва звуковой эффект взрыва воспроизводится через SeaBattle.audBomb.play(); , Если вы используете браузер Safari без QuickTime, этот браузер создает исключение. Обработчик исключений может отображать сообщение или выполнять другие действия. В настоящее время мы игнорируем исключение.

Вывод

Наше исследование архитектуры SeaBattle почти завершено. В следующую пятницу, часть 5 завершает это исследование, сначала показывая вам, как сценарий игры рисуется на холсте. Далее кратко рассматриваются API-интерфейсы HTML5 Audio, Canvas и Web Storage, чтобы помочь новичкам в этих API лучше понять SeaBattle. После предоставления идей по улучшению этой игры, часть 5 заканчивает эту серию, выводя SeaBattle за пределы рабочего стола.