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