На прошлой неделе наша игровая серия углубилась в архитектуру 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 за пределы рабочего стола.